[完结]Linux内核中的V4L2核心框架分析(V4L2 framework,video for linux 2,linux视频子系统)


标题: V4L2核心框架分析   

 

驱动的结构
------------------------------------------------------
1)一个为设备实例定义的,并且包含设备状态信息的结构;
2)一种初始化和命令子设备(sub-devices)的方式;
3)创建V4L2设备节点(/dev/videoX, /dev/vbiX, /dev/radioX and /dev/vtxX)
   并且 keeping track of device-node specific data.
4)Filehandle-specific structs containing per-filehandle data;
5)视频buffer处理;

下面有个大略的关系描述图:

    device instances
      |
      +-sub-device instances
      |
      \-V4L2 device nodes
      |
      \-filehandle instances

 


框架的结构
------------------------------------------------------------
框架结构与驱动结构类似:
它有:
    一个用于device实例数据的结构体: v4l2_device
    一个用于sub-device实例的结构体:v4l2_subdev
    一个存储设备节点数据的结构体:video_device
    将来会用于保持对文件操作实例的追踪的结构体:v4l2_fh

 


v4l2_device结构体(struct v4l2_device)
-----------------------------------------------------------------------
每个设备实例是由一个struct v4l2_device结构(v4l2-device.h)来描述的。
很简单的设备可以只分配这个结构,但是,大部分时候,你将嵌入这个结构到一个更大的与描述特定设备的结构。
你必须注册设备实例(在drivers/media/video/v4l2-device.c中):
 

  v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev);

 

注册将初始化v4l2_device结构,和连接(link) dev->driver_data 到v4l2_dev。
如果v4l2_dev->name 是空的,那么它将被设置成一个值,这个值是由dev参数得到的(严格来说,驱动名跟随bus_id)。
如果你在调用v4l2_device_register()之前设置了v4l2_dev->name,那么v4l2_dev->name将不被改变(即使用你之前设置的值)。
如果dev是NULL,那么你必须在调用v4l2_device_register()之前填充v4l2_dev->name。

你可以使用 v4l2_device_set_name()来设置这个名称为驱动的名字 和 一个  driver-global atomic_t instance。
这将产生一些名字如: ivtv0, ivtv1 等。如果这些名字(ivtv0中的ivtv就是一个名字)以一个数字结束,那么它将插入一个“-”,然后跟上一个顺序的编号。
v4l2_device_set_name()这个函数返回这个实例号。

 

 

    int v4l2_device_set_name(struct v4l2_device *v4l2_dev, const char *basename,
                            atomic_t *instance)
    {
        int num = atomic_inc_return(instance) - 1;
        int len = strlen(basename);

        if (basename[len - 1] >= '0' && basename[len - 1] <= '9')//如果名字的最后是数字;
            snprintf(v4l2_dev->name, sizeof(v4l2_dev->name),
                    "%s-%d", basename, num);//那么名字后面加“-”
        else
            snprintf(v4l2_dev->name, sizeof(v4l2_dev->name),
                    "%s%d", basename, num);
        return num;
    }
    EXPORT_SYMBOL_GPL(v4l2_device_set_name);


 

现在回头说v4l2_device_register函数。它的第一个参数dev通常都是一个pci_dev
、usb_interface 或者 platform_device 的“struct device”类型的指针。
dev基本上不会是NULL,但是在ISA devices或者当一个设备创建了多个PCI设备的时候,这是可能发生的。
这时候它与v4l2_dev产生了关系,v4l2_dev下可能有个dev成员(即v4l2_dev是父,dev是子)。

你也可以提供一个 notify()的回调函数,子设备(sub-devices)可以调用这个回调函数通知你一些事件(events);
你是否需要设置这个回调函数,取决于这个子设备。一个子设备支持的任何通知(notifications)必须被定义在头文件“include/media/.h”当中。
比如include/media/ 下有如下文件:
    v4l2-chip-ident.h  v4l2-fh.h          v4l2-mem2mem.h         videobuf-dvb.h
    v4l2-common.h      v4l2-i2c-drv.h     v4l2-subdev.h          videobuf-vmalloc.h
    v4l2-dev.h         v4l2-int-device.h  videobuf-core.h            tvp5150.h(这个就是subdevice的头文件)
    v4l2-device.h      v4l2-ioctl.h       videobuf-dma-contig.h
    v4l2-event.h       v4l2-mediabus.h    videobuf-dma-sg.h     …………

