Hi everybody,

The recent Linux Foundation Collaboration Summit outlined the need for device 
drivers to implement proper power management. Many drivers have no 
suspend/resume support at all. Hopefully, the Linux UVC driver will soon not 
be part of them anymore: here is a patch that implements suspend/resume.

As I'm quite new to power management, there might be many issues (small or 
big) lying in this patch. I would like beta testers to give it a try before 
committing it to the SVN repository.

You should be able to suspend and resume your computer properly, even if the 
webcam is streaming. Controls should be restored to their original state. 
Suspend to RAM as been tested, but Suspend to Disk hasn't.

As usual, please any problem you run into.

Thanks in advance for your help.

Best regards,

Laurent Pinchart
Index: uvc_ctrl.c
===================================================================
--- uvc_ctrl.c	(revision 110)
+++ uvc_ctrl.c	(working copy)
@@ -29,6 +29,8 @@
 #define UVC_CONTROL_GET_MAX	(1 << 3)
 #define UVC_CONTROL_GET_RES	(1 << 4)
 #define UVC_CONTROL_GET_DEF	(1 << 5)
+/* Control should be saved at suspend and restored at resume. */
+#define UVC_CONTROL_RESTORE	(1 << 6)
 
 #define UVC_CONTROL_GET_RANGE	(UVC_CONTROL_GET_CUR | UVC_CONTROL_GET_MIN | \
 				 UVC_CONTROL_GET_MAX | UVC_CONTROL_GET_RES | \
@@ -48,63 +50,72 @@
 		.selector	= PU_BRIGHTNESS_CONTROL,
 		.index		= 0,
 		.size		= 2,
