2.14. V4L2 事件

V4L2 事件提供了一种将事件传递到用户空间的通用方法。驱动程序必须使用 v4l2_fh 才能支持 V4L2 事件。

事件是按文件句柄订阅的。事件规范由 type 组成,并且可以选择与通过 id 字段标识的对象关联。如果未使用,则 id 为 0。因此,事件由 (type, id) 元组唯一标识。

v4l2_fh 结构在其 subscribed 字段上具有已订阅事件的列表。

当用户订阅事件时,会将一个 v4l2_subscribed_event 结构添加到 v4l2_fh.subscribed,每个订阅的事件一个。

每个 v4l2_subscribed_event 结构以 v4l2_kevent 环形缓冲区结束,其大小由 v4l2_event_subscribe() 的调用者给出。此环形缓冲区用于存储驱动程序引发的任何事件。

因此,每个 (type, ID) 事件元组都将有自己的 v4l2_kevent 环形缓冲区。这保证了如果驱动程序在短时间内生成大量一种类型的事件,则不会覆盖另一种类型的事件。

但是,如果您获得的一种类型的事件多于 v4l2_kevent 环形缓冲区的大小,则最旧的事件将被删除,并添加新的事件。

v4l2_kevent 结构链接到 v4l2_fh 结构的 available 列表,因此 ioctl VIDIOC_DQEVENT 将知道首先取消排队哪个事件。

最后,如果事件订阅与特定的对象(例如 V4L2 控件)关联,则该对象也需要了解这一点,以便该对象可以引发事件。因此,可以使用 node 字段将 v4l2_subscribed_event 结构链接到此类对象的列表中。

因此,总结如下

此外,内部的 struct v4l2_subscribed_event 具有 merge()replace() 回调,驱动程序可以设置它们。当引发新事件并且没有更多空间时,将调用这些回调。

replace() 回调允许您将旧事件的有效负载替换为新事件的有效负载,将旧有效负载中的任何相关数据合并到替换它的新有效负载中。当此事件类型的环形缓冲区大小为 1 时,即环形缓冲区中只能存储一个事件时,将调用此回调。

merge() 回调允许您将最旧事件的有效负载合并到第二旧事件的有效负载中。当环形缓冲区的大小大于 1 时,将调用此回调。

这样就不会丢失状态信息,而只是导致该状态的中间步骤。

这些 replace/merge 回调的一个很好的示例在 v4l2-event.c 中:控件事件的 ctrls_replace()ctrls_merge() 回调。

注意

这些回调可以从中断上下文中调用,因此它们必须很快。

为了将事件排队到视频设备,驱动程序应调用

驱动程序的唯一责任是填写类型和数据字段。其他字段将由 V4L2 填写。

2.14.1. 事件订阅

订阅事件通过以下方式进行

v4l2_event_subscribe (fh, sub , elems, ops)

此函数用于实现 video_device-> ioctl_ops-> vidioc_subscribe_event,但驱动程序必须首先检查驱动程序是否能够生成具有指定事件 ID 的事件,然后应调用 v4l2_event_subscribe() 来订阅事件。

elems 参数是此事件的事件队列的大小。如果为 0,则框架将填充默认值(这取决于事件类型)。

ops 参数允许驱动程序指定一些回调函数

回调函数

描述

add

当添加新的侦听器时调用(两次订阅同一事件只会导致此回调函数被调用一次)

del

当侦听器停止侦听时调用

replace

用事件“new”替换事件“old”。

merge

将事件“old”合并到事件“new”中。

所有 4 个回调函数都是可选的,如果您不想指定任何回调函数,则 ops 参数本身可以为 NULL

2.14.2. 取消订阅事件

通过以下方式取消订阅事件:

此函数用于实现 video_device-> ioctl_ops-> vidioc_unsubscribe_event。驱动程序可以直接调用 v4l2_event_unsubscribe(),除非它想参与取消订阅过程。