有注册,就有反注册。那对应的反注册的函数是:

    v4l2_device_unregister(struct v4l2_device *v4l2_dev);

 

反注册,也将会自动从设备反注册掉所有的子设备(subdevs)。

如果你有一个可热插拔的(hotpluggable)的设备(例如USB设备),那么的那个一个disconnect(断开)发生时,父设备变成无效的(invalid)。
因为v4l2_device中有一个指针指向父设备,所以他可以同时被清除掉,并且标记父设备is gone(这里应该是在父设备的结构里标记子设备已经无效了)。
    v4l2_device_disconnect(struct v4l2_device *v4l2_dev); //热插拔使用的断开函数

 

    void v4l2_device_disconnect(struct v4l2_device *v4l2_dev)
    {
        if (v4l2_dev->dev) {//这个函数制式标记了父设备那边告诉子设备已经无效。
            dev_set_drvdata(v4l2_dev->dev, NULL);
            v4l2_dev->dev = NULL;
        }
    }
    EXPORT_SYMBOL_GPL(v4l2_device_disconnect);



v4l2_device_disconnect不会反注册掉子设备,所以你依然需要为子设备调用v4l2_device_unregister这个函数。
事实上,v4l2_device_unregister函数中还是会调用v4l2_device_disconnect的。

    void v4l2_device_unregister(struct v4l2_device *v4l2_dev)
    {
        struct v4l2_subdev *sd, *next;

        if (v4l2_dev == NULL)
            return;
        v4l2_device_disconnect(v4l2_dev);
            …………
    }

 

如果你的设备不支持热插拔,那么就不需要调用v4l2_device_disconnect()了。

有时候,你需要迭代把所有的设备通过一个特定的驱动注册。这种情况通常发生在,多个设备驱动使用同一个硬件(hardware)的时候。
例如(E.g.),ivtvfb 驱动是一个 帧缓冲(framebuffer)设备驱动,这个驱动使用ivtv这个硬件。类似的 alsa驱动也是一样。

你可以像如下这样迭代所有已注册的设备:

 

    static int callback(struct device *dev, void *p)
    {
        struct v4l2_device *v4l2_dev = dev_get_drvdata(dev);

        /* test if this device was inited */
        if (v4l2_dev == NULL)
            return 0;
        ...
        return 0;
    }

    int iterate(void *p)
    {
        struct device_driver *drv;
        int err;

        /* Find driver 'ivtv' on the PCI bus.
           pci_bus_type is a global. For USB busses use usb_bus_type. */
        drv = driver_find("ivtv", &pci_bus_type);//在&pci_bus_type上查找ivtv的驱动,并返回驱动的指针drv
        /* iterate over all ivtv device instances */
        err = driver_for_each_device(drv, NULL, p, callback);//这里用到了callback,和drv
        put_driver(drv);//在争用的那个硬件上使用驱动drv;
        return err;
    }


有时候你需要保持一个正在运行的设备实例的计数器(counter)。这个计数器经常用于映射(map)一个设备实例到一个模块可选数组的索引(an index of a module option array)。
建议的方法如下:

    static atomic_t drv_instance = ATOMIC_INIT(0);

    static int __devinit drv_probe(struct pci_dev *pdev,
                    const struct pci_device_id *pci_id)
    {
        ...
        state->instance = atomic_inc_return(&drv_instance) - 1;//atomic_inc_return(&drv_instance)自加的同时返回值
    }

 

 

子设备结构(struct v4l2_subdev)
-----------------------------------------------------------------------------------
       许多驱动需要与子设备(sub-devices)通信。这些设备可以对任务进行排序,但是更常见的是,它们处理audio and/or video复用(muxing),以及编码和解码。
对于webcams网络摄像头通常子设备是传感器(sensors)和摄像头控制器(camera controllers)。
通常这些是I2C设备,但不一定就是I2C设备。为了提供一个具有一致接口的驱动给这些子设备, v4l2_subdev 结构(v4l2-subdev.h)就是为此而诞生的.

        每个子设备驱动必须有一个 v4l2_subdev 结构。这个结构可以让简单的子设备保持独立,或者可以被嵌入到一个更大的结构体(如果更多的状态信息需要被存储的话)。