-		.flags		= UVC_CONTROL_SET_CUR | UVC_CONTROL_GET_RANGE,
+		.flags		= UVC_CONTROL_SET_CUR | UVC_CONTROL_GET_RANGE
+				| UVC_CONTROL_RESTORE,
 	},
 	{
 		.entity		= UVC_GUID_UVC_PROCESSING,
 		.selector	= PU_CONTRAST_CONTROL,
 		.index		= 1,
 		.size		= 2,
-		.flags		= UVC_CONTROL_SET_CUR | UVC_CONTROL_GET_RANGE,
+		.flags		= UVC_CONTROL_SET_CUR | UVC_CONTROL_GET_RANGE
+				| UVC_CONTROL_RESTORE,
 	},
 	{
 		.entity		= UVC_GUID_UVC_PROCESSING,
 		.selector	= PU_HUE_CONTROL,
 		.index		= 2,
 		.size		= 2,
-		.flags		= UVC_CONTROL_SET_CUR | UVC_CONTROL_GET_RANGE,
+		.flags		= UVC_CONTROL_SET_CUR | UVC_CONTROL_GET_RANGE
+				| UVC_CONTROL_RESTORE,
 	},
 	{
 		.entity		= UVC_GUID_UVC_PROCESSING,
 		.selector	= PU_SATURATION_CONTROL,
 		.index		= 3,
 		.size		= 2,
-		.flags		= UVC_CONTROL_SET_CUR | UVC_CONTROL_GET_RANGE,
+		.flags		= UVC_CONTROL_SET_CUR | UVC_CONTROL_GET_RANGE
+				| UVC_CONTROL_RESTORE,
 	},
 	{
 		.entity		= UVC_GUID_UVC_PROCESSING,
 		.selector	= PU_SHARPNESS_CONTROL,
 		.index		= 4,
 		.size		= 2,
-		.flags		= UVC_CONTROL_SET_CUR | UVC_CONTROL_GET_RANGE,
+		.flags		= UVC_CONTROL_SET_CUR | UVC_CONTROL_GET_RANGE
+				| UVC_CONTROL_RESTORE,
 	},
 	{
 		.entity		= UVC_GUID_UVC_PROCESSING,
 		.selector	= PU_GAMMA_CONTROL,
 		.index		= 5,
 		.size		= 2,
-		.flags		= UVC_CONTROL_SET_CUR | UVC_CONTROL_GET_RANGE,
+		.flags		= UVC_CONTROL_SET_CUR | UVC_CONTROL_GET_RANGE
+				| UVC_CONTROL_RESTORE,
 	},
 	{
 		.entity		= UVC_GUID_UVC_PROCESSING,
 		.selector	= PU_BACKLIGHT_COMPENSATION_CONTROL,
 		.index		= 8,
 		.size		= 2,
-		.flags		= UVC_CONTROL_SET_CUR | UVC_CONTROL_GET_RANGE,
+		.flags		= UVC_CONTROL_SET_CUR | UVC_CONTROL_GET_RANGE
+				| UVC_CONTROL_RESTORE,
 	},
 	{
 		.entity		= UVC_GUID_UVC_PROCESSING,
 		.selector	= PU_GAIN_CONTROL,
 		.index		= 9,
 		.size		= 2,
-		.flags		= UVC_CONTROL_SET_CUR | UVC_CONTROL_GET_RANGE,
+		.flags		= UVC_CONTROL_SET_CUR | UVC_CONTROL_GET_RANGE
+				| UVC_CONTROL_RESTORE,
 	},
 	{
 		.entity		= UVC_GUID_UVC_PROCESSING,
 		.selector	= PU_POWER_LINE_FREQUENCY_CONTROL,
 		.index		= 10,
 		.size		= 1,
-		.flags		= UVC_CONTROL_SET_CUR | UVC_CONTROL_GET_RANGE,
+		.flags		= UVC_CONTROL_SET_CUR | UVC_CONTROL_GET_RANGE
+				| UVC_CONTROL_RESTORE,
 	},
 	{
 		.entity		= UVC_GUID_UVC_PROCESSING,
@@ -112,7 +123,7 @@
 		.index		= 11,
 		.size		= 1,
 		.flags		= UVC_CONTROL_SET_CUR | UVC_CONTROL_GET_CUR
-				| UVC_CONTROL_GET_DEF,
+				| UVC_CONTROL_GET_DEF | UVC_CONTROL_RESTORE,
 	},
 	{
 		.entity		= UVC_GUID_LOGITECH_MOTOR,
@@ -137,14 +148,16 @@
 		.index		= 1,
 		.size		= 1,
 		.flags		= UVC_CONTROL_SET_CUR | UVC_CONTROL_GET_CUR
-				| UVC_CONTROL_GET_DEF | UVC_CONTROL_GET_RES,
+				| UVC_CONTROL_GET_DEF | UVC_CONTROL_GET_RES
+				| UVC_CONTROL_RESTORE,
 	},
 	{
 		.entity		= UVC_GUID_UVC_CAMERA,
 		.selector	= CT_EXPOSURE_TIME_ABSOLUTE_CONTROL,
 		.index		= 3,
 		.size		= 4,
-		.flags		= UVC_CONTROL_SET_CUR | UVC_CONTROL_GET_RANGE,
+		.flags		= UVC_CONTROL_SET_CUR | UVC_CONTROL_GET_RANGE
+				| UVC_CONTROL_RESTORE,
 	},
 	{
 		.entity		= UVC_GUID_UVC_PROCESSING,
@@ -152,14 +165,15 @@
 		.index		= 12,
 		.size		= 1,
 		.flags		= UVC_CONTROL_SET_CUR | UVC_CONTROL_GET_CUR
-				| UVC_CONTROL_GET_DEF,
+				| UVC_CONTROL_GET_DEF | UVC_CONTROL_RESTORE,
 	},
 	{
 		.entity		= UVC_GUID_UVC_PROCESSING,
 		.selector	= PU_WHITE_BALANCE_TEMPERATURE_CONTROL,
 		.index		= 6,
 		.size		= 2,
-		.flags		= UVC_CONTROL_SET_CUR | UVC_CONTROL_GET_RANGE,
+		.flags		= UVC_CONTROL_SET_CUR | UVC_CONTROL_GET_RANGE
+				| UVC_CONTROL_RESTORE,
 	}
 };
 
@@ -590,7 +604,7 @@
 	return mutex_lock_interruptible(&video->ctrl_mutex) ? -ERESTARTSYS : 0;
 }
 
