Linux IIO 子系统详解 文章封面

Linux IIO 子系统详解

一、什么是 IIO 子系统

IIO(Industrial I/O) 是 Linux 内核专为传感器类设备设计的通用驱动框架,2009 年由 Jonathan Cameron 主导引入,现为内核主线的标准子系统(drivers/iio/)。

适用场景

IIO 专门处理:

  • ADC / DAC(模拟转换)
  • 环境传感器:光照、接近、温湿度、气压
  • 运动传感器:加速度计、陀螺仪、磁力计
  • 电压 / 电流监控

与 miscdevice 方案对比

维度miscdeviceIIO 子系统
用户接口/dev/xxx,自定义 ioctl/sys/bus/iio/devices/iio:deviceX/,标准 sysfs 属性
工具生态无通用工具iio_info、iio_readdev、libiio 直接可用
通道抽象手动声明式,内核自动生成属性文件
缓冲/触发需自己实现内置 kfifo 缓冲 + 触发机制
代码量较多样板代码驱动只需关注硬件读取逻辑
适用性任意字符设备传感器/ADC 类设备

结论:做传感器驱动首选 IIO;做控制类外设(如 LED、蜂鸣器)用 miscdevice/platform 更直接。


二、架构概览

┌─────────────────────────────────────────┐
│            用户空间                       │
│  cat in_illuminance_raw                  │
│  iio_readdev /dev/iio:device0            │
└───────────────┬─────────────────────────┘
                │ sysfs / dev
┌───────────────▼─────────────────────────┐
│           IIO 核心层                      │
│  iio_dev  iio_chan_spec  iio_info        │
│  sysfs 属性自动生成                       │
│  触发器 / 缓冲管理(可选)               │
└───────────────┬─────────────────────────┘
                │ read_raw / write_raw
┌───────────────▼─────────────────────────┐
│           具体传感器驱动                  │
│  ap3216c.c  bmp280.c  mpu6050.c ...      │
└───────────────┬─────────────────────────┘
                │ I²C / SPI / 寄存器
┌───────────────▼─────────────────────────┐
│             硬件                          │
│  ALS / PS / Gyro / ADC ...               │
└─────────────────────────────────────────┘


三、核心数据结构

3.1 struct iio_dev — 设备描述符

一个传感器设备对应一个 iio_dev,由 devm_iio_device_alloc() 分配,同时在其尾部附加驱动私有数据。

struct iio_dev {
    const char          *name;         // sysfs name 属性
    const struct iio_info      *info;         // 驱动操作函数表
    const struct iio_chan_spec *channels;     // 通道数组
    int                  num_channels;  // 通道数量
    unsigned long        modes;         // INDIO_DIRECT_MODE 等
    /* ... 内核内部字段 ... */
};

关键 API:

/* 分配:sizeof(*priv) 追加在 iio_dev 后面 */
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*priv));
priv = iio_priv(indio_dev);          // 取私有数据指针

/* 注册(devm 版,设备移除时自动注销)*/
ret = devm_iio_device_register(&client->dev, indio_dev);

3.2 struct iio_chan_spec — 通道描述

每个物理测量量对应一个通道(channel),IIO 核心据此自动生成 sysfs 属性文件。

struct iio_chan_spec {
    enum iio_chan_type  type;              // IIO_LIGHT / IIO_ACCEL / ...
    int                channel;           // 通道编号(多通道时使用)
    int                channel2;          // 修饰符编号
    unsigned long      modified    : 1;   // 是否使用 channel2 修饰
    unsigned long      indexed     : 1;   // 是否使用 channel 编号
    long               info_mask_separate; // 该通道独有的 info 类型
    long               info_mask_shared_by_type; // 同类型通道共享的 info
};

通道类型(常用)

iio_chan_typesysfs 前缀典型场景
IIO_LIGHTin_illuminance环境光传感器
IIO_PROXIMITYin_proximity接近传感器
IIO_INTENSITYin_intensity光强(+修饰符区分 IR/可见光)
IIO_TEMPin_temp温度
IIO_ACCELin_accel加速度
IIO_ANGL_VELin_anglvel角速度(陀螺仪)
IIO_MAGNin_magn磁场
IIO_VOLTAGEin_voltage电压
IIO_PRESSUREin_pressure气压
IIO_HUMIDITYRELATIVEin_humidityrelative相对湿度

通道修饰符(常用)

iio_modifier含义
IIO_MOD_X/Y/Z三轴(加速度计、陀螺仪)
IIO_MOD_LIGHT_IRIR 光强
IIO_MOD_LIGHT_BOTH可见光 + IR 总光强

sysfs 属性命名规则

in_{type}[_{modifier}][_{index}]_{info}

举例:
  in_illuminance_raw          ← ALS (IIO_LIGHT)
  in_proximity_raw            ← PS  (IIO_PROXIMITY)
  in_intensity_ir_raw         ← IR  (IIO_INTENSITY + IIO_MOD_LIGHT_IR)
  in_accel_x_raw              ← 加速度 X 轴
  in_voltage3_raw             ← 第3路ADC
  in_temp_scale               ← 温度比例系数