通常,有一个低层次的(low-level)设备结构(例如:i2c_client),包含了这个设备数据(device data),它由内核建立。
建议使用v4l2_set_subdevdata()函数存储一个i2c_client的指针到“v4l2_subdev”结构中的私有数据区域( private data)。
这样使得很容易从v4l2_subdev交互到一个实际的低层次的特定总线(low-level bus-specific)设备数据。

你也需要一种方法去从低层次的结构 走到 v4l2_subdev。对于共有的i2c_client 结构,i2c_set_clientdata()这个函数调用可以被用来存储一个v4l2_subdev类型的指针,对于其他总线,你可能必须使用其他方法。

从桥接驱动的观点上来看(From the bridge driver perspective),你要加载子设备模块,并且以某种方式(somehow)获得v4l2_subdev的指针。
对于i2c设备这是很容易的:你调用i2c_get_clientdata()函数就可以了。对于其他总线,有类似的的做法。
在一个I2C总线的子设备中有帮助函数存在,这些函数会帮你做大部分繁琐复杂的工作。

每个v4l2_subdev包含了若干函数指针,子设备驱动可以实现这些函数(或者让它为 NULL,如果不用到它的话)。因为子设备可以做任何这样的事情,并且你不想以一个巨大的ops结构(一大串通常已经实现的ops,ops即包含上述函数指针的结构体)。这些函数指针已经按照分类排序了,并且每个分类具有它自己的ops结构体。

顶层的(top-level)的ops结构体包含了到分类ops结构体的指针,它们可以是NULL,如果子设备驱动(subdev driver)不支持这个分类的任何功能的话。

这些ops看起来是这样:

 

struct v4l2_subdev_core_ops {
    int (*g_chip_ident)(struct v4l2_subdev *sd, struct v4l2_dbg_chip_ident *chip);     int (*log_status)(struct v4l2_subdev *sd);
    int (*init)(struct v4l2_subdev *sd, u32 val);
    ...
};

struct v4l2_subdev_tuner_ops {
    ...
};

struct v4l2_subdev_audio_ops {
    ...
};

struct v4l2_subdev_video_ops {
    ...
};

//最后这个子设备的 ops 包含了 core_ops 、tuner_ops、audio_ops、video_ops 的指针。
struct v4l2_subdev_ops {
    const struct v4l2_subdev_core_ops  *core;
    const struct v4l2_subdev_tuner_ops *tuner;
    const struct v4l2_subdev_audio_ops *audio;
    const struct v4l2_subdev_video_ops *video;
};


核心ops(core_ops)是所有子设备(subdevs)公用的,其他分类的ops是否试下取决于子设备。例如,一个视频设备不太可能去支持一个 audio ops 和vice versa。那么就不用事先相应分类的ops。

这种建立ops关系的方式,在依然保持它们易于添加新的ops和分类的同时,也限制了函数指针的数量。

一个子设备驱动使用如下函数初始化 v4l2_subdev结构:

        v4l2_subdev_init(sd, &ops);


然后,你需要初始化 使用一个唯一的名字来初始化 subdev->name,并且设置这个 module owner。如果你使用I2C帮助函数,那么这些帮助函数都已经为你做了这些工作了。

一个设备(桥接)驱动,需要使用如下函数去把 v4l2_subdev 与 v4l2_device 注册到一起:

    int err = v4l2_device_register_subdev(v4l2_dev, sd);//sd代表v4l2_subdev;v4l2_dev代表:v4l2_device


 如果子设备模块在它成功注册之前消失了,那这个注册函数肯定会失败的。
这个函数成功执行之后, subdev->dev 就会指向 v4l2_device。于是就关联起来了。

  

/*******2012年8月14日10:00 更新*********/
 
子设备移除(反注册);你也可以通过这个函数反注册掉之前注册的子设备:
 v4l2_device_unregister_subdev(sd);
在这个函数执行之后,子设备模块就被卸载了(be unloaded),并且sd->dev==NULL;

你可以也可以直接调用ops函数:
   err = sd->ops->core->g_chip_ident(sd, &chip);
但是,用下面这个宏的话,更方便,更快捷:
    err = v4l2_subdev_call(sd, core, g_chip_ident, &chip);
 
 这个宏将做NULL指针的检查,并且返回错误码 -ENODEV(如果子设备(subdev)是NULL的话);
  subdev->core 或 subdev->core->g_chip_ident 是NULL的话,就会返回 -ENOIOCTLCMD;
 如果输入参数都没有问题,那就会返回 subdev->ops->core->g_chip_ident 这个操作的返回值。

