初识V4l2(二)-------浅析video_register_device

在V4l2初识(一)中,我们已经知道当插上一个摄像头的时候,在uvc_driver.c中最终会调用函数video_register_device函数。接下来我们就简要分析这个函数做了哪些事情,揭开其神秘面纱。

/* 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 devices,有一点注意,video_device_release函数需要开发者手动调用

static inline int video_register_device(struct video_device *vdev, int type, int nr) { return __video_register_device(vdev, type, nr, 1, vdev->fops->owner); }

就video_register_device函数中的形参进行说明:

参数一: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 

参数三:nr:device node number

0 == /dev/video0     1 == /dev/video1 ........    -1 == first free

__video_register_device深入分析过程如下:

/**
 *    __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;//次设备号的偏移量,对于不同类型的设备,次设备号的基数是不同的,最终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 */
    vdev->minor = -1;  //次设备号为-1,表明这个设备没有被注册

    /* the release callback MUST be present */
    if (WARN_ON(!vdev->release)) //如果video_device结构体中没有提供release函数,就会返回出错。因此该函数必须存在
        return -EINVAL;

    /* v4l2_fh support */
    spin_lock_init(&vdev->fh_lock);
    INIT_LIST_HEAD(&vdev->fh_list); //初始化链表fh_list

    /* Part 1: check device type */
    //1.1 根据传入的设备类型,给name_base赋值。从这个地方看设备的名称就是以下的4种之一。
    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
",
               __func__, type);
        return -EINVAL;
    }
   //1.2
    vdev->vfl_type = type;
    vdev->cdev = NULL;
    if (vdev->v4l2_dev) {
        if (vdev->v4l2_dev->dev)
            vdev->parent = vdev->v4l2_dev->dev;

    /*
    在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进行了关联
    */
        
        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;
    }

    /* 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). */
     //2.1得到各类设备的次设备的偏移量,为的就是求次设备号
    switch (type) {
    case VFL_TYPE_GRABBER:
        minor_offset = 0;   //对于图像采集类设备,次设备号的偏移量是0
        minor_cnt = 64;
        break;
    case VFL_TYPE_RADIO:
        minor_offset = 64;  //对于radio类设备,次设备号的偏移量为64
        minor_cnt = 64;
        break;
    case VFL_TYPE_VBI:  //对于VBI类设备,次设备号的偏移量为224
        minor_offset = 224;
        minor_cnt = 32;
        break;
    default:
        minor_offset = 128;  //对于其他类的设备,次设备号的偏移量为128
        minor_cnt = 64;
        break;
    }
#endif

//2.2 得到设备节点的号
    /* Pick a device node number */
    mutex_lock(&videodev_lock);
   /*获取一个没有被使用的设备节点序号,如果传入的nr=-1,就会从0-64中选择,将选择一个没有被使用的设备节点序号并返回*/
    nr = devnode_find(vdev, nr == -1 ? 0 : nr, minor_cnt);
    if (nr == minor_cnt)  //如果设备节点号正好等于64,那么就会从0-64中选择一个设备节点号,为什么又执行一次
        nr = devnode_find(vdev, 0, minor_cnt);
    if (nr == minor_cnt) {    //如果nr=64,那么说明没有空的设备节点号。因为是做多就是64个设备节点号,下标0-63。
        printk(KERN_ERR "could not get a free device node number
");
        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++)
        //在video_device数组中找到一个空项。0-63存放的是VFL_TYPE_GRABBER,64-127存放的是VFL_TYPE_RADIO, 
        if (video_device[i] == NULL)    
            break;
    if (i == VIDEO_NUM_DEVICES) {
        mutex_unlock(&videodev_lock);
        printk(KERN_ERR "could not get a free minor
");
        return -ENFILE;
    }
#endif
    vdev->minor = i + minor_offset;    //次设备号就等于基数i + 次设备号的偏移量
    vdev->num = nr;      //设备号的结点,比如说nr=10,对应的类型是VFL_TYPE_GRABBER,那么最后应该看到/dev/video10
    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);

    /* Part 3: Initialize the character device ,这个地方就是注册一个字符设备的常规操作*/
    vdev->cdev = cdev_alloc();
    if (vdev->cdev == NULL) {
        ret = -ENOMEM;
        goto cleanup;
    }
    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
", __func__);
        kfree(vdev->cdev);
        vdev->cdev = NULL;
        goto cleanup;
    }

    /* Part 4: register the device with sysfs */
    // 设置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);   //假设对应的是VFL_TYPE_GRABBER类设备,那么name_base就是video,即最后看到的应是/dev/videoxx
   /*利用device_register,注册字符设备驱动。这个地方和我们写的字符设备驱动程序是一样的*/
    ret = device_register(&vdev->dev);
    if (ret < 0) {
        printk(KERN_ERR "%s: device_register failed
", __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
", __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
",
                   __func__);
    }
#endif
    /* Part 6: Activate this minor. The char device can now be used. */
   /* Flag to mark the video_device struct as registered.
   Drivers can clear this flag if they want to block all future
   device access. It is cleared by video_unregister_device. 
 
   #define V4L2_FL_REGISTERED    (0)

    将这个unsigned long flags 的第0 位 置1,表示这个video_device 是注册过的了,
    在其他位置,会调用video_is_registeried( ) 来判断,其依据 还是测试这个flags的第0位。
    */
    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);

注1:VBI:

VBI= vertical blanking interval,也就是我们讲的场消隐期间。
VBI在电视处理中是用来是垂直扫描完成从屏幕底部回到屏幕顶部的时间。在这期间,没有任何的图像信息,在原来是基本废置不用的。后来,利用这期间来传输一些信息,比如CC,图文,VPS/PDC,GEMSTAR等等信息服务。