1.v4l2 framework框架

本文讲述V4L2的框架所提供的各种结构以及它们之间的连系.


1、简介

由于硬件的复杂性v412驱动往往是非常复杂的: 大多数设备有多个IC(集成电路),在/dev目录下有多个设备节点, 并也创建non-V4L2的设备,如DVB(数字视频广播Digital Video Broadcasting),ALSA【Advanced Linux Sound Architecture,高级Linux声音架构的简称,它在Linux操作系统上提供了音频和MIDI(Musical Instrument Digital Interface,音乐设备数字化接口)的支持】,FB(多功能的音频播放器),I2C和input(IR)设备。

特别是v412驱动支持很多的音频/视频、多路复用/编码/解码芯片,使得它比大多数模块更加复杂的。通常这些芯片通过一个或多个I2C总线被连接到主桥驱动器总线,但也可以使用其他总线。这种设备被称为“子设备”。

很长一段时间这个框架被限制在video_device结构,用来创建v4l设备节点和视频缓冲区处理的video_buf(请注意,本文档不讨论video_buf框架)。

这意味着,所有驱动程序必须设置设备实例的并连接本身的子设备。这部分是相当复杂,很多驱动程序很难这样正确处理。 
由于缺乏一个框架使很多共同的代码不可重构。因此,这个框架设置所有驱动到需要的基本结构单元,与此相同的框架更容重构被所有驱动共享的相同代码。

首先来看看所有的v4l2驱动都必须要有的几个组成部分:

– 用来描述每一个v4l2设备实例状态的结构(structv4l2_device)

– 用来初始化和控制子设备的方法(structv4l2_subdev)

– 要能创建设备节点并且能够对该节点所持有的数据进行跟踪(structvideo_device)

– 为每一个被打开的节点维护一个文件句柄(structv4l2_fh)

– 视频缓冲区的处理(videobuf或者videobuf2 framework)


linux3.0以上的内核对这些结构的定义,从定义当中就可以窥探整个v4l2的框架。这些结构体有:

struct v4l2_device; 用来描述一个v4l2设备实例

struct v4l2_subdev, 用来描述一个v4l2的子设备实例

struct video_device; 用来创建设备节点/dev/videoX

struct v4l2_fh; 用来跟踪文件句柄实例

我们把videobufvideobuf2框架放到后面的系列来讨论。


用一个比较粗糙的图来表现他们之间的关系,大致为:

设备实例(v4l2_device)

           |______子设备实例(v4l2_subdev)

           |______视频设备节点(video_device)

           |______文件访问控制(v4l2_fh)

           |______视频缓冲的处理(videobuf/videobuf2)


2、驱动程序的结构
---------------------

所有的驱动程序有以下结构:
1) 一个结构体,包含设备的状态的设备实例。
2) 一个方法,初始化和控制子设备(如果有).
3) 创建V4L2的设备节点 (/dev/videoX, /dev/vbiX and /dev/radioX)和与设备节点的特殊数据保持关联。
4) 文件处理特定的结构,包含每个预先文件处理数据;
5) 视频缓冲处理。

这是一个粗略的示意图:
    device instances(设备实例)
      |
      +-sub-device instances(子设备实例)
      |
      \-V4L2 device nodes(V4L2的设备节点)
            |
            \-filehandle instances(文件操作实例)
3、框架结构
--------------------------
V4L2_device结构体:代表设备实例数据 
v4l2_subdev结构体:关联子设备实例
video_device结构体:保存v4l2设备节点数据
v4l2_fb结构体:跟文件处理实例联系在一起
--------------------------
V4L2框架也包含media 框架,如果一个驱动设置了v4l2_device结构体的mdev成员,子设备和视频节点将自动出现media框架中的实体。
4、struct v4l2_device
1).设备的注册
每个设备实例通过结构体v4l2_device(V4L2-device.h中)来代表。只是很简单的设备可以仅仅分配这个结构,但大多数的时候要把这个结构体嵌入到一个更大的结构体中。
必须注册设备的实例:
 v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev);

注册过程将初始化 v4l2_device 结构体. 如果 dev->driver_data字段是空, 它将连接到 v4l2_dev.