3.3 struct iio_info — 驱动操作接口

struct iio_info {
    /* 读取测量值,最核心的回调 */
    int (*read_raw)(struct iio_dev *indio_dev,
                    struct iio_chan_spec const *chan,
                    int *val, int *val2, long mask);

    /* 写入配置(量程、采样率等),可选 */
    int (*write_raw)(struct iio_dev *indio_dev,
                     struct iio_chan_spec const *chan,
                     int val, int val2, long mask);

    struct module *driver_module;  // THIS_MODULE
};

read_raw 的 mask 参数

mask 值含义返回
IIO_CHAN_INFO_RAW原始 ADC 值*val = 整数,返回 IIO_VAL_INT
IIO_CHAN_INFO_SCALE量程系数*val / *val2,返回 IIO_VAL_FRACTIONAL
IIO_CHAN_INFO_OFFSET零偏*val = 整数,返回 IIO_VAL_INT
IIO_CHAN_INFO_SAMP_FREQ采样率*val = Hz,返回 IIO_VAL_INT

3.4 数据结构全景类图

下图展示 IIO 核心层各结构体之间的关系,以及它们如何与上层(用户空间)和下层(总线设备)衔接:

classDiagram
    class iio_dev {
        +char *name
        +unsigned long modes
        +struct device dev
        +iio_info *info
        +iio_chan_spec *channels
        +int num_channels
    }
    class iio_chan_spec {
        +iio_chan_type type
        +int modified
        +int channel2
        +long info_mask_separate
        +long info_mask_shared_by_type
    }
    class iio_info {
        +read_raw() int
        +write_raw() int
        +module *driver_module
    }
    class iio_buffer {
        <<可选 触发缓冲>>
        +int length
        +int bytes_per_datum
        +read()
    }
    class DriverPrivData {
        <<驱动私有数据 iio_priv>>
        +i2c_client *client
        +mutex lock
        +其他硬件状态
    }
    class i2c_client {
        <<总线设备>>
        +u16 addr
        +i2c_adapter *adapter
    }
    class SysfsAttr {
        <<用户空间 sysfs>>
        +in_xxx_raw
        +in_xxx_scale
        +in_xxx_offset
        +sampling_frequency
        +name
    }
    class IioCharDev {
        <<用户空间 devnode>>
        +/dev/iio:deviceX
        +read() 二进制流
    }

    iio_dev --> iio_info : .info 函数表
    iio_dev "1" --> "N" iio_chan_spec : .channels[] 通道声明
    iio_dev --> iio_buffer : .buffer 可选
    iio_dev --> DriverPrivData : iio_priv 内存紧跟
    DriverPrivData --> i2c_client : .client 总线设备
    iio_dev ..> SysfsAttr : IIO核心按channels自动生成
    iio_dev ..> IioCharDev : 启用buffer时注册字符设备
    iio_chan_spec ..> SysfsAttr : type+modifier决定属性名

关键关系说明:

关系说明
iio_dev → iio_info.info 指针,持有 read_raw/write_raw 函数表;IIO 核心通过它调用驱动
iio_dev → iio_chan_spec[].channels[] 数组声明物理测量通道;IIO 核心据此生成 sysfs 属性文件
iio_dev → DriverPrivDataiio_priv() 取到紧跟在 iio_dev 后面的私有数据块(devm_iio_device_alloc 的第二参数决定大小)
DriverPrivData → i2c_client驱动私有数据保存总线设备指针,用于实际的寄存器读写
iio_dev ⇢ SysfsAttr虚线:IIO 核心在注册时根据 iio_chan_spec.type 和 modifier 自动创建 in_{type}[_{mod}]_{info} 属性
iio_dev ⇢ IioCharDev虚线:仅当驱动调用 iio_triggered_buffer_setup() 时,IIO 核心才创建 /dev/iio:deviceX 字符设备

驱动开发者只需关注三件事:声明 iio_chan_spec[]、实现 iio_info.read_raw、在 probe 里调用 devm_iio_device_register,其余全由 IIO 核心自动完成。


四、IIO 驱动开发三步走

Step 1:声明通道数组

static const struct iio_chan_spec mysen_channels[] = {
    {
        .type               = IIO_TEMP,
        .info_mask_separate = BIT(IIO_CHAN_INFO_RAW)
                            | BIT(IIO_CHAN_INFO_SCALE),
    },
    {
        .type               = IIO_PRESSURE,
        .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
    },
};

Step 2:实现 read_raw

