Linux IIO子系统分析6(基于Linux6.6)---device的驱动开发流程介绍
一、IIO子系统的关键技术点总结
IIO子系统主要使用如下几个关键技术点实现其主要功能:
- 借助sysfs、kobject机制,实现IIO DEVICE各类的设备属性,并借助于sysfs暴露给应用程序。
- 实现iio event相关的设备属性,并将属性参数以sysfs文件的形式显示在IIO DEVICE的events子目录(/sys/bus/iio/devices/iio:deviceX/events),包括event的使能、event参数设置与读取文件等;
- 实现iio trigger设备及其属性,并将属性参数以sysfs文件的显示在trigger子目录(/sys/bus/iio/devices/triggerX),可查看trigger的名称;而iio device则可以通过文件/sys/bus/iio/devices/iio:deviceX/trigger查看或修改当前iio device绑定的trigger;
- 实现iio buffer相关的设备属性,用于进行iio buffer的使能与否、各channel 相关的数据采集缓存使能等,主要在目录/sys/bus/iio/devices/iio:deviceX/scan_elements、/sys/bus/iio/devices/iio:deviceX/buffer下;
- 实现iio device相关的raw 数据读取等设备属性(在目录/sys/bus/iio/devices/iio:deviceX/下),实现读取各channel的raw数据信息(可理解单次数据采集,其与iio buffer采集是互斥的,即一个channel若开启了iio buffer进行数据采集及缓存,则无法通过dir_type_raw文件获取该通道的raw data);
- 借助于字符设备文件机制,应用程序可获取到iio buffer缓存的采集数据以及iio device触发的iio event信息,这两个信息目前仅能通过字符设备文件进行获取;
- iio trigger内部通过实现虚拟的irq chip,实现iio trigger与iio trigger consumer的1:N绑定,而不需要关注iio trigger consumer具体的数据类型等信息;
IIO子系统实现以上三种技术完成了iio device及其内部iio buffer、iio event、iio trigger等模块的功能实现。
二、IIO DEVICE的注册与注销接口说明
iio device注册接口为devm_iio_device_register、iio_device_register,而devm_iio_device_register接口则实现了iio device的自动注销功能,因此iio device driver尽量使用该接口进行iio device的注册
2.1 iio_device_register实现的主要功能
include/linux/iio/iio.h
/**
* iio_device_register() - register a device with the IIO subsystem
* @indio_dev: Device structure filled by the device driver
**/
#define iio_device_register(indio_dev) \
__iio_device_register((indio_dev), THIS_MODULE)
int __iio_device_register(struct iio_dev *indio_dev, struct module *this_mod);
void iio_device_unregister(struct iio_dev *indio_dev);
drivers/iio/industrialio-core.c
int __iio_device_register(struct iio_dev *indio_dev, struct module *this_mod)
{
struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev);
struct fwnode_handle *fwnode = NULL;
int ret;
if (!indio_dev->info)
return -EINVAL;
iio_dev_opaque->driver_module = this_mod;
/* If the calling driver did not initialize firmware node, do it here */
if (dev_fwnode(&indio_dev->dev))
fwnode = dev_fwnode(&indio_dev->dev);
/* The default dummy IIO device has no parent */
else if (indio_dev->dev.parent)
fwnode = dev_fwnode(indio_dev->dev.parent);
device_set_node(&indio_dev->dev, fwnode);
fwnode_property_read_string(fwnode, "label", &indio_dev->label);
ret = iio_check_unique_scan_index(indio_dev);
if (ret < 0)
return ret;
ret = iio_check_extended_name(indio_dev);
if (ret < 0)
return ret;
iio_device_register_debugfs(indio_dev);
ret = iio_buffers_alloc_sysfs_and_mask(indio_dev);
if (ret) {
dev_err(indio_dev->dev.parent,
"Failed to create buffer sysfs interfaces\n");
goto error_unreg_debugfs;
}
ret = iio_device_register_sysfs(indio_dev);
if (ret) {
dev_err(indio_dev->dev.parent,
"Failed to register sysfs interfaces\n");
goto error_buffer_free_sysfs;
}
ret = iio_device_register_eventset(indio_dev);
if (ret) {
dev_err(indio_dev->dev.parent,
"Failed to register event set\n");
goto error_free_sysfs;
}
if (indio_dev->modes & INDIO_ALL_TRIGGERED_MODES)
iio_device_register_trigger_consumer(indio_dev);
if ((indio_dev->modes & INDIO_ALL_BUFFER_MODES) &&
indio_dev->setup_ops == NULL)
indio_dev->setup_ops = &noop_ring_setup_ops;
if (iio_dev_opaque->attached_buffers_cnt)
cdev_init(&iio_dev_opaque->chrdev, &iio_buffer_fileops);
else if (iio_dev_opaque->event_interface)
cdev_init(&iio_dev_opaque->chrdev, &iio_event_fileops);
if (iio_dev_opaque->attached_buffers_cnt || iio_dev_opaque->event_interface) {
indio_dev->dev.devt = MKDEV(MAJOR(iio_devt), iio_dev_opaque->id);
iio_dev_opaque->chrdev.owner = this_mod;
}
/* assign device groups now; they should be all registered now */
indio_dev->dev.groups = iio_dev_opaque->groups;
ret = cdev_device_add(&iio_dev_opaque->chrdev, &indio_dev->dev);
if (ret < 0)
goto error_unreg_eventset;
return 0;
error_unreg_eventset:
iio_device_unregister_eventset(indio_dev);
error_free_sysfs:
iio_device_unregister_sysfs(indio_dev);
error_buffer_free_sysfs:
iio_buffers_free_sysfs_and_mask(indio_dev);
error_unreg_debugfs:
iio_device_unregister_debugfs(indio_dev);
return ret;
}
EXPORT_SYMBOL(__iio_device_register);
iio device register主要实现如下几个功能:
- 完成该iio device对应的字符设备文件的创建,字符设备文件的操作接口为iio_buffer_fileops;
- 完成iio buffer对象指针的创建及iio buffer相关的设备属性的创建(主要通过函数iio_buffer_alloc_sysfs_and_mask实现);
- 完成iio event interface的内存申请及初始化,并完成iio event相关的设备属性的创建(iio_device_register_eventset);
- 完成iio device相关的设备属性的创建(即是上面所说的raw data获取相关的属性文件,通过iio_device_add_channel_sysfs、iio_device_register_sysfs接口调用实现);
- 若iio device支持trigger-buffer、trigger-event,则通过iio_device_register_trigger_consumer完成current_trigger属性的创建,以便进行iio device与trigger的绑定与解绑操作;
- 调用device_add完成iio device对应struct device类型变量的注册,因为上面所需创建的设备属性均是指struct device类型变量;
iio device register主要就完成以上6步的操作,也就是我们上面一中所说的大部分内容,而iio device的注销则主要完成相反的功能。
三、IIO DEVICE的驱动开发流程
在 Linux 中,IIO(Industrial I/O)子系统用于处理传感器、数据采集设备等硬件的输入输出操作。IIO 设备驱动的开发涉及创建驱动程序,以便正确地与硬件进行交互,提供数据采集、触发、缓冲区管理等功能。IIO 驱动开发流程包括设备的初始化、数据采集、错误处理、与用户空间的交互等多个环节。
下面是 IIO 设备驱动开发的基本流程:
1. 了解硬件特性
首先,必须理解设备的硬件特性,包括:
- 数据采集方式(单次采样、连续采样、轮询、触发等)。
- 支持的传感器类型(温度、压力、加速度、磁场等)。
- 设备的接口(SPI、I2C、GPIO、并行总线等)。
- 设备支持的功能(例如缓冲区、触发机制、数据格式等)。
2. 创建 IIO 设备结构 (iio_dev)
在 IIO 驱动中,最重要的数据结构是 iio_dev,它表示一个 IIO 设备。你需要定义并初始化一个 iio_dev 结构体,用于描述你的设备。
定义 iio_dev 结构体
#include <linux/iio/iio.h>
#include <linux/module.h>
#include <linux/platform_device.h>
static int my_iio_device_probe(struct platform_device *pdev)
{
struct iio_dev *indio_dev;
int ret;
indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*indio_dev));
if (!indio_dev)
return -ENOMEM;
indio_dev->dev.parent = &pdev->dev;
indio_dev->name = "my_iio_device"; // 设置设备名称
indio_dev->modes = INDIO_DIRECT_MODE; // 设备工作模式
indio_dev->info = &my_iio_info; // 设备的操作函数
ret = iio_device_register(indio_dev);
if (ret)
return ret;
platform_set_drvdata(pdev, indio_dev);
return 0;
}
static int my_iio_device_remove(struct platform_device *pdev)
{
struct iio_dev *indio_dev = platform_get_drvdata(pdev);
iio_device_unregister(indio_dev);
return 0;
}
static const struct of_device_id my_iio_device_dt_ids[] = {
{ .compatible = "my_company,my_device", },
{ }
};
MODULE_DEVICE_TABLE(of, my_iio_device_dt_ids);
static struct platform_driver my_iio_driver = {
.probe = my_iio_device_probe,
.remove = my_iio_device_remove,
.driver = {
.name = "my_iio_driver",
.of_match_table = my_iio_device_dt_ids,
},
};
module_platform_driver(my_iio_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("IIO Device Driver Example");
3. 定义 IIO 信息结构体 (iio_info)
iio_info 结构体包含与 IIO 设备交互的各种操作函数。例如,获取传感器数据、配置设备、控制缓冲区等。
定义操作函数
static const struct iio_info my_iio_info = {
.read_raw = my_iio_read_raw, // 读取原始数据
.write_raw = my_iio_write_raw, // 写入原始数据
.driver_module = THIS_MODULE,
};
实现读写操作函数
read_raw 和 write_raw 函数是用来处理传感器数据的核心函数,通常这些函数与硬件的 I/O 操作直接相关。
static ssize_t my_iio_read_raw(struct iio_dev *indio_dev,
const struct iio_chan_spec *chan,
int *val, int *val2, long mask)
{
switch (mask) {
case IIO_CHAN_INFO_RAW:
// 从硬件中读取原始数据
*val = read_sensor_data();
return IIO_VAL_INT;
default:
return -EINVAL;
}
}
static ssize_t my_iio_write_raw(struct iio_dev *indio_dev,
const struct iio_chan_spec *chan,
int val, int val2, long mask)
{
switch (mask) {
case IIO_CHAN_INFO_RAW:
// 写入数据到硬件
write_sensor_data(val);
return IIO_VAL_INT;
default:
return -EINVAL;
}
}
4. 配置 IIO 设备的通道
每个 IIO 设备可能有多个数据通道。通过 iio_chan_spec 结构来描述这些通道。每个通道通常对应一个传感器的某个数据输出(如温度、加速度、磁场等)。
static const struct iio_chan_spec my_iio_channels[] = {
{
.type = IIO_TEMP,
.indexed = 0,
.channel = 0,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), // 指定读取 raw 数据
},
{ }
};
5. 处理缓冲区
IIO 子系统支持缓冲区机制,可以将数据批量采集并存储到内存中,然后供用户空间访问。你可以选择启用缓冲区,以便在硬件驱动和用户空间之间高效地传输数据。
static int my_iio_device_buffer_postenable(struct iio_dev *indio_dev)
{
/* 初始化硬件或开始数据采集 */
start_data_collection();
return 0;
}
static int my_iio_device_buffer_predisable(struct iio_dev *indio_dev)
{
/* 停止数据采集 */
stop_data_collection();
return 0;
}
static const struct iio_buffer_setup_ops my_iio_buffer_ops = {
.buffer_postenable = my_iio_device_buffer_postenable,
.buffer_predisable = my_iio_device_buffer_predisable,
};
static const struct iio_info my_iio_info_with_buffer = {
.driver_module = THIS_MODULE,
.buffer_setup_ops = &my_iio_buffer_ops,
};
6. 注册 IIO 设备
设备初始化完成后,需要调用 iio_device_register() 来注册 IIO 设备。这个过程将设备与 IIO 子系统连接,开始接收数据并与用户空间交互。
ret = iio_device_register(indio_dev);
if (ret) {
pr_err("IIO device registration failed\n");
return ret;
}
7. 与用户空间交互
IIO 驱动通常会通过 sysfs、ioctl 或直接的设备文件接口与用户空间进行交互。通过 sysfs,用户可以查询设备状态、控制缓冲区等。
static ssize_t iio_device_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_device_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_device_enable_show, iio_device_enable_store);
8. 清理和卸载
当设备被卸载时,你需要正确地移除 IIO 设备、释放资源并停止缓冲区。
static void my_iio_device_remove(struct platform_device *pdev)
{
struct iio_dev *indio_dev = platform_get_drvdata(pdev);
iio_device_unregister(indio_dev);
}
9.总结
IIO 驱动的开发流程涉及几个关键步骤:
- 理解硬件特性:包括传感器类型、接口、数据采集方式等。
- 初始化 IIO 设备结构:定义
iio_dev结构,并与硬件交互。 - 定义操作函数:如
read_raw和write_raw,以及缓冲区操作函数。 - 配置设备的通道:定义和注册 IIO 设备的各个通道。
- 缓冲区管理:根据需要启用数据缓冲区。
- 与用户空间交互:通过 sysfs 或设备文件与用户空间通信。
- 卸载清理:设备移除时进行资源释放。
6866

被折叠的 条评论
为什么被折叠?



