程序项目代做,有需求私信(小程序、网站、爬虫、电路板设计、驱动、应用程序开发、毕设疑难问题处理等)

linux驱动移植-USB摄像头uvc驱动

在前面的章节我们已经介绍了usb鼠标驱动的移植,那我们是不是通过该usb接口扩展一些其它的外设呢,比如usb键盘、usb声卡、usb摄像头;正好我手上有一款usb摄像头,同时带有麦克风功能,我们尝试一下能不能自己移植一个usb摄像头驱动。

一、前言

S3C2440内核采用的ARM9架构,型号为ARM920T,其可接入的摄像头分为两类:

  • CAMER接口的摄像头;
  • USB接口接口的摄像头;

这里主要介绍usb摄像头的设备驱动程序。

1.1 概念介绍

1.1.1 UVC

UVC全称为USB Video Class,即:USB视频类,是一种为USB视频捕获设备定义的协议标准。

如今的主流操作系统(如Windows XP SP2 and later, Linux 2.4.6 and later, MacOS 10.5 and later)都已提供 UVC 设备驱动,因此符合 UVC 规格的硬件设备在不需要安装任何的驱动程序下即可在主机中正常使用。使用 UVC 技术的包括摄像头、数码相机、类比影像转换器、电视棒及静态影像相机等设备。

Linux UVC driver(uvc) 驱动适用于符合USB视频类规范的摄像头设备,它包括V4L2内核设备驱动和用户空间工具补丁。只要符合这类标准,则不同厂商的USB camera设备,不需要特定的driver就能在Linux下使用。

1.1.2 V4L2

简单的讲V4L2就是用来管理UVC设备的并且能够提供视频相关的一些API。那么这些API怎么使用或者能被谁使用呢。在Linux系统上有很多的开源软件能够支持V4L2。常见的有FFmpeg、opencv、Skype、Mplayer等等。

我们知道在linux下,一切设备皆是是文件,可以像访问普通文件一样对其进行读写。因此,我们不难猜出,V4L2提供的API实际上就是一系列read、write、open、ioctl等函数:

APP在应用层调用read、write、open等接口,调用库函数,触发swi软件异常,进入内核,最终会调用到驱动程序的open、read、write等等。

1.2 确认usb摄像头支持UVC

1.2.1 linux系统

我们首先将摄像头插入我们的台式机上,这里我们是连接到我们虚拟机的linux操作系统上,使用dmeg查看内核打印信息:

[  347.559618] usb 1-1: new high-speed USB device number 2 using ehci-pci
[  347.926071] usb 1-1: New USB device found, idVendor=0c45, idProduct=6340
[  347.926074] usb 1-1: New USB device strings: Mfr=2, Product=1, SerialNumber=0
[  347.926076] usb 1-1: Product: USB 2.0 Camera
[  347.926077] usb 1-1: Manufacturer: Sonix Technology Co., Ltd.
[  347.981873] media: Linux media interface: v0.10
[  347.986155] Linux video capture interface: v2.00
[  348.130562] usb 1-1: 3:1: cannot get freq at ep 0x84
[  349.190706] usbcore: registered new interface driver snd-usb-audio
[  349.194058] uvcvideo: Found UVC 1.00 device USB 2.0 Camera (0c45:6340)
[  349.217865] input: USB 2.0 Camera: USB Camera as /devices/pci0000:00/0000:00:11.0/0000:02:03.0/usb1/1-1/1-1:1.0/input/input6
[  349.218030] usbcore: registered new interface driver uvcvideo
[  349.218030] USB Video Class driver (1.1.1)
[  349.270678] usb 1-1: 3:1: cannot get freq at ep 0x84
[  349.374801] usb 1-1: 3:1: cannot get freq at ep 0x84
[  349.389464] retire_capture_urb: 12 callbacks suppressed

从输出信息可以看到:

  • usb主机控制器ehci识别到了我们的usb设备,并且为usb设备分配了一个地址为2,同时可以看到我们这是一个高速usb设备,即我们的usb设备采用usb 2.0通信协议。
  • 此外内核根据识别的usb设备信息,进行设备驱动匹配,匹配成功后,注册usb接口驱动snd-usb-audio(声卡驱动)、uvcvideo(usb摄像头驱动)
  • 注册事件类输入设备节点/dev/input/event6,用于usb设备数据的上报;

此外,从输出信息中我们也可以得知usb设备的VID=0x0c45、PID=0x6340。

用 ls /dev/video* 查看设备节点:

root@zhengyang:~#  ls /dev/video*
/dev/video0
1.2.2 windows系统

我们将usb设备插入到window操作系统,我们打开设备管理器:

右键属性 -> 详细信息 –> 属性 选择硬件 Id 查看:

 

可以得到插的usb摄像头VID=0x0c45、PID=0x6340。

此外我这usb摄像头还自带麦克风,因此也可以在设备管理器中看到:

1.3 确定usb摄像头种类

通过这个网页 http://www.ideasonboard.org/uvc/ 来查看是否支持 UVC,这个网站是 USB Video Class Linux device driver 的主页,里面有 UVC 的详细的介绍。

根据前面的打印信息,根据自己的 ID 号,这里是搜索 usb摄像头的VID=0x0c45、PID=0x6340。

 

 从图上我们只找到了0c45:6310,实际上我们的usb摄像头是支持UVC的,可以在linux系统下查看:

root@zhengyang:~# lsusb -d 0c45:6340 -v | grep "14 Video"
      bFunctionClass         14 Video
      bInterfaceClass        14 Video
      bInterfaceClass        14 Video
      bInterfaceClass        14 Video
      bInterfaceClass        14 Video
      bInterfaceClass        14 Video
      bInterfaceClass        14 Video
      bInterfaceClass        14 Video
      bInterfaceClass        14 Video

如果该摄像头兼容UVC,则会输出上面类似信息,若无以上信息,则是non-UVC设备。

1.4 测试摄像头

在linux下,安装 xawtv 测试软件:

root@zhengyang:~# sudo apt-get install xawtv

执行xawtv后面带usb摄像头的设备节点:

root@zhengyang:~# xawtv /dev/video0
This is xawtv-3.103, running on Linux/x86_64 (4.15.0-142-generic)
xinerama 0: 2560x1440+0+0
Xlib:  extension "XVideo" missing on display "localhost:10.0".
Xlib:  extension "XVideo" missing on display "localhost:10.0".
vid-open-auto: using grabber/webcam device /dev/video0
Alsa devices: cap: hw:1,0 (/dev/video0), out: default
Warning: Missing charsets in String to FontSet conversion

如果在运行时出现如下错误:v4l2:oops:select timeout,且界面显示的图像卡顿:

解决办法:修改虚拟机设置,将USB控制器选项中的 USB 兼容性 选择为 USB 3.0 即可。

可以看到如下图像:

 二、内核配置UVC

确定usb摄像头在台式机上可以使用之后,就需要让它在我们的开发板也能用上这个摄像头。但是将摄像头接插接入Mini2440开发版的usb接口,发现内核却没显示 PC机上打印的信息。

2.1 配置内核

默认情况是没有编译UVC驱动进内核的,我们运行make menuconfig,进行如下配置(下面只把需要变更的配置项展示出来了):

Device Drivers -->

<*> Multimedia support --->

[*]Cameras/video grabbers support

[*]Media Controller API(NEW)

[*]V4L2 sub-device userspace API(NEW)