成媒体设备框架的驱动程序需要设置dev-> driver_data指向嵌入结构体v4l2_device实例中driver-specifc设备结构。这样就可以通过dev_set_drvdata()调用之前注册V4L2的设备实例。他们必须还设置结构体v4l2_device MDEV指向一个正确初始化和注册的media_device实例。

2).v4l2_dev->name的处理
如果v4l2_dev->name是空,那么这将被设置为从dev取得一个值(通过驱动程序名称后的bus_id)。 如果你在调用v4l2_device_register之前设置它,那么它将不会被改变。如果dev是NULL,那么你‘必须’在调v4l2_device_register前设置v4l2_dev->name。

可以使用v4l2_device_set_name()根据驱动程序的名字和
driver-global atomic_t实例设置名称。这将产生类似于ivtv0,ivtv1等的名字。如果名字最后一个数字,然后将它插入一个破折号:cx18-0,cx18-1等,这个函数返回的实例数量。

第一个参数‘dev’通常是一个pci_dev\ usb接口或者platform_device的struct device指针。dev很少为NULL,但它是ISA设备或一个设备创建的多个PCI设备时,这种情况有可能发生。这可能和v4l2_dev的特殊父设备有关系

您也可以提供一个notify()回调子设备,可以通过调用子设备通知事件给你。取决于你是否需要设置子设备。一个子设备支持的任何通知必须在头文件中定义include/media/<subdevice>.h.

3)设备注销:

 v4l2_device_unregister(struct v4l2_device *v4l2_dev);

如果dev-> driver_data字段指向v4l2_dev,它将被重置为NULL。注销也将自动注销设备所有子设备

如果你有一个可热插拔设备(如USB设备),然后发生断开连接的时候父设备将变为无效。由于v4l2_device有指向父设备的指针,它被清除时,要标记父设备消失。要做到这一点调用:

 v4l2_device_disconnect(struct v4l2_device *v4l2_dev);

这并*不*注销subdevs,所以你仍然需要调用该的v4l2_device_unregister()函数。如果你的驱动是不能热插拔,则有无需调用
v4l2_device_disconnect()

4)遍历所有设备
有时你需要遍历一个特定的驱动程序注册的所有设备。如果多个设备驱动程序使用相同的硬件时会发生这种情况。例如ivtvfb驱动程序是一个使用IVTV硬件的framebuffer驱动(帧缓冲)。
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);
    /* iterate over all ivtv device instances */
    err = driver_for_each_device(drv, NULL, p, callback);
    put_driver(drv);
    return err;
}
5).设备引用计数
有时你需要保持一个设备实例运行计数器。这是常用的映射设备实例模块选项数组索引。

建议的方法如下:
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;
}

如果你有多个设备节点,是很难知道什么时候安全注销v4l2_device。
v4l2_device引用计数的就是为了处理这种情况。video_register_device
时被称之为增加引用计数,有设备节点被释放时减少引用计数。当引用计数达到零,则v4l2_device回调release()。可以做最后的清理。
如果其他设备节点(例如ALSA)的创建,然后你可以增加和减少以及手动调用引用计数:
void v4l2_device_get(struct v4l2_device *v4l2_dev);
or:
int v4l2_device_put(struct v4l2_device *v4l2_dev);
5、struct v4l2_subdev

1)、作用
许多驱动程序需要与子设备进行通信。这些设备可以完成所有这类任务
最常用子设备有处理音频 和/或 视频混流、编码或解码器、摄像头传感器和摄像头控制器

通常情况下,它们都是I2C器件,但也不全是。为了给这些子设备提供一致的接口,v4l2_subdev结构(V4L2 subdev.h)被创创建。
每个子设备驱动程序必须有一个v4l2_subdev结构。
这个结构可单独作为简单的设备存在或者如果需要存储更多的状态信息时它可能被嵌入在一个更大的结构体中。一般有一个低级别设备结构(如i2c_client),其中包含通过内核设置的设备数据。建议通过v4l2_set_subdevdata()函数存储这种指针到v4l2_subdev的私有数据。这样就会使从v4l2_subdev中很容易的获取实际的低级总线的特殊设备数据。

