简述

硬件:正点原子阿尔法开发板;某宝购买的免驱USB摄像头。

开发板环境:出厂固件,内核版本:linux-imx-4.1.15-2.1.0-g06f53e4-v2.1

问题现象:

1. 开发板插入USB摄像头能正常识别,但是uvcvideo.ko 报错No valid video chain found

2. 摄像头接入Ubuntu能正常使用

注: cannot get freq at ep 0x82这里是音频使用的endpoint报错,后面有空再研究。

原因及解决方案

原因

摄像头USB设备描述符中,OUTPUT_TERMINAL的 bSourceID 本该指向上一个描述符节点EXTENSION_UNIT,但是实际指向了空的节点,目测是厂商留下了的bug。最终导致建立不了video chain。

在豆包中再次得到确认:(现在AI可真好用)

最简单的解决方案

添加如下代码:

在uvc_probe()函数能够获取USB的PID和VID,判断是此USB摄像头,直接指定bSourceID到上一个正确的节点,我这里的id是3,指向EXTENSION_UNIT节点

我这里是自己使用,故直接偷懒:

=========================================================================

video chain解析过程

video chain解析过程其实是对USB描述符的VideoControl接口描述符进行遍历

设备描述符(简

Device Descriptor:
  Configuration Descriptor:
    ...
    Interface Descriptor:
      ...
      iInterface              5 USB Camera
      VideoControl Interface Descriptor:
        bDescriptorSubtype      1 (HEADER)
      VideoControl Interface Descriptor:
        bDescriptorSubtype      2 (INPUT_TERMINAL)
        bTerminalID             1                     # 当前单元 / 终端id
        wTerminalType      0x0201 Camera Sensor
      VideoControl Interface Descriptor:
        bDescriptorSubtype      5 (PROCESSING_UNIT)
        bUnitID                 2                        # 当前单元 / 终端id
        bSourceID               1                     # 等于INPUT_TERMINAL的bTerminalID
      VideoControl Interface Descriptor:
        bDescriptorSubtype      6 (EXTENSION_UNIT)
        bUnitID                 3                        # 当前单元 / 终端id
        baSourceID( 0)          2                  # 等于PROCESSING_UNIT的bUnitID
      VideoControl Interface Descriptor:
        bDescriptorSubtype      3 (OUTPUT_TERMINAL)
        bTerminalID             5                    # 当前单元 / 终端id
        bSourceID               4                    # 应为3(bUnitID),并指向EXTENSION_UNIT单元

可以看出每一个单元的【bSourceID】都指向上一个【单元/终端】,在代码中也是这样反向遍历的。

遍历过程:

检查到UVC设备后

uvc_probe()

| -> uvc_scan_device()                      # 扫描VideoControl接口

|     -> UVC_ENTITY_IS_OTERM      # 寻找 VideoControl中的output terminal终端

|         -> uvc_scan_chain()                # 反过来查找并添加到video chain链表

| -> uvc_register_chains()                  # 注册到 /dev/videoX

最终获取到的video chain链表

OUTPUT_TERMINAL -> EXTENSION_UNIT -> PROCESSING_UNIT

-> INPUT_TERMINAL

源码

static int uvc_probe(struct usb_interface *intf,
		     const struct usb_device_id *id)
{
	struct usb_device *udev = interface_to_usbdev(intf);
	struct uvc_device *dev;
	int ret;

	if (id->idVendor && id->idProduct)
		uvc_trace(UVC_TRACE_PROBE, "Probing known UVC device %s "
				"(%04x:%04x)\n", udev->devpath, id->idVendor,
				id->idProduct);
	else
		uvc_trace(UVC_TRACE_PROBE, "Probing generic UVC device %s\n",
				udev->devpath);

	/* Allocate memory for the device and initialize it. */
	if ((dev = kzalloc(sizeof *dev, GFP_KERNEL)) == NULL)
		return -ENOMEM;

	INIT_LIST_HEAD(&dev->entities);
	INIT_LIST_HEAD(&dev->chains);
	INIT_LIST_HEAD(&dev->streams);
	atomic_set(&dev->nstreams, 0);
	atomic_set(&dev->nmappings, 0);
	mutex_init(&dev->lock);

	dev->udev = usb_get_dev(udev);
	dev->intf = usb_get_intf(intf);
	dev->intfnum = intf->cur_altsetting->desc.bInterfaceNumber;
	dev->quirks = (uvc_quirks_param == -1)
		    ? id->driver_info : uvc_quirks_param;

	if (udev->product != NULL)
		strlcpy(dev->name, udev->product, sizeof dev->name);
	else
		snprintf(dev->name, sizeof dev->name,
			"UVC Camera (%04x:%04x)",
			le16_to_cpu(udev->descriptor.idVendor),
			le16_to_cpu(udev->descriptor.idProduct));

	/* Parse the Video Class control descriptor. */
	if (uvc_parse_control(dev) < 0) {
		uvc_trace(UVC_TRACE_PROBE, "Unable to parse UVC "
			"descriptors.\n");
		goto error;
	}

	uvc_printk(KERN_INFO, "Found UVC %u.%02x device %s (%04x:%04x)\n",
		dev->uvc_version >> 8, dev->uvc_version & 0xff,
		udev->product ? udev->product : "<unnamed>",
		le16_to_cpu(udev->descriptor.idVendor),
		le16_to_cpu(udev->descriptor.idProduct));

	if (dev->quirks != id->driver_info) {
		uvc_printk(KERN_INFO, "Forcing device quirks to 0x%x by module "
			"parameter for testing purpose.\n", dev->quirks);
		uvc_printk(KERN_INFO, "Please report required quirks to the "
			"linux-uvc-devel mailing list.\n");
	}

	/* Register the media and V4L2 devices. */
#ifdef CONFIG_MEDIA_CONTROLLER
	dev->mdev.dev = &intf->dev;
	strlcpy(dev->mdev.model, dev->name, sizeof(dev->mdev.model));
	if (udev->serial)
		strlcpy(dev->mdev.serial, udev->serial,
			sizeof(dev->mdev.serial));
	strcpy(dev->mdev.bus_info, udev->devpath);
	dev->mdev.hw_revision = le16_to_cpu(udev->descriptor.bcdDevice);
	dev->mdev.driver_version = LINUX_VERSION_CODE;
	if (media_device_register(&dev->mdev) < 0)
		goto error;

	dev->vdev.mdev = &dev->mdev;
#endif
	if (v4l2_device_register(&intf->dev, &dev->vdev) < 0)
		goto error;

	/* Initialize controls. */
	if (uvc_ctrl_init_device(dev) < 0)
		goto error;

	/* Scan the device for video chains. */
	if (uvc_scan_device(dev) < 0)
		goto error;

	/* Register video device nodes. */
	if (uvc_register_chains(dev) < 0)
		goto error;

	/* Save our data pointer in the interface data. */
	usb_set_intfdata(intf, dev);

	/* Initialize the interrupt URB. */
	if ((ret = uvc_status_init(dev)) < 0) {
		uvc_printk(KERN_INFO, "Unable to initialize the status "
			"endpoint (%d), status interrupt will not be "
			"supported.\n", ret);
	}

	uvc_trace(UVC_TRACE_PROBE, "UVC device initialized.\n");
	usb_enable_autosuspend(udev);
	return 0;

error:
	uvc_unregister_video(dev);
	return -ENODEV;
}

static int uvc_scan_device(struct uvc_device *dev)
{
	struct uvc_video_chain *chain;
	struct uvc_entity *term;

	list_for_each_entry(term, &dev->entities, list) {

		// 不是输出终端: 跳过
		if (!UVC_ENTITY_IS_OTERM(term))
			continue;

		/* If the terminal is already included in a chain, skip it.
		 * This can happen for chains that have multiple output
		 * terminals, where all output terminals beside the first one
		 * will be inserted in the chain in forward scans.
		 */
		if (term->chain.next || term->chain.prev)
			continue;

		chain = kzalloc(sizeof(*chain), GFP_KERNEL);
		if (chain == NULL)
			return -ENOMEM;

		INIT_LIST_HEAD(&chain->entities);
		mutex_init(&chain->ctrl_mutex);
		chain->dev = dev;
		v4l2_prio_init(&chain->prio);

		term->flags |= UVC_ENTITY_FLAG_DEFAULT;

		if (uvc_scan_chain(chain, term) < 0) {
			kfree(chain);
			continue;
		}

		uvc_trace(UVC_TRACE_PROBE, "Found a valid video chain (%s).\n",
			  uvc_print_chain(chain));

		list_add_tail(&chain->list, &dev->chains);
	}

	if (list_empty(&dev->chains)) {
		uvc_printk(KERN_INFO, "No valid video chain found. euan\n");
		return -1;
	}

	return 0;
}


static int uvc_scan_chain(struct uvc_video_chain *chain,
			  struct uvc_entity *term)
{
	struct uvc_entity *entity, *prev;

	uvc_trace(UVC_TRACE_PROBE, "Scanning UVC chain:");

	entity = term;
	prev = NULL;

	while (entity != NULL) {
		/* Entity must not be part of an existing chain */
		if (entity->chain.next || entity->chain.prev) {
			uvc_trace(UVC_TRACE_DESCR, "Found reference to "
				"entity %d already in chain.\n", entity->id);
			return -EINVAL;
		}

		/* Process entity */
		if (uvc_scan_chain_entity(chain, entity) < 0)
			return -EINVAL;

		/* Forward scan */
		if (uvc_scan_chain_forward(chain, entity, prev) < 0)
			return -EINVAL;

		/* Backward scan */
		prev = entity;
		if (uvc_scan_chain_backward(chain, &entity) < 0)
			return -EINVAL;
	}

	return 0;
}

驱动路径

/lib/modules/4.1.15-g06f53e4/kernel/drivers/media/usb/uvc/uvcvideo.ko

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