特殊类型 V4L2_EVENT_ALL 可用于取消订阅所有事件。驱动程序可能希望以特殊方式处理此情况。

2.14.3. 检查是否有待处理的事件

通过以下方式检查是否有待处理的事件:

此函数返回待处理事件的数量。在实现 poll 时很有用。

2.14.4. 事件的工作原理

事件通过 poll 系统调用传递到用户空间。驱动程序可以使用 v4l2_fh->wait (a wait_queue_head_t) 作为 poll_wait() 的参数。

有标准事件和私有事件。新的标准事件必须使用最小的可用事件类型。驱动程序必须从自己的类中分配事件,从类基址开始。类基址为 V4L2_EVENT_PRIVATE_START + n * 1000,其中 n 是最小的可用数字。类中的第一个事件类型保留供将来使用,因此第一个可用的事件类型是“类基址 + 1”。

有关如何使用 V4L2 事件的示例,请参见 OMAP 3 ISP 驱动程序(drivers/media/platform/ti/omap3isp)。

子设备可以使用 V4L2_DEVICE_NOTIFY_EVENT 直接向 v4l2_device 通知函数发送事件。这允许桥接器将发送事件的子设备映射到与需要被告知此类事件的子设备关联的视频节点。

2.14.4.1. V4L2 事件函数和数据结构

struct v4l2_kevent

内部内核事件结构。

定义:

struct v4l2_kevent {
    struct list_head        list;
    struct v4l2_subscribed_event *sev;
    struct v4l2_event       event;
    u64 ts;
};

成员

list

v4l2_fh->available 列表的列表节点。

sev

指向父 v4l2_subscribed_event 的指针。

event

事件本身。

ts

事件的时间戳。

struct v4l2_subscribed_event_ops

订阅事件操作。

定义:

struct v4l2_subscribed_event_ops {
    int (*add)(struct v4l2_subscribed_event *sev, unsigned int elems);
    void (*del)(struct v4l2_subscribed_event *sev);
    void (*replace)(struct v4l2_event *old, const struct v4l2_event *new);
    void (*merge)(const struct v4l2_event *old, struct v4l2_event *new);
};

成员

add

可选回调,当添加新的侦听器时调用

del

可选回调,当侦听器停止侦听时调用

replace

可选回调,可以用事件“new”替换事件“old”。

merge

可选回调,可以将事件“old”合并到事件“new”中。

struct v4l2_subscribed_event

表示订阅事件的内部结构。

定义:

struct v4l2_subscribed_event {
    struct list_head        list;
    u32 type;
    u32 id;
    u32 flags;
    struct v4l2_fh          *fh;
    struct list_head        node;
    const struct v4l2_subscribed_event_ops *ops;
    unsigned int            elems;
    unsigned int            first;
    unsigned int            in_use;
    struct v4l2_kevent      events[] ;
};

成员

list

v4l2_fh->subscribed 列表的列表节点。

type

事件类型。

id

关联的对象 ID(例如,控制 ID)。如果没有,则为 0。

flags

v4l2_event_subscription->flags 的副本。

fh

订阅此事件的文件句柄。

node

挂钩到对象的事件列表的列表节点(如果存在)。

ops

v4l2_subscribed_event_ops

elems

事件数组中的元素数量。

first

包含最旧的可用事件的事件的索引。

in_use

排队的事件数量。

events

一个包含 elems 个事件的数组。

int v4l2_event_dequeue(struct v4l2_fh *fh, struct v4l2_event *event, int nonblocking)

从视频设备中取出事件。

参数

struct v4l2_fh *fh

指向 struct v4l2_fh 的指针

struct v4l2_event *event

指向 struct v4l2_event 的指针

int nonblocking

如果非零,则等待事件到达

void v4l2_event_queue(struct video_device *vdev, const struct v4l2_event *ev)

将事件排队到视频设备。

参数