还需要一种方法从低级结构体中获取v4l2_subdev,对于普通的i2c_client结构体,i2c_set_client()调用时可以保存一个v4l2_subdev指针,对于其他的总线可以用相应的方法。
桥式驱动器可能也需要保存per-subdev 私有数据,例如一个指向桥特定的per-subdev 的私有数据,v4l2_subdev 结构体为了能够访问v4l2_get_subdev_hostdata()和v4l2_set_subdev_hostdata,从而提供给主机的私有数据。
从桥驱动角度来说,加载子设备模块和某种方式下v4l2_subdev指针的成员,对于i2c设备来说很容易:可以调用i2c_get_clientdata()函数。对于其他总线来说也要用相似的方法。在i2c总线上子设备的协助功能可以让工作更有效。

2)、子设备(操作函数集)
每个v4l2_subdev包含子设备驱动程序可以实现函数指针(或保留NULL,如果它不适用)。由于子设备可以做很多不同的东西,因此不能结束一个只实现了很少操作的函数操作集结构,函数指针按类别存储的,每个类别都有其自己的OPS结构。
顶层OPS结构包含指向的类别的OPS结构,如果在subdev驱动程序不支持任何策略,这可能是NULL。
它看起来像这样:

核心操作:
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{
    ...
};

设备操作集结构

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;
};
核心操作对所有的子设备都是一样的,其他类别的完成依靠子设备的功能,例如视频设备是不大可能支持音频操作,反之亦然。

这种设置限制的函数指针的数目,同时还使其易于添加新操作函数和类别。

 

3)、子设备初始化
子设备驱动初始化的v4l2_subdev结构使用:

 v4l2_subdev_init(sd, &ops);

之后,您需要初始化一个独一无二的名称subdev->name和设置模块所有者。这样做是为了如果你使用的I2C协助功能。

如果需要整合媒介框架,你必须通过调用media_entity_init()初始化嵌入在v4l2_subdev结构中的media_entity结构

 struct media_pad *pads = &my_sd->pads;
 int err;

 err = media_entity_init(&sd->entity, npads, pads, 0);

pads数组必须此前已初始化。不需要手动设置结构media_entity类型和name字段,但如果需要,修改字段必须初始化。

参考实体将在subdev设备节点(如果有的话)打开/关闭时自动获得/释放时。
在子设备注销之前要调用下边函数清除媒介实体:
              media_entity_cleanup(&sd->entity);

4)、注册和注销:
 
设备(桥)驱动程序需要注册用v4l2_subdevv4l2_device:
           int err = v4l2_device_register_subdev(v4l2_dev, sd);

如果subdev模块消失之前它被注册,可能会失败。如果subdev模块消失之后,这个功能调用成功,它使subdev-> dev指针指向v4l2_device。
如果的v4l2_device父设备具有非空MDEV字段,子设备实体将自动注册media设备
你可以注销一个子设备使用:
             
v4l2_device_unregister_subdev(sd);

调用之后subdev模块被卸载,sd->dev == NULL.

5)、操作函数的调用:
你可以直接调用操作函数:
             err = sd->ops->core->g_chip_ident(sd, &chip);
但是使用这个宏将更好和更容易:
1. err = v4l2_subdev_call(sd, core, g_chip_ident, &chip);

宏将具有正确的空指针检测功能,如果subdev是NULL则返回-ENODEV;
如果其他的subdev->core或者subdev->core->g_chip_ident是空的,那么返回
-ENOIOCTLCMD;或者执行正常的操作subdev->ops->core->g_chip_ident().

2.它也可以通过下边的函数调用全部或部分子设备:

 v4l2_device_call_all(v4l2_dev, 0, core, g_chip_ident, &chip);

不支持这个OPS的任何子设备跳过并且错误的结构将被忽略。如果你想检查是否有错误,使用这个:

 err = v4l2_device_call_until_err(v4l2_dev, 0, core, g_chip_ident, &chip);

任何不是-ENOIOCTLCMD 的错误将退出循环,如果没有错误发生,将返回0.

这两个调用的第二个参数是一组ID。如果为0,然后所有subdevs被调用。如果不为零,但那么只有那些其组ID匹配值将被调用。桥驱动注册一个subdev之前它可以设置sd-> grp_id的值(默认值是0)。这个值被桥驱动器和子设备驱动拥有,程序将不会修改或使用它
这组ID给桥驱动器控制如何调用回调。例如,可能有多个音频芯片在板子上,每一个都能改变音量。但通常情况下只有一个将被用来当用户想改变音量时被操作。你可以设置subdev组ID,例如AUDIO_CONTROLLER和改变指定组ID值时调用v4l2_device_call_all()。以确保它只会去的subdev需要。

