2. Linux IIO子系统概述

Linux IIO(Industrial I/O,工业输入/输出)子系统是内核为传感器、数据转换器(ADC/DAC)、编码器等设备提供的统一驱动框架。它解决了传统字符设备驱动在采样速率、触发模式、缓冲管理等方面的碎片化问题,成为嵌入式Linux中模拟量采集的标准抽象层。

2.1 IIO框架设计思想

IIO的设计遵循“设备-通道-触发-缓冲”四层分离原则,核心思想包括:

  • 设备抽象:每个物理ADC/DAC芯片注册为一个iio_dev设备,屏蔽硬件寄存器细节。
  • 通道化:将多路输入/输出抽象为独立通道,每个通道可独立配置类型、量程、偏移。
  • 触发驱动:采样动作由触发源(定时器、GPIO、硬件中断)驱动,与数据读取解耦。
  • 缓冲传输:高速连续采样通过内核环形缓冲区传递,避免每次读取都陷入系统调用。
  • 用户空间统一接口:通过/sys/bus/iio/devices/iio:deviceX/暴露通道属性,通过字符设备节点/dev/iio:deviceX提供缓冲数据流。
设计目标:让驱动开发者只需关注硬件寄存器操作,而采样调度、数据同步、用户空间交互由IIO框架统一管理。
IIO框架分层架构 用户空间 sysfs属性节点 /dev/iio:deviceX 字符设备 IIO核心层(drivers/iio/industrialio-core.c) iio_dev 注册/注销 | 通道管理 | 触发调度 | 缓冲管理 sysfs属性生成 | 字符设备操作 | 事件通知 IIO设备驱动层 iio_info 回调实现 | 寄存器读写 | 触发使能 | 缓冲数据推送 硬件设备(ADC/DAC/传感器)

2.2 核心数据结构

IIO框架围绕三个核心数据结构构建:struct iio_dev、struct iio_chan_spec、struct iio_info。理解它们是编写IIO驱动的基础。

2.2.1 struct iio_dev — 设备实例

每个IIO设备对应一个iio_dev实例,由iio_device_alloc()分配,iio_device_register()注册。关键成员如下:

成员 类型 说明
id int 设备编号,自动分配
dev struct device 内核设备对象,用于sysfs和设备模型
channels struct iio_chan_spec * 通道描述符数组,定义设备的所有输入/输出通道
num_channels unsigned int 通道数量
info const struct iio_info * 驱动回调函数集合
modes unsigned long 支持的操作模式(单次/触发/缓冲)
buffer struct iio_buffer * 数据缓冲区,用于高速连续采样
trig struct iio_trigger * 当前绑定的触发源

2.2.2 struct iio_chan_spec — 通道描述符

通道描述符定义了ADC的每个输入引脚或DAC的每个输出通道的属性。对于ADC驱动,最常用的配置如下:

struct iio_chan_spec adc_channels[] = {
    {
        .type           = IIO_VOLTAGE,       // 通道类型:电压
        .indexed        = 1,                  // 使用索引号区分通道
        .channel        = 0,                  // 通道编号0
        .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |    // 暴露原始值
                              BIT(IIO_CHAN_INFO_SCALE) |  // 暴露缩放系数
                              BIT(IIO_CHAN_INFO_OFFSET),  // 暴露偏移量
        .scan_index     = 0,                  // 扫描索引,用于缓冲模式
        .scan_type      = {
            .sign        = 'u',               // 无符号
            .realbits    = 12,                // 有效位数
            .storagebits = 16,                // 存储位数
            .shift       = 0,                 // 右移位数
        },
    },
    {
        .type           = IIO_VOLTAGE,
        .indexed        = 1,
        .channel        = 1,                  // 通道编号1
        .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
                              BIT(IIO_CHAN_INFO_SCALE),
        .scan_index     = 1,
        .scan_type      = {
            .sign        = 'u',
            .realbits    = 12,
            .storagebits = 16,
        },
    },
};
关键字段说明:info_mask_separate决定哪些属性在sysfs中按通道独立显示;scan_index决定缓冲模式下数据在采样帧中的排列顺序。

2.2.3 struct iio_info — 驱动回调