static int mysen_read_raw(struct iio_dev *indio_dev,
                          struct iio_chan_spec const *chan,
                          int *val, int *val2, long mask)
{
    struct mysen_data *data = iio_priv(indio_dev);

    switch (mask) {
    case IIO_CHAN_INFO_RAW:
        switch (chan-&gt;type) {
        case IIO_TEMP:
            *val = read_temperature(data);
            return IIO_VAL_INT;
        case IIO_PRESSURE:
            *val = read_pressure(data);
            return IIO_VAL_INT;
        default:
            return -EINVAL;
        }

    case IIO_CHAN_INFO_SCALE:
        *val  = 0;
        *val2 = 100;   /* 0.01 °C/LSB */
        return IIO_VAL_INT_PLUS_MICRO;

    default:
        return -EINVAL;
    }
}

static const struct iio_info mysen_info = {
    .read_raw      = mysen_read_raw,
    .driver_module = THIS_MODULE,
};

Step 3:probe 中分配、填充、注册

static int mysen_probe(struct i2c_client *client,
                       const struct i2c_device_id *id)
{
    struct iio_dev     *indio_dev;
    struct mysen_data  *data;
    int ret;

    /* 1. 分配(私有数据追加在 iio_dev 后面)*/
    indio_dev = devm_iio_device_alloc(&amp;client-&gt;dev, sizeof(*data));
    if (!indio_dev)
        return -ENOMEM;

    data = iio_priv(indio_dev);
    data-&gt;client = client;
    mutex_init(&amp;data-&gt;lock);
    i2c_set_clientdata(client, indio_dev);

    /* 2. 硬件初始化 */
    ret = mysen_hw_init(data);
    if (ret)
        return ret;

    /* 3. 填充描述符 */
    indio_dev-&gt;dev.parent  = &amp;client-&gt;dev;
    indio_dev-&gt;name        = "mysen";
    indio_dev-&gt;info        = &amp;mysen_info;
    indio_dev-&gt;modes       = INDIO_DIRECT_MODE;
    indio_dev-&gt;channels    = mysen_channels;
    indio_dev-&gt;num_channels = ARRAY_SIZE(mysen_channels);

    /* 4. 注册 */
    return devm_iio_device_register(&amp;client-&gt;dev, indio_dev);
}


五、用户空间访问方式

5.1 sysfs(最常用)

# 列出所有 IIO 设备
ls /sys/bus/iio/devices/

# 查看设备名
cat /sys/bus/iio/devices/iio:device0/name

# 读取原始值
cat /sys/bus/iio/devices/iio:device0/in_illuminance_raw
cat /sys/bus/iio/devices/iio:device0/in_proximity_raw
cat /sys/bus/iio/devices/iio:device0/in_intensity_ir_raw

# 读取实际值(需要 scale 和 offset)
# 实际值 = (raw + offset) * scale

5.2 字符设备(用于缓冲读取)

当启用 IIO 缓冲时,数据可通过 /dev/iio:deviceX 以二进制流读取,适合高速采样场景。

iio_readdev -s 512 -b 256 /dev/iio:device0

5.3 iio_info 工具

iio_info   # 列出所有设备及其属性(需 libiio 工具包)


六、devm_ 系列函数与资源管理

IIO 驱动几乎全程使用 devm_(device-managed)版本:

函数说明
devm_iio_device_alloc()分配 iio_dev,设备移除时自动释放
devm_iio_device_register()注册到 IIO 核心,设备移除时自动注销
devm_iio_triggered_buffer_setup()设置触发缓冲(高级功能)

devm_ 机制使得 remove() 回调通常只需关闭硬件(掉电),无需手动注销驱动框架资源:

static int mysen_remove(struct i2c_client *client)
{
    /* devm_ 自动完成 iio_device_unregister / kfree */
    hw_powerdown(client);  /* 仅需关闭硬件 */
    return 0;
}


七、常见问题

Q1:为什么 in_illuminance_raw 不出现?

  • 通道声明中 info_mask_separate 未包含 BIT(IIO_CHAN_INFO_RAW)
  • indio_dev->info 未正确赋值
  • devm_iio_device_register() 返回非零

Q2:iio_priv() 返回 NULL?

  • 分配时 sizeof(*priv) 传了 0
  • 或对同一 indio_dev 多次调用了 iio_priv()(不会,这个函数很简单,就是 indio_dev + 1 对齐后的指针)

Q3:sysfs 读取时偶发错误值(竞争问题)

  • read_raw 回调内忘记加 mutex_lock
  • I²C 读操作分两次读高低字节,存在寄存器更新竞争;应使用 i2c_transfer 一次读取完整字节对

八、参考资料

  • Documentation/iio/ — 内核官方 IIO 文档
  • drivers/iio/light/ — 光线/接近传感器驱动参考(bh1750.c、vcnl4000.c)
  • drivers/iio/imu/ — IMU 传感器参考(mpu6050.c)
  • Jonathan Cameron 的 IIO 入门演讲(FOSDEM 2012)

延伸阅读

掌握 IIO 框架后,可通过光照传感器和六轴 IMU 两个驱动验证抽象。

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部