-static int uvc_ctrl_commit_entity(struct uvc_video_device *video,
+static int uvc_ctrl_commit_entity(struct uvc_device *dev,
 	struct uvc_entity *entity, int rollback)
 {
 	struct uvc_control *ctrl;
@@ -606,8 +620,8 @@
 			continue;
 
 		if (!rollback)
-			ret = uvc_query_ctrl(video->dev, SET_CUR, ctrl->entity->id,
-				video->dev->intfnum, ctrl->info->selector,
+			ret = uvc_query_ctrl(dev, SET_CUR, ctrl->entity->id,
+				dev->intfnum, ctrl->info->selector,
 				uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT),
 				ctrl->info->size);
 		else
@@ -636,18 +650,18 @@
 	int ret = 0;
 
 	/* Find the control. */
-	ret = uvc_ctrl_commit_entity(video, video->processing, rollback);
+	ret = uvc_ctrl_commit_entity(video->dev, video->processing, rollback);
 	if (ret < 0)
 		goto done;
 
 	list_for_each_entry(entity, &video->iterms, chain) {
-		ret = uvc_ctrl_commit_entity(video, entity, rollback);
+		ret = uvc_ctrl_commit_entity(video->dev, entity, rollback);
 		if (ret < 0)
 			goto done;
 	}
 
 	list_for_each_entry(entity, &video->extensions, chain) {
-		ret = uvc_ctrl_commit_entity(video, entity, rollback);
+		ret = uvc_ctrl_commit_entity(video->dev, entity, rollback);
 		if (ret < 0)
 			goto done;
 	}
@@ -723,11 +737,56 @@
 		uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT), mapping);
 
 	ctrl->dirty = 1;
+	ctrl->modified = 1;
 	return 0;
 }
 
 
 /* --------------------------------------------------------------------------
+ * Suspend/resume
+ */
+
+/*
+ * Restore control values after resume, skipping controls that haven't been
+ * changed.
+ *
+ * TODO
+ * - Don't restore modified controls that are back to their default value.
+ * - Handle restore order (Auto-Exposure Mode should be restored before
+ *   Exposure Time).
+ */
+int uvc_ctrl_resume_device(struct uvc_device *dev)
+{
+	struct uvc_control *ctrl;
+	struct uvc_entity *entity;
+	unsigned int i;
+	int ret;
+
+	/* Walk the entities list and restore controls when possible. */
+	list_for_each_entry(entity, &dev->entities, list) {
+
+		for (i = 0; i < entity->ncontrols; ++i) {
+			ctrl = &entity->controls[i];
+
+			if (ctrl->info == NULL || !ctrl->modified ||
+			    (ctrl->info->flags & UVC_CONTROL_RESTORE) == 0)
+				continue;
+
+			printk(KERN_INFO "restoring control " UVC_GUID_FORMAT
+				"/%u/%u\n", UVC_GUID_ARGS(ctrl->info->entity),
+				ctrl->info->index, ctrl->info->selector);
+			ctrl->dirty = 1;
+		}
+
+		ret = uvc_ctrl_commit_entity(dev, entity, 0);
+		if (ret < 0)
+			return ret;
+	}
+
+	return 0;
+}
+
+/* --------------------------------------------------------------------------
  * Control and mapping handling
  */
 
Index: uvc_video.c
===================================================================
--- uvc_video.c	(revision 118)
+++ uvc_video.c	(working copy)
@@ -567,6 +567,9 @@
 			"completion handler.\n", urb->status);
 
 	case -ENOENT:		/* usb_kill_urb() called. */
+		if (queue->frozen)
+			return;
+
 	case -ECONNRESET:	/* usb_unlink_urb() called. */
 	case -ESHUTDOWN:	/* The endpoint is being disabled. */
 		uvc_queue_cancel(queue);
@@ -755,6 +758,104 @@
 	return 0;
 }
 
