一、V4L2 驱动核心:
V4L2 驱动源码在 drivers/media/video 目录下,主要核心代码有:
- v4l2-dev.c:Linux 版本 2 视频捕捉接口,主要结构体 video_device 的注册;
- v4l2-common.c:在 Linux 操作系统体系采用低级别的操作一套设备 struct tures/vectors 的通用视频设备接口;
- v4l2-device.c:V4L2 的设备支持,注册 v4l2_device;
- v4l2-ioctl.c:处理 V4L2 的 ioctl 命令的一个通用的框架;
- v4l2-subdev.c:v4l2 子设备;
- v4l2-mem2mem.c:内存到内存为 Linux 和 videobuf 视频设备的框架,设备的辅助函数,使用其源和目的 videobuf 缓冲区。
直接来看驱动源码的话,还是对驱动的框架没有一个感性的认识,尤其这个 V4L2 框架非常复杂,我们先从内核源码中提供的虚拟视频驱动程序 vivi.c 来分析,内核版本 3.4.2。
二、虚拟视频驱动程序 vivi.c 源码分析
2.1、分析一个程序从它的 init 入口函数开始分析:
代码语言:javascript复制static int __init vivi_init(void)
{
const struct font_desc *font = find_font("VGA8x16");
int ret = 0, i;
if (font == NULL) {
printk(KERN_ERR "vivi: could not find fontn");
return -ENODEV;
}
font8x16 = font->data;
if (n_devs <= 0)
n_devs = 1;
for (i = 0; i < n_devs; i ) {
ret = vivi_create_instance(i);
if (ret) {
/* If some instantiations succeeded, keep driver */
if (i)
ret = 0;
break;
}
}
if (ret < 0) {
printk(KERN_ERR "vivi: error %d while loading drivern", ret);
return ret;
}
printk(KERN_INFO "Video Technology Magazine Virtual Video "
"Capture Board ver %s successfully loaded.n",
VIVI_VERSION);
/* n_devs will reflect the actual number of allocated devices */
n_devs = i;
return ret;
}
static void __exit vivi_exit(void)
{
vivi_release();
}
module_init(vivi_init);
module_exit(vivi_exit);
其中 n_devs 的定义在前面,如下所示:
代码语言:javascript复制static unsigned n_devs = 1;
module_param(n_devs, uint, 0644);
MODULE_PARAM_DESC(n_devs, "numbers of video devices to create");
写的很清楚了,n_devs 表示想要创建的 video devices 个数。
注:一般用户态传递参数是通过 main 函数,第一个参数表示 args 个数,第二个参数表示具体的参数。在 kernel 态 ,无法通过这样的方式传递参数,一般使用 module_param 的方式,步骤如下:
- 使用 module_param 指定模块的参数;
- 加载 driver 时给模块传递参数;
去掉其他的判断语句,发现重要的函数只有一个 vivi_create_instance(i) 函数,我们下面就来分析这个函数。
2.2、vivi_create_instance(i) 函数:
代码语言:javascript复制static int __init vivi_create_instance(int inst)
{
struct vivi_dev *dev;
struct video_device *vfd;
struct v4l2_ctrl_handler *hdl;
struct vb2_queue *q;
int ret;
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
if (!dev)
return -ENOMEM;
snprintf(dev->v4l2_dev.name, sizeof(dev->v4l2_dev.name),
"%s-d", VIVI_MODULE_NAME, inst);
ret = v4l2_device_register(NULL, &dev->v4l2_dev);
if (ret)
goto free_dev;
dev->fmt = &formats[0];
dev->width = 640;
dev->height = 480;
hdl = &dev->ctrl_handler;
v4l2_ctrl_handler_init(hdl, 11);
dev->volume = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
V4L2_CID_AUDIO_VOLUME, 0, 255, 1, 200);
dev->brightness = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
V4L2_CID_BRIGHTNESS, 0, 255, 1, 127);
dev->contrast = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
V4L2_CID_CONTRAST, 0, 255, 1, 16);
dev->saturation = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
V4L2_CID_SATURATION, 0, 255, 1, 127);
dev->hue = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
V4L2_CID_HUE, -128, 127, 1, 0);
dev->autogain = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
V4L2_CID_AUTOGAIN, 0, 1, 1, 1);
dev->gain = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
V4L2_CID_GAIN, 0, 255, 1, 100);
dev->button = v4l2_ctrl_new_custom(hdl, &vivi_ctrl_button, NULL);
dev->int32 = v4l2_ctrl_new_custom(hdl, &vivi_ctrl_int32, NULL);
dev->int64 = v4l2_ctrl_new_custom(hdl, &vivi_ctrl_int64, NULL);
dev->boolean = v4l2_ctrl_new_custom(hdl, &vivi_ctrl_boolean, NULL);
dev->menu = v4l2_ctrl_new_custom(hdl, &vivi_ctrl_menu, NULL);
dev->string = v4l2_ctrl_new_custom(hdl, &vivi_ctrl_string, NULL);
dev->bitmask = v4l2_ctrl_new_custom(hdl, &vivi_ctrl_bitmask, NULL);
if (hdl->error) {
ret = hdl->error;
goto unreg_dev;
}
v4l2_ctrl_auto_cluster(2, &dev->autogain, 0, true);
dev->v4l2_dev.ctrl_handler = hdl;
/* initialize locks */
spin_lock_init(&dev->slock);
/* initialize queue */
q = &dev->vb_vidq;
memset(q, 0, sizeof(dev->vb_vidq));
q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ;
q->drv_priv = dev;
q->buf_struct_size = sizeof(struct vivi_buffer);
q->ops = &vivi_video_qops;
q->mem_ops = &vb2_vmalloc_memops;
vb2_queue_init(q);
mutex_init(&dev->mutex);
/* init video dma queues */
INIT_LIST_HEAD(&dev->vidq.active);
init_waitqueue_head(&dev->vidq.wq);
ret = -ENOMEM;
vfd = video_device_alloc();
if (!vfd)
goto unreg_dev;
*vfd = vivi_template;
vfd->debug = debug;
vfd->v4l2_dev = &dev->v4l2_dev;
set_bit(V4L2_FL_USE_FH_PRIO, &vfd->flags);
/*
* Provide a mutex to v4l2 core. It will be used to protect
* all fops and v4l2 ioctls.
*/
vfd->lock = &dev->mutex;
ret = video_register_device(vfd, VFL_TYPE_GRABBER, video_nr);
if (ret < 0)
goto rel_vdev;
video_set_drvdata(vfd, dev);
/* Now that everything is fine, let's add it to device list */
list_add_tail(&dev->vivi_devlist, &vivi_devlist);
if (video_nr != -1)
video_nr ;
dev->vfd = vfd;
v4l2_info(&dev->v4l2_dev, "V4L2 device registered as %sn",
video_device_node_name(vfd));
return 0;
rel_vdev:
video_device_release(vfd);
unreg_dev:
v4l2_ctrl_handler_free(hdl);
v4l2_device_unregister(&dev->v4l2_dev);
free_dev:
kfree(dev);
return ret;
}
1)、函数首先为 struct vivi_dev *dev 分配内存,然后将 dev->v4l2_dev.name 的名字设置为 “vivi-i” 的形式,然后调用 v4l2_device_register 这个函数来注册 dev->v4l2_dev 这个结构体,结构体 v4l2_device 如下所示,看它的名字就叫 V4L2 设备,它肯定就是 V4L2 设备的核心结构体:
代码语言:javascript复制struct v4l2_device {
/* dev->driver_data points to this struct.
Note: dev might be NULL if there is no parent device
as is the case with e.g. ISA devices.*/
#if defined(CONFIG_MEDIA_CONTROLLER)
struct media_device *mdev;
#endif
/* used to keep track of the registered subdevs */
struct list_head subdevs;
/* lock this struct, can be used by the drivers as well if this
struct is embedded into a larger struct.*/
spinlock_t lock;
/* unique device name, by default the driver name bus ID */
char name[V4L2_DEVICE_NAME_SIZE];
/* notify callback called by some sub-devices. */
void (*notify)(struct v4l2_subdev *sd, unsigned int notification, void *arg);
/* The control handler. May be NULL. */
struct v4l2_ctrl_handler *ctrl_handler;
/* Device's priority state */
struct v4l2_prio_state prio;
/* BKL replacement mutex.Temporary solution only. */
struct kref ref;
/* Release function that is called when the ref count goes to 0. */
void (*release)(struct v4l2_devcie *v4l2_dev);
};
可以看到这个结构体里面包含一个 device 父设备成员,一个 subdevs 子设备链表头,一个自旋锁,一个 notify 函数指针,v4l2_ctrl_handler 控制句柄,prio 优先级,ref 引用计数,还有一个 release 函数指针。暂时先不对这个结构体进行具体的分析,在以后分析 V4L2 框架的时候再分析。
2)、它通过 v4l2_devie_register(NULL, &dev->v4l2_dev) 函数来完成对结构体的注册,可以看出在 “vivi.c” 中,它的父设备为 NULL,v4l2_device_register 函数在 v4l2-device.c 中:
代码语言:javascript复制int v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev)
{
if (v4l2_dev == NULL)
return -EINVAL;
INIT_LIST_HEAD(&v4l2_dev->subdevs);
spin_lock_init(&v4l2_dev->lock);
mutex_init(&v4l2_dev->ioctl_lock);
v4l2_prio_init(&v4l2_dev->prio);
kref_init(&v4l2_dev->ref);
get_device(dev);
v4l2_dev->dev = dev;
if (dev == NULL) {
/* If dev == NULL, then name must be filled in by the caller */
WARN_ON(!v4l2_dev->name[0]);
return 0;
}
/* Set name to driver name device name if it is empty. */
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_set_drvdata(dev, v4l2_dev);
return 0;
}
EXPORT_SYMBOL_GPL(v4l2_device_register);
这个函数完成了子设备链表的初始化,自旋锁,互斥量,优先级,引用计数的初始化,其他并没做太多工作。
3)、继续回到 vivi_create_instance(i) 函数中进行分析,下一句话:dev->fmt = &formats[0];
通过向前搜索发现,vivi.c 维持着一个 formats 数组,它表示 vivi.c 支持的数据格式。关于视频的格式我们在 V4L2 框架中介绍,通过这行代码,我们知道了 vivi.c 所支持的格式为:V4L2_PIX_FMT_YUYV。
代码语言:javascript复制struct vivi_fmt {
char *name;
u32 fourcc; /* v4l2 format id */
int depth;
};
static struct vivi_fmt formats[] = {
{
.name = "4:2:2, packed, YUYV",
.fourcc = V4L2_PIX_FMT_YUYV,
.depth = 16,
},
{
.name = "4:2:2, packed, UYVY",
.fourcc = V4L2_PIX_FMT_UYVY,
.depth = 16,
},
{
.name = "RGB565 (LE)",
.fourcc = V4L2_PIX_FMT_RGB565, /* gggbbbbb rrrrrggg */
.depth = 16,
},
{
.name = "RGB565 (BE)",
.fourcc = V4L2_PIX_FMT_RGB565X, /* rrrrrggg gggbbbbb */
.depth = 16,
},
{
.name = "RGB555 (LE)",
.fourcc = V4L2_PIX_FMT_RGB555, /* gggbbbbb arrrrrgg */
.depth = 16,
},
{
.name = "RGB555 (BE)",
.fourcc = V4L2_PIX_FMT_RGB555X, /* arrrrrgg gggbbbbb */
.depth = 16,
},
};
4)、继续在 vivi_create_instance(i) 函数中分析:
代码语言:javascript复制hdl = &dev->ctrl_handler;
v4l2_ctrl_handler_init(hdl, 11);
v4l2_ctrl_handler 结构体在 v4l2-ctrls.h 中定义,如下所示:
代码语言:javascript复制struct v4l2_ctrl_handler {
struct mutex lock;
struct list_head ctrls;
struct list_head ctrl_refs;
struct v4l2_ctrl_ref *cached;
struct v4l2_ctrl_ref ** buckets;
u16 nr_of_buckets;
int error;
};
v4l2_ctrl_handler 是用于保存子设备控制方法集的结构体,对于视频设备这些 ctrls 包括设置亮度、饱和度、对比度和清晰度等,用链表的方式来保存 ctrls,可以通过 v4l2_ctrl_new_std 函数向链表添加 ctrls。在下面的代码中用到了这个函数。
代码语言:javascript复制/* Initialize the handler */
int v4l2_ctrl_handler_init(struct v4l2_ctrl_handler *hdl, unsigned nr_of_controls_hint)
{
mutex_init(&hdl->lock);
INIT_LIST_HEAD(&hdl->ctrls);
INIT_LIST_HEAD(&hdl->ctrl_refs);
hdl->nr_of_buckets = 1 nr_of_controls_hint / 8;
hdl->buckets = kcalloc(hdl->nr_of_buckets, sizeof(hdl->buckets[0]), GFP_KERNEL);
hdl->error = hdl->buckets ? 0 : -ENOMEM;
return hdl->error;
}
通过 nr_of_control_hint 变量的大小,计算出 nr_of_buckets,计算出 nr_of_buckets,并为 buckets 申请空间,并将申请结果保存在 error 变量中。
5)、继续在 vivi_create_instance(i) 函数中分析,继续设置 dev 结构体中的其他一些参数,对 volume,brightness,contrast,saturation 等参数设置的时候,调用了 v4l2_ctrl_new_std 这个函数,以及对 button,int32,menu,bitmask 等参数设置,调用了 v4l2_ctrl_new_custom 这个函数,一看就知道这两个函数是 V4L2 框架所提供的接口函数。
代码语言:javascript复制struct v4l2_ctrl *v4l2_ctrl_new_std(struct v4l2_ctrl_handler *hdl, const struct v4l2_ctrl_ops *ops, u32 id, s32 min, s32 max, u32 step, s32 def);
- hdl 是初始化好的 v4l2_ctrl_handler 结构体;
- ops 是 v4l2_ctrl_ops 结构体,包含 ctrls 的具体实现;
- id 是通过 IOCTL 的 arg 参数传过来的指令,定义在 v4l2-controls.h 文件;
- min、max 用来定义某操作对象的范围。如:v4l2_ctrl_new_std(hdl, ops, V4L2_CID_BRIGHTNESS, -208, 127, 1, 0);
用户空间可以通过 ioctl 的 VIDIOC_S_CTRL 指令调用到 v4l2_ctrl_handler, id 透过 arg 参数传递。
通过几个含糊是来完成对视频中 亮度,饱和度等的设置。
6)、然后是缓冲区队列的操作,设置 vb2_queue 队列 q 的一些参数,最主要的是下面两个参数:
代码语言:javascript复制q->ops = &vivi_qops;
q->mem_ops = &vb2_vmalloc_memops;
可以看到:q->ops = &vivi_video_qops 中 vivi_video_qops 是需要 vivi.c 实现的一个操作函数集,它在 vivi.c 中定义如下:
代码语言:javascript复制static struct vb2_ops vivi_video_qops = {
.queue_setup = queue_setup,
.buf_init = buffer_init,
.buf_prepare = buffer_prepare,
.buf_finish = buffer_finish,
.buf_cleanup = buffer_cleanup,
.buf_queue = buffer_queue,
.start_streaming = start_streaming,
.stop_streaming = stop_streaming,
.wait_prepare = vivi_unlock,
.wait_finish = vivi_lock,
};
这几个函数是需要我们写驱动程序的时候自己实现的函数。
其中 vb2_ops 结构体在 videobuf2-core.h 中定义,如下所示:
代码语言:javascript复制struct vb2_ops {
int (*queue_setup)(struct vb2_queue *q, const struct v4l2_format *fmt,
unsigned int *num_buffers, unsigned int *num_planes,
unsigned int sizes[], void *alloc_ctxs[]);
void (*wait_prepare)(struct vb2_queue *q);
void (*wait_finish)(struct vb2_queue *q);
int (*buf_init)(struct vb2_buffer *vb);
int (*buf_prepare)(struct vb2_buffer *vb);
int (*buf_finish)(struct vb2_buffer *vb);
void (*buf_cleanup)(struct vb2_buffer *vb);
int (*start_streaming)(struct vb2_queue *q, unsigned int count);
int (*stop_streaming)(struct vb2_queue *q);
void (*buf_queue)(struct vb2_buffer *vb);
};
对于 vb2_vmalloc_memops 结构体,它在 videobuf2-vmalloc.c 中定义,如下所示:
代码语言:javascript复制const struct vb2_mem_ops vb2_vmalloc_memops = {
.alloc = vb2_vmalloc_alloc,
.put = vb2_vmalloc_put,
.get_userptr = vb2_vmalloc_get_userptr,
.put_userptr = vb2_vmalloc_put_userptr,
.vaddr = vb2_vmalloc_vaddr,
.mmap = vb2_vmalloc_mmap,
.num_users = vb2_vmalloc_num_users,
};
EXPORT_SYMBOL_GPL(vb2_vmalloc_memops);
看它的名字是 vb2 开头的,这几个函数应该都是系统为我们提供好的函数,通过查看源码发现,它确实存在于 videobuf2-vmalloc.c 中。
然后调用 vb2_queue_init(q) 函数来初始化它,vb2_queue_init(q) 函数如下所示:
代码语言:javascript复制/**
* vb2_queue_init() - initialize a videobuf2 queue
* @q: videobuf2 queue; this structure should be allocated in driver
*
* The vb2_queue structure should be allocated by the driver. The driver is
* responsible of clearing it's content and setting initial values for some
* required entries before calling this function.
* q->ops, q->mem_ops, q->type and q->io_modes are mandatory. Please refer
* to the struct vb2_queue description in include/media/videobuf2-core.h
* for more information.
*/
int vb2_queue_init(struct vb2_queue *q)
{
BUG_ON(!q);
BUG_ON(!q->ops);
BUG_ON(!q->mem_ops);
BUG_ON(!q->type);
BUG_ON(!q->io_modes);
BUG_ON(!q->ops->queue_setup);
BUG_ON(!q->ops->buf_queue);
INIT_LIST_HEAD(&q->queued_list);
INIT_LIST_HEAD(&q->done_list);
spin_lock_init(&q->done_lock);
init_waitqueue_head(&q->done_wq);
if (q->buf_struct_size == 0)
q->buf_struct_size = sizeof(struct vb2_buffer);
return 0;
}
EXPORT_SYMBOL_GPL(vb2_queue_init);
它只是完成了一些检查判断的语句,进行了一些链表,自旋锁等的初始化。
7)、/* init video dma queues */
INIT_LIST_HEAD(&dev->vidq.active);
init_waitqueue_head(&dev->vidq.wq);
8)、下面是对 video_device 的操作,它算是这个函数中核心的操作:
代码语言:javascript复制struct video_device *vfd;
vfd = video_device_alloc();
*vfd = vivi_template;
ret = video_register_device(vfd, VFL_TYPE_GRABBER, video_nr);
video_set_drvdata(vfd, dev);
8.1)、先来看这个 video_device 结构体,它在 v4l2-dev.h 中定义,显示如下:
代码语言:javascript复制struct video_device {
/* device ops */
const struct v4l2_file_operations *fops;
/* sysfs */
struct device dev; /* v4l device */
struct cdev *cdev; /* character device */
/* Set either parent or v4l2_dev if your driver uses v4l2_device */
struct device *parent; /* device parent */
struct v4l2_device *v4l2_dev; /* v4l2_device parent */
/* Control handler associated with this device node. May be NULL */
struct v4l2_ctrl_handler *ctrl_handler;
/* Priority state. If NULL, then v4l2_dev->prio will be used */
struct v4l2_prio_state *prio;
/* device info */
char name[32];
int vfl_type;
/* 'minor' is set to -1 if the registration failed */
int minor;
u16 num;
/* use bitops to set/clear/test flags */
unsigned long flags;
/* attribute to differentiate multiple indices on one physical device */
int index;
/* V4L2 file handles */
spinlock_t fh_lock; /* Lock for all v4l2_fhs */
struct list_head fh_list; /* List of struct v4l2_fh */
int debug; /* Activates debug level */
/* Video standed vars */
v4l2_std_id tvnorms; /* Supproted tv norms */
v4l2_std_id current_norm; /* Current tv norm */
/* callbacks */
void (*release)(struct video_device *vdev);
/* ioctl callbacks */
const struct v4l2_ioctl_ops *ioctl_ops;
/* serialization lock */
struct mutex *lock;
};
根据注释应该能大致了解各个成员的意义。后面有这个函数的一些初始化和注册函数,里面肯定有对这个结构体成员的设置初始化等,所以我们在后面再具体分析这些成员。
8.2)、下面来看 video_device_alloc 函数。它在 v4l2-dev.c 中定义:
代码语言:javascript复制struct video_device *video_device_alloc(void)
{
return kzalloc(sizeof(struct video_device), GFP_KERNEL);
}
EXPORT_SYMBOL(video_device_alloc);
只是分配了一段内存,然后将它置为 0,并没有对 video_device 结构体里面的成员进行设置。
8.3)、然后 vivi.c 中下一句是 *vfd = vivi_template 在 vivi.c 搜索发现,它在前面定义:
代码语言:javascript复制static struct video_device vivi_template = {
.name = "vivi",
.fops = &vivi_fops,
.ioctl_ops = &vivi_ioctl_ops,
.release = video_device_release,
.tvnorms = V4L2_STD_525_60,
.current_norm = V4L2_STD_NTSC_M,
};
对比 video_device 结构体中的成员,可以确定就是在这进行赋值的。它只是对其中某些成员进行了赋值。
8.3.1)、video_device 结构体中首先是 .fops = &vivi_fops,在 vivi.c 中搜索如下所示:
代码语言:javascript复制static const struct v4l2_file_operations vivi_fops = {
.owner = THIS_MODULE,
.open = v4l2_fh_open,
.release = vivi_close,
.read = vivi_read,
.poll = vivi_poll,
.unlocked_ioctl = video_ioctl2, /* V4L2 ioctl handler */
.mmap = vivi_mmap,
};
先来看这几个函数的名字,其中 open 函数和 unlocked_ioctl 函数的名字与其他的不同,直觉告诉我,他俩是系统提供的,其他的函数名字都是 vivi 开头的,应该是这个文件里面实现的函数,我们在 vivi.c 中搜索就可以找到,但是我们暂时先不具体分析这几个函数。
8.3.2)、video_device 结构体中第二个是 .ioctl_ops = &vivi_ioctl_ops ,看名字也是在 vivi.c 中定义的:
代码语言:javascript复制static const struct v4l2_ioctl_ops vivi_ioctl_ops = {
.vidioc_querycap = vidioc_querycap,
.vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap,
.vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap,
.vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap,
.vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap,
.vidioc_reqbufs = vidioc_reqbufs,
.vidioc_querybuf = vidioc_querybuf,
.vidioc_qbuf = vidioc_qbuf,
.vidioc_dqbuf = vidioc_dqbuf,
.vidioc_s_std = vidioc_s_std,
.vidioc_enum_input = vidioc_enum_input,
.vidioc_g_input = vidioc_g_input,
.vidioc_s_input = vidioc_s_input,
.vidioc_streamon = vidioc_streamon,
.vidioc_streamoff = vidioc_streamoff,
.vidioc_log_status = v4l2_ctrl_log_status,
.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
};
可以看到,这是一大堆的 ioctl 调用,大致可分为以下几类:
- 查询性能 query capability 的:vidioc_querycap;
- 对 format 的一些操作:vidioc_enum_fmt_vid_cap,vidioc_g_fmt_vid_cap,vidioc_try_fmt_vid_cap,vidioc_s_fmt_vid_cap;
- 对缓冲区的一些操作:vidioc_reqbufs,vidioc_querybuf,vidioc_qbuf,vidioc_dqbuf;
- 对标准 standard 的操作:vidioc_s_std;
- 对输入 input 的操作:vidioc_enum_input,vidioc_g_input,vidiocs_s_input;
- 对流 stream 的操作:vidioc_streamon,vidioc_streamoff;
以上几个 ioctl 调用都是需要我们自己实现的,后面 3 个 ioctl 的名字是 v4l2 开头的,应该是系统里面实现好的函数,搜索可以发现在 v4l2-ctrls.c 和 v4l2-event.c 中定义。
8.3.3)、video_device 结构体中第三个是 .release = video_device_release,它在 v4l2-dev.c 中定义,如下所示:
代码语言:javascript复制void video_device_release(struct video_device *vdev)
{
kfree(vdev);
}
EXPORT_SYMBOL(video_device_release);
8.3.4)、video_device 结构体中第四,五个是:
代码语言:javascript复制.tvnorms = V4L2_STD_525_60,
.current_norm = V4L2_STD_NTSC_M,
通过看 video_device 结构体中的注释:
代码语言:javascript复制/* Video standard vars */
v4l2_std_id tvnorms; /* Supported tv norms */
v4l2_std_id current_norm; /* Current tvnorm */
是指支持的 TV 制式以及当前的 TV 制式。
8.3.5)、分析完了 vivi_template 中的成员,也即 video_device 结构体中的成员,还是有点迷惑的,在 video_device 结构体中,有一个 struct v4l2_file_operation 成员,这个成员又包含一个 unlocked_ioctl,同时 video_device 结构体中还有一个 struct v4l2_ioctl_ops 成员,怎么有两个 ioctl 成员函数啊?先大致分析一些,struct v4l2_file_operations vivi_fops 中 .unlocked_ioctl = video_ioctl2,这个 video_ioctl2 在 v4l2-ioctl.c 中:
代码语言:javascript复制long video_ioctl2(struct file *file, unsigned int cmd, unsigned long arg)
{
return video_usercopy(file, cmd, arg, __video_do_ioctl);
}
EXPORT_SYMBOL(video_ioctl2);
它又调用了 __video_do_ioctl 函数,也在 v4l2-ioctl.c 中,这个 __video_do_ioctl 函数是一个大的 switch,case 语句,根据不同的 case,调用不同的函数,以 VIDIOC_QUERYCAP 为例:
代码语言:javascript复制struct video_device *vfd = video_devdata(file);
const struct v4l2_ioctl_ops *ops = vfd->ioctl_ops;
case VIDIOC_QUERYCAP:
ret = ops->vidioc_querycap(file, fh, cap);
用在 vivi.c 这个例子就是:vfd 这个结构体就是 vivi_template,Ops 就是 vivi_template 中的 ioctl_ops 成员,也就是 vivi_ioctl_ops,对于 VIDIOC_QUERCAP 宏,真正调用的是 vivi_ioctl_ops 中的 .vidiioc_querycap 成员,也即我们在 vivi.c 中自己实现的 static int vidioc_querycap(struct file *file, void *priv, struct v4l2_capability *cap) 函数。有点绕,我已晕@_@!~~ 咱们在后面再具体分析。
8.4)、继续在 vivi_create_instance 中分析:
代码语言:javascript复制vfd->debug = debug;
vfd->v4l2_dev = &dev->v4l2_dev;
注意这个语句,从这里可以看出在注册 video_device 之前必须先注册了 v4l2_device.
代码语言:javascript复制set_bit(V4L2_FL_USE_FH_PRIO, &vfd->flags);
vfd->lock = &dev->mutex;
进行了一些设置,然后就是大 boss 了:
代码语言:javascript复制ret = video_register_device(vfd, VFL_TYPE_GRABBER, video_nr);
它在 v4l2-dev.h 中定义,如下所示:
代码语言:javascript复制static inline int __must_check video_register_device_no_warm(struct video_device *vdev, int type, int nr)
{
return __video_register_device(vdev, type, nr, 0, vdev->fops->owner);
}
__video_register_device 在 v4l2-dev.c 中定义:(就直接在代码中注释了)
代码语言:javascript复制/**
* __video_register_device - register video4linux devices
* @vdev: video device structure we want to register
* @type: type of device to register
* @nr: which device node number (0 == /dev/video0, 1 == /dev/video1, ...
* -1 == first free)
* @warn_if_nr_in_use: warn if the desired device node number
* was already in use and another number was chosen instead.
* @owner: module that owns the video device node
*
* The registration code assigns minor numbers and device node numbers
* based on the requested type and registers the new device node with
* the kernel.
*
* This function assumes that struct video_device was zeroed when it
* was allocated and does not contain any stale date.
*
* An error is returned if no free minor or device node number could be
* found, or if the registration of the device node failed.
*
* Zero is returned on success.
*
* Valid types are
*
* %VFL_TYPE_GRABBER - A frame grabber
*
* %VFL_TYPE_VBI - Vertical blank data (undecoded)
*
* %VFL_TYPE_RADIO - A radio card
*
* %VFL_TYPE_SUBDEV - A subdevice
*/
int __video_register_device(struct video_device *vdev, int type, int nr,
int warn_if_nr_in_use, struct module *owner)
{
int i = 0;
int ret;
int minor_offset = 0;
int minor_cnt = VIDEO_NUM_DEVICES;
const char *name_base;
/* A minor value of -1 marks this video device as never
having been registered */
vdev->minor = -1;
/* the release callback MUST be present */
if (WARN_ON(!vdev->release))
return -EINVAL;
/* 如果没有提供这个release函数的话,就直接返回错误,它就在vivi_template中提供了。 */
/* v4l2_fh support */
spin_lock_init(&vdev->fh_lock);
INIT_LIST_HEAD(&vdev->fh_list);
/* Part 1: check device type */
switch (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;
default:
printk(KERN_ERR "%s called with unknown type: %dn",
__func__, type);
return -EINVAL;
}
/* 根据传进来的type参数,确定设备在/dev目录下看到的名字 */
vdev->vfl_type = type;
vdev->cdev = NULL;
if (vdev->v4l2_dev) {
if (vdev->v4l2_dev->dev)
vdev->parent = vdev->v4l2_dev->dev;
if (vdev->ctrl_handler == NULL)
vdev->ctrl_handler = vdev->v4l2_dev->ctrl_handler;
/* If the prio state pointer is NULL, then use the v4l2_device
prio state. */
if (vdev->prio == NULL)
vdev->prio = &vdev->v4l2_dev->prio;
}
/* 进行vdev中父设备和ctrl处理函数的初始化。*/
/* Part 2: find a free minor, device node number and device index. */
#ifdef CONFIG_VIDEO_FIXED_MINOR_RANGES
/* Keep the ranges for the first four types for historical
* reasons.
* Newer devices (not yet in place) should use the range
* of 128-191 and just pick the first free minor there
* (new style). */
switch (type) {
case VFL_TYPE_GRABBER:
minor_offset = 0;
minor_cnt = 64;
break;
case VFL_TYPE_RADIO:
minor_offset = 64;
minor_cnt = 64;
break;
case VFL_TYPE_VBI:
minor_offset = 224;
minor_cnt = 32;
break;
default:
minor_offset = 128;
minor_cnt = 64;
break;
}
#endif
/* Pick a device node number */
mutex_lock(&videodev_lock);
nr = devnode_find(vdev, nr == -1 ? 0 : nr, minor_cnt);
if (nr == minor_cnt)
nr = devnode_find(vdev, 0, minor_cnt);
if (nr == minor_cnt) {
printk(KERN_ERR "could not get a free device node numbern");
mutex_unlock(&videodev_lock);
return -ENFILE;
}
#ifdef CONFIG_VIDEO_FIXED_MINOR_RANGES
/* 1-on-1 mapping of device node number to minor number */
i = nr;
#else
/* The device node number and minor numbers are independent, so
we just find the first free minor number. */
for (i = 0; i < VIDEO_NUM_DEVICES; i )
if (video_device[i] == NULL)
break;
if (i == VIDEO_NUM_DEVICES) {
mutex_unlock(&videodev_lock);
printk(KERN_ERR "could not get a free minorn");
return -ENFILE;
}
#endif
vdev->minor = i minor_offset;
vdev->num = nr;
devnode_set(vdev);
/* Should not happen since we thought this minor was free */
WARN_ON(video_device[vdev->minor] != NULL);
vdev->index = get_index(vdev);
mutex_unlock(&videodev_lock);
/* 上面的part2就是确定设备的次设备号 */
/* Part 3: Initialize the character device */
vdev->cdev = cdev_alloc();
if (vdev->cdev == NULL) {
ret = -ENOMEM;
goto cleanup;
}
/* 在这进行设备的注册,用cdev_alloc函数,从这我们就可以看出来,它是一个普通的字符设备驱动,然后设置它的一些参数。怎么就是字符设备驱动了???这个在后面的v4l2框架中再说。 */
vdev->cdev->ops = &v4l2_fops;
/* cdev结构体里面的ops指向了v4l2_fops这个结构体,这个v4l2_fops结构体也是在v4l2-dev.c这个文件中。又一个file_operations操作函数集,在vivi.c中有一个v4l2_file_operations vivi_fops,他俩又是什么关系呢? */
vdev->cdev->owner = owner;
ret = cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1);
if (ret < 0) {
printk(KERN_ERR "%s: cdev_add failedn", __func__);
kfree(vdev->cdev);
vdev->cdev = NULL;
goto cleanup;
}
/* Part 4: register the device with sysfs */
vdev->dev.class = &video_class;
vdev->dev.devt = MKDEV(VIDEO_MAJOR, vdev->minor);
if (vdev->parent)
vdev->dev.parent = vdev->parent;
dev_set_name(&vdev->dev, "%s%d", name_base, vdev->num);
ret = device_register(&vdev->dev);
if (ret < 0) {
printk(KERN_ERR "%s: device_register failedn", __func__);
goto cleanup;
}
/* Register the release callback that will be called when the last
reference to the device goes away. */
vdev->dev.release = v4l2_device_release;
if (nr != -1 && nr != vdev->num && warn_if_nr_in_use)
printk(KERN_WARNING "%s: requested %s%d, got %sn", __func__,
name_base, nr, video_device_node_name(vdev));
/* Increase v4l2_device refcount */
if (vdev->v4l2_dev)
v4l2_device_get(vdev->v4l2_dev);
/* 在sysfs中创建类,在类下创建设备结点 */
#if defined(CONFIG_MEDIA_CONTROLLER)
/* Part 5: Register the entity. */
if (vdev->v4l2_dev && vdev->v4l2_dev->mdev &&
vdev->vfl_type != VFL_TYPE_SUBDEV) {
vdev->entity.type = MEDIA_ENT_T_DEVNODE_V4L;
vdev->entity.name = vdev->name;
vdev->entity.info.v4l.major = VIDEO_MAJOR;
vdev->entity.info.v4l.minor = vdev->minor;
ret = media_device_register_entity(vdev->v4l2_dev->mdev,
&vdev->entity);
if (ret < 0)
printk(KERN_WARNING
"%s: media_device_register_entity failedn",
__func__);
}
#endif
/* 创建实体entity,这一步并不是必须的,需要配置了CONFIG_MEDIA_CONTROLLER选项后才会执行这一步,在这一步里面有一个media_entity实体结构体,在后面再分析它。 */
/* Part 6: Activate this minor. The char device can now be used. */
set_bit(V4L2_FL_REGISTERED, &vdev->flags);
/* 设置标志位 */
mutex_lock(&videodev_lock);
video_device[vdev->minor] = vdev;
/* 将设置好的video_device结构体vdev按照次设备号保存到video_device数组中。这个数组是在前面static struct video_device *video_device[VIDEO_NUM_DEVICES];定义的。 */
mutex_unlock(&videodev_lock);
return 0;
cleanup:
mutex_lock(&videodev_lock);
if (vdev->cdev)
cdev_del(vdev->cdev);
devnode_clear(vdev);
mutex_unlock(&videodev_lock);
/* Mark this video device as never having been registered. */
vdev->minor = -1;
return ret;
}
EXPORT_SYMBOL(__video_register_device);
8.5)、注册完 video_device 结构体后继续 vivi_create_instance 中执行:
代码语言:javascript复制/* 将 vivi_dev dev 添加到 video_device vfd 中,为什么要这样做呢?是为了以后字符设备驱动接口的使用 */
video_set_drvdata(vfd, dev);
/* 添加到 device list 链表中 */
list_add_tail(&dev->vivi_devlist, &vivi_devlist);
/* 用于计数 */
if(video_nr != -1)
video_nr ;
/* 关联 video_device 和 vivi_dev */
dev->vfd = vfd;
三、分析总结:
到这里我们就分析完了 vivi_init 和 vivi_create_instance 函数,vivi.c 中剩下的代码,基本就是以下 3 个结构体的具体实现代码我们暂时先不分析。
代码语言:javascript复制static struct video_device vivi_template = {
.name = "vivi",
.fops = &vivi_fops,
.ioctl_ops = &vivi_ioctl_ops,
.release = video_device_release,
.tvnorms = V4L2_STD_525_60,
.current_norm = V4L2_STD_NTSC_M,
};
static const struct v4l2_ioctl_ops vivi_ioctl_ops = {
.vidioc_querycap = vidioc_querycap,
.vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap,
.vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap,
.vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap,
.vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap,
.vidioc_reqbufs = vidioc_reqbufs,
.vidioc_querybuf = vidioc_querybuf,
.vidioc_qbuf = vidioc_qbuf,
.vidioc_dqbuf = vidioc_dqbuf,
.vidioc_s_std = vidioc_s_std,
.vidioc_enum_input = vidioc_enum_input,
.vidioc_g_input = vidioc_g_input,
.vidioc_s_input = vidioc_s_input,
.vidioc_streamon = vidioc_streamon,
.vidioc_streamoff = vidioc_streamoff,
.vidioc_log_status = v4l2_ctrl_log_status,
.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
};
static const struct v4l2_file_operations vivi_fops = {
.owner = THIS_MODULE,
.open = v4l2_fh_open,
.release = vivi_close,
.read = vivi_read,
.poll = vivi_poll,
.unlocked_ioctl = video_ioctl2, /* V4L2 ioctl handler */
.mmap = vivi_mmap,
};
我们首先分析这个 vivi.c 的目的是为了先大致看一些 v4l2 驱动的代码,留一些疑问,以后分析 v4l2 的代码框架及一些概念的时候,还会以这个 vivi.c 为例子来说明。等到分析完大致的框架之后,我们再来继续仔细分析 vivi.c 中那些具体代码的实现。
实例说明:
以 read 为例,应用程序在调用 read 的时候,对应到驱动 file_operations v4l2_fops 中的 v4l2_read 函数,在函数里面通过 ret = vdev->fops->read(filp, buf, sz, off) 最后调用到我们在 vivi.c 中申请注册的 video_device vivi_template 结构体里面的 fops->read 函数,即 vivi_read 函数。即 V4L2 框架只是提供了一个中转站的效果。再看 vivi_read 函数里面:return vb2_read(&dev->vb_vidq, data, count, ppos, file->f_flags & O_NONBLOCK);它又调用了 videobuf2-core.c 中的 vb2_read 函数。确实说明了 v4l2 框架的中转作用。
这样相似的函数有read,write,poll,mmap,release 等,比较特别的是 ioctl 函数,在后面分析它。它们都是应用程序调用,通过 V4L2 框架中转到对应的驱动程序中,然后驱动程序根据不同的调用,选择调用 videobuf 或 ioctl 中的函数。
对于 const struct v4l2_ioctl_ops *ioctl_ops,在 vivi.c 中就是 static const struct v4l2_ioctl_ops vivi_ioctl_ops;这个 ioctl 更麻烦,首先作为字符设备驱动,当应用程序调用 ioctl 的时候,就调用到了 file_operations v4l2_fops 中的 .unlocked_ioctl = v4l2_ioctl,这个 v4l2_ ioctl 同样通过 ret = vdev->fops->ioctl(filp, cmd, arg) 就调用到了 vivi.c 中申请注册的 struct video_device vivi_template 结构体里面的 fops->unlocked_ioctl 函数,即 v4l2_file_operatios vivi_fops 里面的 video_ioctl2 函数,这个 video_ioctl2 函数又调用 __video_do_ioctl 函数(以上两个函数都在 v4l2-ioctl.c 中),根据不同的 cmd 宏,以 VIDIOC_QUERYCAP 为例:
通过 ret = ops->vidioc_querycap(file, fh, cap);其中 struct video_device *vfd = video_devdata(file);const struct v4l2_ioctl_ops *ops = vfd->ioctl_ops;可以看出,__video_do_ioctl 函数又调用了 struct video_device vivi_template 结构体李曼的 .ioctl_ops = &vivi_ioctl_ops,然后根据宏的名字来选择 struct v4l2_ioctl_ios vivi_ioctl_ops 中对应的函数,即 vidioc_querycap 函数。