基于linux kernel-3.0版本代码分析
drivers\media\video\vivi.c
1.struct vivi_dev结构体重点关注对象
核心结构体struct vivi_dev, 其中内嵌的三个结构体需要重点关注struct v4l2_device v4l2_dev, struct v4l2_ctrl_handler ctrl_handler, struct video_device *vfd
struct vivi_dev {
struct list_head vivi_devlist;
struct v4l2_device v4l2_dev;
struct v4l2_ctrl_handler ctrl_handler;
/* controls */
struct v4l2_ctrl *brightness;
struct v4l2_ctrl *contrast;
struct v4l2_ctrl *saturation;
struct v4l2_ctrl *hue;
struct v4l2_ctrl *volume;
struct v4l2_ctrl *button;
struct v4l2_ctrl *boolean;
struct v4l2_ctrl *int32;
struct v4l2_ctrl *int64;
struct v4l2_ctrl *menu;
struct v4l2_ctrl *string;
spinlock_t slock;
struct mutex mutex;
/* various device info */
struct video_device *vfd;
struct vivi_dmaqueue vidq;
/* Several counters */
unsigned ms;
unsigned long jiffies;
unsigned button_pressed;
int mv_count; /* Controls bars movement */
/* Input Number */
int input;
/* video capture */
struct vivi_fmt *fmt;
unsigned int width, height;
struct vb2_queue vb_vidq;
enum v4l2_field field;
unsigned int field_count;
u8 bars[9][3];
u8 line[MAX_WIDTH * 4];
};
2.驱动初始化
vivi_init();
ret = vivi_create_instance(i);
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); //给struct vivi_dev申请空间
if (!dev)
return -ENOMEM;
snprintf(dev->v4l2_dev.name, sizeof(dev->v4l2_dev.name),
"%s-%03d", VIVI_MODULE_NAME, inst); //给v4l2_dev.name赋值为vivi-000
ret = v4l2_device_register(NULL, &dev->v4l2_dev); //注册v4l2_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); //初始化ctrl_handler,并给11个v4l2_ctrl分配空间
//下面就是对11个struct v4l2_ctrl类型的ctrl进行初始化
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->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);
if (hdl->error) {
ret = hdl->error;
goto unreg_dev;
}
dev->v4l2_dev.ctrl_handler = hdl; //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)); //初始化vb2_queue
q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; //设置vb2_queue的类型是捕获类型
q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ;
q->drv_priv = dev;
q->buf_struct_size = sizeof(struct vivi_buffer); //一帧数据所需要的buffer大小,如果赋值为0的话就使用vb2_buffer类型大小
q->ops = &vivi_video_qops; //对于vb2_queue的一些回调函数
q->mem_ops = &vb2_vmalloc_memops; //内存分配回收mmap等的回调函数
vb2_queue_init(q); //这里其实就是检查关键成员变量有没有赋值,也就是上面初始化的。然后初始化一些vb2_queue里面的一些链表头,spin lock, waitqueue的头
mutex_init(&dev->mutex);
/* init video dma queues */
INIT_LIST_HEAD(&dev->vidq.active); //初始化vivi_dmaqueue
init_waitqueue_head(&dev->vidq.wq);
ret = -ENOMEM;
vfd = video_device_alloc(); //为video_device申请空间
if (!vfd)
goto unreg_dev;
*vfd = vivi_template; //初始化video_device主要将成员变量name, fops, ioctl_ops, release赋值
vfd->debug = debug; //debug变量用于打印调试信息
vfd->v4l2_dev = &dev->v4l2_dev; //video_device的v4l2_dev执行struct v4l2_device
set_bit(V4L2_FL_USE_FH_PRIO, &vfd->flags); //设置video_device使用V4L2_FL_USE_FH_PRIO
/*
* 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); //注册video_device后面会详细分析
if (ret < 0)
goto rel_vdev;
video_set_drvdata(vfd, dev); //video_device->dev->p->driver_data执向vivi_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; //vivi_dev->vfd指向video_device
v4l2_info(&dev->v4l2_dev, "V4L2 device registered as %s\n",
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;
}
3.video_device注册函数video_register_device(vfd, VFL_TYPE_GRABBER, video_nr);分析
static inline int __must_check video_register_device(struct video_device *vdev,
int type, int nr)
{
return __video_register_device(vdev, type, nr, 1, vdev->fops->owner);
}
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 */
WARN_ON(!vdev->release);
if (!vdev->release)
return -EINVAL;
/* 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"; //这里的name_base就是后面会在/dev/创建/dev/videoX
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: %d\n",
__func__, type);
return -EINVAL;
}
vdev->vfl_type = type;
vdev->cdev = NULL;
if (vdev->v4l2_dev) {
if (vdev->v4l2_dev->dev) //vivi的v4l2_dev->dev为NULL, 下面不走
vdev->parent = vdev->v4l2_dev->dev;
if (vdev->ctrl_handler == NULL)
vdev->ctrl_handler = vdev->v4l2_dev->ctrl_handler; //将video_device->ctrl_handler指向v4l2_device->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; //走这里
}
... ... //中间省略了点代码
/* 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 number\n");
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 minor\n");
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); //由于vdev->parent为NULL这个函数直接返回0
mutex_unlock(&videodev_lock);
/* Part 3: Initialize the character device */
vdev->cdev = cdev_alloc(); //为cdev分配空间
if (vdev->cdev == NULL) {
ret = -ENOMEM;
goto cleanup;
}
vdev->cdev->ops = &v4l2_fops; //cdev的ops赋值,这个v4l2_fops很关键,应用和这个ops直接交互。通过这个ops调用到具体的video_device中的fops和ioctrl_ops
vdev->cdev->owner = owner;
ret = cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1); //注册字符设备
if (ret < 0) {
printk(KERN_ERR "%s: cdev_add failed\n", __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); //在sysfs下面注册设备
if (ret < 0) {
printk(KERN_ERR "%s: device_register failed\n", __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 %s\n", __func__,
name_base, nr, video_device_node_name(vdev));
/* Increase v4l2_device refcount */
if (vdev->v4l2_dev)
v4l2_device_get(vdev->v4l2_dev);
//vivi驱动v4l2_device->mdev为NULL,所以下面entity注册没有走
#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.v4l.major = VIDEO_MAJOR;
vdev->entity.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 failed\n",
__func__);
}
#endif
/* Part 6: Activate this minor. The char device can now be used. */
set_bit(V4L2_FL_REGISTERED, &vdev->flags); //看上面英文注释,就是现在可以使用/dev/videoX这个字符设备了
mutex_lock(&videodev_lock);
video_device[vdev->minor] = vdev; //把video_device放入到以次设备为数组下标的全局数组video_device
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);
4.v4l2_ctrl的初始化
现在回过头来看一下v4l2_ctrl的初始化,以brigtness为例
dev->brightness = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
V4L2_CID_BRIGHTNESS, 0, 255, 1, 127);
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)
{
const char *name;
enum v4l2_ctrl_type type;
u32 flags;
//下面v4l2_ctrl_fill主要做了1.根据id给name、type、flag有必要也会给min,max,step,def也赋值
v4l2_ctrl_fill(id, &name, &type, &min, &max, &step, &def, &flags);
if (type == V4L2_CTRL_TYPE_MENU) {
handler_set_err(hdl, -EINVAL);
return NULL;
}
return v4l2_ctrl_new(hdl, ops, id, name, type,
min, max, step, def, flags, NULL, NULL);
}
static struct v4l2_ctrl *v4l2_ctrl_new(struct v4l2_ctrl_handler *hdl,
const struct v4l2_ctrl_ops *ops,
u32 id, const char *name, enum v4l2_ctrl_type type,
s32 min, s32 max, u32 step, s32 def,
u32 flags, const char * const *qmenu, void *priv)
{
struct v4l2_ctrl *ctrl;
unsigned sz_extra = 0;
... ... //省略了些条件值判断代码
if (type == V4L2_CTRL_TYPE_BUTTON)
flags |= V4L2_CTRL_FLAG_WRITE_ONLY;
else if (type == V4L2_CTRL_TYPE_CTRL_CLASS)
flags |= V4L2_CTRL_FLAG_READ_ONLY;
else if (type == V4L2_CTRL_TYPE_STRING)
sz_extra += 2 * (max + 1);
//给v4l2_ctrl分配空间
ctrl = kzalloc(sizeof(*ctrl) + sz_extra, GFP_KERNEL);
if (ctrl == NULL) {
handler_set_err(hdl, -ENOMEM);
return NULL;
}
INIT_LIST_HEAD(&ctrl->node);
ctrl->handler = hdl; //重点ctrl->handler指向了v4l2_ctrl_handler
ctrl->ops = ops; //ops赋值为vivi_ctrl_ops
ctrl->id = id; //id为V4L2_CID_BRIGHTNESS
ctrl->name = name; //"Brightness"
ctrl->type = type; //V4L2_CTRL_TYPE_INTEGER
ctrl->flags = flags; //V4L2_CTRL_FLAG_SLIDER
ctrl->minimum = min; //0
ctrl->maximum = max; //255
ctrl->step = step; //1
ctrl->qmenu = qmenu; //NULL
ctrl->priv = priv; //NULL
ctrl->cur.val = ctrl->val = ctrl->default_value = def; //127
if (ctrl->type == V4L2_CTRL_TYPE_STRING) {
ctrl->cur.string = (char *)&ctrl[1] + sz_extra - (max + 1);
ctrl->string = (char *)&ctrl[1] + sz_extra - 2 * (max + 1);
if (ctrl->minimum)
memset(ctrl->cur.string, ' ', ctrl->minimum);
}
if (handler_new_ref(hdl, ctrl)) {
kfree(ctrl);
return NULL;
}
mutex_lock(&hdl->lock);
//把v4l2_ctrl的node节点,加入到v4l2_ctrl_handler的ctrls链表中去
list_add_tail(&ctrl->node, &hdl->ctrls);
mutex_unlock(&hdl->lock);
return ctrl;
}
static int handler_new_ref(struct v4l2_ctrl_handler *hdl,
struct v4l2_ctrl *ctrl)
{
struct v4l2_ctrl_ref *ref;
struct v4l2_ctrl_ref *new_ref;
u32 id = ctrl->id;
u32 class_ctrl = V4L2_CTRL_ID2CLASS(id) | 1; //通过V4L2_CID_BRIGHTNESS id获取到了V4L2_CID_USER_CLASS
int bucket = id % hdl->nr_of_buckets; /* which bucket to use */
//如果是第一次添加v4l2_ctrl所以下面if条件成立,先将V4L2_CID_USER_CLASS创建v4l2_ctrl加入到hdl->ctrl_refs ctrl_refs是按id升序插入
/* Automatically add the control class if it is not yet present. */
if (id != class_ctrl && find_ref_lock(hdl, class_ctrl) == NULL)
if (!v4l2_ctrl_new_std(hdl, NULL, class_ctrl, 0, 0, 0, 0))
return hdl->error;
if (hdl->error)
return hdl->error;
//v4l2_ctrl实际上是通过struct v4l2_ctrl_ref组织挂载到v4l2_ctrl_handler->ctrl_refs链表上
new_ref = kzalloc(sizeof(*new_ref), GFP_KERNEL);
if (!new_ref)
return handler_set_err(hdl, -ENOMEM);
new_ref->ctrl = ctrl;
if (ctrl->handler == hdl) {
//这里条件为true所以会往下走,看代码实际上ctrl->cluster也是指向自己的
/* By default each control starts in a cluster of its own.
new_ref->ctrl is basically a cluster array with one
element, so that's perfect to use as the cluster pointer.
But only do this for the handler that owns the control. */
ctrl->cluster = &new_ref->ctrl;
ctrl->ncontrols = 1;
}
INIT_LIST_HEAD(&new_ref->node);
mutex_lock(&hdl->lock);
/* Add immediately at the end of the list if the list is empty, or if
the last element in the list has a lower ID.
This ensures that when elements are added in ascending order the
insertion is an O(1) operation. */
//上面英文注释就是如果ctrl_refs为空或id大于链表最后一个ctrl的id,那么直接将
//new_ref加入到hdl->ctrl_refs链表尾, 直接跳转到insert_in_hash标号
if (list_empty(&hdl->ctrl_refs) || id > node2id(hdl->ctrl_refs.prev)) {
list_add_tail(&new_ref->node, &hdl->ctrl_refs);
goto insert_in_hash;
}
//遍历hdl->ctrl_refs链表,按升序找到合适的位置插入
/* Find insert position in sorted list */
list_for_each_entry(ref, &hdl->ctrl_refs, node) {
if (ref->ctrl->id < id)
continue;
/* Don't add duplicates */
if (ref->ctrl->id == id) {
kfree(new_ref);
goto unlock;
}
list_add(&new_ref->node, ref->node.prev);
break;
}
insert_in_hash:
//这里我看下来就是每次把new_ref->next指向上次buckets[bucket]数组里存放的v4l2_ctrl_ref
//并把先加入的new_ref赋值给buckets[bucket]
/* Insert the control node in the hash */
new_ref->next = hdl->buckets[bucket];
hdl->buckets[bucket] = new_ref;
unlock:
mutex_unlock(&hdl->lock);
return 0;
}
总结一下handler_new_ref这个函数这里存在好几条链表,一条就是v4l2_ctrl通过v4l2_ctrl_ref
来组织挂载到hdl->ctrl_refs链表上并且这个链表是按升序排列。还有几条链表是按v4l2_ctrl->id
对 hdl->nr_of_buckets求余,余数相同的v4l2_ctrl通过v4l2_ctrl_ref->next串联起来。
//以vivi驱动为例11个v4l2_ctrl有2个bucket, 假设id为1,2,3,4四个ctrl, 那么2,4会通过next
//串联起来形成一条链表,1,3串联起来形成一条链表
总结vivi驱动初始化一共有这么几点:
1、struct vivi_dev内嵌入struct v4l2_device, struct v4l2_ctrl_handler, struct video_device, , struct vb2_queue这么几个核心结构体
2.对v4l2_device进行初始化并注册
3.初始化v4l2_ctrl_handler, 并创建v4l2_ctrl控件加入到v4l2_ctrl_handler中去
4.vb2_queue的初始化和一些回调函数赋值
5.video_device的初始化并注册, 其中video_device中的fops和ioctl_ops回调函数初始化是需要关注的重点