[*] Media USB Adapters(启用usb总线的媒体驱动程序,drivers/media/usb/*)-->

<*> USB Video Class(UVC)(我们的摄像头支持UVC,选择这个驱动即可)

[*] UVC input events device  supports(NEW)

<> GSPCA based webcams --> (GSPCA 是一个法国程序员在业余时间制作的一个万能USB 摄像头驱动程序,在此可以选择对应类型USB摄像头的支持)

<> SONIX Bayer USB Camer Driver (NEW)

<> OV772x/OV965x/... 系列摄像头支持

<> ....  

[*]V4L platform devices

<*> USB support  -->

[*] USB announce new devices(输出识别的每个usb设备的基本信息,比如idVendor、idProduct、制造商、产品、和序列号等)

修改完配置后,保存文件,输入文件名s3c2440_defconfig,在当前路径下生成s3c2440_defconfig:存档:

mv s3c2440_defconfig ./arch/arm/configs/

此时重新执行:

make s3c2440_defconfig

2.2 编译内核和模块

如果修改了内核代码,则需要重新编译内核:

root@zhengyang:~# cd /work/sambashare/linux-5.2.8/
make V=1 uImage

将uImage复制到tftp服务器路径下:

 cp /work/sambashare/linux-5.2.8/arch/arm/boot/uImage /work/tftpboot/

2.3 烧录内核

开发板uboot启动完成后,内核启动前,按下任意键,进入uboot,可以通过print查看uboot中已经设置的环境变量。

设置开发板ip地址,从而可以使用网络服务:

SMDK2440 # set ipaddr 192.168.0.105
SMDK2440 # save
Saving Environment to NAND...
Erasing NAND...

Erasing at 0x40000 -- 100% complete.
Writing to NAND... OK
SMDK2440 # ping 192.168.0.200
dm9000 i/o: 0x20000000, id: 0x90000a46 
DM9000: running in 16 bit mode
MAC: 08:00:3e:26:0a:5b
operating at unknown: 0 mode
Using dm9000 device
host 192.168.0.200 is alive

设置tftp服务器地址,也就是我们ubuntu服务器地址:

set serverip 192.168.0.200
save

下载内核到内存,并写NAND FLASH:

tftp 30000000 uImage
nand erase.part kernel
nand write 30000000 kernel
bootm

开发板启动后,插入我们的usb摄像头:

usb 1-1: new full-speed USB device number 2 using s3c2410-ohci
random: fast init done
usb 1-1: New USB device found, idVendor=0c45, idProduct=6340, bcdDevice= 0.00
usb 1-1: New USB device strings: Mfr=2, Product=1, SerialNumber=0
usb 1-1: Product: USB 2.0 Camera
usb 1-1: Manufacturer: Sonix Technology Co., Ltd.
uvcvideo: Found UVC 1.00 device USB 2.0 Camera (0c45:6340)
uvcvideo 1-1:1.0: Entity type for entity Extension 4 was not initialized!
uvcvideo 1-1:1.0: Entity type for entity Processing 3 was not initialized!
uvcvideo 1-1:1.0: Entity type for entity Camera 1 was not initialized!
input: USB 2.0 Camera: USB Camera as /devices/platform/s3c2410-ohci/usb1/1-1/1-1:1.0/input/input0

从上面的信息可以看到这里,已经识别到了我们的usb摄像头,VID=0c45、PID=6340,并且注册了事件类输入设备节点/dev/intput/event0:

[root@zy:/]# ls /dev/input/event0 -l
crw-rw----    1 0        0          13,  64 Jan  1 00:00  /dev/input/event

可以看到主设备号为13,次设备号从64,设备类型为字符设备。

同时我们可以用 ls /dev/video0 -l 查看设备节点:

[root@zy:/]# ls /dev/video0 -l 
crw-rw----    1 0        0          81,   0 Jan  1 00:00 35m/dev/video0

三、UVC驱动分析

在linux内核5.2.8中,UVC的驱动在drivers/media/usb/uvc/文件夹下,如下:

Kconfig   uvc_ctrl.c     uvc_driver.c  uvc_isight.c    uvc_queue.c   uvc_v4l2.c   uvcvideo.h
Makefile  uvc_debugfs.c  uvc_entity.c  uvc_metadata.c  uvc_status.c  uvc_video.c

3.1 Kconfig

我们首先来看一下Kconfig文件,这个文件定义了配置项USB_VIDEO_CLASS,和USB_VIDEO_CLASS_INPUT_EVDEV:

# SPDX-License-Identifier: GPL-2.0-only
config USB_VIDEO_CLASS
        tristate "USB Video Class (UVC)"
        depends on VIDEO_V4L2
        select VIDEOBUF2_VMALLOC
        help
          Support for the USB Video Class (UVC).  Currently only video
          input devices, such as webcams, are supported.

          For more information see: <http://linux-uvc.berlios.de/>

config USB_VIDEO_CLASS_INPUT_EVDEV
        bool "UVC input events device support"
        default y
        depends on USB_VIDEO_CLASS
        depends on USB_VIDEO_CLASS=INPUT || INPUT=y
        help
          This option makes USB Video Class devices register an input device
          to report button events.

          If you are in doubt, say Y.
~

我们再来看一下Makefile文件:

# SPDX-License-Identifier: GPL-2.0
uvcvideo-objs  := uvc_driver.o uvc_queue.o uvc_v4l2.o uvc_video.o uvc_ctrl.o \
                  uvc_status.o uvc_isight.o uvc_debugfs.o uvc_metadata.o
ifeq ($(CONFIG_MEDIA_CONTROLLER),y)
uvcvideo-objs  += uvc_entity.o
endif
obj-$(CONFIG_USB_VIDEO_CLASS) += uvcvideo.o

可以看到内核编译时配置了CONFIG_USB_VIDEO_CLASS,将会编译uvcvideo.o文件,该文件是由以下依赖链接生成:

uvcvideo-objs  := uvc_driver.o uvc_queue.o uvc_v4l2.o uvc_video.o uvc_ctrl.o \
                  uvc_status.o uvc_isight.o uvc_debugfs.o uvc_metadata.o

3.2 uvc_driver.c

uvc_driver.c文件是UVC驱动模块的入口文件,我们定位到模块的入口和出口函数:

static int __init uvc_init(void)
{
        int ret;

        uvc_debugfs_init();

        ret = usb_register(&uvc_driver.driver);
        if (ret < 0) {
                uvc_debugfs_cleanup();
                return ret;
        }

        printk(KERN_INFO DRIVER_DESC " (" DRIVER_VERSION ")\n");
        return 0;
}

static void __exit uvc_cleanup(void)
{
        usb_deregister(&uvc_driver.driver);
        uvc_debugfs_cleanup();
}

module_init(uvc_init);
module_exit(uvc_cleanup);

由于UVC属于usb设备,所以在uvc_init函数,通过usb_register进行了usb接口驱动uvc_driver.driver的注册。

3.2.1 uvc_driver
struct uvc_driver {
    struct usb_driver driver;
};
struct uvc_driver uvc_driver = {
        .driver = {
                .name           = "uvcvideo",
                .probe          = uvc_probe,
                .disconnect     = uvc_disconnect,
                .suspend        = uvc_suspend,
                .resume         = uvc_resume,
                .reset_resume   = uvc_reset_resume,
                .id_table       = uvc_ids,
                .supports_autosuspend = 1,
        },
};

当在系统中insmod装载该驱动程序时,会在入口函数直接注册usb_drvier结构体,通过比较usb设备提供的信息,和id_table比较,若匹配,则表明驱动支持该usb。uvc_ids里面可以填充某个厂家特定的设备,也可以填充通用的设备,如下所示:

/*
 * The Logitech cameras listed below have their interface class set to
 * VENDOR_SPEC because they don't announce themselves as UVC devices, even
 * though they are compliant.
 */
static const struct usb_device_id uvc_ids[] = {
        /* LogiLink Wireless Webcam */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x0416,
          .idProduct            = 0xa91a,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_probe_minmax },
        /* Genius eFace 2025 */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x0458,
          .idProduct            = 0x706e,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_probe_minmax },
        /* Microsoft Lifecam NX-6000 */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x045e,
          .idProduct            = 0x00f8,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_probe_minmax },
        /* Microsoft Lifecam NX-3000 */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x045e,
          .idProduct            = 0x0721,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_probe_def },
        /* Microsoft Lifecam VX-7000 */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x045e,
          .idProduct            = 0x0723,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_probe_minmax },
 /* Logitech Quickcam Fusion */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x046d,
          .idProduct            = 0x08c1,
          .bInterfaceClass      = USB_CLASS_VENDOR_SPEC,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0 },
        /* Logitech Quickcam Orbit MP */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x046d,
          .idProduct            = 0x08c2,
          .bInterfaceClass      = USB_CLASS_VENDOR_SPEC,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0 },
        /* Logitech Quickcam Pro for Notebook */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x046d,
          .idProduct            = 0x08c3,
          .bInterfaceClass      = USB_CLASS_VENDOR_SPEC,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0 },
        /* Logitech Quickcam Pro 5000 */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x046d,
          .idProduct            = 0x08c5,
          .bInterfaceClass      = USB_CLASS_VENDOR_SPEC,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0 },
        /* Logitech Quickcam OEM Dell Notebook */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x046d,
          .idProduct            = 0x08c6,
          .bInterfaceClass      = USB_CLASS_VENDOR_SPEC,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0 },
        /* Logitech Quickcam OEM Cisco VT Camera II */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x046d,
          .idProduct            = 0x08c7,
          .bInterfaceClass      = USB_CLASS_VENDOR_SPEC,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0 },
        /* Logitech HD Pro Webcam C920 */
  { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x046d,
          .idProduct            = 0x082d,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = UVC_INFO_QUIRK(UVC_QUIRK_RESTORE_CTRLS_ON_INIT) },
        /* Chicony CNF7129 (Asus EEE 100HE) */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x04f2,
          .idProduct            = 0xb071,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = UVC_INFO_QUIRK(UVC_QUIRK_RESTRICT_FRAME_RATE) },
        /* Alcor Micro AU3820 (Future Boy PC USB Webcam) */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x058f,
          .idProduct            = 0x3820,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_probe_minmax },
        /* Dell XPS m1530 */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x05a9,
          .idProduct            = 0x2640,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_probe_def },
        /* Dell SP2008WFP Monitor */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x05a9,
          .idProduct            = 0x2641,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_probe_def },
        /* Dell Alienware X51 */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x05a9,
          .idProduct            = 0x2643,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_probe_def },
        /* Dell Studio Hybrid 140g (OmniVision webcam) */
 { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x05a9,
          .idProduct            = 0x264a,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_probe_def },
        /* Dell XPS M1330 (OmniVision OV7670 webcam) */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x05a9,
          .idProduct            = 0x7670,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_probe_def },
        /* Apple Built-In iSight */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x05ac,
          .idProduct            = 0x8501,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = UVC_INFO_QUIRK(UVC_QUIRK_PROBE_MINMAX
                                        | UVC_QUIRK_BUILTIN_ISIGHT) },
        /* Apple Built-In iSight via iBridge */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x05ac,
          .idProduct            = 0x8600,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_probe_def },
        /* Foxlink ("HP Webcam" on HP Mini 5103) */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x05c8,
          .idProduct            = 0x0403,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_fix_bandwidth },
        /* Genesys Logic USB 2.0 PC Camera */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x05e3,
          .idProduct            = 0x0505,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_stream_no_fid },
        /* Hercules Classic Silver */
  { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x06f8,
          .idProduct            = 0x300c,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_fix_bandwidth },
        /* ViMicro Vega */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x0ac8,
          .idProduct            = 0x332d,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_fix_bandwidth },
        /* ViMicro - Minoru3D */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x0ac8,
          .idProduct            = 0x3410,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_fix_bandwidth },
        /* ViMicro Venus - Minoru3D */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x0ac8,
          .idProduct            = 0x3420,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_fix_bandwidth },
        /* Ophir Optronics - SPCAM 620U */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x0bd3,
          .idProduct            = 0x0555,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_probe_minmax },
        /* MT6227 */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x0e8d,
          .idProduct            = 0x0004,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = UVC_INFO_QUIRK(UVC_QUIRK_PROBE_MINMAX
                                        | UVC_QUIRK_PROBE_DEF) },
        /* IMC Networks (Medion Akoya) */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x13d3,
          .idProduct            = 0x5103,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_stream_no_fid },
        /* JMicron USB2.0 XGA WebCam */
  { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x152d,
          .idProduct            = 0x0310,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_probe_minmax },
        /* Syntek (HP Spartan) */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x174f,
          .idProduct            = 0x5212,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_stream_no_fid },
        /* Syntek (Samsung Q310) */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x174f,
          .idProduct            = 0x5931,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_stream_no_fid },
        /* Syntek (Packard Bell EasyNote MX52 */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x174f,
          .idProduct            = 0x8a12,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_stream_no_fid },
        /* Syntek (Asus F9SG) */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x174f,
          .idProduct            = 0x8a31,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_stream_no_fid },
        /* Syntek (Asus U3S) */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x174f,
          .idProduct            = 0x8a33,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_stream_no_fid },
        /* Syntek (JAOtech Smart Terminal) */
{ .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x174f,
          .idProduct            = 0x8a34,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_stream_no_fid },
        /* Miricle 307K */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x17dc,
          .idProduct            = 0x0202,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_stream_no_fid },
        /* Lenovo Thinkpad SL400/SL500 */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x17ef,
          .idProduct            = 0x480b,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_stream_no_fid },
        /* Aveo Technology USB 2.0 Camera */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x1871,
          .idProduct            = 0x0306,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = UVC_INFO_QUIRK(UVC_QUIRK_PROBE_MINMAX
                                        | UVC_QUIRK_PROBE_EXTRAFIELDS) },
        /* Aveo Technology USB 2.0 Camera (Tasco USB Microscope) */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x1871,
          .idProduct            = 0x0516,
          .bInterfaceClass      = USB_CLASS_VENDOR_SPEC,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0 },
        /* Ecamm Pico iMage */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x18cd,
          .idProduct            = 0xcafe,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = UVC_INFO_QUIRK(UVC_QUIRK_PROBE_EXTRAFIELDS) },
        /* Manta MM-353 Plako */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x18ec,
          .idProduct            = 0x3188,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_probe_minmax }
   /* FSC WebCam V30S */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x18ec,
          .idProduct            = 0x3288,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_probe_minmax },
        /* Arkmicro unbranded */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x18ec,
          .idProduct            = 0x3290,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_probe_def },
        /* The Imaging Source USB CCD cameras */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x199e,
          .idProduct            = 0x8102,
          .bInterfaceClass      = USB_CLASS_VENDOR_SPEC,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0 },
        /* Bodelin ProScopeHR */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_DEV_HI
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x19ab,
          .idProduct            = 0x1000,
          .bcdDevice_hi         = 0x0126,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = UVC_INFO_QUIRK(UVC_QUIRK_STATUS_INTERVAL) },
        /* MSI StarCam 370i */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x1b3b,
          .idProduct            = 0x2951,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_probe_minmax },
        /* Generalplus Technology Inc. 808 Camera */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x1b3f,
          .idProduct            = 0x2002,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_probe_minmax },
        /* SiGma Micro USB Web Camera */
  { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x1c4f,
          .idProduct            = 0x3000,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = UVC_INFO_QUIRK(UVC_QUIRK_PROBE_MINMAX
                                        | UVC_QUIRK_IGNORE_SELECTOR_UNIT) },
        /* Oculus VR Positional Tracker DK2 */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x2833,
          .idProduct            = 0x0201,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_force_y8 },
        /* Oculus VR Rift Sensor */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x2833,
          .idProduct            = 0x0211,
          .bInterfaceClass      = USB_CLASS_VENDOR_SPEC,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_force_y8 },
        /* Intel RealSense D4M */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x8086,
          .idProduct            = 0x0b03,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = UVC_INFO_META(V4L2_META_FMT_D4XX) },
        /* Generic USB Video Class */
        { USB_INTERFACE_INFO(USB_CLASS_VIDEO, 1, UVC_PC_PROTOCOL_UNDEFINED) },
        { USB_INTERFACE_INFO(USB_CLASS_VIDEO, 1, UVC_PC_PROTOCOL_15) },
        {}
};
View Code