+/*
+ * Initialize isochronous/bulk URBs and allocate transfer buffers.
+ */
+static int uvc_init_video(struct uvc_video_device *video)
+{
+	struct usb_interface *intf = video->streaming->intf;
+	struct usb_host_interface *alts;
+	struct usb_host_endpoint *ep;
+	int intfnum = video->streaming->intfnum;
+	unsigned int bandwidth, psize, i;
+	int ret;
+
+	if (intf->num_altsetting > 1) {
+		/* Isochronous endpoint, select the alternate setting. */
+		bandwidth = video->streaming->ctrl.dwMaxPayloadTransferSize;
+
+		for (i = 0; i < intf->num_altsetting; ++i) {
+			alts = &intf->altsetting[i];
+			ep = uvc_find_endpoint(alts,
+					video->streaming->input.bEndpointAddress);
+			if (ep == NULL)
+				continue;
+
+			/* Check if the bandwidth is high enough. */
+			psize = le16_to_cpu(ep->desc.wMaxPacketSize);
+			psize = (psize & 0x07ff) * (1 + ((psize >> 11) & 3));
+			if (psize >= bandwidth)
+				break;
+		}
+
+		if (i >= intf->num_altsetting)
+			return -EIO;
+
+		if ((ret = usb_set_interface(video->dev->udev, intfnum, i)) < 0)
+			return ret;
+
+		return uvc_init_video_isoc(video, ep);
+	} else {
+		/* Bulk endpoint, proceed to URB initialization. */
+		ep = uvc_find_endpoint(&intf->altsetting[0],
+				video->streaming->input.bEndpointAddress);
+		if (ep == NULL)
+			return -EIO;
+
+		return uvc_init_video_bulk(video, ep);
+	}
+}
+
+/* --------------------------------------------------------------------------
+ * Suspend/resume
+ */
+
+/*
+ * Stop streaming without disabling the video queue.
+ *
+ * To let userspace applications resume without trouble, we must not touch the
+ * video buffers in any way. We mark the queue as frozen to make sure the URB
+ * completion handler won't try to cancel the queue when we kill the URBs.
+ */
+int uvc_video_suspend(struct uvc_video_device *video)
+{
+	if (!video->queue.streaming)
+		return 0;
+
+	video->queue.frozen = 1;
+	uvc_uninit_video(video);
+	usb_set_interface(video->dev->udev, video->streaming->intfnum, 0);
+	return 0;
+}
+
+/*
+ * Reconfigure the video interface and restart streaming if it was enable
+ * before suspend.
+ *
+ * If an error occurs, disable the video queue. This will wake all pending
+ * buffers, making sure userspace applications are notified of the problem
+ * instead of waiting forever.
+ */
+int uvc_video_resume(struct uvc_video_device *video)
+{
+	int ret;
+
+	video->queue.frozen = 0;
+
+	if ((ret = uvc_set_video_ctrl(video, &video->streaming->ctrl, 0)) < 0) {
+		uvc_queue_enable(&video->queue, 0);
+		return ret;
+	}
+
+	if (!video->queue.streaming)
+		return 0;
+
+	if ((ret = uvc_init_video(video)) < 0)
+		uvc_queue_enable(&video->queue, 0);
+
+	return ret;
+}
+
 /* ------------------------------------------------------------------------
  * Video device
  */
@@ -833,16 +934,12 @@
  */
 int uvc_video_enable(struct uvc_video_device *video, int enable)
 {
-	struct usb_interface *intf = video->streaming->intf;
-	struct usb_host_interface *alts;
-	struct usb_host_endpoint *ep;
-	int intfnum = video->streaming->intfnum;
-	unsigned int bandwidth, psize, i;
 	int ret;
 
 	if (!enable) {
 		uvc_uninit_video(video);
-		usb_set_interface(video->dev->udev, intfnum, 0);
+		usb_set_interface(video->dev->udev,
+			video->streaming->intfnum, 0);
 		uvc_queue_enable(&video->queue, 0);
 		return 0;
 	}
@@ -850,39 +947,6 @@
 	if ((ret = uvc_queue_enable(&video->queue, 1)) < 0)
 		return ret;
 
-	if (intf->num_altsetting > 1) {
-		/* Isochronous endpoint, select the alternate setting. */
-		bandwidth = video->streaming->ctrl.dwMaxPayloadTransferSize;
-
-		for (i = 0; i < intf->num_altsetting; ++i) {
-			alts = &intf->altsetting[i];
-			ep = uvc_find_endpoint(alts,
-					video->streaming->input.bEndpointAddress);
-			if (ep == NULL)
-				continue;
-
-			/* Check if the bandwidth is high enough. */
-			psize = le16_to_cpu(ep->desc.wMaxPacketSize);
-			psize = (psize & 0x07ff) * (1 + ((psize >> 11) & 3));
-			if (psize >= bandwidth)
-				break;
-		}
-
-		if (i >= intf->num_altsetting)
-			return -EIO;
-
-		if ((ret = usb_set_interface(video->dev->udev, intfnum, i)) < 0)
-			return ret;
-
-		return uvc_init_video_isoc(video, ep);
-	} else {
-		/* Bulk endpoint, proceed to URB initialization. */
-		ep = uvc_find_endpoint(&intf->altsetting[0],
-				video->streaming->input.bEndpointAddress);
-		if (ep == NULL)
-			return -EIO;
-
-		return uvc_init_video_bulk(video, ep);
-	}
+	return uvc_init_video(video);
 }
 
Index: uvc_driver.c
===================================================================
--- uvc_driver.c	(revision 120)
+++ uvc_driver.c	(working copy)
@@ -1599,6 +1599,45 @@
 	mutex_unlock(&uvc_driver.open_mutex);
 }
 