一般也会调用 sub-devices中的所有函数,或是一个子集(某类函数):
    v4l2_device_call_all(v4l2_dev, 0, core, g_chip_ident, &chip);
 任何不支持这个ops的子设备(subdev),会被跳过,并且错误的结果也会被忽略。

 如果你想检查错误,请使用:
    err = v4l2_device_call_until_err(v4l2_dev, 0, core, g_chip_ident, &chip);
 除了-ENOIOCTLCMD的任何错误,将带着错误退出循环(loop)。除了-ENOIOCTLCMD以外,如果没有错误发生,那么就返回0。

 以上两个函数(v4l2_device_call_all,v4l2_device_call_until_err)的第二个参数是一个group ID。如果是0,那么所有的 subdevs被调用。如果是“非零”,那么这个非零的数字就是group ID,并且执行这个groupID内的所有的函数。
 在一个桥接驱动注册一个subdev之前,它可以设置sd->grp_id为任意它希望的值(默认是0).这个值是桥接驱动(bridge driver)所拥有的,并且子设备驱动(sub-device driver)将不会修改或者使用它。

group ID 给了桥接驱动更多的控制,这些控制就是回调函数(callbacks)。
例如,现在有多个audio芯片在板上,每个都由改变音量的能力。但是,通常实际被使用的仅有一个(当用户需要去改变音量的时候)。那么你可以为那个子设备(subdev)设置 group ID 。例如,AUDIO_CONTROLLER,并且在调用v4l2_device_call_all()时,可以指定AUDIO_CONTROLLER 为group ID 值(第二个入参)。这样可以确保,它仅操作它需要的子设备(subdev)。

如果子设备需要通知(notify …… of ……) 它的父设备 v4l2_device 一个事件,那么就可以调用: v4l2_subdev_notify(sd, notification, arg)。这个宏会检查是否有一个 notify() 的回调函数已经定义,如果没有定义的话,就返回错误码:-ENODEV。否则,当然就是有定义notify() 回调,那自然就调用这个回调。

使用v4l2_subdev这个结构的优势是,它是一个通用的结构,并且没有包含任何底层硬件的特性(does
not contain any knowledge about the underlying hardware)。也就是说它是硬件无关,完全不用改动就可移植的。所以一个驱动包含了若干个subdevs,这些子设备(subdevs)都使用同一个I2C总线。但是也有一个subdev,它是可以被GPIO引脚来控制的。这些差别仅当设置设备的时候才有关系。但是一旦这个子设备被注册,它就是完全透明的。

 

 

I2C子设备驱动(I2C sub-device drivers)
---------------------------------------------------------------------------
因为这些驱动是公用的,这里提供了特别的帮助函数,它能有效的提升使用这些驱动的易用性(v4l2-common.h)。

在一个I2C驱动中添加v4l2_subdev支持,一个建议的方法是把v4l2_subdev结构体嵌入到为每个I2C设备实例创建的状态结构(state struct )当中去。很简单的设备可能没有状态结构(state struct),并且在这样的情况下,你可以直接创建一个v4l2_subdev。

一个典型的状态结构(state struct )将看起来像这样的(里面的“chipname”,应该是由 实际chip的名字来替代的):

struct chipname_state {
    struct v4l2_subdev sd;
    ...  /* additional state fields */
};

初始化 v4l2_subdev 结构:

    v4l2_i2c_subdev_init(&state->sd, client, subdev_ops);

这个函数将填充所有v4l2_subdev的字段(fields),并且确保v4l2_subdev 和i2c_client 都指向对方。

你应该也添加一个内联帮助函数去从 v4l2_subdev 指针跳转到 chipname_state 的结构(即获得chipname_state结构的指针):

 static inline struct chipname_state *to_state(struct v4l2_subdev *sd)
 {
  return container_of(sd, struct chipname_state, sd);//实际上是使用container_of跳转的;ddl3这本书里有说明这个宏是怎么用的。
 }

使用如下函数从 v4l2_subdev 跳转到 i2c_client 结构(即获得指向i2c_client结构的指针):
    struct i2c_client *client = v4l2_get_subdevdata(sd);