数组最后一个元素:

        /*这个设备属于USB_CLASS_VIDEO类(14),子类是1,协议是0/1*/
        { USB_INTERFACE_INFO(USB_CLASS_VIDEO, 1, UVC_PC_PROTOCOL_UNDEFINED) },
        { USB_INTERFACE_INFO(USB_CLASS_VIDEO, 1, UVC_PC_PROTOCOL_15) },

假设我们接入一个usb摄像头,而这个usb摄像头的信息在uvc_ids中,驱动usb_driver.probe函数将会被调用。

3.2.2 uvc_probe
static int uvc_probe(struct usb_interface *intf,
                     const struct usb_device_id *id)
{
        struct usb_device *udev = interface_to_usbdev(intf);  // 根据usb接口获取usb设备对象
        struct uvc_device *dev;        // uvc设备
        const struct uvc_device_info *info =
                (const struct uvc_device_info *)id->driver_info;
        int function;
        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. */
        dev = kzalloc(sizeof(*dev), GFP_KERNEL);  // 动态申请内存空间
        if (dev == NULL)
                return -ENOMEM;

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

        dev->udev = usb_get_dev(udev);          // 初始化uvc设备
        dev->intf = usb_get_intf(intf);
        dev->intfnum = intf->cur_altsetting->desc.bInterfaceNumber;
        dev->info = info ? info : &uvc_quirk_none;
        dev->quirks = uvc_quirks_param == -1
                    ? dev->info->quirks : uvc_quirks_param;

        if (udev->product != NULL)
                strscpy(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));

        /*
         * Add iFunction or iInterface to names when available as additional
         * distinguishers between interfaces. iFunction is prioritized over
         * iInterface which matches Windows behavior at the point of writing.
         */
        if (intf->intf_assoc && intf->intf_assoc->iFunction != 0)
                function = intf->intf_assoc->iFunction;
        else
                function = intf->cur_altsetting->desc.iInterface;
        if (function != 0) {
                size_t len;

                strlcat(dev->name, ": ", sizeof(dev->name));
                len = strlen(dev->name);
                usb_string(udev, function, dev->name + len,
                           sizeof(dev->name) - len);
        }

        /* 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 != dev->info->quirks) {
                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");
        }

        /* Initialize the media device and register the V4L2 device. */