如果子设备需要通知v4l2_device父设备一个事件,那么它可以调用v4l2_subdev_notify(sd,notification,arg).这个宏检查是否有一个notify()的回调函数被定义,如果没有返回-ENODEV。否则调用回掉函数并正常返回

使用v4l2_subdev优势是它是一个通用结构,不包含任何对底层硬件操作。因此驱动程序可能包含几个subdevs,使用I2C总线进行控制,但也通过GPIO引脚控制subdev。这种区别只是设置设备时有关,一旦subdev注册是后是完全透明的

 


6、V4L2的子设备用户空间的API
-----------------------------
除了通过v4l2_subdev_ops结构给出V4L2的内核API,V4L2的子设备也可以直接被用户空间控制,可以直接访问。

子设备在/ dev中创建名为V4L-subdevX设备节点。如果一个子设备支持直接用户空间的配置,它必须在注册之前设置V4L2_SUBDEV_FL_HAS_DEVNODE标志
子设备注册之后,v4l2_device驱动可以通过调用v4l2_device_register_subdev_nodes()函数给所有标记为V4L2_SUBDEV_FL_HAS_DEVNODE的已注册子设备创建设备节点。子设备未注册的那些设备节点会被自动删除。

设备节点处理的是V4L2 API的一个子集。
VIDIOC_QUERYCTRL
VIDIOC_QUERYMENU
VIDIOC_G_CTRL
VIDIOC_S_CTRL
VIDIOC_G_EXT_CTRLS
VIDIOC_S_EXT_CTRLS
VIDIOC_TRY_EXT_CTRLS
     控制ioctls函数与V4L2中定义是相同的。他们的行为是相同的,唯一不同的是,他们只处理子设备的执行控制。根据驱动程序,这些控制也可以通过一个(或几个)V4L2的设备节点来访问。
VIDIOC_DQEVENT
VIDIOC_SUBSCRIBE_EVENT
VIDIOC_UNSUBSCRIBE_EVENT

      事件ioctls函数与V4L2中定义是相同的。他们的行为是相同的,唯一不同的是,他们只处理子设备的事件产生。根据驱动程序,这些控制也可以通过一个(或几个)V4L2的设备节点来报告。

     子设备驱动程序要使用事件时,需要设置的V4L2_SUBDEV_USES_EVENTS v4l2_subdev::flags和在注册之前初始化子设备v4l2_subdev:: nevents事件队列深度。注册事件后可以像往常一样在v4l2_subdev:: devnode设备节上排队。

     要正确地支持事件,poll()的文件操作函数必须实现。
私有ioctl:
没有在上述列表所有ioctls函数调用会直接传递到子设备驱动程序通过core::ioctl操作。

7、I2C sub-device drivers
----------------------
由于这些驱动程序是很常见的,特殊的辅助功能可用于更容易使用这些驱动程序(V4L2-common.h)。
1)、建立i2c client和subdev之间的关系
对加入v4l2_subdev支持I2C驱动的推荐的方法是:嵌入V4L2_subdev结构体到一个状态结构体,这个结构体被每个I2C设备实例创建。很简单的设备,没有状态结构,在这种情况下,你可以直接创建一个v4l2_subdev。
一个典型的状态结构看起来像这样(where 'chipname' is replaced bythe name of the chip):
struct chipname_state {
 struct v4l2_subdev sd;
 ...  /* additional state fields */
};

如下初始化v4l2_subdev结构:
        v4l2_i2c_subdev_init(&state->sd, client, subdev_ops);

此函数将给4l2_subdev的所有成员赋值,并且确保v4l2_sudev和i2c_client都指向彼此。

您还应该添加一个辅助inline函数从v4l2_subdev指针得到chipname_state结构:

static inline struct chipname_state *to_state(struct v4l2_subdev *sd)
{
    return container_of(sd, struct chipname_state, sd);
}