使用如下函数反过来从 i2c_client 跳转到 v4l2_subdev结构:
    struct v4l2_subdev *sd = i2c_get_clientdata(client);

确保当remove()回调函数被调用的时候,调用 v4l2_device_unregister_subdev(sd) 来反注册子设备。
这将从桥接驱动(bridge driver)反注册掉子设备(sub-device)。如果子设备从未被注册,那这个调用也是安全的。

你需要这么做,因为当桥接驱动从销毁I2C adapter的时候, 这个remove()回调会被在哪个adapter上的i2c设备调用。
在这之后,相应的v4l2_subdev结构,是无效的,所以它们需要被先反注册掉。在 remove()回调函数中,调用v4l2_device_unregister_subdev(sd) 可以确保总是正确的(不会忘记,或者在不恰当的地方调用反注册)。

桥接驱动也具有一些帮助性的函数:
struct v4l2_subdev *sd = v4l2_i2c_new_subdev(v4l2_dev, adapter,
                            "module_foo", "chipid", 0x36, NULL);

这个加载了一些给出的模块(可以是NULL,如果没有模块需要被加载的话),并且调用
 i2c_new_device() ,
 使用给定的i2c_adapter 和 chip/address 作为入参。
如果一切没有问题的话,那么把subdev与v4l2_device 注册到一起的话就肯定会成功了。

你也可以使用
 v4l2_i2c_new_subdev()
的最后一个参数来传递一个 可能的I2C addresses的数组,而且应该probe。
如果前一个参数是0的话,那么仅使用这些probe addresses。一个非零的参数,意味着你知道确切的I2C地址,所以,在那种情况下probing将不会发生。

如果有错误的话,i2c_new_device() 和 v4l2_i2c_new_subdev() 两个函数,都返回NULL。

注意 传递给 v4l2_i2c_new_subdev() 的 chipid 通常是与模块名是一样的。 这允许你制定一个chip的变量。例如"saa7114" 和 "saa7115"。通常第,尽管i2c驱动自动探测名字。
chipid的使用是为了满足需要去更近的查看一个最新的日期。它区别了i2c驱动和可能混淆的东西。
要看被支持的chip variants(变量),你可以查看  i2c driver 的代码,查找 i2c_device_id 这个表。i2c_device_id中列出了所有可能的。

这里有更多帮助的函数:
    v4l2_i2c_new_subdev_cfg() // 位于 driver/media/video/v4l2-common.c
    这个 函数添加了新的irq和platform_data 参数,和 'addr' 和 'probed_addrs' 两个参数。
 如果addr不是0,“non-probing variant”将被使用,否则,“probed_addrs” 被probed。
 
 例如:这里将probe addresse 0x10:
 struct v4l2_subdev *sd = v4l2_i2c_new_subdev_cfg(v4l2_dev, adapter,
           "module_foo", "chipid", 0, NULL, 0, I2C_ADDRS(0x10));

v4l2_i2c_new_subdev_board 使用一个 i2c_board_info 结构(是被传递到i2c driver,并且替代 irq, platform_data 和 addr 这三个参数的)。

如果subdev 支持  s_config core ops ,那么在子设备建立之后,那个op是被调用,并传入 irq 和  platform_data 两个参数。更早的一个函数v4l2_i2c_new_(probed_)subdev ,也会调用 s_config ,但是入参 irq是0,platform_data是NULL。

 

video_device结构体(struct video_device)
------------------------------------------------------------------------------------
实际的设备节点 被创建(使用video_device 结构体(v4l2-dev.h))在 /dev 目录。这个结构体也可以被动态分配,或是嵌入到更大的结构体。

 

 

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 */
    /* 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 standard vars */
    v4l2_std_id tvnorms;        /* Supported tv norms */
    v4l2_std_id current_norm;   /* Current tvnorm */
    /* callbacks */
    void (*release)(struct video_device *vdev);
    /* ioctl callbacks */
    const struct v4l2_ioctl_ops *ioctl_ops;
};


 

 动态分配使用:

    struct video_device *vdev = video_device_alloc();

    if (vdev == NULL)
        return -ENOMEM;

    vdev->release = video_device_release;

如果嵌入它到一个更大的结构体,那么你必须设置  release() 回调函数为你自己的函数。

    struct video_device *vdev = &my_vdev->vdev;

    vdev->release = my_vdev_release;