#ifdef CONFIG_MEDIA_CONTROLLER
        dev->mdev.dev = &intf->dev;
        strscpy(dev->mdev.model, dev->name, sizeof(dev->mdev.model));
        if (udev->serial)
                strscpy(dev->mdev.serial, udev->serial,
                        sizeof(dev->mdev.serial));
        usb_make_path(udev, dev->mdev.bus_info, sizeof(dev->mdev.bus_info));
        dev->mdev.hw_revision = le16_to_cpu(udev->descriptor.bcdDevice);
        media_device_init(&dev->mdev);

        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;

#ifdef CONFIG_MEDIA_CONTROLLER
        /* Register the media device node */
        if (media_device_register(&dev->mdev) < 0)
                goto error;
#endif
        /* 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);
        kref_put(&dev->ref, uvc_delete);
        return -ENODEV;
}

在这里我们看到了一个新的数据结构struct uvc_device,定义在drivers/media/usb/uvc/uvcvideo.h:

struct uvc_device {
        struct usb_device *udev;
        struct usb_interface *intf;
        unsigned long warnings;
        u32 quirks;
        int intfnum;
        char name[32];

        const struct uvc_device_info *info;

        struct mutex lock;              /* Protects users */
        unsigned int users;
        atomic_t nmappings;

        /* Video control interface */
