v4l2框架usb-driver代码分析
USB摄像头驱动–UVC驱动的深入理解与编写uvc_driver代码分析嵌入式Linux驱动笔记------详解V4L2框架
驱动调用流程
分析内核摄像头驱动
对于linux内核10中,UVC驱动在drivers/media/usb/uvc/文件夹里,下面对uvc_driver.c进行分析。
构造一个usb_driver结构体
该结构体相当于告诉内核我这是一个USB设备,需要进行USB设备的相关函数调用。这里进行usb的设置。kernel/msm-14/drivers/media/usb/uvc/uvcvideo.h
struct uvc_driver {
struct usb_driver driver;
};
kernel/msm-14/include/linux/usb.h-----------structusb_driver标识usbcore的USB接口驱动程序
struct usb_driver {
//驱动程序名称在USB驱动程序中应是唯一的,通常应与模块名称相同。
>---const char *name;
//调用此函数以查看驱动程序是否愿意管理设备上的特定接口。
//如果是,probe返回零,并使用usb_set_intfdata()将特定于驱动程序的数据与接口关联。
//它还可以使用usb_set_interface()来指定适当的ALTSETING。
//如果不愿意管理接口,则返回-ENODEV,如果发生真正的IO错误,则返回适当的负errno值。
>---int (*probe) (struct usb_interface *intf,
>--->--- const struct usb_device_id *id);
//当接口不再可访问时调用,通常是因为其设备已(或正在)断开连接或驱动程序模块正在卸载。
>---void (*disconnect) (struct usb_interface *intf);
>---int (*unlocked_ioctl) (struct usb_interface *intf, unsigned int code,
>--->--->---void *buf);
//当设备将由系统从系统睡眠或运行时挂起上下文挂起时调用。
//返回值将在系统睡眠上下文中被忽略,因此如果在这种情况下挂起失败,请不要尝试继续使用设备。
//相反,让恢复或重置恢复例程从故障中恢复。
>---int (*suspend) (struct usb_interface *intf, pm_message_t message);
//当系统恢复设备时调用。
>---int (*resume) (struct usb_interface *intf);
//当挂起的设备被重置而不是恢复时调用。
>---int (*reset_resume)(struct usb_interface *intf);
>---int (*pre_reset)(struct usb_interface *intf);
>---int (*post_reset)(struct usb_interface *intf);
//USB驱动程序使用ID表支持热插拔。
//将其与MODULE_DEVICE_TABLE(usb,…)一起导出。必须设置此值,否则将永远不会调用驱动程序的探测函数。
>---const struct usb_device_id *id_table;
>---struct usb_dynids dynids;
>---struct usbdrv_wrap drvwrap; //驱动程序模型结构的包装器
>---unsigned int no_dynamic_id:1;
>---unsigned int supports_autosuspend:1;
>---unsigned int disable_hub_initiated_lpm:1;
>---unsigned int soft_unbind:1;
};
struct uvc_driver uvc_driver = {
.driver = {
.name = "uvcvideo",
.probe = uvc_probe, //支持的video设备插入就会进入
.disconnect = uvc_disconnect,
.suspend = uvc_suspend,
.resume = uvc_resume,
.reset_resume = uvc_reset_resume,
.id_table = uvc_ids, // .id_table 表示该USB驱动支持哪些设备
.supports_autosuspend = 1,
},
};
设置结构体uvc_probe代码分析
设置相当于将其中的函数定一下来,一开始可以写作空函数,以便判定框架是否正确。在probe函数中进行这个结构体的具体内容进行设置,包括分配video_device结构体,设置结构体,注册video_device结构体,fops结构体中需要进行ioctrl的具体操作进行设置。
uvc_probe
kzalloc //分配video_device
uvc_register_chains
uvc_register_terms
uvc_register_video
vdev->v4l2_dev = &dev->vdev; //设置video_device
vdev->fops = &uvc_fops;
vdev->ioctl_ops = &uvc_ioctl_ops;
vdev->release = uvc_release;
video_register_device //注册video_device
uvc_probe代码分析,当特定的usb设备被插入时,就会触发probe函数:函数太长了,省略了部分内容,但是可以看出,主要的就是做几件事情:分配一个dev给dev设置各种参数,如dev->udevudev调用uvc_parse_control函数分析设备的控制描述符调用v4l2_device_register函数初始化v4l2_dev调用uvc_ctrl_init_device函数初始化uvc控制设备调用uvc_register_chains函数注册所有通道调用uvc_status_init函数初始化uvc状态
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;
//完成产品相关信息的打印工作
/*
#define uvc_trace(flag, msg...)
do {
if (uvc_trace_param & flag)
printk(KERN_DEBUG "uvcvideo: " msg);
} while (0)
uvc_trace_param这个全局变量是通过用户空间来传值实现的
*/
if (id->idVendor && id->idProduct)
uvc_trace(UVC_TRACE_PROBE, "Probing known UVC device %s "
"(%04x:%04x)
", udev->devpath, id->idVendor,
id->idProduct);
else
uvc_trace(UVC_TRACE_PROBE, "Probing generic UVC device %s
",
udev->devpath);
/* Allocate memory for the device and initialize it. */
if ((dev = kzalloc(sizeof *dev, GFP_KERNEL)) == NULL) //【1】分配一个dev
return -ENOMEM;
/*初始化链表*/
INIT_LIST_HEAD(&dev->entities);//初始化entities(实体)链表 Terminal或Unit
INIT_LIST_HEAD(&dev->chains);//初始化chains(链)链表
INIT_LIST_HEAD(&dev->streams);//初始化streams(视频流)链表
/*设置原子变量的值,以便后续做原子操作*/
atomic_set(&dev->nstreams, 0);
atomic_set(&dev->users, 0);
atomic_set(&dev->nmappings, 0);
/*
//以下两个函数在写法上使用container_of()只需要简单的操作就能返回原结构体
usb_get_dev():通过struct usb_device --> struct device --> struct kobject
增加引用计数
usb_get_intf():通过struct usb_interface --> struct device --> struct kobject
增加引用计数
同时将struct usb_device及其struct usb_interface赋值给uvc结构体中的struct usb_device
和struct usb_interface 结构体成员
*/
dev->udev = usb_get_dev(udev); //【2】给dev设置各种参数,如dev->udevudev
dev->intf = usb_get_intf(intf);
/*通过接口找到接口描述符,这样得到接口数量*/
dev->intfnum = intf->cur_altsetting->desc.bInterfaceNumber;
//quirks: 怪癖的意思,也就是说它某种特性与通常的USB设备不相同
/*uvc_quirks_param 这个全局变量来自用户空间的输入*/
dev->quirks = (uvc_quirks_param == -1)
? id->driver_info : uvc_quirks_param;
/*
此段代码的主要功能是将strcut usb_device 中的一些产品的
信息(如产品号、版本号等)写入到struct uvc_device中,
主要涉及到 struct uvc_device -> char name[32]
*/
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. */
/*uvc解析usb视频类控制描述符*/
/*整个函数都是围绕着struct usb_host_interface 中的extra成员展开的*/
if (uvc_parse_control(dev) < 0) { //【3】调用uvc_parse_control函数分析设备的控制描述符
uvc_trace(UVC_TRACE_PROBE, "Unable to parse UVC "
"descriptors.
");
goto error;
}
uvc_printk(KERN_INFO, "Found UVC %u.%02x device %s (%04x:%04x)
",
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.
", dev->quirks);
uvc_printk(KERN_INFO, "Please report required quirks to the "
"linux-uvc-devel mailing list.
");
}
/* Register the media and V4L2 devices. */
/*
以下的操作均为对 struct uvc_device --> struct media_device 的
成员进行赋值操作,赋值的成员如下:
struct device *dev
char model[32]
char serial[40]
char bus_info[32]
u32 hw_revision
u32 driver_version
然后使用了media_device_register()函数进行media设备的注册工作
*/
#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 = DRIVER_VERSION_NUMBER;
/*
1.在下面的函数中发现了struct media_file_operations 其中有open、release、ioctl函数。
但是上述的函数都是空函数,没有任何意义
*/
if (media_device_register(&dev->mdev) < 0)
goto error;
/*
struct uvc_device --> strcut v412_device vdev -->struct media_device
*/
dev->vdev.mdev = &dev->mdev;
#endif
/*
【4】调用v4l2_device_register函数初始化v4l2_dev
【5】调用uvc_ctrl_init_device函数初始化uvc控制设备
【6】调用uvc_register_chains函数注册所有通道
【7】调用uvc_status_init函数初始化uvc状态
*/
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. */
/* 下面这个函数存在着以下调用关系
uvc_register_chains() --> uvc_register_terms() --> uvc_register_video()
函数初始化关系图如下:见笔记章节
其中uvc_register_video()函数非常重要!
因为这个函数中存在着 v4l2操作函数集(uvc_fops) 和 真正的ioctl操作
集(3.19以后的内核才存在vdev->ioctl_ops = &uvc_ioctl_ops;)
vdev->fops = &uvc_fops;
vdev->release = uvc_release;
*/
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. */
/*
uvc_status_init()这个函数主要有三个作用:
1.动态申请一个urb(interrupt urb)
2.给struct uvc_device --> _u8 *status 申请一个内存空间(作为urb的缓冲区)
3.初始化中断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.
", ret);
}
uvc_trace(UVC_TRACE_PROBE, "UVC device initialized.
");
//打开usb设备的自动挂起功能,以便实现低功耗的要求
usb_enable_autosuspend(udev);
return 0;
error:
uvc_unregister_video(dev);
return -ENODEV;
}
我们来一个个分析下::调用uvc_parse_control函数看下调用关系:
uvc_parse_control(dev)
uvc_parse_standard_control(dev, buffer, buflen)
uvc_parse_streaming(dev, intf)
跟踪下uvc_parse_streaming函数:
static int uvc_parse_streaming(struct uvc_device *dev,
struct usb_interface *intf)
{
/*以下大部分内容省略,只显示重要的*/
struct uvc_streaming *streaming = NULL;
struct uvc_format *format;
struct uvc_frame *frame;
streaming = kzalloc(sizeof *streaming, GFP_KERNEL);
size = nformats * sizeof *format + nframes * sizeof *frame
+ nintervals * sizeof *interval;
format = kzalloc(size, GFP_KERNEL);//申请format数组存放格式
streaming->format = format;//设置格式
streaming->nformats = nformats;//最多支持nformats种格式
ret = uvc_parse_format(dev, streaming, format,
&interval, buffer, buflen);//分析格式
list_add_tail(&streaming->list, &dev->streams);
return 0;
}
这里面申请了streaming和format内存streaming是uvc_streaming结构体,视频流,很重要,大部分参数都是存在里面。这函数里申请了之后进行了很多设置,不过现在我省略了写。format内存存放的是视频的格式,frame存放的是如分辨率这里面都把他设置到了streaming里面最后调用uvc_parse_format函数分析格式:
static int uvc_parse_format()
{
fmtdesc = uvc_format_by_guid(&buffer[5]);//通过GUID找到格式format
/*里面还会对frame进行各种分析和设置,
*如设置format->nframes得出最多有多少种分辨率选择
*暂时忽略*/
}
里面uvc_format_by_guid函数会从uvc_fmts数组中通过匹配guid找到格式:
static struct uvc_format_desc uvc_fmts[] = {
{
.name = "YUV 4:2:2 (YUYV)",
.guid = UVC_GUID_FORMAT_YUY2,
.fcc = V4L2_PIX_FMT_YUYV,
},
{
.name = "YUV 4:2:2 (YUYV)",
.guid = UVC_GUID_FORMAT_YUY2_ISIGHT,
.fcc = V4L2_PIX_FMT_YUYV,
},
{
.name = "YUV 4:2:0 (NV12)",
.guid = UVC_GUID_FORMAT_NV12,
.fcc = V4L2_PIX_FMT_NV12,
},
{
.name = "MJPEG",
.guid = UVC_GUID_FORMAT_MJPEG,
.fcc = V4L2_PIX_FMT_MJPEG,
},
/*后面省略......*/
}
这样的工作就完成了,我们来看下的:
int v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev)
{
INIT_LIST_HEAD(&v4l2_dev->subdevs);//用来管理v4l2_device 下的subdevs实例
spin_lock_init(&v4l2_dev->lock);
v4l2_prio_init(&v4l2_dev->prio);
kref_init(&v4l2_dev->ref);
get_device(dev);
v4l2_dev->dev = dev;
if (!v4l2_dev->name[0])
snprintf(v4l2_dev->name, sizeof(v4l2_dev->name), "%s %s",
dev->driver->name, dev_name(dev));
if (!dev_get_drvdata(dev))//dev->driver_data 域 为 NULL
dev_set_drvdata(dev, v4l2_dev);//就将其指向 v4l2_dev
return 0;
}
简单,没啥好讲的,就是初始化v4l2_dev->subdevs子设备实例的链表,然后设置名字和设置dev->driver_data
看下调用uvc_ctrl_init_device
int uvc_ctrl_init_device(struct uvc_device *dev)
{
/*省略了部分内容*/
list_for_each_entry(entity, &dev->entities, list) {
bmControls = entity->extension.bmControls;//控制位图
bControlSize = entity->extension.bControlSize;//控制位域大小
entity->controls = kcalloc(ncontrols, sizeof(*ctrl),
GFP_KERNEL);//分配ncontrols个uvc控制内存
if (entity->controls == NULL)
return -ENOMEM;
entity->ncontrols = ncontrols;//设置uvc控制个数
/* Initialize all supported controls */
ctrl = entity->controls;//指向uvc控制数组
for (i = 0; i < bControlSize * 8; ++i) {
if (uvc_test_bit(bmControls, i) == 0)//跳过控制位域设置0的
continue;
ctrl->entity = entity;
ctrl->index = i;//设置控制位域索引
uvc_ctrl_init_ctrl(dev, ctrl);//初始化uvc控件
ctrl++;//uvc控制 指向下一个uvc控制数组项
}
}
}
uvc_ctrl_init_device主要就是初始化控制参数,里面就会遍历uvc设备实体entities链表,然后设置位和位域大小最后还会调用uvc_ctrl_init_ctrl函数设置背光,色温等等————————————————————————————————————————————————
int uvc_ctrl_init_device(struct uvc_device *dev)
{
struct uvc_entity *entity;
unsigned int i;
/* Walk the entities list and instantiate controls */
list_for_each_entry(entity, &dev->entities, list) {
struct uvc_control *ctrl;
unsigned int bControlSize = 0;//控制位域大小
unsigned int ncontrols = 0;//控制组件个数
__u8 *bmControls = NULL;//控制位图
/*判断每次遍历到的实体的类型,并对类型相应的参数进行赋值*/
if (UVC_ENTITY_TYPE(entity) == UVC_VC_EXTENSION_UNIT) {
bmControls = entity->extension.bmControls;
bControlSize = entity->extension.bControlSize;
} else if (UVC_ENTITY_TYPE(entity) == UVC_VC_PROCESSING_UNIT) {
bmControls = entity->processing.bmControls;
bControlSize = entity->processing.bControlSize;
} else if (UVC_ENTITY_TYPE(entity) == UVC_ITT_CAMERA) {
bmControls = entity->camera.bmControls;
bControlSize = entity->camera.bControlSize;
}
/* Remove bogus/blacklisted controls 移除假的/黑名单控制组件*/
uvc_ctrl_prune_entity(dev, entity);
/* Count supported controls and allocate the controls array */
for (i = 0; i < bControlSize; ++i)
/*
hweight8()函数的功能:能快速得出一个字段内有多少个bit是1
bmControls 中的每一位表示一种控制组件,所以我们只需要通过使用
hweight8()函数就可以得出我们控制组件的个数
又因为 bmControls 的数据类型为 __u8 所以我们将数据分为三组存储,
这就是这里需要用到for循环的原因(bmControls 这个数据实际存在24位)
*/
ncontrols += hweight8(bmControls[i]);
if (ncontrols == 0)
continue;
/*给每一个实体中的所有要使用到的控制组件都申请一段内存*/
entity->controls = kzalloc(ncontrols * sizeof(*ctrl),
GFP_KERNEL);
if (entity->controls == NULL)
return -ENOMEM;
entity->ncontrols = ncontrols;
/* Initialize all supported controls */
ctrl = entity->controls;
for (i = 0; i < bControlSize * 8; ++i) {
if (uvc_test_bit(bmControls, i) == 0)
continue;
ctrl->entity = entity;
ctrl->index = i;
uvc_ctrl_init_ctrl(dev, ctrl);//初始化uvc控制组件
ctrl++;
}
}
return 0;
}
static void uvc_ctrl_init_ctrl(struct uvc_device *dev, struct uvc_control *ctrl)
{
const struct uvc_control_info *info = uvc_ctrls;
const struct uvc_control_info *iend = info + ARRAY_SIZE(uvc_ctrls);
const struct uvc_control_mapping *mapping = uvc_ctrl_mappings;
const struct uvc_control_mapping *mend =
mapping + ARRAY_SIZE(uvc_ctrl_mappings);
/* XU controls initialization requires querying the device for control
* information. As some buggy UVC devices will crash when queried
* repeatedly in a tight loop, delay XU controls initialization until
* first use.
*/
if (UVC_ENTITY_TYPE(ctrl->entity) == UVC_VC_EXTENSION_UNIT)
return;
/*
uvc_entity_match_guid()函数的主要作用:
1.首先通过传入的uvc_control中的实体成员判断其实体的类型,主要是筛选以下
四种类型(UVC_ITT_CAMERA、UCV_ITT_MEDIA_TRANSPORT_INPUT、UVC_VC_PROCESSING_UNIT、
UVC_VC_EXTENSION_UNIT)
2.然后将控制信息(uvc_control_info)中的实体标识与默认的类型标识做对比,如果一致
则不需要添加新的控制信息,否则需要使用uvc_ctrl_add_info()去添加
*/
for (; info < iend; ++info) {
if (uvc_entity_match_guid(ctrl->entity, info->entity) &&
ctrl->index == info->index) {
uvc_ctrl_add_info(dev, ctrl, info);
break;
}
}
if (!ctrl->initialized)
return;
for (; mapping < mend; ++mapping) {
if (uvc_entity_match_guid(ctrl->entity, mapping->entity) &&
ctrl->info.selector == mapping->selector)
__uvc_ctrl_add_mapping(dev, ctrl, mapping);
}
}
/*
struct uvc_entity 这个结构体描述的主要为uvc协议中的:
IT、OT、PU、SU、EU、XU 这些实体
*/
static int uvc_scan_device(struct uvc_device *dev)
{
struct uvc_video_chain *chain;//uvc视频链
struct uvc_entity *term;//uvc实体
/*
遍历整个实体链表
循环遍历每一个term中的list子项, &dev->entities为链表头
*/
list_for_each_entry(term, &dev->entities, list) {
/*
获取实体链表中的输出Terminal实体
通过struct uvc_entity --> __u16 type 来判断Terminal的类型
#define UVC_ENTITY_IS_TERM(entity) (((entity)->type & 0xff00) != 0)
#define UVC_ENTITY_IS_OTERM(entity)
(UVC_ENTITY_IS_TERM(entity) &&
((entity)->type & 0x8000) == UVC_TERM_OUTPUT)
这里主要完成了两个判断:
(其中的0x8000为判断Terminal类型的掩码,0xff00位判断实体类型的掩码)
1.判断这个实体是否为Terminal
2.判断这个实体是否为output Terminal
*/
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;
/*初始化uvc视频链entities(实体)链表*/
INIT_LIST_HEAD(&chain->entities);
mutex_init(&chain->ctrl_mutex);
/*捆绑uvc视频链和uvc设备*/
chain->dev = dev;
/*
(自己的观点)
uvc_scan_chain()这个函数主要作用扫描到的每个实体经过
类型判断及其相关信息打印后,将实体添加到uvc视频链
*/
if (uvc_scan_chain(chain, term) < 0) {
kfree(chain);
continue;
}
uvc_trace(UVC_TRACE_PROBE, "Found a valid video chain (%s).
",
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.
");
return -1;
}
return 0;
}
接下来继续看调用uvc_register_chains函数:调用关系
uvc_register_chains
uvc_register_terms(dev, chain)
uvc_stream_by_id
uvc_register_video
uvc_mc_register_entities(chain)
static int uvc_register_terms(struct uvc_device *dev,
struct uvc_video_chain *chain)
{
struct uvc_streaming *stream;
struct uvc_entity *term;
int ret;
list_for_each_entry(term, &chain->entities, chain) {
/*如果判断此实体的类型不是streaming的话则重新遍历*/
if (UVC_ENTITY_TYPE(term) != UVC_TT_STREAMING)
continue;
/*找到每个实体相对应的视频流(也就是说找到与之对应的 struct uvc_streaming 这个结构体)*/
stream = uvc_stream_by_id(dev, term->id);
if (stream == NULL) {
uvc_printk(KERN_INFO, "No streaming interface found "
"for terminal %u.", term->id);
continue;
}
/*将uvc视频链和uvc流中的uvc视频链进行绑定*/
stream->chain = chain;
/*对其中的uvc视频流做初始化工作,并初始化视频设备且注册*/
ret = uvc_register_video(dev, stream);
if (ret < 0)
return ret;
/*将uvc实体中的struct video_device和uvc流中的struct video_device进行绑定*/
term->vdev = stream->vdev;
}
return 0;
}
uvc_stream_by_id函数会通过函数传入的id和dev->streams链表的header.bTerminalLink匹配,寻找到stream这不是重点,我们的重点是uvc_register_video函数,找到stream会就要注册:
static int uvc_register_video(struct uvc_device *dev,
struct uvc_streaming *stream)
{
/*部分内容省略......*/
struct video_device *vdev = &stream->vdev;
ret = uvc_queue_init(&stream->queue, stream->type, !uvc_no_drop_param);//初始化队列
ret = uvc_video_init(stream);//初始化
uvc_debugfs_init_stream(stream);
vdev->v4l2_dev = &dev->vdev;
vdev->fops = &uvc_fops;//v4l2操作函数集
vdev->ioctl_ops = &uvc_ioctl_ops;//设置真正的ioctl操作集
vdev->release = uvc_release;//释放方法
vdev->prio = &stream->chain->prio;
strlcpy(vdev->name, dev->name, sizeof vdev->name);
video_set_drvdata(vdev, stream);//将uvc视频流作为v4l2设备的驱动数据
ret = video_register_device(vdev, VFL_TYPE_GRABBER, -1);//注册
return 0;
}
这是非常重要的函数,我们来一点一点分析:看下uvc_queue_init函数,队列初始化,队列这东西,我们视频传输时会调用到,在ioctl里操作:
static struct vb2_ops uvc_queue_qops = {
.queue_setup = uvc_queue_setup,
.buf_prepare = uvc_buffer_prepare,
.buf_queue = uvc_buffer_queue,
.buf_finish = uvc_buffer_finish,
.wait_prepare = vb2_ops_wait_prepare,
.wait_finish = vb2_ops_wait_finish,
.start_streaming = uvc_start_streaming,
.stop_streaming = uvc_stop_streaming,
};
int uvc_queue_init(struct uvc_video_queue *queue, enum v4l2_buf_type type,
int drop_corrupted)
{
queue->queue.type = type;
queue->queue.io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF;
queue->queue.drv_priv = queue;
queue->queue.buf_struct_size = sizeof(struct uvc_buffer);
queue->queue.ops = &uvc_queue_qops;//stream->queue->queue.ops
queue->queue.mem_ops = &vb2_vmalloc_memops;
queue->queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC
| V4L2_BUF_FLAG_TSTAMP_SRC_SOE;
queue->queue.lock = &queue->mutex;
ret = vb2_queue_init(&queue->queue);//初始化queue
mutex_init(&queue->mutex);
spin_lock_init(&queue->irqlock);
INIT_LIST_HEAD(&queue->irqqueue);//初始化stream->queue->irqqueue
queue->flags = drop_corrupted ? UVC_QUEUE_DROP_CORRUPTED : 0;
return 0;
}
里面先对队列进行初始化设置,如设置type和ops。这里queue->queue.ops=&uvc_queue_qops非常重要,之后我们调用vidioc_streamon回调函数时就是调用到这里的uvc_queue_qops结构体里的.start_streaming函数这函数里对各种队列进行了初始化:
vb2_queue_init(&queue->queue)
q->buf_ops = &v4l2_buf_ops;
vb2_core_queue_init(struct vb2_queue *q)
INIT_LIST_HEAD(&q->queued_list);//stream->queue->queue->queued_list
INIT_LIST_HEAD(&q->done_list);//stream->queue->done_list
INIT_LIST_HEAD(&queue->irqqueue);//初始化stream->queue->irqqueue
我们继续看回uvc_register_video函数,里面接着调用了uvc_video_init函数初始化UVC视频设备:完整:
int uvc_video_init(struct uvc_streaming *stream)
{
struct uvc_streaming_control *probe = &stream->ctrl;
struct uvc_format *format = NULL;
struct uvc_frame *frame = NULL;
unsigned int i;
int ret;
/*首先检查UVC视频流中是否有格式信息,如果没有则返回错误*/
if (stream->nformats == 0) {
uvc_printk(KERN_INFO, "No supported video formats found.
");
return -EINVAL;
}
atomic_set(&stream->active, 0);
/* Initialize the video buffers queue. */
/*初始化队列,并且通过队列来管理视频缓冲区*/
uvc_queue_init(&stream->queue, stream->type, !uvc_no_drop_param);
/* Alternate setting 0 should be the default, yet the XBox Live Vision
* Cam (and possibly other devices) crash or otherwise misbehave if
* they don"t receive a SET_INTERFACE request before any other video
* control request.
*/
/*这个函数功能在自己看来作用为:改变指定usb设备的接口设置*/
usb_set_interface(stream->dev->udev, stream->intfnum, 0);
/* Set the streaming probe control with default streaming parameters
* retrieved from the device. Webcams that don"t suport GET_DEF
* requests on the probe control will just keep their current streaming
* parameters.
*/
/*
uvc_get_video_ctrl()获得uvc流的默认参数设置,如果参数一切正常将返回0,
否则返回负值
uvc_set_video_ctrl()设置uvc视频控制
*/
if (uvc_get_video_ctrl(stream, probe, 1, UVC_GET_DEF) == 0)
uvc_set_video_ctrl(stream, probe, 1);
/* Initialize the streaming parameters with the probe control current
* value. This makes sure SET_CUR requests on the streaming commit
* control will always use values retrieved from a successful GET_CUR
* request on the probe control, as required by the UVC specification.
*/
/*获取当前的控制值,确认上面的设置是否正确*/
ret = uvc_get_video_ctrl(stream, probe, 1, UVC_GET_CUR);
if (ret < 0)
return ret;
/* Check if the default format descriptor exists. Use the first
* available format otherwise.
*/
for (i = stream->nformats; i > 0; --i) {
format = &stream->format[i-1];
if (format->index == probe->bFormatIndex)
break;
}
/*检查frame的选择数量,如果为0则返回错误值*/
if (format->nframes == 0) {
uvc_printk(KERN_INFO, "No frame descriptor found for the "
"default format.
");
return -EINVAL;
}
/* Zero bFrameIndex might be correct. Stream-based formats (including
* MPEG-2 TS and DV) do not support frames but have a dummy frame
* descriptor with bFrameIndex set to zero. If the default frame
* descriptor is not found, use the first available frame.
*/
for (i = format->nframes; i > 0; --i) {
frame = &format->frame[i-1];
if (frame->bFrameIndex == probe->bFrameIndex)
break;
}
//设置uvc视频流控制的格式索引为uvc格式的索引
probe->bFormatIndex = format->index;
//设置uvc视频流控制的帧索引为uvc帧索引
probe->bFrameIndex = frame->bFrameIndex;
/*设置UVC视频流的当前的UVC视频格式及其UVC视频帧*/
stream->cur_format = format;
stream->cur_frame = frame;
.
.
省 略
.
.
return 0;
}
————————————————————————————————————
int uvc_video_init(struct uvc_streaming *stream)
{
/*省略部分内容*/
struct uvc_streaming_control *probe = &stream->ctrl;//获取uvc数据流的uvs数据流控制对象
if (uvc_get_video_ctrl(stream, probe, 1, UVC_GET_DEF) == 0)//先得到定义的控制参数
uvc_set_video_ctrl(stream, probe, 1);//再设置uvc视频控制
ret = uvc_get_video_ctrl(stream, probe, 1, UVC_GET_CUR);//最后在get一次
for (i = stream->nformats; i > 0; --i) {
format = &stream->format[i-1];//获取对应的uvc格式
if (format->index == probe->bFormatIndex)
break;
}
probe->bFormatIndex = format->index;//设置uvc视频流控制的格式索引为uvc格式的索引
probe->bFrameIndex = frame->bFrameIndex;//设置uvc视频流控制的分辨率索引为uvc分辨率的索引
stream->def_format = format;
stream->cur_format = format;//设置uvc格式为uvc数据流的cur_format成员
stream->cur_frame = frame;//设置uvc帧为uvc数据流的cur_frame成员
if (stream->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) {//视频采集
if (stream->dev->quirks & UVC_QUIRK_BUILTIN_ISIGHT)
stream->decode = uvc_video_decode_isight;
else if (stream->intf->num_altsetting > 1)
stream->decode = uvc_video_decode_isoc;//同步方式
else
stream->decode = uvc_video_decode_bulk;//bluk方式
}
return 0;
}
这里面内容就比较多了,先得到,然后设置uvc的控制参数,里面会操作urb发出usb数据。然后通过probe->bFormatIndex索引找到使用的format格式和通过probe->bFrameIndex找到对应的frame分辨率,然后设置到stream里。最后选择解码方式,如同步方式或者bluk方式,解码方式会在数据完成时被回调函数complete里调用。
再次回到uvc_register_video函数,没办法,这个函数太重要了:里面继续:
vdev->fops = &uvc_fops;//v4l2操作函数集
vdev->ioctl_ops = &uvc_ioctl_ops;//设置真正的ioctl操作集
vdev->release = uvc_release;//释放方法
ret = video_register_device(vdev, VFL_TYPE_GRABBER, -1);
里面就是vdev->v4l2_dev=&dev->vdev;这样v4l2_device就与video_device关联起来,也就是我们文章一开始那个看到的。然后设置fops操作函数vdev->fops=&uvc_fops,虽然这不是给用户空间使用的open、read、write函数,但是最后vdev->cdev->ops还是最调用到这个uvc_fops的,所以用户空间实际上的pen、read、write函数还是会在这调用。然后ioctl操作函数最终是会调用到vdev->ioctl_ops=&uvc_ioctl_ops。可以说,V4L2最重要的就是各种形式的ioctl了,这里先不讲,下一节在分析看看。然后最终就是我们的注册函数了:video_register_device里调用到__video_register_device函数:
int __video_register_device(struct video_device *vdev, int type, int nr,
int warn_if_nr_in_use, struct module *owner)
{
/*省略部分函数*/
vdev->minor = -1;//-1表明这个video device从未被注册过
switch (type) {//根据type选择设备名称
case VFL_TYPE_GRABBER:
name_base = "video";
break;
case VFL_TYPE_VBI:
name_base = "vbi";
break;
case VFL_TYPE_RADIO:
name_base = "radio";
break;
case VFL_TYPE_SUBDEV:
name_base = "v4l-subdev";
break;
case VFL_TYPE_SDR:
name_base = "swradio";
break;
default:
printk(KERN_ERR "%s called with unknown type: %d
", __func__, type);
return -EINVAL;
}
switch (type) {//选择得到次设备号偏移值
case VFL_TYPE_GRABBER://用于视频输入/输出设备的 videoX
minor_offset = 0;
minor_cnt = 64;
break;
case VFL_TYPE_RADIO://用于广播调谐器的 radioX
minor_offset = 64;
minor_cnt = 64;
break;
case VFL_TYPE_VBI://用于垂直消隐数据的 vbiX (例如,隐藏式字幕,图文电视)
minor_offset = 224;
minor_cnt = 32;
break;
default:
minor_offset = 128;
minor_cnt = 64;
break;
}
nr = devnode_find(vdev, 0, minor_cnt);//获取一个没有被使用的设备节点序号
for (i = 0; i < VIDEO_NUM_DEVICES; i++)
if (video_device[i] == NULL)//从video_device[]数组中选择一个空缺项,这个空缺项的索引值放到i中
break;
vdev->minor = i + minor_offset;//设备的次设备号
video_device[vdev->minor] = vdev;//注意:将设置好的video_device放入到video_device[]
vdev->cdev->ops = &v4l2_fops;//操作用户空间操作函数集
ret = cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1);//添加字符设备到系统
ret = device_register(&vdev->dev);//设备注册
set_bit(V4L2_FL_REGISTERED, &vdev->flags);//将flags第0为设置为1,表示这个video_device是注册过的了
return 0;
}
我们梳理一下里面做的事情:确定设备名称,也就是我们在/dev/下生成的video啊,radio之类的得到次设备的偏移值找到一个空的video_device数组,把vdev存进去设置vdev->cdev,这里就设置了vdev->cdev->ops=&v4l2_fops;里面就是真正的用户空间操作集合注册video_device设备就是标志此video_device以注册
最后调用uvc_register_chains函数里还会调用一个uvc_mc_register_entities函数,里面继续调用uvc_mc_init_entity函数,这就是v4l2_device_register_subdev函数,进行注册v4l2_subdev,同时初始化然后连接到v4l2_dev->subdevs管理。
好了,调用uvc_register_chains函数:就分析完了,我们最后剩一个了:
文章为作者独立观点,不代表股票交易接口观点