这个 release() 回调函数 必须被设置,并且他是在最后一次使用video设备退出的时候被调用的。

默认的  video_device_release()  回调函数 仅在 调用kfree 去 释放被分配的内存的时候,才调用。

你也需要设置如下字段:
 - v4l2_dev: 设置为 v4l2_device 父设备;
 - name:  设置为某个描述名字,保证唯一性;
 - fops : 设置为 v4l2_file_operations 结构体;
 - ioctl_ops: 如果你使用这个 v4l2_ioctl_ops 去简化 ioctl 的维护,那么设置它为 你的 v4l2_ioctl_ops 结构体。强烈建议使用 v4l2_ioctl_ops 结构体,这在将来是必须的。
 - parent: 如果 v4l2_device 使用 NULL的作为父设备的结构体参数,如果没有父设备的话。这样也是可以被注册的。但是这仅发生在一个硬件具有多个PCI设备,并且所有的都共享同一个 v4l2_device core 的情况下。

 cx88 驱动就是这样一个例子:一个 核心的v4l2_device结构体,但是,它只被一个  raw video PCI device (cx8800) 和 a MPEG PCI device (cx8802) 使用。因为 v4l2_device 不能与特殊的 没有父设备的PCI设备 关联起来。 但是,当 v4l2_device 被建立,你可以知道使用哪个父PCI设备。

如果你使用 v4l2_ioctl_ops 那么你需要设置 v4l2_file_operations 结构体内的 .unlocked_ioctl 或 .ioctl   为 video_ioctl2 。

v4l2_file_operations结构体 是一个 file_operations 的子集。主要的不同在于,inode  参数被忽略了,因为它从未被使用到。

 


video_device的注册(video_device registration)
----------------------------------------------------------------------------
你注册一个视频设备的时候,会创建一个字符设备。

    err = video_register_device(vdev, VFL_TYPE_GRABBER, -1); //在这个函数里使用了cdev_add()
    if (err) {
        video_device_release(vdev); /* or kfree(my_vdev); */
        return err;
    }

 被注册的设备取决于 type这个参数(第二个参数)。有如下的类型存在:
 VFL_TYPE_GRABBER:

                  videoX for video input/output devices //视频输入输出设备
 