#ifdef CONFIG_MEDIA_CONTROLLER
        struct media_device mdev;
#endif
        struct v4l2_device vdev;
        u16 uvc_version;
        u32 clock_frequency;

        struct list_head entities;
        struct list_head chains;

        /* Video Streaming interfaces */
        struct list_head streams;
        struct kref ref;

        /* Status Interrupt Endpoint */
        struct usb_host_endpoint *int_ep;
        struct urb *int_urb;
        u8 *status;
        struct input_dev *input;
        char input_phys[64];

        struct uvc_ctrl_work {
                struct work_struct work;
                struct urb *urb;
                struct uvc_video_chain *chain;
                struct uvc_control *ctrl;
                const void *data;
        } async_ctrl;
};
View Code

然后这里我们分析一下uvc_register_chains函数的调用链:

uvc_register_chains(dev)

uvc_register_terms(dev, chain);

uvc_register_video(dev, stream);

ret = uvc_video_init(stream);

video = video_device_alloc();

vdev->v4l2_dev = &dev->vdev;

video->fops = &uvc_fops;

vdev->release = uvc_release;

strlcpy(vdev->name, dev->name, sizeof vdev->name);

stream->vdev = vdev;

video_set_drvdata(vdev, stream);

video_register_device(video, VFL_TYPE_GRABBER, -1);   // 这里实际上就是注册一个video字符设备,名称为/dev/videoox,有兴趣的可以看看 

可以看到这里分配了一个video_device结构体,并进行成员的初始化,然后注册video_device结构体,其中fops成员设置为uvc_fops。

3.3 uvc_fops

uvc_fops定义在drivers/media/usb/uvc/uvc_v4l2.c:

const struct v4l2_file_operations uvc_fops = {
        .owner          = THIS_MODULE,
        .open           = uvc_v4l2_open,
        .release        = uvc_v4l2_release,
        .unlocked_ioctl = video_ioctl2,
#ifdef CONFIG_COMPAT
        .compat_ioctl32 = uvc_v4l2_compat_ioctl32,
#endif
        .read           = uvc_v4l2_read,
        .mmap           = uvc_v4l2_mmap,
        .poll           = uvc_v4l2_poll,
#ifndef CONFIG_MMU
        .get_unmapped_area = uvc_v4l2_get_unmapped_area,
#endif
};

我们由系统调用已经知道,应用程序在调用open函数打开/dev/videox设备时,最终调用uvc_v4l2_open函数,而调用close函数时,最终调用的是uvc_v4l2_release函数,其他函数也是类似。

mmap函数与poll函数由于需要bufferr的操作,所以在ioctl函数申请buffer之后才能进行。

3.3.1  uvc_v4l2_open
static int uvc_v4l2_open(struct file *file)
{
        struct uvc_streaming *stream;
        struct uvc_fh *handle;
        int ret = 0;

        uvc_trace(UVC_TRACE_CALLS, "uvc_v4l2_open\n");
        stream = video_drvdata(file);

        ret = usb_autopm_get_interface(stream->dev->intf);
        if (ret < 0)
                return ret;

        /* Create the device handle. */
        handle = kzalloc(sizeof(*handle), GFP_KERNEL);
        if (handle == NULL) {
                usb_autopm_put_interface(stream->dev->intf);
                return -ENOMEM;
        }

        mutex_lock(&stream->dev->lock);
        if (stream->dev->users == 0) {
                ret = uvc_status_start(stream->dev, GFP_KERNEL);
                if (ret < 0) {
                        mutex_unlock(&stream->dev->lock);
                        usb_autopm_put_interface(stream->dev->intf);
                        kfree(handle);
                        return ret;
                }
        }

        stream->dev->users++;
        mutex_unlock(&stream->dev->lock);

        v4l2_fh_init(&handle->vfh, &stream->vdev);
        v4l2_fh_add(&handle->vfh);
        handle->chain = stream->chain;
        handle->stream = stream;
        handle->state = UVC_HANDLE_PASSIVE;
        file->private_data = handle;

        return 0;
}
3.3.2 uvc_v4l2_release
static int uvc_v4l2_release(struct file *file)
{
        struct uvc_fh *handle = file->private_data;
        struct uvc_streaming *stream = handle->stream;

        uvc_trace(UVC_TRACE_CALLS, "uvc_v4l2_release\n");

        /* Only free resources if this is a privileged handle. */
        if (uvc_has_privileges(handle))
                uvc_queue_release(&stream->queue);

        /* Release the file handle. */
        uvc_dismiss_privileges(handle);
        v4l2_fh_del(&handle->vfh);
        v4l2_fh_exit(&handle->vfh);
        kfree(handle);
        file->private_data = NULL;

        mutex_lock(&stream->dev->lock);
        if (--stream->dev->users == 0)
                uvc_status_stop(stream->dev);
        mutex_unlock(&stream->dev->lock);

        usb_autopm_put_interface(stream->dev->intf);
        return 0;
}
3.3.3 video_ioctl2