使用这个从v4l2_subdev结构得到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函数。这将从桥驱动中注销子设备,即使是没有注册子设备,这个调用也是安全的。
这样做的原因时:当桥式驱动器的注销I2C适配器时,remove()回调函数被适配器上的I2C设备调用。这样之后相应v4l2_subdev结构就会变成无效,所以它们必须首先被注销。从remove()回掉函数调用v4l2_device_unregister_subdev(sd)是为了保证这样做的正确性。

2)、i2c 从设备地址探测
桥式驱动器也有一些辅助功能,它可以使用:

struct v4l2_subdev *sd = v4l2_i2c_new_subdev(v4l2_dev, adapter,
           "module_foo", "chipid", 0x36, NULL);

这个是加载模块(如果没有需要加载的模块,可以为NULL)并调i2c_new_device()输入的参数是i2c_adapter和芯片/地址。如果一切顺利,那么它注册这个v4l2_device 子设备

您还可以使用v4l2_i2c_new_subdev()最后一个参数传递一个可能的I2C地址数组,它可以被探测。如果之前的参数是零,那么这些探测地址就会被用。一个非零的参数意味着你有已知的i2c地址,所以在这种情况下,测将不会发生。

如果出错了,函数返回NULL。

请注意,你传递 给v4l2_i2c_new_subdev()的chipid参数通常跟模块名称相同。它允许你指定一个芯片的变型,例如“SAA7114”或“SAA7115”。一般来说,虽然I2C驱动程序会自动检测这个。在后些时候,要用的的chipid一般是寻找最近。它不同于之间的I2C驱动程序和因为有时候可能会造成混淆。你可以在I2C驱动程序代码的i2c_device_id表中寻找支持该的芯片变种,它将列出所有的可能性芯片。

有两个辅助函数:
v4l2_i2c_new_subdev_cfg: 这个函数将添加新的中端和平台数据参数,有 'addr' 和 'probed_addrs' 参数,如果’addr’不为0,就用它,否则‘probed_addrs’将被探测。

例如:这将探测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驱动来替代irq、 platform_data 和 addr 参数。

如果subdev支持s_config core ops,在子设备被设置之前,这个操作将被调用,其参数就是ird和platform_data。旧的v4l2_i2c_new_(probed_)subdev 函数也会调用 s_config,但是参数ira和platform都是0.
8、struct video_device
1)、video device的创建
在/dev目录下实际的设备节点是使用的video_device结构(V4L2 dev.h)创建的。这个结构可以动态分配或嵌入在一个更大的结构中
如下动态分配:
    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_device_release()回调函数只是仅仅调用kfree释放分配的内存。

2)、video_device结构体成员介绍
你也应该设定这些字段(video_device的成员):
- v4l2_dev: 设置这个v4l2_device父设备
- name: 设置为描述性和独特的东西。
- fops: 设置v4l2_file_operations结构。
- ioctl_ops: 
如果你想用v4l2_ioctl_ops简化ioctl的维护(强烈推荐使用这个参数,因为在以后可能会被强制使用),设置你的v4l2_ioctl_ops结构体。
- lock:
 如果你想要做驱动中的所有锁,那么设置其为NULL。否则你可以使其指向一个mutex_lock的结构体,在任何v4l2_file_operations操作被调用之前,这个锁将有效,并在之后释放。
- prio:
 记录优先级。. 经常实现为 VIDIOC_G/S_PRIORITY.
  如果左边是NULL(If left to NULL),将在v4l2_device
中用 v4l2_prio_state结构体。如果你想在每个设备节点拥有一个分离的优先级状态你可以指向你自己的v4l2_prio_state结构体。


-
parent:
 当v4l2_device作为父设备被注册为NULL时,应该设置这个成员。这仅仅发生在一个硬件设备有多个PCI设备,并且它们都共享相同的v4l2_device核心。.

  cx88驱动就是这样的一个例子: 只有一个v4l2_device结构作为核心, 但是他被用在原始视频PCI设备和MPEG PCI 设备,因此v4l2_device结构没有连接到特殊的PCI设备,这个设备没有设置父设备 。 But when the struct video_device is setup you do know which parent PCI device to use.
- flags: 
可选择的.如果你想让框架处理VIDIOC_G/S_PRIORITY控制,那么设置为 V4L2_FL_USE_FH_PRIO 。这需要你用结构体v4l2_fh。
 一旦所有的驱动用核心优先级处理,最终这个标记将消失。但是现在需要显式的指定。.
