1 2 版本:Android O、P Linux 4.9
问题描述 罗技 C270 摄像头插入 Android 单板后报 uvcvideo: Failed to query( GET_ DEF) UVC control2 on unit2:-110 异常错误。
1 2 3 4 5 6 7 8 9 10 usb1-3 : new high-speed USB device number 9 using ehci-platform uvcvideo: Found UVC 1.00 device <unnamed> (046 d:0825 ) input: UVC Camera (046 d:0825 ) as /devices/platform/soc/f9890000.ehci/usb1/1 -3 /1 -3 :1.0 /input/input8 usb1-3 : HotpLugThread timed out on ep0in len=0 /2 uvcvideo: Failed to query (GET_DEF) UVC control2 on unit2:-110 (exp .2 ). usb1-3 : HotpLugThread timed uvcvideo: Failed to query (GET_DEF) UVC control5 on unit2:-110 (exp .1 ). usb1-3 : HotpLugThread timed out onep0in len=0 /1 uvcvideo: Failed to query (GET_CUR) UVC control2 on unit1:-110 (exp .1 ). usb1-3 : set resolution quirk: cval->res = 384
问题原因 1、从 log 分析,Camera 插入单板, USB 枚举完成后,上层开始获取 Camera 支持的功能。如 “GET_ DEF UVC control2 on unit2”,”GET_ DEF UVC control5 on unit2”,就是在获取 video 相关的功能。但获取上述功能描述都失败了,上报 了-110 的错误码,即 timeout / 超时。
2、跟踪代码
log 打印位置在 “../drivers/media/usb/uvc/uvc_video.c +85” 的 uvc_query_ctrl() 函数内。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 int uvc_query_ctrl (struct uvc_device *dev, u8 query, u8 unit, u8 intfnum, u8 cs, void *data, u16 size) { int ret; u8 error; u8 tmp; ret = __uvc_query_ctrl(dev, query, unit, intfnum, cs, data, size, UVC_CTRL_CONTROL_TIMEOUT); if (likely(ret == size)) return 0 ; uvc_printk(KERN_ERR, "Failed to query (%s) UVC control %u on unit %u: %d (exp. %u).\n" , uvc_query_name(query), cs, unit, ret, size); if (ret != -EPIPE) return ret;
-110 是 __uvc_query_ctrl() 函数的返回值,ret 。
追踪 __uvc_query_ctrl() 函数,位于 “../drivers/media/usb/uvc/uvc_video.c +33”。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 static int __uvc_query_ctrl(struct uvc_device *dev, u8 query, u8 unit, u8 intfnum, u8 cs, void *data, u16 size, int timeout) { u8 type = USB_TYPE_CLASS | USB_RECIP_INTERFACE; unsigned int pipe; pipe = (query & 0x80 ) ? usb_rcvctrlpipe(dev->udev, 0 ) : usb_sndctrlpipe(dev->udev, 0 ); type |= (query & 0x80 ) ? USB_DIR_IN : USB_DIR_OUT; return usb_control_msg(dev->udev, pipe, query, type, cs << 8 , unit << 8 | intfnum, data, size, timeout); }
返回值则来自 usb_control_msg() 函数,追踪 __uvc_query_ctrl() 函数,位于 “../drivers/usb/core/message.c +135”。
1 2 3 4 5 6 7 8 dr->wIndex = cpu_to_le16(index); dr->wLength = cpu_to_le16(size); ret = usb_internal_control_msg(dev, pipe, dr, data, size, timeout);if (dev->quirks & USB_QUIRK_DELAY_CTRL_MSG) msleep(200 );
返回值则来自 usb_internal_control_msg() 函数,追踪 usb_internal_control_msg() 函数,位于 “../drivers/usb/core/message.c +85”。
1 2 3 4 5 6 7 8 usb_fill_control_urb(urb, usb_dev, pipe, (unsigned char *)cmd, data, len, usb_api_blocking_completion, NULL ); retv = usb_start_wait_urb(urb, timeout, &length);if (retv < 0 ) return retv;else return length;
返回值则来自 usb_start_wait_urb() 函数,追踪 usb_start_wait_urb() 函数,位于 “../drivers/usb/core/message.c +48”。
1 2 3 4 5 6 7 8 retval = usb_submit_urb(urb, GFP_NOIO);if (unlikely(retval)) goto out; expire = timeout ? msecs_to_jiffies(timeout) : MAX_SCHEDULE_TIMEOUT;if (!wait_for_completion_timeout(&ctx.done, expire)) { usb_kill_urb(urb); retval = (ctx.status == -ENOENT ? -ETIMEDOUT : ctx.status);
到这里,我们终于找到了 -110 的真身了,就是 wait_for_completion_timeout(&ctx.done, expire) 的状态 ctx.status 如果是 -ENOENT ,则上报 -ETIMEDOUT 即 -110 。具体意思就是系统等待 usb_submit_urb() (urb,USB 协议对传输数据封装的结构体)的完成,但是等待超时了。
一般 USB timeout 会设置为 5000ms,所以我倒追 timeout 值,它就是 __uvc_query_ctrl 函数的
UVC_CTRL_CONTROL_TIMEOUT,定义在 “../drivers/media/usb/uvc/uvcvideo.h +179”
1 #define UVC_CTRL_CONTROL_TIMEOUT 500
发现只有短短的 500ms,真的少。
3、USB 协议分析仪抓包
因不方便贴出 USB 协议分析仪抓取的协议包图片,所以用文字描述代替。
| 类型 | | 命令 | 内容 | 时间
SET_INTERFAEC | audio | class | SET_CUR | SAMPLING_FREQ_CONTROL | 1.310sec
SET_INTERFAEC | video | class | GET_DEF | PU_BRIGHTNESS_CONTROL | 1.752ms
类型指该 packet 是谁发出的,命令与内容就类似 JSON 的 key:value,设置当前的某个选项,时间就是花费的时间。具体如 audio 的 packet,设置当前的采样率,花费1.3秒。
4、上层对 Camera 的 audio 和 video 设置都要使用控制传输(Control Transaction),即使用 ep0 端点获取或设置 device 的 descriptor、interface。在上层, audio 和 video 是并行的,但 USB 管道(ep0)只有一条,数据传输都是要排着队的。从协议包来看,Camera 对 audio packet 一直回 NAK,堵塞控制传输管道1.3秒,导致 video 上层超时,进而报 -110 timeout。
5、虽然目前该问题没影响到 Camera 的功能,但也不是不可能的。在特殊情况下,存在 get descriptor 或 set interface 失败,而导致 host 不知道获取失败的 descriptor 对应的能力,例如丢失了 mjpeg 1280*720 30fps 的 descriptor,那 host 就默认device 不支持 mjpeg 1280*720 30fps。
解决方法 1、参考 kernel urb transaction 的超时一般设置是 5000ms。
2、考虑对其他设备的兼容性。
3、对 kernel v4l2 代码的信任。
决定将 UVC_CTRL_CONTROL_TIMEOUT 由 500 改为 1500。
1 #define UVC_CTRL_CONTROL_TIMEOUT 1500