Linux IIO子系统分析5

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主要实现两个功能:

  1. 用于读取iio device缓存的已采集数据,从而让应用程序对采集的数据进行分析;
  2. 借助该字符设备文件的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主要包括如下几个方面的内容:

  1. iio_buffer缓存数据的个数(即length);
  2. iio_buffer每一次采集数据的长度(bytes_per_datum,而bytes_per_datum*length即为kfifo存储数据的内存空间大小);
  3. Scan_el_dev_attr_list主要用于将所有iio_buffer子模块创建的属性变量集合在一起(iio_buffer);
  4. scan_el_attrs存储各设备驱动自行定义的静态属性(生成的属性文件在scan_elements子目录下);
  5. attrs也是存储存储各设备驱动自行定义的静态属性(该变量定义的属性文件在buffer子目录下);
  6. buffer_group、scan_el_group包含iio buffer子模块下所有属性,其中buffer_group里的属性均在buffer子目录下创建对应的属性文件;scan_el_group里的属性均在scan_elements子目录下创建对应的属性文件;
  7. pollq为等待队列,主要为iio device的字符设备文件使用(该字符设备文件对应的读接口和poll接口使用,当buffer中不存在数据时则sleep在该等待队列中);
  8. 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的部分。具体说明如下:

  1. 实现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中;
  2. 而应用程序则通过iio buffer 字符设备文件接口,从iio buffer的kfifo中读取采集的数据信息。)

 

借助以上的信息,则实现了iio trigger与iio buffer的结合,并实现iio device 采集数据的缓存与读取操作。

结合 IIO TriggerIIO Buffer 的机制来实现 IIO 设备的采集数据缓存和读取操作,是嵌入式驱动开发中一个常见的需求。这个功能通常用于传感器数据的实时采集,并能够在数据达到一定条件(如触发器事件)时将数据存入缓存中。

以下是一个示例,展示如何实现 IIO Trigger 与 IIO Buffer 的结合,并实现数据的采集、缓存和读取操作。

3.1、结构和基本概念

  • IIO Trigger:用于在特定条件下触发数据采集事件,通常会在数据到达一定阈值时触发。
  • IIO Buffer:用于缓存数据,它允许将采集到的数据存储起来,用户可以从缓存中读取数据。
  • IIO Device:表示一个 IIO 设备,它可以包含传感器的数据源、触发器、缓冲区等。

3.2、总体流程

  1. 定义数据缓冲区:为设备创建一个缓冲区,存储采集到的数据。
  2. 设置触发器:创建一个触发器,当数据满足某些条件时(如阈值超限)触发事件。
  3. 采集数据:当触发器触发时,采集数据并将数据放入缓冲区。
  4. 用户空间读取数据:用户空间通过 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 缓冲区和触发器
  1. 定义一个事件触发器:这是一个在特定条件下触发数据采集的机制。
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");
}
  1. 初始化触发器:使用 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用于实现该功能,该接口实现的功能主要包括:

  1. 申请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;
};
  1. 完成iio_dev->setup_ops的赋值(即struct iio_buffer_setup_ops*类型的变量,该数据结构主要用于实现iio buffer与iiotrigger的绑定与解绑接口,一般使用iio buffer实现的默认变量iio_triggered_buffer_setup_ops), iio_triggered_buffer_setup_ops的定义如下。
    1. 其中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中。
    2. 而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属性文件:

  1. iio buffer使能、iio kfifo长度、应用程序可读取数据的最小等待个数,主要是在/sys/bus/iio/devices/iio:deviceX目录下创建子目录buffer,该子目录下创建三个属性文件,分别为enable、length、watermark;
    1. 其中enable用于实现iio buffer使能的设置及查看,当通过该文件使能iio buffer时,则完成iio buffer的kfifo大小申请、单次采集数据大小更新、已使能的采集通道数据等更新,并最终调用iio_buffer_setup_ops->postenable接口,实现iio trigger与iio buffer的绑定;
    2. length主要设置iio buffer的kfifo的容量大小;
    3. watermark则主要修改应用程序一次可读取数据的最小等待个数,当buffer中存储的数据个数超过该值后,则在poll接口iio_buffer_poll中标记该文件可读(epoll、select监控该标记);
  2. 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 来定义,每个属性对应一个函数(showstore)。

#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 文件暴露采样率参数。
  • 触发器状态:显示触发器是否已经触发或正在等待触发。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值