Hi Laurent, UVCers,
the quirks parameter was recently introduced to the linux-uvc module,
and is very practical to knowingly force certain behavior.
While I was and am fighting with the ยง%&#! behavior of the SPCA525 ASIC
in my Logitech QuickCam Fusion 046d:08c1, I luckily came across the
"reset" patches, kindly linked from the QC Team site[*]. And the
patches seem to have helped me, but I'll follow up with more info once
I'm sure whether it works.
I understand why you don't want to make the hacked fixes default in the
code (it has been stressed many times on the list), but while Logitech
try to come up with a solution, perhaps a quirks based integration
would allow many users not to have to patch the driver. The code would
only become active if explicitly selected with a module parameter. I
believe that this is what the quirks parameter is perfectly made for,
and I consider it non-intrusive for the unknowing user.
I have ported the two relevant patches:
a) the reset patch as found in
http://lists.berlios.de/pipermail/linux-uvc-devel/2007-March/001476.html
b) the reset + resubmit urb patch, probably by Evgeny, found in
https://lists.berlios.de/pipermail/linux-uvc-devel/2008-January/002830.html
which adds a second mechanism to the first patch.
Instead of making the code changes "absolute"/"final" or #ifdef'd, I
made the sections depend on a corresponding quirks mask setting. (Note:
these are new quirks only defined by module parameter, not the kind
defined by default by the device model.)
The patch is attached. It introduces two new quirks UVC_QUIRK_RESET
(0x20, I would write it (1<<5)), and UVC_QUIRK_RESUBMIT_URB (0x40,
better (1<<6)). The functionality is separated.
The parameter quirks=32 has consistantly helped the initialization of
my Logitech QuickCam (incl. safe behavior through plug/unplug,
load/unload, start/stop of apps) on both my machines, and quirks=96 has
helped on my laptop through consistent failures (to be confirmed). I'm
not sure the reset urb section is needed on my desktop, I think that
machine has always behaved fine once the UVC device could init itself.
But activating the reset urb code via the quirk has not hurt either, as
far as I can tell. Sorry, I don't want to sound like I want this code
as default behavior - the opposite is the case! :-)
Your opinion is welcomed.
Patch is against SVN r195.
I've tested with Logitech QuickCam Fusion on two machines, both USB 1.1
and USB 2.0.
Kind regards,
and many thanks, especially to Laurent, Evgeny, and Michel,
Moritz
[*]
http://www.quickcamteam.net/documentation/faq/logitech-webcam-linux-usb-incompatibilities
diff -ur linux-uvc-trunk-r195/uvc_driver.c
linux-uvc-trunk-r195-reset_quirk_resubmit_urb/uvc_driver.c
--- linux-uvc-trunk-r195/uvc_driver.c 2008-03-20 10:59:20.000000000 +0100
+++ linux-uvc-trunk-r195-reset_quirk_resubmit_urb/uvc_driver.c 2008-03-20
14:49:03.000000000 +0100
@@ -1431,9 +1431,26 @@
* parameters.
*/
if ((ret = uvc_video_init(&dev->video)) < 0) {
- uvc_printk(KERN_ERR, "Failed to initialize the device "
- "(%d).\n", ret);
- return ret;
+ if (dev->quirks & UVC_QUIRK_RESET) {
+ uvc_printk(KERN_ERR, "Failed to initialize the device "
+ "(%d), trying to reset ...\n", ret);
+
+ if ((ret = uvc_usb_reset(dev)))
+ return ret;
+
+ if ((ret = uvc_video_init(&dev->video)) < 0) {
+ uvc_printk(KERN_ERR, "Failed to initialize the
device "
+ "(%d).\n", ret);
+ return ret;
+ }
+ }
+ else {
+ uvc_printk(KERN_ERR, "Failed to initialize the device "
+ "(%d).\n", ret);
+ return ret;
+
+ }
+
}
/* Register the device with V4L. */
@@ -1552,6 +1569,7 @@
dev->udev = usb_get_dev(udev);
dev->intf = usb_get_intf(intf);
dev->intfnum = intf->cur_altsetting->desc.bInterfaceNumber;
+ dev->last_urb = 0;
dev->quirks = id->driver_info | uvc_quirks_param;
/* Parse the Video Class control descriptor */
diff -ur linux-uvc-trunk-r195/uvc_status.c
linux-uvc-trunk-r195-reset_quirk_resubmit_urb/uvc_status.c
--- linux-uvc-trunk-r195/uvc_status.c 2008-02-10 22:53:53.000000000 +0100
+++ linux-uvc-trunk-r195-reset_quirk_resubmit_urb/uvc_status.c 2008-03-20
15:08:02.000000000 +0100
@@ -96,9 +96,20 @@
/* Resubmit the URB. */
urb->interval = dev->int_ep->desc.bInterval;
if ((ret = usb_submit_urb(urb, GFP_ATOMIC)) < 0) {
- uvc_printk(KERN_ERR, "Failed to resubmit status URB (%d).\n",
- ret);
+ if (dev->quirks & UVC_QUIRK_RESUBMIT_URB) {
+ if (ret == -EL2NSYNC) {
+ if ((ret = usb_submit_urb(urb, GFP_ATOMIC)) <
0) {
+ uvc_printk(KERN_ERR, "Failed to
resubmit status URB (%d).\n",
+ ret);
+ }
+ }
+ else {
+ uvc_printk(KERN_ERR, "Failed to resubmit status
URB (%d).\n",
+ ret);
+ }
+ }
}
+ dev->last_urb = jiffies;
}
int uvc_status_init(struct uvc_device *dev)
@@ -106,6 +117,7 @@
struct usb_host_endpoint *ep = dev->int_ep;
unsigned int pipe;
int interval;
+ int ret = 0;
if (ep == NULL)
return 0;
@@ -128,7 +140,21 @@
dev->status, sizeof dev->status, uvc_status_complete,
dev, interval);
- return usb_submit_urb(dev->int_urb, GFP_KERNEL);
+ if (dev->quirks & UVC_QUIRK_RESUBMIT_URB) {
+ if ((ret = usb_submit_urb(dev->int_urb, GFP_KERNEL)) < 0) {
+ if (ret == -EL2NSYNC) {
+ if ((ret = usb_submit_urb(dev->int_urb,
GFP_KERNEL)) < 0) {
+ uvc_printk(KERN_ERR, "Failed to
resubmit status URB (%d).\n",
+ ret);
+ }
+ }
+ }
+ dev->last_urb = jiffies;
+ return ret;
+ }
+ else {
+ return usb_submit_urb(dev->int_urb, GFP_KERNEL);
+ }
}
void uvc_status_cleanup(struct uvc_device *dev)
@@ -145,9 +171,24 @@
int uvc_status_resume(struct uvc_device *dev)
{
+ int ret = 0;
if (dev->int_urb == NULL)
return 0;
- return usb_submit_urb(dev->int_urb, GFP_KERNEL);
+ if (dev->quirks & UVC_QUIRK_RESUBMIT_URB) {
+ if ((ret = usb_submit_urb(dev->int_urb, GFP_KERNEL)) < 0) {
+ if (ret == -EL2NSYNC) {
+ if ((ret = usb_submit_urb(dev->int_urb,
GFP_KERNEL)) < 0) {
+ uvc_printk(KERN_ERR, "Failed to
resubmit status URB (%d).\n",
+ ret);
+ }
+ }
+ }
+ dev->last_urb = jiffies;
+ return ret;
+ }
+ else {
+ return usb_submit_urb(dev->int_urb, GFP_KERNEL);
+ }
}
diff -ur linux-uvc-trunk-r195/uvc_v4l2.c
linux-uvc-trunk-r195-reset_quirk_resubmit_urb/uvc_v4l2.c
--- linux-uvc-trunk-r195/uvc_v4l2.c 2008-03-20 10:59:20.000000000 +0100
+++ linux-uvc-trunk-r195-reset_quirk_resubmit_urb/uvc_v4l2.c 2008-03-20
11:04:04.000000000 +0100
@@ -456,6 +456,12 @@
mutex_unlock(&video->queue.mutex);
}
+ if (video->dev->quirks & UVC_QUIRK_RESET) {
+ /* leave usb device in a clean state */
+ if (video->dev->state & UVC_DEV_IOERROR)
+ uvc_video_reinit(video);
+ }
+
/* Release the file handle. */
uvc_dismiss_privileges(handle);
kfree(handle);
diff -ur linux-uvc-trunk-r195/uvc_video.c
linux-uvc-trunk-r195-reset_quirk_resubmit_urb/uvc_video.c
--- linux-uvc-trunk-r195/uvc_video.c 2008-03-20 10:59:20.000000000 +0100
+++ linux-uvc-trunk-r195-reset_quirk_resubmit_urb/uvc_video.c 2008-03-20
14:41:40.000000000 +0100
@@ -39,24 +39,69 @@
__u8 type = USB_TYPE_CLASS | USB_RECIP_INTERFACE;
unsigned int pipe;
int ret;
+ int delayed = 0;
pipe = (query & 0x80) ? usb_rcvctrlpipe(dev->udev, 0)
: usb_sndctrlpipe(dev->udev, 0);
type |= (query & 0x80) ? USB_DIR_IN : USB_DIR_OUT;
+ if (dev->quirks & UVC_QUIRK_RESUBMIT_URB) {
+ if (dev->last_urb) {
+ while (time_before(jiffies, dev->last_urb + 2)) {
+ schedule();
+ delayed = 1;
+ }
+ }
+ }
+
ret = usb_control_msg(dev->udev, pipe, query, type, cs << 8,
unit << 8 | intfnum, data, size, timeout);
+ dev->last_urb = jiffies;
if (ret != size) {
uvc_printk(KERN_ERR, "Failed to query (%u) UVC control %u "
"(unit %u) : %d (exp. %u).\n", query, cs, unit, ret,
size);
+ if (dev->quirks & UVC_QUIRK_RESUBMIT_URB) {
+ if (delayed) {
+ uvc_printk(KERN_ERR,"usb_control_msg was
delayed\n");
+ } else {
+ uvc_printk(KERN_ERR,"usb_control_msg was NOT
delayed\n");
+ }
+ }
+ if (ret == -ETIMEDOUT) // reset the device in case of -110 error
+ dev->state |= UVC_DEV_IOERROR;
+
+ dev->state |= UVC_DEV_IOERROR;
return -EIO;
}
return 0;
}
+int uvc_usb_reset(struct uvc_device *dev)
+{
+ int l, ret;
+
+ l = usb_lock_device_for_reset(dev->udev, dev->intf);
+
+ if (l >= 0) {
+ ret = usb_reset_device(dev->udev);
+ if (l)
+ usb_unlock_device(dev->udev);
+ }
+ else
+ ret = -EBUSY;
+
+ if (ret)
+ uvc_printk(KERN_DEBUG, "uvc_usb_reset: Unable to reset usb
device"
+ "(%d).\n", ret);
+ else
+ dev->state &= ~UVC_DEV_IOERROR;
+
+ return ret;
+}
+
int uvc_query_ctrl(struct uvc_device *dev, __u8 query, __u8 unit,
__u8 intfnum, __u8 cs, void *data, __u16 size)
{
@@ -535,9 +580,20 @@
video->decode(urb, video, buf);
if ((ret = usb_submit_urb(urb, GFP_ATOMIC)) < 0) {
- uvc_printk(KERN_ERR, "Failed to resubmit video URB (%d).\n",
- ret);
- }
+ if (video->dev->quirks & UVC_QUIRK_RESUBMIT_URB) {
+ if (ret == -EL2NSYNC) {
+ if ((ret = usb_submit_urb(urb, GFP_ATOMIC)) <
0) {
+ uvc_printk(KERN_ERR, "Failed to
resubmit video URB (%d).\n",
+ ret);
+ }
+ }
+ }
+ else {
+ uvc_printk(KERN_ERR, "Failed to resubmit video URB
(%d).\n",
+ ret);
+ }
+ }
+ video->dev->last_urb = jiffies;
}
/*
@@ -684,6 +740,25 @@
return 0;
}
+/*
+ * Reset and Re-Initialize video device
+ */
+int uvc_video_reinit(struct uvc_video_device *video)
+{
+ int ret;
+
+ if ((ret = uvc_usb_reset(video->dev)) < 0)
+ return ret;
+
+ if ((ret = uvc_set_video_ctrl(video, &video->streaming->ctrl, 0)) < 0) {
+ uvc_printk(KERN_DEBUG, "uvc_video_reinit: Unable to commit
format "
+ "(%d).\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
/*
* Initialize isochronous/bulk URBs and allocate transfer buffers.
*/
@@ -755,6 +830,7 @@
return ret;
}
}
+ video->dev->last_urb = jiffies;
return 0;
}
diff -ur linux-uvc-trunk-r195/uvcvideo.h
linux-uvc-trunk-r195-reset_quirk_resubmit_urb/uvcvideo.h
--- linux-uvc-trunk-r195/uvcvideo.h 2008-03-20 10:59:20.000000000 +0100
+++ linux-uvc-trunk-r195-reset_quirk_resubmit_urb/uvcvideo.h 2008-03-20
14:33:51.000000000 +0100
@@ -299,6 +299,8 @@
#define UVC_QUIRK_PROBE_EXTRAFIELDS 0x00000004
#define UVC_QUIRK_BUILTIN_ISIGHT 0x00000008
#define UVC_QUIRK_STREAM_NO_FID 0x00000010
+#define UVC_QUIRK_RESET 0x00000020
+#define UVC_QUIRK_RESUBMIT_URB 0x00000040
/* Format flags */
#define UVC_FMT_FLAG_COMPRESSED 0x00000001
@@ -588,6 +590,7 @@
enum uvc_device_state {
UVC_DEV_DISCONNECTED = 1,
+ UVC_DEV_IOERROR = 2,
};
struct uvc_device {
@@ -597,6 +600,7 @@
int intfnum;
enum uvc_device_state state;
+ unsigned long last_urb;
struct kref kref;
struct list_head list;
@@ -702,6 +706,8 @@
extern struct file_operations uvc_fops;
/* Video */
+extern int uvc_usb_reset(struct uvc_device *dev);
+extern int uvc_video_reinit(struct uvc_video_device *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);
_______________________________________________
Linux-uvc-devel mailing list
[email protected]
https://lists.berlios.de/mailman/listinfo/linux-uvc-devel