VFL_TYPE_VBI:

                  vbiX for vertical blank data (i.e. closed captions, teletext)//用于垂直块数据的VBI

 VFL_TYPE_RADIO:

                  radioX for radio tuners //radio调谐设备


 VFL_TYPE_VTX:

                   vtxX for teletext devices (deprecated, don't use //vtx 图文电视广播设备

 

最后的参数(第三个参数)给出一个被使用的确定的在设备的设备节点号(如videoX 中的 “X”)。通常,你要传-1 使 v4l2框架选择第一个未使用的数字。但是,有时候,用户希望选择一个特定的节点号。通常,驱动允许用户通过一个驱动选项,去选择一个特定的设备节点号(device node number)。这个节点号,会被传递到这个函数,video_register_device 将试图去选择哪个设备节点号。如果那么节点号已经在使用的那个中,那么下一个临近的没有使用的设备节点号将被选择,并且它发送一个警告到kernel 的log。

另外一个使用情况是,如果一个驱动创建了许多设备(many devices)。在这种情况下,它(video_register_device)可以把不同的视频设备放置到不同独立的范围(in separate ranges)。例如,视频捕获设备(video capture devices)是从0 开始,视频输出设备是从16开始。所以你可以使用最近的参数去指定一个最小的设备节点号,并且v4l2框架将尝试去选择第一个空闲的没有被使用的数字,那可能等于或者大于你传递进来的数字(设备节点号)。
如果申请等于或大于你传递进来的数字失败(因为之前可能已经被申请过了),那么它将会选择第一个没被使用的数字。

如果在这种情况下,你不关心这样的警告(不能选择特定设备节点号的警告),你可以调用这个函数:
 video_register_device_no_warn() //忽略警告

无论什么时候创建设备节点,一些属性同时也会被创建。如果你查看  /sys/class/video4linux 这个目录,你会看到这些设备。 如 video0,和 你将看到 “name” 和 “index” 属性。“name”属性是 video_device 结构体的 “name” 字段。

“index”属性是这个设备节点的索引(index):
每一次调用 video_register_device() 成功,index就加1 。 第一个视频设备节点,你总可以从索引值为0的数字开始注册。

用户可以建立 udev 规则(rules) ,利用index属性去制作一个 喜爱的设备名(比如mpegX。 用于 MPEG 视频捕获节点)。

在设备成功注册之后,你可以使用如下字段(都是video_device结构体中的字段):

 - vfl_type: 传递给 video_register_device 的设备类型.
 - minor: 最后被分配的次设备号
 - num: 设备节点号 the device node number (i.e. the X in videoX).
 - index: 设备索引号 the device index number.

如果注册失败,那么你需要去调用 video_device_release() 来 释放分配的video_device结构体,或者 释放你自己的结构 ,如果 video_device 被嵌入在其中的话。  vdev->release() 回调将永远不会被调用, 如果注册失败的话。也你也不应该尝试反注册(unregister)这个设备,如果注册失败了。

 

 

video_device清理(video_device cleanup)
--------------------------------------------------------------------
当视频设备节点必须被移除的时候,可以在驱动的卸载器件,或者USB设备断开连接的时候,反注册它们(unregister them):
    video_unregister_device(vdev);
这将从sysfs移除设备节点(导致udev从 /dev 移除它们)。

在  video_unregister_device() 返回之后,不能进行新的 open,即不能再打开这个设备。然而,USB设备的某些应用在这种情况下,依然有一个打开的设备节点。所以,在unregister所有的文件操作(all file operations )之后,将返回一个错误,除了ioctl 和 unlocked_ioctl 文件操作:
 这些将依然被传递,因为一些 buffer ioctls 可能依然被需要。

 当最后一个用户使用的视频设备节点存在时, vdev->release() 回调函数被调用,并且你可以在这个函数里做最后的清理(final cleanup)。
 

 
video_device帮助函数(video_device helper functions)
------------------------------------------------------------------------------------------
有一些使用的帮助函数:


*- file/video_device private data

你可以  set/get  驱动的私有数据(private data,在 video_device 结构中):

 void *video_get_drvdata(struct video_device *vdev);
 void video_set_drvdata(struct video_device *vdev, void *data);

注意,你可以在 video_register_device() 调用之前,安全的调用  video_set_drvdata()

下面这个video_devdata函数:
 struct video_device *video_devdata(struct file *file);
返回了 属于这个 file结构的 video_device 结构。

video_drvdata 函数包含了 video_get_drvdata 和 video_devdata:
 void *video_drvdata(struct file *file);

 你可以从 video_device 结构 跳转至 v4l2_device 使用:
 struct v4l2_device *v4l2_dev = vdev->v4l2_dev;

 
*- 设备节点名 (Device node name)

 video_device的节点 内核名,可以被使用如下函数检索:
 const char *video_device_node_name(struct video_device *vdev);

名字是被用作用户空间工具的示意,例如 udev (udev貌似是用户控件操作驱动的一类工具?)。这个函数应该在“可能代替访问 video_device::num h 和 video_device::minor 这两个字段的地方” 使用。

 

 

video buffer 帮助函数( helper functions)
--------------------------------------------------------------------------
v4l2内核API为处理video buffers 提供了一系列的标准方法(被叫做"videobuf")。这些方法允许一个驱动以常见的方式实现 read(), mmap() 和 overlay() 这些函数。当前已经有一些方法用于在设备上使用video buffers ,这些设备支持DMA;而DMA支持 scatter/gather 方法(videobuf-dma-sg)、线性访问(videobuf-dma-contig)、 vmalloced buffers ,通常被用在USB驱动上(videobuf-vmalloc)。

更多关于如何使用 videobuf 层的信息,请参考 Documentation/video4linux/videobuf 。

 


v4l2_fh结构体(struct v4l2_fh)
-----------------------------------------------------------
v4l2_fh结构体(fh 是 file handle的缩写)提供了一种方式使得“保持文件处理(file handle)特定的数据”变得容易(被V4L2框架使用的数据)。对于驱动来说是否使用 v4l2_fh结构体 是可选的。

v4l2_fh结构体(在V4L2框架中,而不是驱动中) 的使用者通过测试 video_device->flags 的 V4L2_FL_USES_V4L2_FH bit位 来知道是否一个驱动应该使用 v4l2_fh结构体 (file->private_data 指针指向v4l2_fh)。

很有用的函数:

- v4l2_fh_init()
  初始化文件处理(file handle)。这些 *MUST* 由驱动中的 v4l2_file_operations->open() 来处理。

- v4l2_fh_add()
  添加一个 v4l2_fh 结构体到 video_device 的文件处理列表(file handle list)。它可能在初始化文件处理(file handle)之后被调用。

- v4l2_fh_del()
  从 video_device() 取消对 v4l2_fh 的关联。文件处理退出函数可能在此时被调用。

- v4l2_fh_exit()
  反初始化(Uninitialise)文件处理。在反初始化操作之后,v4l2_fh 结构体所占的内存被释放。

v4l2_fh结构体被当做驱动自己的文件处理结构的一部分 被分配内存。并且它在驱动的 open函数中,被驱动设置v4l2_fh指针为 file->private_data 。也就是说 一旦open函数被调用之后, file->private_data 这个指针就指向 v4l2_fh 。驱动可以通过使用 container_of 这个宏来提取(extract)他们自己的文件处理结构。

比如 :

 

struct my_fh {
    int blah;
    struct v4l2_fh fh;
};

...

int my_open(struct file *file)
{
    struct my_fh *my_fh;
    struct video_device *vfd;
    int ret;

    ...

    ret = v4l2_fh_init(&my_fh->fh, vfd);
    if (ret)
        return ret;

    v4l2_fh_add(&my_fh->fh);

    file->private_data = &my_fh->fh;

    ...
}


int my_release(struct file *file)
{
    struct v4l2_fh *fh = file->private_data;
    struct my_fh *my_fh = container_of(fh, struct my_fh, fh); //container_of宏取出my_fh的地址

    ...
}

 

 

V4L2事件(V4L2 events)
----------------------------------------------------
V4L2的事件提供了一种通用的方式去 传递事件到用户空间。驱动必须使用v4l2_fh 结构体才能支持 V4L2事件。
 
很有用的函数:

- v4l2_event_alloc()
  要使用事件,驱动必须为文件处理(file handle)分配事件。通过不止一次调用v4l2_event_alloc()函数,驱动能确保总共至少n个事件被分配。这个函数不能再原子上下文(atomic context)中调用。

- v4l2_event_queue()
  到 视频设备(video device)的 事件队列(Queue events)。驱动仅负责去填充 type 和data 字段。别的字段将由 V4L2填充。

- v4l2_event_subscribe()
  如果 video_device->ioctl_ops->vidioc_subscribe_event 必须检查驱动是否具备处理 具有指定事件ID(event id)的事件过程(produce events )的能力。那么它可以调用v4l2_event_subscribe() 去 签名(subscribe) 这些事件。

- v4l2_event_unsubscribe()
  vidioc_unsubscribe_event 位于 v4l2_ioctl_ops 结构中。 一个驱动可以直接地使用 v4l2_event_unsubscribe() ,除非它想在 unsubscription 进程中被调用。

  这些特殊的类型 V4L2_EVENT_ALL 可以被使用 来对所有事件进行 解除签名(unsubscribe)的操作。驱动希望可以以一个特殊的方式来处理这些。
 
- v4l2_event_pending()
  返回 pending事件(pending events)的 number 。当实现 poll 函数时很有用。


驱动不直接初始化事件。video_device->ioctl_ops->vidioc_subscribe_event 不是NULL的话,这些事件是通过  v4l2_fh_init() 函数来初始化的。这些 *MUST* 是在 驱动的 v4l2_file_operations->open() 处理中进行的。


事件是通过poll系统调用被传递至用户空间的。驱动可以使用 v4l2_fh->events->wait(wait_queue_head_t) 的 作为poll_wait()的一个参数。


事件分为:标准事件 (standard events)和 私密事件(private events)。新的标准事件必须使用最小有效的事件类型(the smallest available event type)。驱动必须从它们自己的类(从基类开始their own class starting from class base)中分配它们的事件。 这里所说的”基类(class base)“ V4L2_EVENT_PRIVATE_START + n * 1000  (n 是 最低有效的数字 )。在class中的第一个事件类型是保留给将来使用的,所以第一个有效的事件类型是 'class base + 1' 。

一个关于如何使用 V4L2 events 的例子,可以在 OMAP 3 ISP 驱动中看到 ( at )。

 

 

 

 

 

 

 

你可能感兴趣的:(嵌入式技术)