struct video_device *vdev

指向 struct video_device 的指针

const struct v4l2_event *ev

指向 struct v4l2_event 的指针

描述

该事件将为所有 struct v4l2_fh 文件句柄排队。

注意

驱动程序的唯一责任是填写类型和数据字段。其他字段将由 V4L2 填写。

void v4l2_event_queue_fh(struct v4l2_fh *fh, const struct v4l2_event *ev)

将事件排队到视频设备。

参数

struct v4l2_fh *fh

指向 struct v4l2_fh 的指针

const struct v4l2_event *ev

指向 struct v4l2_event 的指针

描述

该事件将仅为指定的 struct v4l2_fh 文件句柄排队。

注意

驱动程序的唯一责任是填写类型和数据字段。其他字段将由 V4L2 填写。

void v4l2_event_wake_all(struct video_device *vdev)

唤醒所有文件句柄。

参数

struct video_device *vdev

指向 struct video_device 的指针

描述

在注销视频设备时使用。

int v4l2_event_pending(struct v4l2_fh *fh)

检查是否有事件可用

参数

struct v4l2_fh *fh

指向 struct v4l2_fh 的指针

描述

返回待处理事件的数量。

int v4l2_event_subscribe(struct v4l2_fh *fh, const struct v4l2_event_subscription *sub, unsigned int elems, const struct v4l2_subscribed_event_ops *ops)

订阅一个事件

参数

struct v4l2_fh *fh

指向 struct v4l2_fh 的指针

const struct v4l2_event_subscription *sub

指向 struct v4l2_event_subscription 的指针

unsigned int elems

事件队列的大小

const struct v4l2_subscribed_event_ops *ops

指向 v4l2_subscribed_event_ops 的指针

描述

注意

如果 elems 为零,框架将填充默认值,当前为 1 个元素。

int v4l2_event_unsubscribe(struct v4l2_fh *fh, const struct v4l2_event_subscription *sub)

取消订阅一个事件

参数

struct v4l2_fh *fh

指向 struct v4l2_fh 的指针

const struct v4l2_event_subscription *sub

指向 struct v4l2_event_subscription 的指针

void v4l2_event_unsubscribe_all(struct v4l2_fh *fh)

取消订阅所有事件

参数

struct v4l2_fh *fh

指向 struct v4l2_fh 的指针

int v4l2_event_subdev_unsubscribe(struct v4l2_subdev *sd, struct v4l2_fh *fh, struct v4l2_event_subscription *sub)

v4l2_event_unsubscribe() 的子设备变体

参数

struct v4l2_subdev *sd

指向 struct v4l2_subdev 的指针

struct v4l2_fh *fh

指向 struct v4l2_fh 的指针

struct v4l2_event_subscription *sub

指向 struct v4l2_event_subscription 的指针

描述

注意

此函数应用于 struct v4l2_subdev_core_opsunsubscribe_event 字段。

int v4l2_src_change_event_subscribe(struct v4l2_fh *fh, const struct v4l2_event_subscription *sub)

如果事件是 V4L2_EVENT_SOURCE_CHANGE,则调用 v4l2_event_subscribe() 的辅助函数。

参数

struct v4l2_fh *fh

指向 struct v4l2_fh 的指针

const struct v4l2_event_subscription *sub

指向 struct v4l2_event_subscription 的指针

int v4l2_src_change_event_subdev_subscribe(struct v4l2_subdev *sd, struct v4l2_fh *fh, struct v4l2_event_subscription *sub)

v4l2_event_subscribe() 的变体,旨在仅订阅类型为 V4L2_EVENT_SOURCE_CHANGE 的事件。

参数

struct v4l2_subdev *sd

指向 struct v4l2_subdev 的指针

struct v4l2_fh *fh

指向 struct v4l2_fh 的指针

struct v4l2_event_subscription *sub

指向 struct v4l2_event_subscription 的指针