如果使用v4l2_ioctl_ops,那么你应该在v4l2_file_operations结构中设置unlocked_ioctl到video_ioctl2。

不要使用ioctl,它在将来会被丢弃。

v4l2_file_operations结构是file_operations的一个子集。主要区别是inode参数被忽略,因为它从来没有使用过。
3)、media entity
如果需要整合media框架,你必须初始化media_entity结构体并通过调用media_entity_init()函数嵌入式video_device结构体中(entity字段)

 struct media_pad *pad = &my_vdev->pad;
 int err;
 err = media_entity_init(&vdev->entity, 1, pad, 0);

pad阵列必须预先初始化。没有必要手动设置的结构media_entity的 type和name字段。

一个被引用的media 实体在视频设备打开和关闭时自动请求和释放


9、v4l2_file_operations 和 锁
--------------------------------
你可以在结构体video_device中设置一个指针mutex_lock。通常这将是一个顶层互斥或者使每个设备节点互斥。如果你想更精细的锁那么你必须将其设置为NULL,在用你自己的锁。

驱动开发者会来决定用哪种方法。然而,你的驱动中有一个很长的延迟操作,那么这是就最好不要用这个mutex_lock,而用你自己的锁,这样可以让用户在等待这个长延迟完成时做其他的工作。

如果一个锁被指定,那么所有的文件操作将被依次上锁如果你使用videobuf那么你必须通过相同的锁来锁videobuf队列初始化函数:如果videobuf在等待一帧到达,那么它要短暂的解锁,之后在重新上锁。如果你的驱动程序代码中的存在等待,那么你应该在第一个进程等待时,允许其他进程访问该设备节点


一个热插拔断开实施也应采取之前调用v4l2_device_disconnect锁。

10、video_device 注册
1)、注册视频设备
接下来将注册的视频设备:在这个过程中会创建的字符设备
    err = video_register_device(vdev, VFL_TYPE_GRABBER, -1);
    if (err) {
        video_device_release(vdev); /* or kfree(my_vdev); */
        return err;
    }
如果的v4l2_device其父节点设备具有非空MDEV字段,视频设备的实体将被自动注册media设备。
注册哪种设备依赖于设备类型参数。存在以下几种设备类型:

VFL_TYPE_GRABBER: 视频输入/输出设备videoX
XVFL_TYPE_VBI: 垂直空白数据vbiX(即关闭字幕,图文电视)
VFL_TYPE_RADIO: radioX无线电调谐器
VFL_TYPE_VTX: vtxX图文电视设备(不建议使用,不使用)

2)、设备节点号的获取
最后一个参数给你一定数量的控制了设备使用设备节点(即在videoX中的X)。通常情况下只需传-1让V4L2框架选择第一个空闲的节点号
但有时用户想指定一个特定的节点号。常见的驱动程序允许用户通过驱动模块选项选择一个特定的设备节点号。然后这一数字被传递给video_register_device函数,它将尝试选择该设备节点号。如果该节点号已经使用,那么下一个空闲的设备节点数量将被选中,同时会向内核日志发出警告信息。

外使用情况是,如果一个驱动程序创建许多设备。在这种情况下它可以是有用的放置在不同范围的不同视频设备。例如,视频捕获设备节点从0开始,视频输出设备节点从16开始。

所以,你可以使用最后一个参数指定一个最小设备节点号,V4L2框架将尽量选择与你传递的节点号相近的空闲号。如果失败,那么它会选择第一个空闲的节点号

在这种情况下,如果你不想关心系统不能够选择你指定的设备节点时发出的有关警告,可以调用的函数video_register_device_no_warn()代替注册视频设备。

3)、系统中设备的属性
每当创建一个设备节点时,它的的一些属性也会被创建
在/ sys/class/video4linux目录下你会查看到这些属性。查找video0时,
你会看到它的“name”和“index”属性。 'name'的属性是由video_device结构体中的'name'字段的确定的。

“index”属性是设备节点的索引:每次调用video_register_device()之后,该索引就会自加1。注册第一个视频设备的设备节点总是以0开始的。

用户可以设置udev的规则,利用索引属性设置一些花哨的设备名称(如视频捕捉设备节点的名称为mpegX MPEG)。
该设备注册成功之后,你可以使用这些字段:

- vfl_type: 传递到video_register_device()函数的设备类型。
- minor: 分配给设备的次设备号。
- num: 设备节点数目(i.e. the X in videoX).
- index: 设备索引号。

如果注册失败,那么你需要调用video_device_release()来释放分配的video_device结构,或者自己定义的嵌入video_deviced的结构。如果注册失败,vdev-〉release()回调函数将永远不会被调用。如果注册失败,也不应该尝试注销该设备。

4)、清除video_device 
--------------------
当设备节点被删除、驱动程序被卸载、或因为USB设备被断开,那么你应该用下边函数注销它们:

    video_unregister_device(vdev);

这将从sysfs系统中移除设备节点(会引起从/dev中移除udev).

video_unregister_device()函数返回之后,没有新的设备打开。然而,在这种情况下,USB设备一些应用可能仍然有节点打开,因此要注销所有操作函数(除了release() ),有可能返回错误。

当最后一个用户视频设备节点退出后,个vdev->release()回调函数被调用,做最后的清除。

如果它已经被初始化媒体视频设备的相关实体,那么也要掉用下边的函数清除它:

    media_entity_cleanup(&vdev->entity);
它在release()中被调用

5)、video_device的辅助函数
-----------------------------

有一些有用的辅助功能:
A、文件或视频设备的私有数据
可以用下边的操作设置或获取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()

还有这个函数:
struct video_device *video_devdata(struct file *file);
返回属于file结构体的video_device结构体

video_drvdata function和video_get_drvdata 结合到一块产video_devdata函数 : 获取video_device结构体中的驱动私有数据
void *video_drvdata(struct file *file);

你可以从video_device结构体中获取v4l2_device结构体
struct v4l2_device *v4l2_dev = vdev->v4l2_dev;

B、设备节点数据
video_device节点内核名称可以通过下边函数获取:
const char *video_device_node_name(struct video_device *vdev);
这个名称被用来作为用户空间的线索,例如udev。这个功能在可能的情况下通过video_device:: num和的video_device::minor字段替代。

6)、video buffer 辅助函数
-----------------------------
V4l2核心接口提供了一组处理视频缓冲的标准方法(称作videobuf),这些方法允许一个驱动实现统一的read()、mmap()和overlay操作。并且支持DMA 散聚方法(videobuf-dma-sg), DMA线性访问(videobuf-dma-contig)和内存缓冲访问,多数用于USB驱动(videobuf-vmalloc).

有关videobuf层的信息可以查看Documentation/video4linux/videobuf文件

11、V4L2 文件处理:struct v4l2_fh

1)、功能和使用方法
构体v4l2_fh提供了一种轻松地保持文件处理特定数据的方法,这个数据被V4L2框架使用。新的驱动程序必须使用v4l2_fh结构体,如果的video_device标志中V4L2_FL_USE_FH_PRIO也设置,那么它也可以用来实现优先处理VIDIOC_G/ S_PRIORITY)。

通过测试video_device-> flags中V4L2_FL_USES_V4L2_FH位的,v4l2_fh(在V4L2的框架中,而不是驱动中)的用户就会知道驱动程序是否使用v4l2_fh作为其file->private_data指针。当v4l2_fh_init()被调用时设置此位。
结构体v4l2_fh被分配作为驱动自己文件处理结构的一部分,并且在驱动打开函数中将 file->private_data 设置为v4l2_fh。
在许多情况下,结构体v4l2_fh将嵌到一个大的结构体中,在这种情况下,你将要在open()函数中调用v4l2_fh_init和v4l2_fh_add,在release()函数中调用v4l2_fh_del+v4l2_fh_exit。

驱动可以用过宏container_of来提取自己的文件处理结构。
Example:
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;

    ...

    my_fh = kzalloc(sizeof(*my_fh), GFP_KERNEL);

    ...

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

    ...

    file->private_data = &my_fh->fh;
    v4l2_fh_add(&my_fh->fh);
    return 0;
}

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);

    ...
    v4l2_fh_del(&my_fh->fh);
    v4l2_fh_exit(&my_fh->fh);
    kfree(my_fh);
    return 0;
}

2)、一些函数说明

下面是一些v4l2_fh函数的简短说明:

1.int v4l2_fh_init(struct v4l2_fh *fh, struct video_device *vdev)

  初始化文件处理,必须在驱动调用v4l2_file_operations->open()时被执行

2.void v4l2_fh_add(struct v4l2_fh *fh)

   添加v4l2_fh到video_device文件处理列表。必须在一个文件处理被完全初始化之后调用,即在v4l2_fh_init()函数之后调用

3.void v4l2_fh_del(struct v4l2_fh *fh)

  从video_device()中注销文件处理,文件处理退出函数可能被调用.

4.void v4l2_fh_exit(struct v4l2_fh *fh)

  Uninitialise the file handle. After uninitialisation the v4l2_fh
  memory can be freed.
 当第一个文件处理被打开和最后一个文件处理被关闭时,许多驱动程序需要做一些事情时。增加了两个辅助功能检查,是否v4l2_fh结构是否仅仅打开相关设备节点的文件处理:
int v4l2_fh_is_singular(struct v4l2_fh *fh)

  Returns 1 if the file handle is the only open file handle, else 0.

int v4l2_fh_is_singular_file(struct file *filp)

  Same, but it calls v4l2_fh_is_singular with filp->private_data.
12、V4L2 事件
-----------
V4L2事件提供了一个通用的方式来传递事件到用户空间。驱动程序必须使用v4l2_fh才能够支持V4L2的事件。

1)、事件的定义
事件通过一个类型和一个可选择的ID被定义。这个ID可能跟V4L2目标有关系,例如控制ID,通常情况下,这个ID为0
当用户要用一个事件时,驱动将会给这个事件分配一个kevent结构体,这可以确保驱动在短时间内产生单一类型大量事件时,不会覆盖其他类型的事件。
如果你得到的单一类型事件多于保留的kevents数目时,旧的事件将被丢弃,新的事件被添加。
此外,内部结构体v4l2_subscribed_event 的两个回掉函数merge()和replace()要在驱动中设置。当一个新的事件被提高,并且没有更多的空间时,这些回掉函数被调用,replace()被调用时允许用新的事件代替有负载效应的旧事件,会将旧事件中相关数据合并到新的事件中去。当这个事件类型仅仅有一个kevent被分配时会调用它,merge()回掉函数允许你合并旧的事件到第二个旧的事件,当两个或更多个kevent被分配时,它也会被调用。
这种方法不会有状态信息丢失,正确的内部信息导致了这种状态。
Replace和merge回掉函数一个好的例子是在v4l2-event.c中的ctrls_replace和ctrls_merge()回掉函数控制的事件
Useful functions:
2)、事件处理函数
1- v4l2_event_alloc()
  使用事件驱动程序必须分配的文件句柄的事件。驱动程序通过调用函数不止一次,
  可以保证至少n个总事件已分配。功能可能无法在原子上下文中被调用。
2- v4l2_event_queue()
  视频设备的队列中的事件。驱动程序的唯一责任是填写它的类型和数据字段。其他字段将被V4L2填充。
3- v4l2_event_subscribe()
  video_device-> ioctl_ops> vidioc_subscribe_event必须检查驱动程序是否能够产生与特定事件ID的事件。然后,它调用v4l2_event_subscribe()认同该事件。最后一个参数是这个事件队列的大小,如果为0,V4L2架构依据事件类型设置一个默认的值。
4- v4l2_event_unsubscribe()
 vidioc_unsubscribe_event在结构v4l2_ioctl_ops中。一个驱动程序可以直v4l2_event_unsubscribe()接使用取消订阅进程。

  在特殊类型V4L2_EVENT_ALL的可能被用来取消所有事件。驱动程序可能用一种特殊的方式来处理这些事件。
5- v4l2_event_pending()  
返回挂起的事件数量。实施调查时非常有用。
用户空间通过poll系统调用实现事件的传递,驱动可以用v4l2_fh->wait作为poll_wait调用的参数。
有标准的和私有的事件,新的标准事件必须用最小的可用事件类型,驱动必须从它们所属的类开始到类基址分配它们的事件。类基址是
V4L2_EVENT_PRIVATE_START + n * 1000 ,n是最小的有效数.
类中的第一个事件类型是被保留将来用的,一次第一个有效类类型是“基址+1”

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值