Linux IIO子系统分析4

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对应的字符设备文件节点主要提供哪些服务呢?

主要提供两方面的内容:

  1. 提供对iio device各通道连续采集数据的读取操作(前提是该iio device的某些通道提供了iio buffer功能);
  2. 提供创建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子模块的定义,其中:

  1. 等待队列wait,当应用程序读取触发事件信息时,若当前无数据可读,则将当前进程加入到该等待队列,待调用iio_push_event将触发事件信息加入kfifo后,则wakeup该队列中的进程;
  2. 定义kfifo,存储所有触发的事件信息,供应用程序获取;
  3. 将even子模块动态定义的event attribute均添加至该链表中(属性名称格式为{iio_dir}_{iio_channel_type}{channel-Index/channel_modify}_{ev_type}_{ev_dir}_{ev_info});
  4. 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信息读取两部分,这两部分的关联如下图所示:

  1. 应用程序可直接通过read接口读取event信息(若event信息存在则读取检测到的event信息;若event信息不存在且fd设置为阻塞读方式,则将该读取进程休眠,加入到event的wait队列中);
  2. 应用程序可通过select或者epoll检测event fd是否可读,最终会调用event fd的poll接口,加入到event的等待队列中;
  3. 当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触发eventdetect操作。若将iio eventiiotrigger关联,则执行的流程大致如下所述:

  1. iio event定义中断处理接口,并赋值给struct iio_devpollfunc_event成员(struct iio_poll_func *;
  2. iio trigger中申请一个虚拟的irq,并完成对应中断处理函数的注册(即上述1中的pollfunc_event);
  3. 注册一个iio trigger,用于处理iio device检测的event,则调用iio trigger 提供的iio_trigger_poll接口,由该接口调度iio trigger中所有申请的虚拟irq的中断处理函数;
  4. 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 属性(如 enablethresholdtypedirection)将被自动创建。这些属性将出现在 /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() 等函数来配合缓冲区操作,确保事件在适当的时机被处理。

事件推送的工作流程

  1. 定义事件类型和阈值:通过 iio_event_spec 结构定义要监测的事件条件(如阈值类型、方向等)。
  2. 事件触发器的创建:通过 iio_trigger_alloc() 创建一个事件触发器,并配置事件的触发条件。
  3. 事件的触发:当触发条件满足时,触发器会激活事件回调函数,主动将事件推送给用户空间。
  4. 用户空间接收事件:用户空间可以通过 sysfs 接口或事件文件监听事件的触发。

2. IIO 事件推送的 sysfs 接口

IIO 提供了一些 sysfs 接口,允许用户空间获取和控制事件的推送:

  • /sys/bus/iio/devices/iio:deviceX/events/ 目录下,包含了与事件相关的 sysfs 属性,如 enablethresholdtypedirection 等。通过这些属性,用户空间可以启用、禁用和配置事件。
  • 用户空间可以通过 pollselect 等系统调用来监听某些 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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值