+static int uvc_suspend(struct usb_interface *intf, pm_message_t message)
+{
+	struct uvc_device *dev = usb_get_intfdata(intf);
+
+	uvc_trace(UVC_TRACE_SUSPEND, "Suspending interface %u\n",
+		intf->cur_altsetting->desc.bInterfaceNumber);
+
+	/* Controls are cached on the fly so they don't need to be saved. */
+	if (intf->cur_altsetting->desc.bInterfaceSubClass == SC_VIDEOCONTROL)
+		return 0;
+
+	if (dev->video.streaming->intf != intf) {
+		uvc_trace(UVC_TRACE_SUSPEND, "Suspend: video streaming USB "
+				"interface mismatch.\n");
+		return -EINVAL;
+	}
+
+	return uvc_video_suspend(&dev->video);
+}
+
+static int uvc_resume(struct usb_interface *intf)
+{
+	struct uvc_device *dev = usb_get_intfdata(intf);
+
+	uvc_trace(UVC_TRACE_SUSPEND, "Resuming interface %u\n",
+		intf->cur_altsetting->desc.bInterfaceNumber);
+
+	if (intf->cur_altsetting->desc.bInterfaceSubClass == SC_VIDEOCONTROL)
+		return uvc_ctrl_resume_device(dev);
+
+	if (dev->video.streaming->intf != intf) {
+		uvc_trace(UVC_TRACE_SUSPEND, "Resume: video streaming USB "
+				"interface mismatch.\n");
+		return -EINVAL;
+	}
+
+	return uvc_video_resume(&dev->video);
+}
+
 /* ------------------------------------------------------------------------
  * Driver initialization and cleanup
  */
@@ -1731,6 +1770,8 @@
 		.name		= "uvcvideo",
 		.probe		= uvc_probe,
 		.disconnect	= uvc_disconnect,
+		.suspend	= uvc_suspend,
+		.resume		= uvc_resume,
 		.id_table	= uvc_ids,
 	},
 };
Index: uvcvideo.h
===================================================================
--- uvcvideo.h	(revision 118)
+++ uvcvideo.h	(working copy)
@@ -333,7 +333,8 @@
 
 	__u8 index;	/* Used to match the uvc_control entry with a uvc_control_info. */
 	__u8 dirty : 1,
-	     loaded : 1;
+	     loaded : 1,
+	     modified : 1;
 
 	__u8 *data;
 };
@@ -498,7 +499,8 @@
 
 struct uvc_video_queue {
 	void *mem;
-	unsigned int streaming;
+	unsigned int streaming : 1,
+		     frozen : 1;
 	__u32 sequence;
 	__u8 last_fid;
 
@@ -534,6 +536,7 @@
 
 	struct uvc_video_queue queue;
 
+	/* Video streaming object, must always be non-NULL. */
 	struct uvc_streaming *streaming;
 
 	struct urb *urb[UVC_URBS];
@@ -603,6 +606,7 @@
 #define UVC_TRACE_CALLS		(1 << 5)
 #define UVC_TRACE_IOCTL		(1 << 6)
 #define UVC_TRACE_FRAME		(1 << 7)
+#define UVC_TRACE_SUSPEND	(1 << 8)
 
 extern unsigned int uvc_trace_param;
 
@@ -653,6 +657,8 @@
 
 /* Video */
 extern int uvc_video_init(struct uvc_video_device *video);
+extern int uvc_video_suspend(struct uvc_video_device *video);
+extern int uvc_video_resume(struct uvc_video_device *video);
 extern int uvc_video_enable(struct uvc_video_device *video, int enable);
 extern int uvc_probe_video(struct uvc_video_device *video,
 		struct uvc_streaming_control *probe);
@@ -671,6 +677,7 @@
 extern void uvc_ctrl_add_info(struct uvc_control_info *info);
 extern int uvc_ctrl_init_device(struct uvc_device *dev);
 extern void uvc_ctrl_cleanup_device(struct uvc_device *dev);
+extern int uvc_ctrl_resume_device(struct uvc_device *dev);
 extern void uvc_ctrl_init(void);
 
 extern int uvc_ctrl_begin(struct uvc_video_device *video);
_______________________________________________
Linux-uvc-devel mailing list
[email protected]
https://lists.berlios.de/mailman/listinfo/linux-uvc-devel

Reply via email to