Linux IIO子系统分析5(基于Linux6.6)---BUFFER子模块介绍
一、 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主要实现两个功能:
- 用于读取iio device缓存的已采集数据,从而让应用程序对采集的数据进行分析;
- 借助该字符设备文件的ioctl接口,实现匿名iio event fd的创建,从而让应用程序借助select/epoll监控该匿名文件是否有event信息可读;
如下即是/dev/iio:deviceX的访问流程,应用程序通过open/read/poll/ioctl接口则会调用内核中VFS提供的操作接口,最终则调用iio_buffer_fileops中定义的接口。
iio_buffer_fileops的定义如下:
drivers/iio/industrialio-core.c
static const struct file_operations iio_buffer_fileops = {
.owner = THIS_MODULE,
.llseek = noop_llseek,
.read = iio_buffer_read_outer_addr,
.write = iio_buffer_write_outer_addr,
.poll = iio_buffer_poll_addr,
.unlocked_ioctl = iio_ioctl,
.compat_ioctl = compat_ptr_ioctl,
.open = iio_chrdev_open,
.release = iio_chrdev_release,
};
二、iio buffer相关数据结构
2.1、struct iio_buffer与struct iio_buffer_setup_ops
iio_buffer主要是用于存储连续采集数据的缓存,其主要包括两个主要的数据结构struct iio_buffer、struct iio_buffer_access_func(其实是三个数据结构,还有数据结构struct iio_kfifo,其内部包含struct iio_buffer类型变量和struct kfifo类型变量用于缓存数据)。
针对struct iio_buffer主要包括如下几个方面的内容:
- iio_buffer缓存数据的个数(即length);
- iio_buffer每一次采集数据的长度(bytes_per_datum,而bytes_per_datum*length即为kfifo存储数据的内存空间大小);
- Scan_el_dev_attr_list主要用于将所有iio_buffer子模块创建的属性变量集合在一起(iio_buffer);
- scan_el_attrs存储各设备驱动自行定义的静态属性(生成的属性文件在scan_elements子目录下);
- attrs也是存储存储各设备驱动自行定义的静态属性(该变量定义的属性文件在buffer子目录下);
- buffer_group、scan_el_group包含iio buffer子模块下所有属性,其中buffer_group里的属性均在buffer子目录下创建对应的属性文件;scan_el_group里的属性均在scan_elements子目录下创建对应的属性文件;
- pollq为等待队列,主要为iio device的字符设备文件使用(该字符设备文件对应的读接口和poll接口使用,当buffer中不存在数据时则sleep在该等待队列中);
- watermark为缓存多少个数据后,唤醒pollq(实际内存空间大小为watermark*bytes_per_datum)。
针对struct iio_buffer_access_funcs则是该iio_buffer对应的缓存空间的访问访问,目前使用kfifo缓存数据,则其访问方法为iio_store_to_kfifo、iio_read_first_n_kfifo等,主要是将数据存储至kfifo或从kfifo中取出缓存数据等。
include/linux/iio/buffer_impl.h
/**
* struct iio_buffer - general buffer structure
*
* Note that the internals of this structure should only be of interest to
* those writing new buffer implementations.
*/
struct iio_buffer {
/** @length: Number of datums in buffer. */
unsigned int length;
/** @flags: File ops flags including busy flag. */
unsigned long flags;
/** @bytes_per_datum: Size of individual datum including timestamp. */
size_t bytes_per_datum;
/* @direction: Direction of the data stream (in/out). */
enum iio_buffer_direction direction;
/**
* @access: Buffer access functions associated with the
* implementation.
*/
const struct iio_buffer_access_funcs *access;
/** @scan_mask: Bitmask used in masking scan mode elements. */
long *scan_mask;
/** @demux_list: List of operations required to demux the scan. */
struct list_head demux_list;
/** @pollq: Wait queue to allow for polling on the buffer. */
wait_queue_head_t pollq;
/** @watermark: Number of datums to wait for poll/read. */
unsigned int watermark;
/* private: */
/* @scan_timestamp: Does the scan mode include a timestamp. */
bool scan_timestamp;
/* @buffer_attr_list: List of buffer attributes. */
struct list_head buffer_attr_list;
/*
* @buffer_group: Attributes of the new buffer group.
* Includes scan elements attributes.
*/
struct attribute_group buffer_group;
/* @attrs: Standard attributes of the buffer. */
const struct iio_dev_attr **attrs;
/* @demux_bounce: Buffer for doing gather from incoming scan. */
void *demux_bounce;
/* @attached_entry: Entry in the devices list of buffers attached by the driver. */
struct list_head attached_entry;
/* @buffer_list: Entry in the devices list of current buffers. */
struct list_head buffer_list;
/* @ref: Reference count of the buffer. */
struct kref ref;
};
include/linux/iio/iio.h
/**
* struct iio_buffer_setup_ops - buffer setup related callbacks
* @preenable: [DRIVER] function to run prior to marking buffer enabled
* @postenable: [DRIVER] function to run after marking buffer enabled
* @predisable: [DRIVER] function to run prior to marking buffer
* disabled
* @postdisable: [DRIVER] function to run after marking buffer disabled
* @validate_scan_mask: [DRIVER] function callback to check whether a given
* scan mask is valid for the device.
*/
struct iio_buffer_setup_ops {
int (*preenable)(struct iio_dev *);
int (*postenable)(struct iio_dev *);
int (*predisable)(struct iio_dev *);
int (*postdisable)(struct iio_dev *);
bool (*validate_scan_mask)(struct iio_dev *indio_dev,
const unsigned long *scan_mask);
};
三、IIO trigger-buffer介绍
借助于IIO trigger机制,iio-device在数据可读时,将数据push 到iio buffer子模块的缓存中,实现缓存连续采集的数据。如下图所示,即为iio trigger-buffer的实现机制,其中红线部分即是iio trigger的部分,而右边黄线部分则是iio buffer的部分。具体说明如下:
- 实现iio trigger的注册,该iio trigger 可支持多个consumer(此处的consumer即为iio buffer),而iio trigger主要是借助其内部实现的虚拟中断控制器,从而实现支持多个consumer;而iio trigger需要与iio device进行绑定,仅在iio device与iio trigger绑定后,iio device driver则可以通过监控iio device触发事件(如中断),然后调用iio_trigger_poll接口,该接口调度所有虚拟中断对应的中断处理函数(即调用iio trigger consumer提供的中断处理接口,而在该中断处理接口中从iio device中读取数据,并调用iio_push_to_buffer,将数据push到iio buffer的kfifo中;
- 而应用程序则通过iio buffer 字符设备文件接口,从iio buffer的kfifo中读取采集的数据信息。)
借助以上的信息,则实现了iio trigger与iio buffer的结合,并实现iio device 采集数据的缓存与读取操作。
结合 IIO Trigger 与 IIO Buffer 的机制来实现 IIO 设备的采集数据缓存和读取操作,是嵌入式驱动开发中一个常见的需求。这个功能通常用于传感器数据的实时采集,并能够在数据达到一定条件(如触发器事件)时将数据存入缓存中。
以下是一个示例,展示如何实现 IIO Trigger 与 IIO Buffer 的结合,并实现数据的采集、缓存和读取操作。
3.1、结构和基本概念
- IIO Trigger:用于在特定条件下触发数据采集事件,通常会在数据到达一定阈值时触发。
- IIO Buffer:用于缓存数据,它允许将采集到的数据存储起来,用户可以从缓存中读取数据。
- IIO Device:表示一个 IIO 设备,它可以包含传感器的数据源、触发器、缓冲区等。
3.2、总体流程
- 定义数据缓冲区:为设备创建一个缓冲区,存储采集到的数据。
- 设置触发器:创建一个触发器,当数据满足某些条件时(如阈值超限)触发事件。
- 采集数据:当触发器触发时,采集数据并将数据放入缓冲区。
- 用户空间读取数据:用户空间通过
sysfs
接口或调用read()
从缓冲区中读取数据。
3.3、IIO 设备、触发器和缓冲区的实现
以下是一个实现 IIO Trigger 与 IIO Buffer 结合的示例代码,展示如何配置这些组件并实现数据缓存和读取。
1. 包含必要的头文件
#include <linux/iio/iio.h>
#include <linux/iio/triggered_buffer.h>
#include <linux/iio/events.h>
#include <linux/iio/consumer.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
2. 定义 IIO 设备和触发器结构
static struct iio_dev *iio_device;
static struct iio_trigger *iio_trigger;
static struct iio_buffer *iio_buffer;
3. 设置 IIO 缓冲区和触发器
- 定义一个事件触发器:这是一个在特定条件下触发数据采集的机制。
static void iio_trigger_callback(struct iio_dev *indio_dev, struct iio_trigger *trig)
{
struct iio_buffer *buffer = iio_device_get_buffer(indio_dev);
/* 将数据推送到缓冲区 */
if (buffer)
iio_buffer_push(buffer);
pr_info("IIO Trigger Callback: Data pushed to buffer\n");
}
- 初始化触发器:使用
iio_trigger_alloc()
创建触发器。
static int setup_trigger(struct iio_dev *indio_dev)
{
int ret;
/* 创建触发器 */
iio_trigger = iio_trigger_alloc(indio_dev, IIO_TRIGGER_SHOT);
if (!iio_trigger)
return -ENOMEM;
/* 设置触发器回调函数 */
iio_trigger->dev.driver_data = &iio_trigger_callback;
/* 注册触发器 */
ret = iio_trigger_register(iio_trigger);
if (ret)
return ret;
return 0;
}
4. 创建 IIO 缓冲区
iio_buffer
用于存储从设备采集的数据,可以通过触发器在数据到达时将数据写入缓冲区。
static int setup_buffer(struct iio_dev *indio_dev)
{
struct iio_buffer *buffer;
int ret;
/* 创建 IIO 缓冲区 */
buffer = iio_device_alloc_buffer(indio_dev);
if (!buffer)
return -ENOMEM;
/* 将缓冲区与设备关联 */
indio_dev->buffer = buffer;
ret = iio_buffer_set_size(buffer, 1024); /* 设置缓存大小 */
if (ret)
return ret;
return 0;
}
5. 配置 IIO 设备的采集功能
需要配置 IIO 设备来启动数据采集功能。当触发器被触发时,数据会采集并存入缓冲区。
static int iio_device_probe(struct iio_dev *indio_dev)
{
int ret;
/* 设置触发器 */
ret = setup_trigger(indio_dev);
if (ret) {
pr_err("Failed to setup trigger\n");
return ret;
}
/* 设置缓冲区 */
ret = setup_buffer(indio_dev);
if (ret) {
pr_err("Failed to setup buffer\n");
return ret;
}
/* 启动 IIO 设备的采集功能 */
ret = iio_device_attach_buffer(indio_dev, iio_buffer);
if (ret) {
pr_err("Failed to attach buffer\n");
return ret;
}
return 0;
}
6. 读取缓存数据
用户可以通过 read()
或 sysfs
接口从缓冲区读取采集到的数据。例如,可以在驱动中实现一个 read
操作,将缓冲区中的数据返回给用户空间:
static ssize_t iio_device_read(struct file *file, char __user *buf, size_t count, loff_t *pos)
{
struct iio_dev *indio_dev = file->private_data;
struct iio_buffer *buffer;
int ret;
/* 获取缓冲区 */
buffer = iio_device_get_buffer(indio_dev);
if (!buffer)
return -ENOMEM;
/* 从缓冲区读取数据 */
ret = iio_buffer_read(buffer, buf, count);
if (ret < 0)
return ret;
return ret;
}
7. 驱动的初始化与卸载
在模块的初始化和卸载过程中,创建和销毁触发器和缓冲区。
static int __init iio_device_init(void)
{
struct iio_dev *indio_dev;
int ret;
/* 创建 IIO 设备 */
indio_dev = iio_device_alloc();
if (!indio_dev)
return -ENOMEM;
/* 配置 IIO 设备 */
ret = iio_device_probe(indio_dev);
if (ret) {
iio_device_free(indio_dev);
return ret;
}
return 0;
}
static void __exit iio_device_exit(void)
{
/* 销毁 IIO 设备 */
if (iio_device) {
iio_device_unregister(iio_device);
iio_device_free(iio_device);
}
/* 销毁触发器 */
if (iio_trigger) {
iio_trigger_unregister(iio_trigger);
iio_trigger_free(iio_trigger);
}
/* 销毁缓冲区 */
if (iio_buffer) {
iio_buffer_free(iio_buffer);
}
}
module_init(iio_device_init);
module_exit(iio_device_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("IIO Trigger and Buffer Example");
四、IIO buffer相关接口说明
4.1、iio trigger与iiobuffer关联接口
主要提供接口iio_triggered_buffer_setup用于实现该功能,该接口实现的功能主要包括:
- 申请iio buffer类型的变量,同时完成iio_dev->pollfunc的定义(struct iio_poll_func*类型的变量,该变量主要包含iio trigger内部虚拟中断处理接口的赋值、申请的虚拟中断id等);
include/linux/iio/trigger_consumer.h
struct iio_poll_func {
struct iio_dev *indio_dev;
irqreturn_t (*h)(int irq, void *p);
irqreturn_t (*thread)(int irq, void *p);
int type;
char *name;
int irq;
s64 timestamp;
};
- 完成iio_dev->setup_ops的赋值(即struct iio_buffer_setup_ops*类型的变量,该数据结构主要用于实现iio buffer与iiotrigger的绑定与解绑接口,一般使用iio buffer实现的默认变量iio_triggered_buffer_setup_ops), iio_triggered_buffer_setup_ops的定义如下。
- 其中iio_triggered_buffer_postenable则实现iio trigger与iio buffer的绑定,即通过调用iio_trigger_attach_poll_func接口从iio trigger中申请一个虚拟的irq,并完成该irq的中断处理函数的设置;当iio device调用iio_trigger_poll接口将触发信息发送给iio trigger时,iio trigger则会触发所有虚拟中断处理接口,由各中断的中断处理函数从iio device中进行数据的采集,并调用iio_push_buffer接口,将采集的数据push到iio buffer的kfifo中。
- 而iio_triggered_buffer_predisable则解除iio trigger与iio buffer的绑定,即free申请的虚拟irq。
static const struct iio_buffer_setup_ops iio_triggered_buffer_setup_ops = {
.postenable = &iio_triggered_buffer_postenable,
.predisable = &iio_triggered_buffer_predisable,
};
4.2、iio buffer相关的sysfs属性文件创建接口
iio_buffer_alloc_sysfs_and_mask接口用于实现iio buffer相关sysfs属性文件的创建,其中创建两类sysfs属性文件:
- iio buffer使能、iio kfifo长度、应用程序可读取数据的最小等待个数,主要是在/sys/bus/iio/devices/iio:deviceX目录下创建子目录buffer,该子目录下创建三个属性文件,分别为enable、length、watermark;
- 其中enable用于实现iio buffer使能的设置及查看,当通过该文件使能iio buffer时,则完成iio buffer的kfifo大小申请、单次采集数据大小更新、已使能的采集通道数据等更新,并最终调用iio_buffer_setup_ops->postenable接口,实现iio trigger与iio buffer的绑定;
- length主要设置iio buffer的kfifo的容量大小;
- watermark则主要修改应用程序一次可读取数据的最小等待个数,当buffer中存储的数据个数超过该值后,则在poll接口iio_buffer_poll中标记该文件可读(epoll、select监控该标记);
- iio buffer相关的采集通道的使能与否相关的sysfs属性文件,主要是在/sys/bus/iio/devices/iio:deviceX目录下创建子目录scan_elements,创建采集通道使能属性文件、采集数据的格式、采集通道的index等。
在 IIO (Industrial I/O) 子系统中,IIO Buffer
的 sysfs 属性文件用于与用户空间交互,允许用户通过文件系统接口访问缓冲区的状态和数据。IIO 驱动程序通常会创建一组 sysfs 属性文件,以便用户可以控制缓冲区的操作,如启动/停止缓冲区、读取缓冲区中的数据等。
在 IIO 子系统中,缓冲区的操作一般通过 iio_buffer
接口与 iio_dev
进行交互。为了创建和管理这些属性文件,驱动需要实现特定的 sysfs 文件接口。
4.3、创建 IIO Buffer 相关的 Sysfs 文件
要创建 IIO Buffer 相关的 sysfs 属性文件,我们通常会使用 iio_dev
结构中的 iio_attribute
数组来描述需要暴露的属性,并通过 iio_device_register()
注册这些属性。
4.4、常见的 Buffer Sysfs 属性
IIO Buffer 相关的常见 sysfs 属性包括:
- enable:启用或禁用缓冲区。
- length:缓冲区的长度。
- status:缓冲区的状态(是否处于活动状态)。
- sample_time:数据采集的时间间隔。
- trigger:用于控制触发器的启动。
4.5、创建 sysfs 属性文件的代码示例
下面是如何在 IIO 设备中实现与 IIO Buffer 相关的 sysfs 属性文件。
1. 定义 Sysfs 属性结构
首先定义属性文件对应的操作结构。这个结构通常是通过 iio_attr
来定义,每个属性对应一个函数(show
和 store
)。
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
#include <linux/iio/buffer.h>
static ssize_t iio_buffer_enable_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct iio_dev *indio_dev = iio_device_get(dev);
bool enabled = iio_buffer_enabled(indio_dev);
return snprintf(buf, PAGE_SIZE, "%d\n", enabled);
}
static ssize_t iio_buffer_enable_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t len)
{
struct iio_dev *indio_dev = iio_device_get(dev);
bool enable;
if (kstrtobool(buf, &enable))
return -EINVAL;
if (enable)
iio_buffer_enable(indio_dev);
else
iio_buffer_disable(indio_dev);
return len;
}
static DEVICE_ATTR(enable, S_IRUGO | S_IWUSR, iio_buffer_enable_show, iio_buffer_enable_store);
static ssize_t iio_buffer_length_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct iio_dev *indio_dev = iio_device_get(dev);
struct iio_buffer *buffer = iio_device_get_buffer(indio_dev);
return snprintf(buf, PAGE_SIZE, "%zu\n", buffer->size);
}
static DEVICE_ATTR(length, S_IRUGO, iio_buffer_length_show, NULL);
static ssize_t iio_buffer_status_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct iio_dev *indio_dev = iio_device_get(dev);
bool active = iio_buffer_active(indio_dev);
return snprintf(buf, PAGE_SIZE, "%d\n", active);
}
static DEVICE_ATTR(status, S_IRUGO, iio_buffer_status_show, NULL);
2. 注册 Sysfs 属性
使用 device_create_file
来注册这些属性文件。通常,在 IIO 设备的初始化过程中注册这些属性。
static int iio_device_probe(struct iio_dev *indio_dev)
{
int ret;
/* 注册 IIO 设备 */
ret = iio_device_register(indio_dev);
if (ret)
return ret;
/* 注册缓冲区相关的 sysfs 属性 */
ret = device_create_file(&indio_dev->dev, &dev_attr_enable);
if (ret)
goto err_unregister_device;
ret = device_create_file(&indio_dev->dev, &dev_attr_length);
if (ret)
goto err_remove_enable;
ret = device_create_file(&indio_dev->dev, &dev_attr_status);
if (ret)
goto err_remove_length;
return 0;
err_remove_length:
device_remove_file(&indio_dev->dev, &dev_attr_length);
err_remove_enable:
device_remove_file(&indio_dev->dev, &dev_attr_enable);
err_unregister_device:
iio_device_unregister(indio_dev);
return ret;
}
3. 设备删除时移除属性文件
当设备被卸载时,需要移除 sysfs 中的这些属性文件。使用 device_remove_file()
来移除属性文件。
static void iio_device_remove(struct iio_dev *indio_dev)
{
/* 移除缓冲区相关的 sysfs 属性 */
device_remove_file(&indio_dev->dev, &dev_attr_status);
device_remove_file(&indio_dev->dev, &dev_attr_length);
device_remove_file(&indio_dev->dev, &dev_attr_enable);
/* 注销 IIO 设备 */
iio_device_unregister(indio_dev);
}
4. 常见的 sysfs 属性文件描述
属性名 | 说明 | 读写权限 |
---|---|---|
enable | 启用或禁用缓冲区 | 读写 (R/W ) |
length | 缓冲区的大小,单位通常是字节 | 只读 (R ) |
status | 当前缓冲区的状态(是否启用或正在采集数据) | 只读 (R ) |
sample_time | 数据采集的时间间隔(如果有的话) | 读写 (R/W ) |
trigger | 启动触发器的属性,通常控制缓冲区何时开始填充数据 | 读写 (R/W ) |
5. 示例代码总结
我们已经通过一个简单的例子展示了如何为 IIO Buffer 创建 sysfs 属性文件,包括:
- 启用/禁用缓冲区:
enable
- 缓冲区大小:
length
- 缓冲区状态:
status
这种机制允许用户空间通过操作这些属性来启用或停止缓冲区、查询缓冲区的大小和状态,或与触发器交互。通过这类 sysfs 接口,驱动可以提供灵活的用户空间控制。
6. 进一步扩展
你可以根据需要进一步扩展这些属性,例如:
- 数据采样率:可以通过 sysfs 文件暴露采样率参数。
- 触发器状态:显示触发器是否已经触发或正在等待触发。