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) }, {} };
数组最后一个元素:
/*这个设备属于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; };
然后这里我们分析一下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));
参考文章:
[2]基于S3C2440的Linux-3.6.6移植——基于UVC的USB摄像头移植及视频显示
[4]UVC驱动分析
[6]USB摄像头驱动框架分析