Linux IIO子系统分析4(基于Linux6.6)---EVENT介绍
一、IIO DEVICE字符设备文件操作接口
在IIO 子系统中,每一个IIO DEVICE均会创建一个字符设备文件,名称为/dev/iio:deviceX,该字符设备文件节点在iio_device_register中调用cdev_init、cdev_add完成字符设备文件节点的创建,且文件操作接口为iio_buffer_fileops。而借助sysfs的kobject uevent,则会将cdev add的信息发送给应用程序,应用层的mdev/udev接收到cdev add的uevent之后,则会调用mknod完成字符设备文件节点的创建。如下即是/dev/iio:deviceX的访问流程,应用程序通过open/read/poll/ioctl接口则会调用内核中VFS提供的操作接口,最终则调用iio_buffer_fileops中定义的接口。
iio_buffer_fileops的定义如下
struct iio_event_interface {
wait_queue_head_t wait;
DECLARE_KFIFO(det_events, struct iio_event_data, 16);
struct list_head dev_attr_list;
unsigned long flags;
struct attribute_group group;
struct mutex read_lock;
};
问题来了,iio device对应的字符设备文件节点主要提供哪些服务呢?
主要提供两方面的内容:
- 提供对iio device各通道连续采集数据的读取操作(前提是该iio device的某些通道提供了iio buffer功能);
- 提供创建event数据读取对应的匿名字符设备文件节点的功能(通过ioctl功能,则创建一个匿名的字符设备文件节点,用于进行iio device各通道相关的event数据的读取功能)。所谓匿名即该字符设备文件节点并不会显示在文件系统中(无法在应用层中找到该文件名称),且一个iio device同一时刻仅可创建一个匿名字符设备文件节点。
1.1、IIO 设备的字符设备文件操作接口
IIO 设备通过 file_operations
结构体提供标准的文件操作接口。每个 IIO 设备驱动通常会定义这些操作,以便用户空间应用程序能够通过标准文件 I/O 接口(如 open()
、read()
、write()
等)与设备进行交互。
下面是一些常见的 IIO 设备字符设备操作接口。
1. open()
操作
open()
操作用于打开 IIO 设备。通常,IIO 设备会注册一个 file_operations
结构体,其中包含了 open
操作。
- 作用: 使能设备,通常会执行设备初始化、配置以及资源分配等操作。
- 返回值: 如果成功,返回一个文件描述符;如果失败,返回负值表示错误。
static int iio_device_open(struct inode *inode, struct file *file)
{
struct iio_dev *indio_dev = file->private_data;
int ret;
/* 对设备进行初始化等操作 */
ret = iio_device_enable(indio_dev);
if (ret)
return ret;
/* 其他操作,如配置设备等 */
return 0;
}
2. read()
操作
read()
操作通常用于从 IIO 设备读取数据。例如,读取传感器的原始数据或测量值。该操作通常会将设备数据从内核传递到用户空间。
- 作用: 从设备读取数据到用户空间的缓冲区。
- 返回值: 返回实际读取的字节数;如果没有更多数据可供读取,则返回 0;在失败的情况下返回负值错误代码。
static ssize_t iio_device_read(struct file *file, char __user *buf,
size_t count, loff_t *ppos)
{
struct iio_dev *indio_dev = file->private_data;
ssize_t ret;
/* 读取数据,返回读取的数据字节数 */
ret = iio_device_read_data(indio_dev, buf, count);
if (ret < 0)
return ret;
return ret; /* 返回读取的字节数 */
}
3. write()
操作
write()
操作通常用于向 IIO 设备写入数据,配置设备或者传递控制命令等。
- 作用: 向设备写入数据,配置设备的某些参数或启动特定操作。
- 返回值: 返回成功写入的字节数;在失败的情况下返回负值错误代码。
static ssize_t iio_device_write(struct file *file, const char __user *buf,
size_t count, loff_t *ppos)
{
struct iio_dev *indio_dev = file->private_data;
ssize_t ret;
/* 写入数据,进行设备配置 */
ret = iio_device_write_data(indio_dev, buf, count);
if (ret < 0)
return ret;
return ret; /* 返回写入的字节数 */
}
4. ioctl()
操作
ioctl()
操作用于设备特定的控制,通常用于控制 IIO 设备的工作模式或进行其他特殊操作。
- 作用: 执行设备的特定控制命令,通常通过命令(
cmd
)来指定控制动作。 - 返回值: 由设备控制命令的结果决定,成功时返回 0,失败时返回负值。
static long iio_device_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
struct iio_dev *indio_dev = file->private_data;
long ret;
/* 根据 cmd 来执行控制操作 */
switch (cmd) {
case IIO_IOCTL_SET_MODE:
ret = iio_device_set_mode(indio_dev, arg);
break;
default:
ret = -ENOTTY; /* 未知命令 */
break;
}
return ret;
}
5. release()
操作
release()
操作通常用于关闭 IIO 设备,释放资源并进行清理操作。
- 作用: 关闭设备,释放资源。
- 返回值: 返回 0 表示成功,返回负值表示错误。
static int iio_device_release(struct inode *inode, struct file *file)
{
struct iio_dev *indio_dev = file->private_data;
/* 禁用设备,释放资源等 */
iio_device_disable(indio_dev);
return 0;
}
6. mmap()
操作
mmap()
操作用于将设备的内存映射到用户空间,通常用于高效地处理大量数据(例如,传感器采样数据)。
- 作用: 将设备内存映射到用户空间,允许用户程序直接访问硬件缓冲区。
- 返回值: 返回成功时的地址,失败时返回负值错误码。
static int iio_device_mmap(struct file *file, struct vm_area_struct *vma)
{
struct iio_dev *indio_dev = file->private_data;
/* 映射设备内存到用户空间 */
return remap_pfn_range(vma, vma->vm_start, indio_dev->mem_address >> PAGE_SHIFT,
vma->vm_end - vma->vm_start, vma->vm_page_prot);
}
7. poll()
操作
poll()
操作用于查询设备是否准备好进行读取或写入。通常与设备的事件通知机制结合使用,例如使用触发器。
- 作用: 检查设备是否准备好读取或写入,返回相应的事件。
- 返回值: 返回表示设备状态的位掩码(bitmask)。
static unsigned int iio_device_poll(struct file *file, struct poll_table_struct *wait)
{
struct iio_dev *indio_dev = file->private_data;
unsigned int mask = 0;
/* 判断设备是否准备好读取/写入 */
if (iio_device_is_data_ready(indio_dev))
mask |= POLLIN | POLLRDNORM; /* 数据可读 */
return mask;
}
8. llseek()
操作
llseek()
操作用于设置文件偏移量,通常不常用于 IIO 设备,因为 IIO 设备文件通常是连续的流式数据。
static loff_t iio_device_llseek(struct file *file, loff_t offset, int whence)
{
/* 实现文件偏移功能 */
return 0;
}
1.2、完整的 file_operations
结构体示例
static const struct file_operations iio_device_fops = {
.owner = THIS_MODULE,
.open = iio_device_open,
.read = iio_device_read,
.write = iio_device_write,
.release = iio_device_release,
.ioctl = iio_device_ioctl,
.mmap = iio_device_mmap,
.poll = iio_device_poll,
.llseek = iio_device_llseek,
};
二、IIO EVENT设计分析
2.1、iio event相关的数据结构
struct iio_event_interface是iio event相关的数据结构,该数据结构的定义如下:
drivers/iio/industrialio-event.c
/**
* struct iio_event_interface - chrdev interface for an event line
* @wait: wait queue to allow blocking reads of events
* @det_events: list of detected events
* @dev_attr_list: list of event interface sysfs attribute
* @flags: file operations related flags including busy flag.
* @group: event interface sysfs attribute group
* @read_lock: lock to protect kfifo read operations
* @ioctl_handler: handler for event ioctl() calls
*/
struct iio_event_interface {
wait_queue_head_t wait;
DECLARE_KFIFO(det_events, struct iio_event_data, 16);
struct list_head dev_attr_list;
unsigned long flags;
struct attribute_group group;
struct mutex read_lock;
struct iio_ioctl_handler ioctl_handler;
};
该数据结构主要是对event子模块的定义,其中:
- 等待队列wait,当应用程序读取触发事件信息时,若当前无数据可读,则将当前进程加入到该等待队列,待调用iio_push_event将触发事件信息加入kfifo后,则wakeup该队列中的进程;
- 定义kfifo,存储所有触发的事件信息,供应用程序获取;
- 将even子模块动态定义的event attribute均添加至该链表中(属性名称格式为{iio_dir}_{iio_channel_type}{channel-Index/channel_modify}_{ev_type}_{ev_dir}_{ev_info});
- flags标记该event是否已使能(即应用程序是否通过ioctl调用创建一个匿名fd,若使能则置位IIO_BUSY_BIT_POS)
2.2、iio event fd创建
IIO event数据信息也是通过字符设备文件节点与应用程序进行交互的,但就像上面所说的,iio event数据读取对应的字符设备文件节点是一个匿名字符设备文件节点,且必须借助字符设备文件节点/dev/iio:deviceX的ioctl方可创建。创建流程如下图所示。借助字符设备文件/dev/iio:deviceX提供的ioctl,即创建一个匿名的文件节点,并返回该文件节点对应的event detect fd。
而event detect fd的文件操作接口的定义如下,提供event detect信息的读取及是否可读监控接口poll。
static const struct file_operations iio_event_chrdev_fileops = {
.read = iio_event_chrdev_read,
.poll = iio_event_poll,
.release = iio_event_chrdev_release,
.owner = THIS_MODULE,
.llseek = noop_llseek,
};
2.3、event 检测及读取流程
针对event 信息而言,主要就涉及event检测、event信息读取两部分,这两部分的关联如下图所示:
- 应用程序可直接通过read接口读取event信息(若event信息存在则读取检测到的event信息;若event信息不存在且fd设置为阻塞读方式,则将该读取进程休眠,加入到event的wait队列中);
- 应用程序可通过select或者epoll检测event fd是否可读,最终会调用event fd的poll接口,加入到event的等待队列中;
- 当iio device检测到某一个事件后,则通过中断(或其他的方式通知cpu)通知cpu,cpu则调用中断处理函数处理中断,在中断处理函数中读取检测到的event,然后调用iio_push_event将event加入到iio device对应event子模块的kfifo中,并wakeup event的wait队列,这样就唤醒了上述1和2中sleep的进程,进程即可进行event事件的读取操作。
在IIO子系统中,针对iio event,也设计与iio trigger的关联,即由iio trigger触发event的detect操作。若将iio event与iiotrigger关联,则执行的流程大致如下所述:
- 为iio event定义中断处理接口,并赋值给struct iio_dev的pollfunc_event成员(struct iio_poll_func *);
- 从iio trigger中申请一个虚拟的irq,并完成对应中断处理函数的注册(即上述1中的pollfunc_event);
- 注册一个iio trigger,用于处理iio device检测的event,则调用iio trigger 提供的iio_trigger_poll接口,由该接口调度iio trigger中所有申请的虚拟irq的中断处理函数;
- pollfunc_event接口中读取检测的event接口,然后wakeup event wait队列。
以下即是使用trigger-event方式的event detect检测及读取流程,相比上面的流程而言,则增加了虚拟irq的中断处理流程,而针对event信息而言,一般也就是出现告警等信息而检测的,并不像连续的数据采集那样频繁读取,因此这一种方式的event信息检测及读取流程并没有多少iio device driver采用,iio device driver一般使用上面的检测及读取流程。
三、IIO EVENT相关接口说明
3.1、iio event相关的sysfs属性的创建接口
iio_device_register_eventset接口用于创建iio event相关的event属性,该接口主要由iio_device_register接口调用,主要是根据struct iio_event_spec *event_spec中定义的变量,创建对应的event属性。
在 IIO(Industrial I/O)子系统中,事件(Event)相关的 sysfs
属性是由 IIO 设备驱动通过特定接口创建的。为了在 sysfs
中为 IIO 设备创建事件相关的属性,IIO 驱动程序通常会使用 IIO 的事件相关 API 来注册事件,并将这些事件暴露给用户空间。
IIO 事件相关的 sysfs
属性
事件相关的 sysfs
属性包括但不限于:
- 事件使能(
enable
):控制事件的启用和禁用。 - 事件阈值(
threshold
):设置或查询事件触发的阈值。 - 事件类型(
type
):事件的类型(如阈值触发、上升沿触发等)。 - 事件方向(
direction
):事件的方向(如上升沿或下降沿)。
这些属性通常会通过 sysfs
接口暴露出来,允许用户空间动态地与 IIO 设备的事件机制交互。
IIO 事件的 sysfs
属性创建接口
要创建事件相关的 sysfs
属性,驱动程序需要通过以下几个步骤来实现。
1. 事件触发器的创建
在 IIO 中,事件是通过触发器来管理的。每个事件类型通常都需要一个事件触发器,驱动程序使用 iio_trigger
来管理它。
IIO 驱动程序可以通过以下接口来创建和注册事件触发器:
struct iio_trigger *iio_trigger_alloc(struct iio_dev *indio_dev);
void iio_trigger_free(struct iio_trigger *trig);
这些触发器将绑定到 IIO 设备,并为事件的触发提供支持。
2. 定义事件的类型和阈值
IIO 设备可以通过 iio_event_spec
结构来定义事件的类型和阈值。每个事件可以根据特定的条件(如阈值超限、方向变化等)来触发。
struct iio_event_spec {
u32 type; /* 事件类型(如 IIO_EV_TYPE_THRESH) */
u32 direction; /* 事件方向(如 IIO_EV_DIR_RISING) */
int threshold; /* 事件阈值 */
};
其中:
type
:定义事件的类型(例如,阈值事件、边沿事件等)。direction
:定义事件的方向(如上升沿、下降沿等)。threshold
:设置触发事件的阈值。
3. 注册事件
一旦事件类型和触发条件被定义,驱动程序就需要将事件注册到 IIO 设备上。通常,驱动程序使用 iio_triggered_event_register()
来完成事件的注册。
int iio_triggered_event_register(struct iio_dev *indio_dev, struct iio_event_spec *event_spec);
这个函数会为每个事件创建相应的 sysfs
属性,使得用户可以通过 sysfs
来配置和控制事件。
4. 创建和管理 sysfs
属性
当事件注册成功后,相关的 sysfs
属性(如 enable
、threshold
、type
、direction
)将被自动创建。这些属性将出现在 /sys/bus/iio/devices/iio:deviceX/events/
目录下。
5. 启用事件
sysfs
中的 enable
属性允许用户通过写入操作启用或禁用事件触发。例如:
echo 1 > /sys/bus/iio/devices/iio:device0/events/threshold/enable
通过这个操作,用户可以启用一个阈值事件触发机制。
6. 卸载事件
当不再需要事件时,驱动程序可以通过调用 iio_triggered_event_unregister()
来注销事件,这将移除相关的 sysfs
属性。
int iio_triggered_event_unregister(struct iio_dev *indio_dev);
这将删除该事件的所有 sysfs
属性,并清理相关资源。
示例:如何创建 IIO 事件的 sysfs
属性
以下是一个示例代码,展示了如何为 IIO 设备创建和管理事件相关的 sysfs
属性:
#include <linux/iio/iio.h>
#include <linux/iio/events.h>
static int iio_device_create_event(struct iio_dev *indio_dev)
{
struct iio_event_spec event_spec;
struct iio_trigger *trigger;
int ret;
/* 定义事件的类型和阈值 */
event_spec.type = IIO_EV_TYPE_THRESH; /* 阈值事件 */
event_spec.direction = IIO_EV_DIR_RISING; /* 上升沿触发 */
event_spec.threshold = 100; /* 设置阈值为 100 */
/* 注册事件 */
ret = iio_triggered_event_register(indio_dev, &event_spec);
if (ret)
return ret;
/* 获取或创建触发器 */
trigger = iio_trigger_alloc(indio_dev);
if (!trigger) {
ret = -ENOMEM;
goto error_trigger;
}
/* 将触发器与 IIO 设备关联 */
ret = iio_device_register_event(indio_dev);
if (ret)
goto error_trigger;
return 0;
error_trigger:
kfree(trigger);
return ret;
}
static void iio_device_unregister_event(struct iio_dev *indio_dev)
{
/* 卸载事件,删除相关的 sysfs 属性 */
iio_triggered_event_unregister(indio_dev);
}
3.2 event push接口
iio_event_poll接口主要实现将event信息放入event的kfifo中,并wakeup event wait queue,然后应用程序即可读取该event信息。
IIO(Industrial I/O)事件的“推送”接口通常指的是设备驱动在某些事件条件满足时,将事件信息主动通知给用户空间的机制。IIO 事件机制是基于触发器的,而事件的推送是通过这些触发器与用户空间的通信机制实现的。
1. IIO 事件推送机制
在 IIO 中,事件推送是指通过一个“事件触发器”(iio_trigger
)将特定条件下的事件推送到用户空间。推送通常会在设备满足某个特定条件(例如阈值超限、上升沿或下降沿等)时发生。触发器会通过一个事件处理程序(通常是内核中的一个回调函数)将事件信息推送给用户空间。
事件触发器(iio_trigger
)
事件触发器是 IIO 中事件推送的核心机制。它负责监听某些条件(如传感器数据超出阈值、时间到达等),并在条件满足时触发事件通知。
- 事件触发器通过
iio_trigger
结构来管理,可以通过以下 API 进行分配、注册和操作。
struct iio_trigger *iio_trigger_alloc(struct iio_dev *indio_dev);
void iio_trigger_free(struct iio_trigger *trig);
事件回调函数
当触发器被触发时,通常会调用一个事件回调函数(或处理函数),该函数处理与事件相关的任务(例如,读取传感器数据、更新 sysfs
等)。
- 在 IIO 驱动程序中,回调函数通常使用
iio_triggered_buffer_preenable()
和iio_triggered_buffer_postdisable()
等函数来配合缓冲区操作,确保事件在适当的时机被处理。
事件推送的工作流程
- 定义事件类型和阈值:通过
iio_event_spec
结构定义要监测的事件条件(如阈值类型、方向等)。 - 事件触发器的创建:通过
iio_trigger_alloc()
创建一个事件触发器,并配置事件的触发条件。 - 事件的触发:当触发条件满足时,触发器会激活事件回调函数,主动将事件推送给用户空间。
- 用户空间接收事件:用户空间可以通过
sysfs
接口或事件文件监听事件的触发。
2. IIO 事件推送的 sysfs
接口
IIO 提供了一些 sysfs
接口,允许用户空间获取和控制事件的推送:
/sys/bus/iio/devices/iio:deviceX/events/
目录下,包含了与事件相关的sysfs
属性,如enable
、threshold
、type
和direction
等。通过这些属性,用户空间可以启用、禁用和配置事件。- 用户空间可以通过
poll
或select
等系统调用来监听某些sysfs
文件的变化,从而接收事件的推送。
3. 实现 IIO 事件推送的代码示例
以下是一个简单的示例,展示如何在 IIO 驱动程序中实现事件触发和推送机制。
定义事件和触发器
#include <linux/iio/iio.h>
#include <linux/iio/events.h>
static int iio_device_create_event(struct iio_dev *indio_dev)
{
struct iio_event_spec event_spec;
struct iio_trigger *trigger;
int ret;
/* 定义事件的类型和阈值 */
event_spec.type = IIO_EV_TYPE_THRESH; /* 阈值事件 */
event_spec.direction = IIO_EV_DIR_RISING; /* 上升沿触发 */
event_spec.threshold = 100; /* 设置阈值为 100 */
/* 创建触发器 */
trigger = iio_trigger_alloc(indio_dev);
if (!trigger) {
ret = -ENOMEM;
return ret;
}
/* 注册事件触发器 */
ret = iio_triggered_event_register(indio_dev, &event_spec);
if (ret)
goto error_trigger;
/* 启用事件触发器 */
ret = iio_trigger_register(trigger);
if (ret)
goto error_trigger;
return 0;
error_trigger:
iio_trigger_free(trigger);
return ret;
}
事件回调函数
在事件触发时,回调函数会被调用来处理事件:
static void iio_event_callback(struct iio_dev *indio_dev, struct iio_trigger *trig)
{
/* 事件触发时的处理逻辑 */
pr_info("IIO event triggered!\n");
/* 你可以在这里推送事件信息到用户空间或做其他处理 */
// 例如通过一个事件队列或直接写入到 sysfs
}
注册事件回调
你需要将回调函数注册到触发器,以便在事件发生时调用它:
trigger->dev.driver_data = &iio_event_callback;
卸载事件触发器
当不再需要事件时,可以通过以下方式注销事件触发器:
void iio_device_unregister_event(struct iio_dev *indio_dev)
{
iio_trigger_unregister(trigger);
iio_trigger_free(trigger);
}
4. 用户空间监听事件
用户空间可以通过 sysfs
接口或通过 poll
等方法监听事件的发生。以下是通过 poll
接口监听事件的一个示例:
#include <poll.h>
#include <fcntl.h>
#include <stdio.h>
int main(void)
{
struct pollfd fds[1];
int fd;
/* 打开 IIO 事件文件 */
fd = open("/sys/bus/iio/devices/iio:device0/events/threshold", O_RDONLY);
if (fd < 0) {
perror("Failed to open event file");
return 1;
}
fds[0].fd = fd;
fds[0].events = POLLPRI; /* 监听优先级事件 */
while (1) {
int ret = poll(fds, 1, -1); /* 阻塞等待事件发生 */
if (ret > 0) {
if (fds[0].revents & POLLPRI) {
/* 事件触发时处理 */
printf("Event triggered!\n");
}
}
}
close(fd);
return 0;
}