驱动通过iio_info向框架注册硬件操作函数:

回调 原型 用途
read_raw int (*read_raw)(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int *val, int *val2, long mask) 读取通道原始值、缩放系数或偏移量
write_raw int (*write_raw)(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int val, int val2, long mask) 写入通道属性(如DAC输出值)
set_trigger int (*set_trigger)(struct iio_dev *indio_dev, struct iio_trigger *trig) 绑定/解绑触发源
update_scan_mode int (*update_scan_mode)(struct iio_dev *indio_dev, const unsigned long *scan_mask) 使能/禁用缓冲模式下的通道扫描
IIO核心数据结构关系 struct iio_dev id, dev, modes channels → iio_chan_spec[] info → iio_info struct iio_chan_spec type, channel, indexed info_mask_separate scan_index, scan_type struct iio_info read_raw, write_raw set_trigger update_scan_mode channels 指针 info 指针 驱动只需填充 iio_chan_spec 和 iio_info,框架自动生成 sysfs 和字符设备

2.3 用户空间接口

IIO子系统向用户空间提供两种访问方式:sysfs属性节点用于单次读取和配置,字符设备节点用于高速缓冲数据流。

2.3.1 sysfs接口

设备注册后,在/sys/bus/iio/devices/iio:deviceX/下生成以下典型属性:

属性文件 访问 说明
name 只读 设备名称,与设备树中compatible匹配
in_voltage0_raw 只读 通道0的ADC原始采样值(如0~4095)
in_voltage0_scale 只读 原始值到实际电压的缩放系数(如0.001)
in_voltage0_offset 只读 原始值偏移量(通常为0)
in_voltage1_raw 只读 通道1的ADC原始采样值
buffer/enable 读写 使能/禁用缓冲模式(1使能,0禁用)
buffer/length 读写 缓冲区大小(字节数)
scan_elements/in_voltage0_en 读写 使能通道0参与缓冲扫描
trigger/current_trigger 读写 查看/设置当前触发源

用户空间读取单次采样示例:

# 读取通道0原始值
cat /sys/bus/iio/devices/iio:device0/in_voltage0_raw
2048

# 读取缩放系数
cat /sys/bus/iio/devices/iio:device0/in_voltage0_scale
0.001000

# 计算实际电压:2048 × 0.001 = 2.048 V

2.3.2 字符设备接口

当需要高速连续采样时,使用/dev/iio:deviceX字符设备。典型操作流程如下:

  1. 通过sysfs使能需要采样的通道(scan_elements/in_voltage0_en写1)。
  2. 设置触发源(trigger/current_trigger)。
  3. 使能缓冲(buffer/enable写1)。
  4. 打开/dev/iio:device0,使用read()读取数据帧。
  5. 数据帧格式由scan_type定义,每个通道按scan_index顺序排列。
注意事项:缓冲模式下,read()返回的是多个采样帧的连续数据,每个帧包含所有使能通道的采样值。解析时需按scan_type.storagebits对齐字节边界。
用户空间访问IIO设备流程 单次采样(sysfs) cat in_voltage0_raw 内核调用 read_raw 回调 返回原始值 + 缩放系数 用户空间计算实际电压 V = raw × scale + offset 缓冲采样(字符设备) 使能通道 → 设置触发 → 使能缓冲 内核缓冲填充 触发源驱动采样 → 数据写入环形缓冲 用户空间 read() 数据帧 按 scan_type 解析二进制数据

2.4 触发与缓冲机制

IIO的触发系统支持多种触发源:

  • 定时器触发:iio-trig-hrtimer提供高精度定时触发,适合固定频率采样。
  • GPIO触发:iio-trig-sysfs允许用户通过sysfs手动触发一次采样。
  • 硬件触发:ADC芯片的EOC(转换结束)引脚直接触发,延迟最低。

缓冲模式下,数据流路径为:

硬件ADC → 触发信号 → 驱动读取数据 → iio_push_to_buffers() → 内核环形缓冲 → 用户空间 read()
性能对比:单次采样每次读取约50~100μs(含sysfs开销),缓冲模式可达1~10Msps(取决于ADC硬件和总线速度)。

2.5 本章小结