V4L2的event事件机制是内核空间与用户空间的通信的一种机制,一种实用的机制,可以用于一些驱动状态出现变化,通知应用层对变化做出动作的场景,例如RK3588平台的HDMIIN,分辨率变化、拔插变化都是通过事件的形式上报给应用的。本文主要介绍一下如何在驱动添加私有的V4L2事件。
目录
(1)V4L2事件结构体
struct v4l2_event {
__u32 type;
union {
struct v4l2_event_vsync vsync;
struct v4l2_event_ctrl ctrl;
struct v4l2_event_frame_sync frame_sync;
struct v4l2_event_src_change src_change;
struct v4l2_event_motion_det motion_det;
__u8 data[64];
} u;
__u32 pending;
__u32 sequence;
#ifdef __KERNEL__
struct __kernel_timespec timestamp;
#else
struct timespec timestamp;
#endif
__u32 id;
__u32 reserved[8];
};
type:事件类型的结构体,V4L2原生定义的一些事件类型包括如下,V4L2_EVENT_PRIVATE_START可用于扩展自定义的事件类型。
#define V4L2_EVENT_ALL 0
#define V4L2_EVENT_VSYNC 1
#define V4L2_EVENT_EOS 2
#define V4L2_EVENT_CTRL 3
#define V4L2_EVENT_FRAME_SYNC 4
#define V4L2_EVENT_SOURCE_CHANGE 5
#define V4L2_EVENT_MOTION_DET 6
#define V4L2_EVENT_PRIVATE_START 0x08000000
data:用户自定义的消息实体/内容。在添加私有事件的时候,可以定义data的数据,来向应用层传递相关信息。
sequence:消息的序列号(不是index)。和struct v4l2_fh的成员sequence相同。
timestamp:消息发送的时间戳。
pending:记录消息队列中尚未处理的消息数量。
id:命令ID。
目前实际应用发现,使用私有事件的时候必须指定type和data,可以传递到用户层,其他的参数由V4L2自行填充。
(2)camera sensor驱动添加私有事件
下面介绍一下在camera sensor的驱动如何添加一个私有的事件。
①定义私有V4L2事件的类型
基于V4L2_EVENT_PRIVATE_START进行拓展事件定义。
/* Private v4l2 event */
#define V4L2_EVENT_I2C_LOST \
(V4L2_EVENT_PRIVATE_START + 1)
②v4l2_subdev_core_ops订阅事件接口实现
实现事件订阅的回调接口。
+static int imx415_subscribe_event(struct v4l2_subdev *sd, struct v4l2_fh *fh,
+ struct v4l2_event_subscription *sub)
+{
+ switch (sub->type) {
+ case V4L2_EVENT_I2C_LOST:
+ return v4l2_event_subscribe(fh, sub, 0, NULL);
+ default:
+ return -EINVAL;
+ }
+}
+
static const struct v4l2_subdev_core_ops imx415_core_ops = {
.s_power = imx415_s_power,
+ .subscribe_event = imx415_subscribe_event,
.ioctl = imx415_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl32 = imx415_compat_ioctl32,
③上报事件
在需要上报事件的位置将事件queue到事件队列中,由应用层去dqueue事件。
将事件queue到队列的时候,需要指定事件的类型和驱动需要向用户空间传递的data数据。
static void imx415_event_report(struct v4l2_subdev *sd, int err)
{
struct v4l2_event event = {
.type = V4L2_EVENT_I2C_LOST,
.u.data[0] = err,
};
if (sd->devnode)
v4l2_event_queue(sd->devnode, &event);
}
(3)应用层实现
应用层需要对驱动生成的设备节点订阅相关时间,然后将事件队列的事件dq出来进行处理。具体实现如下:
①订阅事件
打开对应的设备节点,订阅相关事件。
fd = open(dev, O_RDWR, 0);
if (fd < 0) {
printf("open dev:%s fail\n", dev);
return -1;
}
ret = subscribe_event(fd, V4L2_EVENT_I2C_LOST);
......
int subscribe_event(int fd, int event)
{
int ret = 0;
struct v4l2_event_subscription sub;
if (fd < 0) {
printf("fd is err\n");
return -1;
}
sub.type = event;
sub.id = 0;
ret = ioctl(fd, VIDIOC_SUBSCRIBE_EVENT, &sub);
if (ret < 0) {
printf("VIDIOC_SUBSCRIBE_EVENT fail\n");
return -1;
}
return 0;
}
②DQueue事件
类似V4L2 buffer的控制,用户层需要将事件DQ出来进行出来,根据事件类型和传递的data数据,进行后级的应用处理。
while (1) {
printf("VIDIOC_DQEVENT ready %d\n", ret);
ret = ioctl(fd, VIDIOC_DQEVENT, &event);
if (ret) {
printf("VIDIOC_DQEVENT err %d\n", ret);
goto end;
}
printf("VIDIOC_DQEVENT event %x, data: %d\n", event.type, event.u.data[0]);
}
(4)模拟调试
可以使用V4L2-ctl的工具调试,如下命令可以查询对应私有事件的变化,如果驱动有报事件
v4l2-ctl -d /dev/v4l-subdev2 --poll-for-event=0x08000001
268.648750: event 0, pending 0: unknown private event (08000001)
280.348455: event 1, pending 0: unknown private event (08000001)
也可以根据上述的应用实现例子来监测事件。
v4l-event /dev/v4l-subdev2
VIDIOC_DQEVENT ready 0
VIDIOC_DQEVENT event 8000001, data: 0
VIDIOC_DQEVENT ready 0
VIDIOC_DQEVENT event 8000001, data: 251
VIDIOC_DQEVENT ready 0