在V4l2初識(一)中,我們已經知道當插上一個攝像頭的時候,在uvc_driver.c中最終會調用函數video_register_device函數。接下來我們就簡要分析這個函數做了哪些事情,揭開其神秘面紗。
參考原文:https://blog.csdn.net/leesagacious/article/details/49948163
/* Register video devices. Note that if video_register_device fails,
the release() callback of the video_device structure is *not* called, so
the caller is responsible for freeing any data. Usually that means that
you call video_device_release() on failure. */
/*注冊video_device,注意如果注冊失敗,video_device結構體中的release函數不會被調用, 調用者負責釋放所有的數據,通常是調用video_device_release()函數來釋放*/
static inline int video_register_device(struct video_device *vdev,int type, int nr)
{
//調用這個函數來注冊video_device
return __video_register_device(vdev, type, nr, 1, vdev->fops->owner);
}
參數一:video_devide ------即我們想要注冊的video_device結構體
參數二:type-----------要注冊的device類型,其中包括:
define VFL_TYPE_GRABBER 0 圖像采集設備,包括阿攝像頭、調諧器
define VFL_TYPE_VBI1 1 從視頻消隱的時間段取得信息的設備(1)
#define VFL_TYPE_RADIO 2 無線電設備
#define VFL_TYPE_SUBDEV 3 視頻傳播設備
#define VFL_TYPE_MAX 4
參數三:int nr------------device node number
0 == /dev/video 0 1 == /dev/video1 ........ -1 == first free
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; //minor = i+minor_offset
int minor_cnt = VIDEO_NUM_DEVICES; //用於設備節點序號
const char *name_base; //設備的名稱會根據傳入的type來選擇
/* A minor value of -1 marks this video device as never having been registered
次設備號為-1,表明這個設備還沒有被注冊 */
vdev->minor = -1;
/* the release callback MUST be present
如果要注冊的video_device沒有提供release函數,就要出錯返回*/
if (WARN_ON(!vdev->release))
return -EINVAL;
/* v4l2_fh support */
spin_lock_init(&vdev->fh_lock); //獲取自旋鎖
INIT_LIST_HEAD(&vdev->fh_list); //初始化鏈表頭
/* Part 1: check device type
根據傳入的類型type,來選擇設備的名稱。后面會調用函數dev_set_name(&vdev->dev, "%s%d", name_base, vdev->num)來設置設備的名稱
*/
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: %d\n",__func__, type);
return -EINVAL;
}
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)
/*在app應用程序中,可以通過Ioctl來設置、獲得亮度等信息。那么驅動程序誰來接收/存儲/ 設置到硬件,或從硬件中獲得這些信息?
在驅動程序里面抽象出一個結構體V4L2_ctrl。
那么誰來管理v4l2_ctrl呢?
是利用v4l2_ctrl_handler進行管理的,它像鏈表一樣,里面需要填充各個屬 性,也可理解為設置各個屬性。
在注冊video_device之前的vivi_create_instance()函數中,
初始化v4l2_ctrl_handler
v4l2_ctrl_handler_init(hdl, 11);
創建v4l2_ctrl 並放入到v4l2_ctrl_handler鏈表
v4l2_ctrl_new_std()
v4l2_ctrl_new_custom()
dev->v4l2_dev.ctrl_handler = hdl; //dev->v4l2_dev : v4l2_device 每一個v4l2設備都用這個結構來描述
下面的代碼是將v4l2_ctrl_handler與video_device進行了關聯
*/
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;
}
/* 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).
根據傳入的類型type,為選擇次設備號、設備節點序號作准備。可見可見次設備號是分段使用的
minor = i + minor_offset
*/
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); //獲取互斥鎖
/*獲取一個沒有被使用的設備節點序號。如果上面傳入的是-1,VFL_TYPE_GRABBER,它會從0-64中選擇*/
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.
static struct video_device *video_device[256]; 從video_device[]數組中選擇一個空缺項,這個空缺項的索引值放到i中
為下面把video_device放入到video_device[i]中做准備,這個設計和registered_fb[]設計的類似
*/
for (i = 0; i < VIDEO_NUM_DEVICES; i++)
if (video_device[i] == NULL)
break;
//檢查i的值有沒有超過256,否則出錯返回
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
再一次測試這個video_device[i]是否是空缺項*/
WARN_ON(video_device[vdev->minor] != NULL);
vdev->index = get_index(vdev);
mutex_unlock(&videodev_lock); //釋放互斥鎖
/* Part 3: Initialize the character device
分配cdev結構體 cdev代表了字符設備的通用信息,通常被嵌入在一個更大的結構體中*/
vdev->cdev = cdev_alloc();
if (vdev->cdev == NULL) {
ret = -ENOMEM;
goto cleanup;
}
/*設置file_operations為v4l2_fops
用戶層調用open、mmap、ioctl、read...的時候,這個v4l2_fops中的對應的方法會響應。
如 : 用戶層調用mmap(),那么v4l2_fops中的v4l2_mmap()會被調用。
在v4l2_mmap()中,會調用具體設備提供的mmap函數
static int v4l2_mmap(struct file *filp, struct vm_area_struct *vm)
{
....
ret = vdev->fops->mmap(filp, vm);
....
}
*/
vdev->cdev->ops = &v4l2_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 failed\n", __func__);
kfree(vdev->cdev);
vdev->cdev = NULL;
goto cleanup;
}
/* Part 4: register the device with sysfs
填充video_device中的成員值 設置video_device所屬的類,會在/sys/class/下創建目錄*/
vdev->dev.class = &video_class;
/*通過主設備號和次設備號生成dev_t,表示的是一個設備號*/
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);
/*利用device_register,注冊字符設備驅動。這個地方和我們寫的字符設備驅動程序是一樣的*/
ret = device_register(&vdev->dev);
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);
#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 failed\n",__func__);
}
#endif
/* Part 6: Activate this minor. The char device can now be used. */
/*將這個unsigned long flags 的第0 位 置1,表示這個video_device 是注冊過的了,
在其他位置,會調用video_is_registeried( ) 來判斷,其依據 還是測試這個flags的第0位。
video_is_registered( )
{
test_bit( V4L2_FL_REGISTERED, &vdev->flags )
}
*/
//獲取鎖 ---- 訪問臨界區 -----釋放鎖
set_bit(V4L2_FL_REGISTERED, &vdev->flags);
mutex_lock(&videodev_lock);
/*依據次設備號為下標,將設置好的video_device放入到video_device[]中,其他函數會依據次設備號從這個數組中獲取對應的video_device,這個和registered_fb[]設計的類似 static struct video_device*video_device[256];*/
video_device[vdev->minor] = vdev;
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);
VBI在電視處理中是用來是垂直掃描完成從屏幕底部回到屏幕頂部的時間。在這期間,沒有任何的圖像信息,在原來是基本廢置不用的。后來,利用這期間來傳輸一些信息,比如CC,圖文,VPS/PDC,GEMSTAR等等信息服務。