这是ioctl操作函数,在内核中搜索这个名字:

root@zhengyang:/work/sambashare/linux-5.2.8# grep "video_ioctl2(struct" * -nR
drivers/media/v4l2-core/v4l2-ioctl.c:3094:long video_ioctl2(struct file *file,
include/media/v4l2-ioctl.h:732:long int video_ioctl2(struct file *file,

定位到函数:

long video_ioctl2(struct file *file,
               unsigned int cmd, unsigned long arg)
{
        return video_usercopy(file, cmd, arg, __video_do_ioctl);
}

在video_usercopy函数中,调用__video_do_ioctl()根据cmd从v4l2_ioctls[]数组里获取到对应的v4l2_ioctl_info,然后执行v4l2_ioctl_info.func函数,v4l2_ioctls[]数组是在内核中drivers/media/v4l2-core/v4l2-ioctl.c中定义:

static const struct v4l2_ioctl_info v4l2_ioctls[] = {
        IOCTL_INFO(VIDIOC_QUERYCAP, v4l_querycap, v4l_print_querycap, 0),
        IOCTL_INFO(VIDIOC_ENUM_FMT, v4l_enum_fmt, v4l_print_fmtdesc, INFO_FL_CLEAR(v4l2_fmtdesc, type)),
        IOCTL_INFO(VIDIOC_G_FMT, v4l_g_fmt, v4l_print_format, 0),
        IOCTL_INFO(VIDIOC_S_FMT, v4l_s_fmt, v4l_print_format, INFO_FL_PRIO),
        IOCTL_INFO(VIDIOC_REQBUFS, v4l_reqbufs, v4l_print_requestbuffers, INFO_FL_PRIO | INFO_FL_QUEUE),
        IOCTL_INFO(VIDIOC_QUERYBUF, v4l_querybuf, v4l_print_buffer, INFO_FL_QUEUE | INFO_FL_CLEAR(v4l2_buffer, length)),
        IOCTL_INFO(VIDIOC_G_FBUF, v4l_stub_g_fbuf, v4l_print_framebuffer, 0),
        IOCTL_INFO(VIDIOC_S_FBUF, v4l_stub_s_fbuf, v4l_print_framebuffer, INFO_FL_PRIO),
        IOCTL_INFO(VIDIOC_OVERLAY, v4l_overlay, v4l_print_u32, INFO_FL_PRIO),
        IOCTL_INFO(VIDIOC_QBUF, v4l_qbuf, v4l_print_buffer, INFO_FL_QUEUE),
        IOCTL_INFO(VIDIOC_EXPBUF, v4l_stub_expbuf, v4l_print_exportbuffer, INFO_FL_QUEUE | INFO_FL_CLEAR(v4l2_exportbuffer, flags)),
        IOCTL_INFO(VIDIOC_DQBUF, v4l_dqbuf, v4l_print_buffer, INFO_FL_QUEUE),
        IOCTL_INFO(VIDIOC_STREAMON, v4l_streamon, v4l_print_buftype, INFO_FL_PRIO | INFO_FL_QUEUE),
        IOCTL_INFO(VIDIOC_STREAMOFF, v4l_streamoff, v4l_print_buftype, INFO_FL_PRIO | INFO_FL_QUEUE),
        IOCTL_INFO(VIDIOC_G_PARM, v4l_g_parm, v4l_print_streamparm, INFO_FL_CLEAR(v4l2_streamparm, type)),
        IOCTL_INFO(VIDIOC_S_PARM, v4l_s_parm, v4l_print_streamparm, INFO_FL_PRIO),
        IOCTL_INFO(VIDIOC_G_STD, v4l_stub_g_std, v4l_print_std, 0),
        IOCTL_INFO(VIDIOC_S_STD, v4l_s_std, v4l_print_std, INFO_FL_PRIO),
        IOCTL_INFO(VIDIOC_ENUMSTD, v4l_enumstd, v4l_print_standard, INFO_FL_CLEAR(v4l2_standard, index)),
        IOCTL_INFO(VIDIOC_ENUMINPUT, v4l_enuminput, v4l_print_enuminput, INFO_FL_CLEAR(v4l2_input, index)),
        IOCTL_INFO(VIDIOC_G_CTRL, v4l_g_ctrl, v4l_print_control, INFO_FL_CTRL | INFO_FL_CLEAR(v4l2_control, id)),
        IOCTL_INFO(VIDIOC_S_CTRL, v4l_s_ctrl, v4l_print_control, INFO_FL_PRIO | INFO_FL_CTRL),
        IOCTL_INFO(VIDIOC_G_TUNER, v4l_g_tuner, v4l_print_tuner, INFO_FL_CLEAR(v4l2_tuner, index)),
        IOCTL_INFO(VIDIOC_S_TUNER, v4l_s_tuner, v4l_print_tuner, INFO_FL_PRIO),
        IOCTL_INFO(VIDIOC_G_AUDIO, v4l_stub_g_audio, v4l_print_audio, 0),
        IOCTL_INFO(VIDIOC_S_AUDIO, v4l_stub_s_audio, v4l_print_audio, INFO_FL_PRIO),
        IOCTL_INFO(VIDIOC_QUERYCTRL, v4l_queryctrl, v4l_print_queryctrl, INFO_FL_CTRL | INFO_FL_CLEAR(v4l2_queryctrl, id)),
        ...
};

在v4l2_ioctl_info .func函数中,以v4l_querycap函数为例,其又会调用uvc_ioctl_ops.vidioc_querycap,最终调用了是以uvc_ioctl_xxx命名的函数:

const struct v4l2_ioctl_ops uvc_ioctl_ops = {
        .vidioc_querycap = uvc_ioctl_querycap,
        .vidioc_enum_fmt_vid_cap = uvc_ioctl_enum_fmt_vid_cap,
        .vidioc_enum_fmt_vid_out = uvc_ioctl_enum_fmt_vid_out,
        .vidioc_g_fmt_vid_cap = uvc_ioctl_g_fmt_vid_cap,
        .vidioc_g_fmt_vid_out = uvc_ioctl_g_fmt_vid_out,
        .vidioc_s_fmt_vid_cap = uvc_ioctl_s_fmt_vid_cap,
        .vidioc_s_fmt_vid_out = uvc_ioctl_s_fmt_vid_out,
        .vidioc_try_fmt_vid_cap = uvc_ioctl_try_fmt_vid_cap,
        .vidioc_try_fmt_vid_out = uvc_ioctl_try_fmt_vid_out,
        .vidioc_reqbufs = uvc_ioctl_reqbufs,
        .vidioc_querybuf = uvc_ioctl_querybuf,
        .vidioc_qbuf = uvc_ioctl_qbuf,
        .vidioc_expbuf = uvc_ioctl_expbuf,
        .vidioc_dqbuf = uvc_ioctl_dqbuf,
        .vidioc_create_bufs = uvc_ioctl_create_bufs,
        .vidioc_streamon = uvc_ioctl_streamon,
        .vidioc_streamoff = uvc_ioctl_streamoff,
        .vidioc_enum_input = uvc_ioctl_enum_input,
        .vidioc_g_input = uvc_ioctl_g_input,
        .vidioc_s_input = uvc_ioctl_s_input,
        .vidioc_queryctrl = uvc_ioctl_queryctrl,
        .vidioc_query_ext_ctrl = uvc_ioctl_query_ext_ctrl,
        .vidioc_g_ctrl = uvc_ioctl_g_ctrl,
        .vidioc_s_ctrl = uvc_ioctl_s_ctrl,
        .vidioc_g_ext_ctrls = uvc_ioctl_g_ext_ctrls,
        .vidioc_s_ext_ctrls = uvc_ioctl_s_ext_ctrls,
        .vidioc_try_ext_ctrls = uvc_ioctl_try_ext_ctrls,
        .vidioc_querymenu = uvc_ioctl_querymenu,
        .vidioc_g_selection = uvc_ioctl_g_selection,
        .vidioc_g_parm = uvc_ioctl_g_parm,
        .vidioc_s_parm = uvc_ioctl_s_parm,
        .vidioc_enum_framesizes = uvc_ioctl_enum_framesizes,
        .vidioc_enum_frameintervals = uvc_ioctl_enum_frameintervals,
        .vidioc_subscribe_event = uvc_ioctl_subscribe_event,
        .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
        .vidioc_default = uvc_ioctl_default,
};

v4l2_ioctl_ops结构体中,定义了V4L2 API  ioctl中各种命令对应的实现函数。比如我们在应用层调用:

ioctl(fd, VIDIOC_QUERYCAP, cap);

最终会调用到内核中的uvc_ioctl_querycap。

3.3.4 uvc_ioctl_querycap

uvc_ioctl_querycap定义在drivers/media/usb/uvc/uvc_v4l2.c,用于查询设备具有的功能,cap变量中包含了该视频设备的能力信息:

static int uvc_ioctl_querycap(struct file *file, void *fh,
                              struct v4l2_capability *cap)
{
        struct video_device *vdev = video_devdata(file);
        struct uvc_fh *handle = file->private_data;
        struct uvc_video_chain *chain = handle->chain;
        struct uvc_streaming *stream = handle->stream;

        strscpy(cap->driver, "uvcvideo", sizeof(cap->driver));
        strscpy(cap->card, vdev->name, sizeof(cap->card));
        usb_make_path(stream->dev->udev, cap->bus_info, sizeof(cap->bus_info));
        cap->capabilities = V4L2_CAP_DEVICE_CAPS | V4L2_CAP_STREAMING
                          | chain->caps;

        return 0;
}

比如这里就是设置cap->driver = "uvcvideo"、cap->card=vdev->name,而vdev->name信息实际上是来自于usb摄像头对应的usb_device结构体的product成员(uvc_probe函数中拷贝),一般为USB 2.0 Camera,实际上uvc_probe函数后面还会修改dev->name属性信息:

       if (udev->product != NULL)
                strscpy(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));

参考文章:

[1] Linux USB 摄像头驱动

[2]基于S3C2440的Linux-3.6.6移植——基于UVC的USB摄像头移植及视频显示

[3]OV7725摄像头(第1节)—OV7725摄像头简介

[4]UVC驱动分析

[5]USB摄像头驱动--UVC驱动的深入理解与编写

[6]USB摄像头驱动框架分析

posted @ 2022-08-21 21:57  大奥特曼打小怪兽  阅读(7748)  评论(0编辑  收藏  举报
如果有任何技术小问题,欢迎大家交流沟通,共同进步