时间轴

2026-02-15

init


IIO 子系统简介

IIO(Industrial Input/Output,工业输入/输出)是 Linux 内核中的一个子系统,专门用于处理与模数转换器(ADC)、数模转换器(DAC)以及其他工业传感器相关的设备。

IIO 子系统的开发始于 2009 年,随着嵌入式系统和物联网(IoT)技术的发展,越来越多的设备需要与模拟信号或数字信号进行交互。例如:

  1. 模数转换(ADC):将模拟信号(如温度、压力、光强度等)转换为数字信号。
  2. 数模转换(DAC):将数字信号转换为模拟信号(如控制电机的速度或音量)。
  3. 其他传感器:加速度计、陀螺仪、磁力计等。

这些设备的功能各不相同,但都需要与操作系统进行交互。如果没有一个统一的框架,每个设备都需要单独开发驱动程序,这会导致代码重复、维护困难以及性能问题。因此,Linux 社区引入了 IIO 子系统来解决这些问题。

IIO 的特点

  1. 统一的接口:IIO 提供了一个标准化的 API 和数据结构,使得不同类型的设备可以使用相同的接口进行交互。用户空间应用程序可以通过标准的文件系统接口(/sys/bus/iio/devices/)访问 IIO 设备,从而实现对传感器和转换器等硬件的统一管理和操作。
  2. 支持多种设备类型:IIO 子系统能够支持多种设备类型,包括 ADC(模数转换器)、DAC(数模转换器)、温度传感器、光传感器、压力传感器以及加速度计、陀螺仪。这种广泛的设备兼容性使得 IIO 成为处理工业和消费级传感器的理想选择。
  3. 事件和触发机制:IIO 支持基于事件的触发机制,例如当某个传感器的值超过阈值时触发中断。它还支持硬件触发和软件触发,适用于实时数据采集场景,能够满足对时间敏感的应用需求。

IIO 注册流程分析

首先找到 RK3568 设备树中 ADC 的设备节点,具体内容如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
//ADC 按键
adc_keys: adc-keys {
compatible = "adc-keys";
io-channels = <&saradc 0>;
io-channel-names = "buttons";
keyup-threshold-microvolt = <1800000>;
poll-interval = <100>;

vol-up-key {
label = "volume up";
linux,code = <KEY_VOLUMEUP>;
press-threshold-microvolt = <1750>;
};

vol-down-key {
label = "volume down";
linux,code = <KEY_VOLUMEDOWN>;
press-threshold-microvolt = <297500>;
};

menu-key {
label = "menu";
linux,code = <KEY_MENU>;
press-threshold-microvolt = <980000>;
};

back-key {
label = "back";
linux,code = <KEY_BACK>;
press-threshold-microvolt = <1305500>;
};
};

saradc: saradc@fe720000 {
compatible = "rockchip,rk3568-saradc", "rockchip,rk3399-saradc";
reg = <0x0 0xfe720000 0x0 0x100>;
interrupts = <GIC_SPI 93 IRQ_TYPE_LEVEL_HIGH>;
#io-channel-cells = <1>;
clocks = <&cru CLK_SARADC>, <&cru PCLK_SARADC>;
clock-names = "saradc", "apb_pclk";
resets = <&cru SRST_P_SARADC>;
reset-names = "saradc-apb";
status = "disabled";
};

saradc 指的是 Successive Approximation Register Analog-to-Digital Converter 逐次逼近型模数转换器
通过saradccompatible 匹配值找到对应的驱动文件为 drivers/iio/adc/rockchip_saradc.c

struct iio_dev

struct iio_dev 类型的变量,它用来描述 iio 设备,包含了设备的所有关键信息和资源,具体定义在 include/linux/iio/iio.h 文件中。通过这个结构体,IIO 子系统能够统一管理和操作各种工业 I/O 设备(如 ADC、DAC、传感器等)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
/**
* struct iio_dev - industrial I/O device
* @id: [INTERN] used to identify device internally
* @driver_module: [INTERN] used to make it harder to undercut users
* @modes: [DRIVER] operating modes supported by device
* @currentmode: [DRIVER] current operating mode
* @dev: [DRIVER] device structure, should be assigned a parent
* and owner
* @buffer: [DRIVER] any buffer present
* @scan_bytes: [INTERN] num bytes captured to be fed to buffer demux
* @mlock: [INTERN] lock used to prevent simultaneous device state
* changes
* @available_scan_masks: [DRIVER] optional array of allowed bitmasks
* @masklength: [INTERN] the length of the mask established from
* channels
* @active_scan_mask: [INTERN] union of all scan masks requested by buffers
* @scan_timestamp: [INTERN] set if any buffers have requested timestamp
* @scan_index_timestamp:[INTERN] cache of the index to the timestamp
* @trig: [INTERN] current device trigger (buffer modes)
* @trig_readonly: [INTERN] mark the current trigger immutable
* @pollfunc: [DRIVER] function run on trigger being received
* @pollfunc_event: [DRIVER] function run on events trigger being received
* @channels: [DRIVER] channel specification structure table
* @num_channels: [DRIVER] number of channels specified in @channels.
* @name: [DRIVER] name of the device.
* @label: [DRIVER] unique name to identify which device this is
* @info: [DRIVER] callbacks and constant info from driver
* @clock_id: [INTERN] timestamping clock posix identifier
* @info_exist_lock: [INTERN] lock to prevent use during removal
* @setup_ops: [DRIVER] callbacks to call before and after buffer
* enable/disable
* @chrdev: [INTERN] associated character device
* @groups: [INTERN] attribute groups
* @groupcounter: [INTERN] index of next attribute group
* @flags: [INTERN] file ops related flags including busy flag.
* @priv: [DRIVER] reference to driver's private information
* **MUST** be accessed **ONLY** via iio_priv() helper
*/
struct iio_dev {
int id; // 设备的唯一标识符
struct module *driver_module; // 指向驱动模块的指针

int modes; // 支持的工作模式(如直接模式、缓冲模式)
int currentmode; // 当前的工作模式
struct device dev; // 嵌套的 Linux 设备模型结构体

struct iio_buffer *buffer; // 数据缓冲区指针
int scan_bytes; // 扫描数据的字节数
struct mutex mlock; // 互斥锁,保护共享资源

const unsigned long *available_scan_masks; // 可用的扫描掩码
unsigned masklength; // 掩码长度
const unsigned long *active_scan_mask; // 当前激活的扫描掩码
bool scan_timestamp; // 是否在扫描数据中包含时间戳
unsigned scan_index_timestamp; // 时间戳在扫描数据中的索引
struct iio_trigger *trig; // 触发器指针
bool trig_readonly; // 触发器是否只读
struct iio_poll_func *pollfunc; // 轮询函数指针
struct iio_poll_func *pollfunc_event; // 事件轮询函数指针

struct iio_chan_spec const *channels; // 通道规格数组
int num_channels; // 通道数量

const char *name; // 设备名称
const char *label;
const struct iio_info *info; // IIO 设备的操作函数集
clockid_t clock_id; // 时钟 ID
struct mutex info_exist_lock; // 信息存在锁
const struct iio_buffer_setup_ops *setup_ops; // 缓冲区设置操作函数集
struct cdev chrdev; // 字符设备结构体
#define IIO_MAX_GROUPS 6
const struct attribute_group *groups[IIO_MAX_GROUPS + 1]; // 属性组数组
int groupcounter; // 属性组计数器

unsigned long flags; // 标志位,用于存储设备状态
void *priv;
};

可以将 struct iio_dev 看成一个基类,他是用来描述 iio 设备的,但是 ADC、DAC 设备都属于 iio,IIO 区分不同设备实际上是通过 probe 函数中的struct rockchip_saradc 结构体描述的,rockchip_saradc 结构体具体内容如下所示:

struct rockchip_saradc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
struct rockchip_saradc;

struct rockchip_saradc_data {
const struct iio_chan_spec *channels;
int num_channels;
unsigned long clk_rate;
void (*start)(struct rockchip_saradc *info, int chn);
int (*read)(struct rockchip_saradc *info);
void (*power_down)(struct rockchip_saradc *info);
};

struct rockchip_saradc {
void __iomem *regs; // 寄存器的虚拟地址映射,用于访问硬件寄存器
struct clk *pclk; // APB 总线时钟指针
struct clk *clk; // SARADC 转换器时钟指针
struct completion completion; // 完成量,用于同步操作(如等待转换完成)
struct regulator *vref; // 参考电压调节器指针
int uv_vref; // 参考电压的实际值(单位:微伏)
struct reset_control *reset; // 复位控制器指针
const struct rockchip_saradc_data *data; // 设备特定数据(如配置参数)
u16 last_val; // 上一次的 ADC 转换结果
const struct iio_chan_spec *last_chan;
struct notifier_block nb;
bool suspended; // 标志位,指示设备是否处于挂起状态
#ifdef CONFIG_ROCKCHIP_SARADC_TEST_CHN
bool test; // 定时器,用于测试通道
u32 chn; // 标志位,指示是否处于测试模式
spinlock_t lock; // 自旋锁,用于保护共享资源
struct workqueue_struct *wq;
struct delayed_work work;
#endif
};

rockchip_saradc_probe()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
// drivers/iio/adc/rockchip_saradc.c`
static int rockchip_saradc_probe(struct platform_device *pdev)
{
struct rockchip_saradc *info = NULL; // 定义 SARADC 设备私有数据结构指针
struct device_node *np = pdev->dev.of_node; // 获取设备树节点
struct iio_dev *indio_dev = NULL; // 定义 IIO 设备结构体指针
struct resource *mem; // 用于获取内存资源
const struct of_device_id *match; // 用于匹配设备树节点
int ret;
int irq;

// 检查设备树节点是否存在
if (!np)
return -ENODEV;

// 分配 IIO 设备结构体,并为其分配私有数据空间
indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*info));
if (!indio_dev) {
dev_err(&pdev->dev, "failed allocating iio device\n");
return -ENOMEM;
}
info = iio_priv(indio_dev); // 获取 IIO 设备的私有数据区域

// 匹配设备树中的设备 ID
match = of_match_device(rockchip_saradc_match, &pdev->dev);
if (!match) {
dev_err(&pdev->dev, "failed to match device\n");
return -ENODEV;
}

info->data = match->data; // 获取设备匹配的数据(如配置参数)

/* Sanity check for possible later IP variants with more channels */
if (info->data->num_channels > SARADC_MAX_CHANNELS) {
dev_err(&pdev->dev, "max channels exceeded");
return -EINVAL;
}

// 获取设备的寄存器地址资源
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
// 映射寄存器到虚拟地址空间
info->regs = devm_ioremap_resource(&pdev->dev, mem);
if (IS_ERR(info->regs))
return PTR_ERR(info->regs);

/*
* The reset should be an optional property, as it should work
* with old devicetrees as well
*/
/*
* 重置控制器是可选属性,为了兼容旧的设备树,允许不提供该属性
*/
info->reset = devm_reset_control_get_exclusive(&pdev->dev,
"saradc-apb");
if (IS_ERR(info->reset)) {// 获取复位控制器失败
ret = PTR_ERR(info->reset);
if (ret != -ENOENT)
return ret;

dev_dbg(&pdev->dev, "no reset control found\n");
info->reset = NULL;
}

// 初始化完成量,用于同步操作
init_completion(&info->completion);

// 获取中断号
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;

// 注册中断处理函数
ret = devm_request_irq(&pdev->dev, irq, rockchip_saradc_isr,
0, dev_name(&pdev->dev), info);
if (ret < 0) {
dev_err(&pdev->dev, "failed requesting irq %d\n", irq);
return ret;
}

// 获取 APB 总线时钟
info->pclk = devm_clk_get(&pdev->dev, "apb_pclk");
if (IS_ERR(info->pclk)) {
dev_err(&pdev->dev, "failed to get pclk\n");
return PTR_ERR(info->pclk);
}

// 获取 SARADC 转换器时钟
info->clk = devm_clk_get(&pdev->dev, "saradc");
if (IS_ERR(info->clk)) {
dev_err(&pdev->dev, "failed to get adc clock\n");
return PTR_ERR(info->clk);
}

// 获取参考电压调节器
info->vref = devm_regulator_get(&pdev->dev, "vref");
if (IS_ERR(info->vref)) {
dev_err(&pdev->dev, "failed to get regulator, %ld\n",
PTR_ERR(info->vref));
return PTR_ERR(info->vref);
}

// 如果存在复位控制器则执行复位操作
if (info->reset)
rockchip_saradc_reset_controller(info->reset);

/*
* Use a default value for the converter clock.
* This may become user-configurable in the future.
*/
/*
* 设置转换器时钟频率,默认使用设备数据中的时钟频率
* 未来可能会支持用户配置
*/
ret = clk_set_rate(info->clk, info->data->clk_rate);
if (ret < 0) {
dev_err(&pdev->dev, "failed to set adc clk rate, %d\n", ret);
return ret;
}

// 启用参考电压调节器
ret = regulator_enable(info->vref);
if (ret < 0) {
dev_err(&pdev->dev, "failed to enable vref regulator\n");
return ret;
}
// 注册一个清理动作,用于在设备移除时禁用调节器
ret = devm_add_action_or_reset(&pdev->dev,
rockchip_saradc_regulator_disable, info);
if (ret) {
dev_err(&pdev->dev, "failed to register devm action, %d\n",
ret);
return ret;
}

// 获取参考电压的实际值
ret = regulator_get_voltage(info->vref);
if (ret < 0) {
dev_err(&pdev->dev, "failed to get voltage\n");
return ret;
}
// 将参考电压的实际值赋值给info->uv_vref
info->uv_vref = ret;

// 启用 APB 总线时钟
ret = clk_prepare_enable(info->pclk);
if (ret < 0) {
dev_err(&pdev->dev, "failed to enable pclk\n");
return ret;
}
// 注册一个清理动作,用于在设备移除时禁用 APB 时钟
ret = devm_add_action_or_reset(&pdev->dev,
rockchip_saradc_pclk_disable, info);
if (ret) {
dev_err(&pdev->dev, "failed to register devm action, %d\n",
ret);
return ret;
}

// 启用 SARADC 转换器时钟
ret = clk_prepare_enable(info->clk);
if (ret < 0) {
dev_err(&pdev->dev, "failed to enable converter clock\n");
return ret;
}
// 注册一个清理动作,用于在设备移除时禁用转换器时钟
ret = devm_add_action_or_reset(&pdev->dev,
rockchip_saradc_clk_disable, info);
if (ret) {
dev_err(&pdev->dev, "failed to register devm action, %d\n",
ret);
return ret;
}
// 将 IIO 设备与平台设备相关联
platform_set_drvdata(pdev, indio_dev);

// 初始化 IIO 设备的基本信息
indio_dev->name = dev_name(&pdev->dev); // 设备名称
indio_dev->info = &rockchip_saradc_iio_info; // IIO 设备的操作函数集
indio_dev->modes = INDIO_DIRECT_MODE; // 设置工作模式为直接模式

indio_dev->channels = info->data->channels; // 通道列表
indio_dev->num_channels = info->data->num_channels; // 通道数量
ret = devm_iio_triggered_buffer_setup(&indio_dev->dev, indio_dev, NULL,
rockchip_saradc_trigger_handler,
NULL);
if (ret)
return ret;

info->nb.notifier_call = rockchip_saradc_volt_notify;
ret = regulator_register_notifier(info->vref, &info->nb);
if (ret)
return ret;

ret = devm_add_action_or_reset(&pdev->dev,
rockchip_saradc_regulator_unreg_notifier,
info);
if (ret)
return ret;

#ifdef CONFIG_ROCKCHIP_SARADC_TEST_CHN
info->wq = create_singlethread_workqueue("adc_wq"); // 初始化一个工作队列
INIT_DELAYED_WORK(&info->work, rockchip_saradc_test_work); // 初始化一个延时工作
spin_lock_init(&info->lock);
ret = sysfs_create_group(&pdev->dev.kobj, &rockchip_saradc_attr_group);
if (ret)
return ret;

// 注册一个清理动作,用于在设备移除时删除 sysfs 属性组
ret = devm_add_action_or_reset(&pdev->dev,
rockchip_saradc_remove_sysgroup, pdev);
if (ret) {
dev_err(&pdev->dev, "failed to register devm action, %d\n",
ret);
return ret;
}

ret = devm_add_action_or_reset(&pdev->dev,
rockchip_saradc_destroy_wq, info);
if (ret) {
dev_err(&pdev->dev, "failed to register destroy_wq, %d\n",
ret);
return ret;
}
#endif
// 注册 IIO 设备
return devm_iio_device_register(&pdev->dev, indio_dev);
}

可以看到 struct rockchip_saradc 结构体中是没有 iio 相关的描述的,那它和 iio 设备是怎样联系起来的呢?

在 probe 函数,首先,通过 devm_iio_device_alloc 函数分配了一块连续的内存,用于存储 iio_dev 和其私有数据(rockchip_saradc)。然后,使用 iio_priv 函数获取私有数据区域的指针,将两个 iio_devrockchip_saradc 类型的结构体变量连接在了一起,具体代码如下所示:

1
2
3
4
5
6
7
       // 分配 IIO 设备结构体,并为其分配私有数据空间
indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*info));
if (!indio_dev) {
dev_err(&pdev->dev, "failed allocating iio device\n");
return -ENOMEM;
}
info = iio_priv(indio_dev); // 获取 IIO 设备的私有数据区域

probe 源码,为了匹配多个SOC芯片通过 of_match_device 函数对设备树传递过来的值与 rockchip_saradc_match 进行匹配:

1
2
3
4
5
match = of_match_device(rockchip_saradc_match, &pdev->dev);
if (!match) {
dev_err(&pdev->dev, "failed to match device\n");
return -ENODEV;
}

rockchip_saradc_match结构体数组变量内容如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
static const struct of_device_id rockchip_saradc_match[] = {
{
.compatible = "rockchip,saradc",
.data = &saradc_data,
}, {
.compatible = "rockchip,rk3066-tsadc",
.data = &rk3066_tsadc_data,
}, {
.compatible = "rockchip,rk3399-saradc",
.data = &rk3399_saradc_data,
}, {
.compatible = "rockchip,rk3528-saradc",
.data = &rk3528_saradc_data,
}, {
.compatible = "rockchip,rk3562-saradc",
.data = &rk3562_saradc_data,
}, {
.compatible = "rockchip,rk3568-saradc",
.data = &rk3568_saradc_data,
}, {
.compatible = "rockchip,rk3588-saradc",
.data = &rk3588_saradc_data,
}, {
.compatible = "rockchip,rv1106-saradc",
.data = &rv1106_saradc_data,
},
{},
};
MODULE_DEVICE_TABLE(of, rockchip_saradc_match);

MODULE_DEVICE_TABLE() 是 Linux 内核里的一个宏,用来把设备匹配表导出到模块的 ELF 元数据里,让用户空间的自动加载机制(udev / modprobe)知道:

  • 这个驱动支持哪些设备
  • 当内核发现对应设备时,可以自动加载这个模块

rk3568 对应的数据指向了 rk3568_saradc_data,该变量的具体内容如下所示,这段代码的主要作用是定义了一个 SARADC 设备的通道信息和具体的设备参数

1
2
3
4
5
6
7
8
9
10
11
static const struct rockchip_saradc_data rk3568_saradc_data = {	
// 指向通道规格数组
.channels = rockchip_rk3568_saradc_iio_channels,
// 通道数量
.num_channels = ARRAY_SIZE(rockchip_rk3568_saradc_iio_channels),
// 转换器时钟频率:1 MHz
.clk_rate = 1000000,
.start = rockchip_saradc_start_v1,
.read = rockchip_saradc_read_v1,
.power_down = rockchip_saradc_power_down_v1,
};

rockchip_rk3568_saradc_iio_channels 表示通道规格数组,该数组的具体内容如下所示,可以看到 RK3568 有着 8 个 ADC 通道

1
2
3
4
5
6
7
8
9
10
static const struct iio_chan_spec rockchip_rk3568_saradc_iio_channels[] = {
SARADC_CHANNEL(0, "adc0", 10), // 定义 ADC 通道 0,名称为 "adc0"
SARADC_CHANNEL(1, "adc1", 10), // 定义 ADC 通道 1,名称为 "adc1"
SARADC_CHANNEL(2, "adc2", 10), // 定义 ADC 通道 2,名称为 "adc2"
SARADC_CHANNEL(3, "adc3", 10), // 定义 ADC 通道 3,名称为 "adc3"
SARADC_CHANNEL(4, "adc4", 10), // 定义 ADC 通道 4,名称为 "adc4"
SARADC_CHANNEL(5, "adc5", 10), // 定义 ADC 通道 5,名称为 "adc5"
SARADC_CHANNEL(6, "adc6", 10), // 定义 ADC 通道 6,名称为 "adc6"
SARADC_CHANNEL(7, "adc7", 10), // 定义 ADC 通道 7,名称为 "adc7"
};

用来描述通道规则的结构体为 struct iio_chan_spec,定义了通道的类型、编号、数据格式、支持的属性及事件等信息,用来区分不同类型的 ADC 或 DAC 设备(例如 ADC 按键、光环境传感器,加速度计等),该结构体的具体内容如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
/**
* struct iio_chan_spec - specification of a single channel
* @type: What type of measurement is the channel making.
* @channel: What number do we wish to assign the channel.
* @channel2: If there is a second number for a differential
* channel then this is it. If modified is set then the
* value here specifies the modifier.
* @address: Driver specific identifier.
* @scan_index: Monotonic index to give ordering in scans when read
* from a buffer.
* @scan_type: struct describing the scan type
* @scan_type.sign: 's' or 'u' to specify signed or unsigned
* @scan_type.realbits: Number of valid bits of data
* @scan_type.storagebits: Realbits + padding
* @scan_type.shift: Shift right by this before masking out
* realbits.
* @scan_type.repeat: Number of times real/storage bits repeats.
* When the repeat element is more than 1, then
* the type element in sysfs will show a repeat
* value. Otherwise, the number of repetitions
* is omitted.
* @scan_type.endianness: little or big endian
* @info_mask_separate: What information is to be exported that is specific to
* this channel.
* @info_mask_separate_available: What availability information is to be
* exported that is specific to this channel.
* @info_mask_shared_by_type: What information is to be exported that is shared
* by all channels of the same type.
* @info_mask_shared_by_type_available: What availability information is to be
* exported that is shared by all channels of the same
* type.
* @info_mask_shared_by_dir: What information is to be exported that is shared
* by all channels of the same direction.
* @info_mask_shared_by_dir_available: What availability information is to be
* exported that is shared by all channels of the same
* direction.
* @info_mask_shared_by_all: What information is to be exported that is shared
* by all channels.
* @info_mask_shared_by_all_available: What availability information is to be
* exported that is shared by all channels.
* @event_spec: Array of events which should be registered for this
* channel.
* @num_event_specs: Size of the event_spec array.
* @ext_info: Array of extended info attributes for this channel.
* The array is NULL terminated, the last element should
* have its name field set to NULL.
* @extend_name: Allows labeling of channel attributes with an
* informative name. Note this has no effect codes etc,
* unlike modifiers.
* @datasheet_name: A name used in in-kernel mapping of channels. It should
* correspond to the first name that the channel is referred
* to by in the datasheet (e.g. IND), or the nearest
* possible compound name (e.g. IND-INC).
* @modified: Does a modifier apply to this channel. What these are
* depends on the channel type. Modifier is set in
* channel2. Examples are IIO_MOD_X for axial sensors about
* the 'x' axis.
* @indexed: Specify the channel has a numerical index. If not,
* the channel index number will be suppressed for sysfs
* attributes but not for event codes.
* @output: Channel is output.
* @differential: Channel is differential.
*/
struct iio_chan_spec {
enum iio_chan_type type;
int channel;
int channel2;
unsigned long address;
int scan_index;
struct {
char sign;
u8 realbits;
u8 storagebits;
u8 shift;
u8 repeat;
enum iio_endian endianness;
} scan_type;
long info_mask_separate;
long info_mask_separate_available;
long info_mask_shared_by_type;
long info_mask_shared_by_type_available;
long info_mask_shared_by_dir;
long info_mask_shared_by_dir_available;
long info_mask_shared_by_all;
long info_mask_shared_by_all_available;
const struct iio_event_spec *event_spec;
unsigned int num_event_specs;
const struct iio_chan_spec_ext_info *ext_info;
const char *extend_name;
const char *datasheet_name;
unsigned modified:1;
unsigned indexed:1;
unsigned output:1;
unsigned differential:1;
}

而 RK3568 ADC 通道的定义使用的是 SARADC_CHANNEL 宏,该宏的具体内容如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#define SARADC_CHANNEL(_index, _id, _res) {			\
.type = IIO_VOLTAGE, \
.indexed = 1, \
.channel = _index, \
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
.datasheet_name = _id, \
.scan_index = _index, \
.scan_type = { \
.sign = 'u', \
.realbits = _res, \
.storagebits = 16, \
.endianness = IIO_CPU, \
}, \
}
  • .type = IIO_VOLTAGE 通道类型:电压测量
  • .indexed = 1 启用索引模式,表示使用 channel 字段作为索引
  • .channel = _index 主通道编号,由宏参数 _index 指定
  • .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) 单独支持的属性:原始值(RAW)
  • .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) 按类型共享的属性:缩放比例
  • .datasheet_name = _id 数据手册中的名称,由宏参数 _id 指定

devm_iio_device_register()

probe 函数最后返回 return devm_iio_device_register(&pdev->dev, indio_dev);, 而devm_iio_device_register定义如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* devm_iio_device_register - Resource-managed iio_device_register()
* @dev: Device to allocate iio_dev for
* @indio_dev: Device structure filled by the device driver
*
* Managed iio_device_register. The IIO device registered with this
* function is automatically unregistered on driver detach. This function
* calls iio_device_register() internally. Refer to that function for more
* information.
*
* RETURNS:
* 0 on success, negative error number on failure.
*/
#define devm_iio_device_register(dev, indio_dev) \
__devm_iio_device_register((dev), (indio_dev), THIS_MODULE)

__devm_iio_device_register()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// drivers/iio/industrialio-core.c
int __devm_iio_device_register(struct device *dev, struct iio_dev *indio_dev,
struct module *this_mod)
{
struct iio_dev **ptr; // 定义一个指针变量,用于存储分配的资源管理结构
int ret;

// 使用 devres_alloc 分配设备资源管理结构,并指定释放函数为 devm_iio_device_unreg
// sizeof(*ptr) 表示分配的大小为一个 iio_dev 指针的大小
ptr = devres_alloc(devm_iio_device_unreg, sizeof(*ptr), GFP_KERNEL);
if (!ptr)
return -ENOMEM;

// 将分配的资源管理指针指向传入的 iio_dev 结构
*ptr = indio_dev;

// 调用底层的设备注册函数 __iio_device_register 注册 iio_dev 设备
ret = __iio_device_register(indio_dev, this_mod);
if (!ret)
devres_add(dev, ptr);// 如果注册成功,将分配的资源管理结构添加到设备的资源管理链表中
else
devres_free(ptr);// 如果注册失败,释放之前分配的资源管理结构

// 返回注册的结果
return ret;
}
EXPORT_SYMBOL_GPL(__devm_iio_device_register);

__iio_device_register()

__devm_iio_device_register中调用__iio_device_register来注册IIO设备

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
// drivers/iio/industrialio-core.c
int __iio_device_register(struct iio_dev *indio_dev, struct module *this_mod)
{
int ret;

if (!indio_dev->info)
return -EINVAL;

// 将当前模块指针赋值给 indio_dev 的 driver_module 字段
indio_dev->driver_module = this_mod;
/* If the calling driver did not initialize of_node, do it here */
/* 如果调用驱动未初始化 of_node,则在此处进行初始化 */
// 如果设备的 of_node 为空,但其父设备的 of_node 存在,则继承父设备的 of_node
if (!indio_dev->dev.of_node && indio_dev->dev.parent)
indio_dev->dev.of_node = indio_dev->dev.parent->of_node;

indio_dev->label = of_get_property(indio_dev->dev.of_node, "label",
NULL);
// 检查扫描索引是否唯一
ret = iio_check_unique_scan_index(indio_dev);
if (ret < 0)
return ret;

/* configure elements for the chrdev */
/* 配置字符设备的相关元素 */
// 使用主设备号和次设备号生成设备号
indio_dev->dev.devt = MKDEV(MAJOR(iio_devt), indio_dev->id);

// 注册 debugfs 接口
iio_device_register_debugfs(indio_dev);

// 分配缓冲区相关的 sysfs 接口和掩码
ret = iio_buffer_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;
}

// 注册设备的 sysfs 接口
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;
}
// 注册事件集(event set)
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);

// 如果设备支持所有缓冲模式且未设置 setup_ops,则使用默认的 noop_ring_setup_ops
if ((indio_dev->modes & INDIO_ALL_BUFFER_MODES) &&
indio_dev->setup_ops == NULL)
indio_dev->setup_ops = &noop_ring_setup_ops;

// 初始化字符设备结构体,并绑定文件操作函数
cdev_init(&indio_dev->chrdev, &iio_buffer_fileops);

// 设置字符设备的模块所有者
indio_dev->chrdev.owner = this_mod;

// 添加字符设备和设备结构体到系统中
ret = cdev_device_add(&indio_dev->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); // 注销 sysfs 接口
error_buffer_free_sysfs:
iio_buffer_free_sysfs_and_mask(indio_dev); // 释放缓冲区相关的 sysfs 接口和掩码
error_unreg_debugfs:
iio_device_unregister_debugfs(indio_dev); // 注销 debugfs 接口
return ret;
}
EXPORT_SYMBOL(__iio_device_register);
  • 第 6 行:将当前模块指针赋值给 indio_dev->driver_module,记录设备所属的内核模块。
  • 第 10-11 行:通过检查 indio_dev->dev.of_nodeindio_dev->dev.parent->of_node 初始化设备树节点(of_node),继承父设备的设备树节点。
  • 第 14-16 行:通过 iio_check_unique_scan_index 函数检查通道扫描索引是否唯一,确保无重复索引。
  • 第 20 行:通过 MKDEV 函数生成设备号,为主设备和次设备分配唯一标识符。
  • 第 23 行:通过 iio_device_register_debugfs 函数注册 debugfs 接口,用于调试和监控设备状态
  • 第 31 行:通过 iio_buffer_alloc_sysfs_and_mask 函数分配缓冲区相关的 sysfs 接口和掩码,但跳转之后发现该函数为空,所以该部分代码并没有实际意义。
  • 第 39 行:通过 iio_device_register_sysfs 函数注册设备的 sysfs 接口,暴露设备属性给用户空间。
  • 第 47 行:通过 iio_device_register_eventset 函数注册事件集,管理设备触发的事件。
  • 第 55-56 行:通过 iio_device_register_trigger_consumer 函数注册触发消费者,支持设备响应外部触发信号。
  • 第 59-61 行:通过设置 indio_dev->setup_opsnoop_ring_setup_ops 提供默认的缓冲区操作函数。
  • 第 64 行:通过 cdev_init 函数初始化字符设备结构体,并绑定文件操作函数。
  • 第 67 行:通过设置 indio_dev->chrdev.ownerthis_mod,确保模块卸载时释放资源。
  • 第 70 行:通过 cdev_device_add 函数将字符设备和设备结构体添加到系统中。

该函数的重点在第 47 行的 iio_device_register_eventset 函数,它用来注册 iio 的事件,它定义在 drivers/iio/industrialio-event.c 文件中,该函数的具体内容如下所示

iio_device_register_eventset()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
int iio_device_register_eventset(struct iio_dev *indio_dev)
{
struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev);
struct iio_event_interface *ev_int;
struct iio_dev_attr *p; // 用于遍历设备属性列表的指针
int ret = 0, attrcount_orig = 0, attrcount, attrn; // 定义返回值、原始属性计数、总属性计数和当前属性索引
struct attribute **attr; // 指向属性数组的指针

// 如果设备没有事件属性且没有动态事件,则直接返回
if (!(indio_dev->info->event_attrs ||
iio_check_for_dynamic_events(indio_dev)))
return 0;

// 分配内存以存储事件接口结构
ev_int = kzalloc(sizeof(struct iio_event_interface), GFP_KERNEL);
if (ev_int == NULL)
return -ENOMEM;

iio_dev_opaque->event_interface = ev_int;

// 初始化事件接口的设备属性链表头
INIT_LIST_HEAD(&ev_int->dev_attr_list);

// 设置事件接口的中断处理函数
iio_setup_ev_int(ev_int);

// 如果设备有预定义的事件属性,计算其数量
if (indio_dev->info->event_attrs != NULL) {
attr = indio_dev->info->event_attrs->attrs;
while (*attr++ != NULL) // 遍历属性数组,统计原始属性数量
attrcount_orig++;
}
// 初始化总属性计数为原始属性数量
attrcount = attrcount_orig;
// 如果设备有通道,尝试添加动态事件配置属性
if (indio_dev->channels) {
ret = __iio_add_event_config_attrs(indio_dev); // 添加动态事件属性
if (ret < 0) // 如果添加失败,跳转到错误处理
goto error_free_setup_event_lines;
attrcount += ret; // 更新总属性计数
}

ev_int->group.name = iio_event_group_name;
// 分配内存以存储事件组的属性数组
ev_int->group.attrs = kcalloc(attrcount + 1,
sizeof(ev_int->group.attrs[0]),
GFP_KERNEL);
// 如果设备有预定义的事件属性,将其复制到事件组的属性数组中
if (ev_int->group.attrs == NULL) {
ret = -ENOMEM;
goto error_free_setup_event_lines;
}
if (indio_dev->info->event_attrs)
memcpy(ev_int->group.attrs,
indio_dev->info->event_attrs->attrs,
sizeof(ev_int->group.attrs[0]) * attrcount_orig);
// 设置当前属性索引为原始属性数量
attrn = attrcount_orig;
/* Add all elements from the list. */
// 遍历设备属性链表,将所有动态属性添加到事件组的属性数组中
list_for_each_entry(p, &ev_int->dev_attr_list, l)
ev_int->group.attrs[attrn++] = &p->dev_attr.attr;
// 将事件组添加到设备的属性组列表中
indio_dev->groups[indio_dev->groupcounter++] = &ev_int->group;

return 0;

error_free_setup_event_lines:
// 错误处理:释放设备属性链表
iio_free_chan_devattr_list(&ev_int->dev_attr_list);
// 释放事件接口的内存
kfree(ev_int);
// 置空指针
iio_dev_opaque->event_interface = NULL;
return ret;
}

在 IIO 子系统中,常见事件包括阈值事件、数据就绪事件、状态变化事件等等,在 iio 子系统中用来描述对应事件的结 构体为 struct iio_event_interface,该结构体的具体内容如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
* struct iio_event_interface - chrdev interface for an event line
* @wait: wait queue to allow blocking reads of events
* @det_events: list of detected events
* @dev_attr_list: list of event interface sysfs attribute
* @flags: file operations related flags including busy flag.
* @group: event interface sysfs attribute group
* @read_lock: lock to protect kfifo read operations
*/
struct iio_event_interface {
// 等待队列头,用于事件通知机制。用户空间或内核线程可以通过该队列等待事件的发生
wait_queue_head_t wait;

// 定义一个固定大小的 FIFO 队列(大小为 16),用于存储检测到的事件数据。
// 每个事件数据是一个 `struct iio_event_data` 类型的结构体。
DECLARE_KFIFO(det_events, struct iio_event_data, 16);

// 设备属性链表头,用于管理与事件相关的设备属性(如阈值、配置等)
struct list_head dev_attr_list;

// 标志位,用于记录事件接口的状态或特性。例如,是否启用某些功能
unsigned long flags;

// 属性组,用于将事件相关的属性(如阈值、配置等)组织在一起,并暴露给 sysfs
struct attribute_group group;

// 互斥锁,用于保护对事件数据的并发访问,确保在多线程环境下的读取安全。
struct mutex read_lock;
};

根据该结构体变量的成员可以推断出,IIO 子系统利用 FIFO 队列缓存检测到的事件数据,等待队列实现事件通知机制以唤醒监听者,通过链表动态维护事件相关属性并通过 sysfs 暴露给用户空间以便配置和监控,互斥锁确保多线程环境下的并发访问安全,标志位记录事件接口的状态或特性,从而高效完成事件的检测、配置、存储、通知和处理。

IIO 设备驱动分析

IIO 文件操作集函数分析

__iio_device_register 函数中调用cdev_init(&indio_dev->chrdev, &iio_buffer_fileops);对字符设备进行初始化。

1
2
3
4
5
6
7
8
9
10
static const struct file_operations iio_buffer_fileops = {
.read = iio_buffer_read_outer_addr,
.release = iio_chrdev_release,
.open = iio_chrdev_open,
.poll = iio_buffer_poll_addr,
.owner = THIS_MODULE,
.llseek = noop_llseek,
.unlocked_ioctl = iio_ioctl,
.compat_ioctl = compat_ptr_ioctl,
};

iio_ioctl 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/* Somewhat of a cross file organization violation - ioctls here are actually
* event related */
static long iio_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
struct iio_dev *indio_dev = filp->private_data; // 从文件结构中获取 IIO 设备实例
int __user *ip = (int __user *)arg; // 将用户空间传入的参数转换为用户空间指针
int fd; // 用于存储事件文件描述符

// 如果设备的信息结构未初始化,返回错误码 -ENODEV(无设备)
if (!indio_dev->info)
return -ENODEV;

// 判断是否是获取事件文件描述符的 IOCTL 命令
if (cmd == IIO_GET_EVENT_FD_IOCTL) {
fd = iio_event_getfd(indio_dev); // 调用函数获取事件文件描述符
if (fd < 0) // 如果获取失败,直接返回错误码
return fd;
// 将获取到的文件描述符复制到用户空间
if (copy_to_user(ip, &fd, sizeof(fd)))
return -EFAULT;
return 0;
}
// 如果命令不匹配,返回错误码 -EINVAL(无效参数)
return -EINVAL;
}

在 ioctl 函数中只有一个命令,那就是 IIO_GET_EVENT_FD_IOCTL,然后调用了iio_event_getfd 函数获取了事件文件描述符,并通过 copy_to_user 函数将获取到的文件描述符复制到用户空间,iio_event_getfd 函数的具体内容如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
int iio_event_getfd(struct iio_dev *indio_dev)
{
struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev);
// 获取设备的事件接口
struct iio_event_interface *ev_int = iio_dev_opaque->event_interface;
// 用于存储返回的文件描述符
int fd;

// 如果事件接口未初始化,返回 -ENODEV(无设备)
if (ev_int == NULL)
return -ENODEV;

// 尝试获取互斥锁,确保对设备操作的互斥访问
fd = mutex_lock_interruptible(&indio_dev->mlock);
if (fd)
return fd;

// 检查事件接口是否已经被占用(通过标志位 IIO_BUSY_BIT_POS 判断)
if (test_and_set_bit(IIO_BUSY_BIT_POS, &ev_int->flags)) {
fd = -EBUSY;
goto unlock;
}

// 增加设备的引用计数,防止设备在使用过程中被释放
iio_device_get(indio_dev);

// 创建匿名 inode 文件并返回文件描述符
fd = anon_inode_getfd("iio:event", &iio_event_chrdev_fileops,
indio_dev, O_RDONLY | O_CLOEXEC);
if (fd < 0) {
clear_bit(IIO_BUSY_BIT_POS, &ev_int->flags); // 清除占用标志位
iio_device_put(indio_dev); // 减少设备引用计数
} else {
kfifo_reset_out(&ev_int->det_events); // 如果成功,重置 FIFO 队列的输出指针,准备接收新事件
}

unlock:
mutex_unlock(&indio_dev->mlock); // 释放互斥锁
return fd;
}

该函数的返回值为文件描述符 fd,是通过 fd = anon_inode_getfd("iio:event", &iio_event_chrdev_fileops,indio_dev, O_RDONLY | O_CLOEXEC); 返回的,anon_inode_getfd 函数的作用是创建一个匿名的文件描述符 fd,匿名文件描述符它是一种特殊的文件描述符,它并不与实际的文件系统路径或设备节点关联,而是通过内核动态创建的一种虚拟文件。

匿名的文件描述符 fd 也有着他自己的文件操作集,所以我们可以通过 ioctl 创建虚拟文件描述符,从而为用户提供额外的系统调用接口,与内核中的某些功能或数据进行交互,而无需依赖传统的文件系统。

匿名文件描述符 fd 对应的文件操作集为 anon_inode_getfd 函数的第二个参数 iio_event_chrdev_fileops,对应的具体内容如下所示:

1
2
3
4
5
6
7
static const struct file_operations iio_event_chrdev_fileops = {
.read = iio_event_chrdev_read, // 处理 read() 系统调用,读取事件数据
.poll = iio_event_poll, // 处理 poll() 或 select() 调用,监听事件是否可用
.release = iio_event_chrdev_release, // 处理 close() 调用,释放资源
.owner = THIS_MODULE, // 指定模块所有者,确保模块安全卸载
.llseek = noop_llseek, // 不支持文件偏移操作(lseek 无效)
};

iio_event_chrdev_read()

对它的读函数 iio_event_chrdev_read 进行分析,该函数的具体内容如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
static ssize_t iio_event_chrdev_read(struct file *filep,
char __user *buf,
size_t count,
loff_t *f_ps)
{
struct iio_dev *indio_dev = filep->private_data; // 获取 IIO 设备实例
struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev);
struct iio_event_interface *ev_int = iio_dev_opaque->event_interface; // 获取事件接口
unsigned int copied; // 记录实际复制到用户空间的数据量
int ret;

if (!indio_dev->info) // 检查设备是否已初始化
return -ENODEV;

if (count < sizeof(struct iio_event_data)) // 确保缓冲区足够大以容纳事件数据
return -EINVAL;

do {
if (kfifo_is_empty(&ev_int->det_events)) { // 检查事件队列是否为空
if (filep->f_flags & O_NONBLOCK) // 如果是非阻塞模式,直接返回 -EAGAIN
return -EAGAIN;

// 阻塞等待事件队列中有数据或设备被移除
ret = wait_event_interruptible(ev_int->wait,
!kfifo_is_empty(&ev_int->det_events) ||
indio_dev->info == NULL);
if (ret) // 如果等待被中断,返回错误码
return ret;
if (indio_dev->info == NULL) // 如果设备被移除,返回 -ENODEV
return -ENODEV;
}

// 加锁保护对事件队列的访问
if (mutex_lock_interruptible(&ev_int->read_lock))
return -ERESTARTSYS;
// 将事件数据从内核 FIFO 队列复制到用户空间
ret = kfifo_to_user(&ev_int->det_events, buf, count, &copied);
mutex_unlock(&ev_int->read_lock); // 解锁

if (ret) // 如果复制失败,返回错误码
return ret;

/*
* If we couldn't read anything from the fifo (a different
* thread might have been faster) we either return -EAGAIN if
* the file descriptor is non-blocking, otherwise we go back to
* sleep and wait for more data to arrive.
*/
// 如果未读取到任何数据且是非阻塞模式,返回 -EAGAIN
if (copied == 0 && (filep->f_flags & O_NONBLOCK))
return -EAGAIN;

} while (copied == 0); // 如果未读取到数据,继续循环等待

return copied; // 返回实际读取的字节数
}

该函数的 do while 循环,它通过检查内核 FIFO 事件队列是否为空来决定是否需要等待事件数据;
在非阻塞模式下直接返回 -EAGAIN,而在阻塞模式下通过等待队列挂起进程直到有数据或设备被移除;接下来使用互斥锁保护对 FIFO 队列的并发访问,确保线程安全;
最后将事件数据从内核空间复制到用户空间,并根据读取结果处理非阻塞模式下的特殊情况,如果未读取到数据则继续循环等待,直至成功读取并返回实际读取的字节数或错误码。

iio_event_poll()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
* iio_event_poll() - poll the event queue to find out if it has data
* @filep: File structure pointer to identify the device
* @wait: Poll table pointer to add the wait queue on
*
* Return: (EPOLLIN | EPOLLRDNORM) if data is available for reading
* or a negative error code on failure
*/
static __poll_t iio_event_poll(struct file *filep,
struct poll_table_struct *wait)
{
// 获取与文件关联的 IIO 设备结构体
struct iio_dev *indio_dev = filep->private_data;
struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev);
// 获取该设备的事件接口结构体
struct iio_event_interface *ev_int = iio_dev_opaque->event_interface;
// 初始化返回的事件掩码为 0
__poll_t events = 0;

// 如果设备没有有效的 info 结构体,直接返回空事件(无事件)
if (!indio_dev->info)
return events;

// 将当前文件描述符加入到等待队列中,以便在事件发生时唤醒
poll_wait(filep, &ev_int->wait, wait);

// 检查事件 FIFO 队列是否非空
if (!kfifo_is_empty(&ev_int->det_events))
// 如果有事件待处理,设置 POLLIN 和 POLLRDNORM 标志
events = EPOLLIN | EPOLLRDNORM;

// 返回事件掩码
return events;
}

函数通过 poll_wait(filep, &ev_int->wait, wait); 函数将当前文件描述符加入等待队列以便在事件到达时被唤醒。接着检查事件 FIFO 队列是否非空,如果有事件待处理,则设置 EPOLLIN | EPOLLRDNORM 标志表示数据可读,最后返回检测到的事件。

iio_event_chrdev_release()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static int iio_event_chrdev_release(struct inode *inode, struct file *filep)
{
// 从文件结构体中获取与设备关联的 IIO 设备结构体
struct iio_dev *indio_dev = filep->private_data;
struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev);
// 获取该设备的事件接口结构体
struct iio_event_interface *ev_int = iio_dev_opaque->event_interface;

// 清除事件接口标志中的 BUSY 状态位(IIO_BUSY_BIT_POS)
clear_bit(IIO_BUSY_BIT_POS, &ev_int->flags);

// 减少 IIO 设备的引用计数,释放对设备的占用
iio_device_put(indio_dev);

return 0;
}

iio_event_chrdev_release 函数用于在用户空间关闭设备文件时执行清理操作,它通过清除事件接口中的 BUSY 状态标志来标记设备为可用状态,并减少 IIO 设备的引用计数以释放资源,确保设备可以被其他进程安全使用或在不再需要时进行进一步清理。

IIO 子系统中需要支持多个系统调用,可以通过 ioctl 函数创建一个匿名文件描述符,并为该匿名文件描述符绑定一套独立的文件操作集。这种方式不仅能够扩展功能,还能避免污染文件系统,因为匿名文件描述符不会在文件系统中留下任何痕迹,从而保持系统的整洁与高效。

iio_chrdev_open()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
* iio_chrdev_open() - chrdev file open for buffer access and ioctls
* @inode: Inode structure for identifying the device in the file system
* @filp: File structure for iio device used to keep and later access
* private data
*
* Return: 0 on success or -EBUSY if the device is already opened
**/
static int iio_chrdev_open(struct inode *inode, struct file *filp)
{
// 从 inode 中获取对应的 IIO 设备结构体
struct iio_dev *indio_dev = container_of(inode->i_cdev,
struct iio_dev, chrdev);

// 检查设备是否已被占用,如果忙则返回 -EBUSY
if (test_and_set_bit(IIO_BUSY_BIT_POS, &indio_dev->flags))
return -EBUSY;

// 增加设备引用计数,防止设备被释放
iio_device_get(indio_dev);

// 将设备结构体指针存储到文件的私有数据中
filp->private_data = indio_dev;

return 0;
}

该函数 iio_chrdev_open 用于打开 IIO 字符设备,首先通过 container_of 宏从 inode 中获取对应的 IIO 设备结构体,然后检查设备是否已被占用(通过测试并设置忙标志位),如果设备忙则返回-EBUSY。接着调用 iio_device_get 函数增加设备的引用计数以防止设备在使用过程中被释放,并将设备结构体指针存储到文件的私有数据中以便后续操作使用,最后返回 0 表示设备成功打开。

iio_device_get函数的具体内容如下所示:

1
2
3
4
5
6
7
8
9
10
/**
* iio_device_get() - increment reference count for the device
* @indio_dev: IIO device structure
*
* Returns: The passed IIO device
**/
static inline struct iio_dev *iio_device_get(struct iio_dev *indio_dev)
{
return indio_dev ? dev_to_iio_dev(get_device(&indio_dev->dev)) : NULL;
}

该函数的作用是增加 IIO 设备的引用计数,确保设备在使用期间不会被意外释放,同时返回设备指针以供后续操作使用。它首先检查传入的 struct indio_dev 是否为空,如果非空,则通过 get_device 增加设备的引用计数,并将设备指针从 dev 转换回 iio_dev 类型返回;如果为空,则直接返回 NULL。这种实现既保证了设备的有效性,又避免了对空指针的操作。

iio_buffer_read_outer_addr()

接下来对文件操作集中的 read 函数 iio_buffer_read_outer_addr 进行分析:

1
2
// drivers/iio/iio_core.h
#define iio_buffer_read_outer_addr (&iio_buffer_read_outer)

iio_buffer_read_outer定义如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
// drivers/iio/industrialio-buffer.c
/**
* iio_buffer_read_outer() - chrdev read for buffer access
* @filp: File structure pointer for the char device
* @buf: Destination buffer for iio buffer read
* @n: First n bytes to read
* @f_ps: Long offset provided by the user as a seek position
*
* This function relies on all buffer implementations having an
* iio_buffer as their first element.
*
* Return: negative values corresponding to error codes or ret != 0
* for ending the reading activity
**/
ssize_t iio_buffer_read_outer(struct file *filp, char __user *buf,
size_t n, loff_t *f_ps)
{
struct iio_dev *indio_dev = filp->private_data; // indio_dev = 一个 IIO 设备
struct iio_buffer *rb = indio_dev->buffer; // rb = 绑定的 ring buffer,rb->access->read 是真正的 buffer 实现(DMA / kfifo / hw buffer)
DEFINE_WAIT_FUNC(wait, woken_wake_function);
size_t datum_size;
size_t to_wait;
int ret = 0;

// 合法性检查
if (!indio_dev->info)
return -ENODEV;

if (!rb || !rb->access->read)
return -EINVAL;

datum_size = rb->bytes_per_datum; // 一个 datum = 一次采样数据的字节大小。

/*
* If datum_size is 0 there will never be anything to read from the
* buffer, so signal end of file now.
*/
if (!datum_size) // 说明这个 buffer 根本不产生数据
return 0;

if (filp->f_flags & O_NONBLOCK) // 非阻塞模式
to_wait = 0;
else // 阻塞模式
to_wait = min_t(size_t, n / datum_size, rb->watermark);

add_wait_queue(&rb->pollq, &wait);
do {
if (!indio_dev->info) {
ret = -ENODEV;
break;
}

// buffer 里是否已经有足够数据?
if (!iio_buffer_ready(indio_dev, rb, to_wait, n / datum_size)) {
// 如果没有信号
if (signal_pending(current)) { // 检查信号,用户 Ctrl+C
ret = -ERESTARTSYS; // 返回 -ERESTARTSYS
break;
}

// 睡眠,进程进入 interruptible sleep
wait_woken(&wait, TASK_INTERRUPTIBLE,
MAX_SCHEDULE_TIMEOUT);
continue;
}

ret = rb->access->read(rb, n, buf); // 真正读数据
if (ret == 0 && (filp->f_flags & O_NONBLOCK)) // 非阻塞模式特殊处理
ret = -EAGAIN;
} while (ret == 0); // 只要没读到数据就继续等
remove_wait_queue(&rb->pollq, &wait); // 清理等待队列

return ret;
}

返回值语义:

  • > 0:成功读到字节数
  • 0 :EOF(没有数据且永远不会有)
  • < 0:错误码

这是 Linux kernel 里一个典型“阻塞式读取缓冲区”的实现。

1
2
3
4
5
6
7
8
while (没有数据) {
如果非阻塞 → EAGAIN
如果被信号打断 → 退出
睡眠等待
}

读数据
返回

其中 struct iio_buffer 定义如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
/**
* 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;// 缓冲区中数据单元的数量

/** @bytes_per_datum: Size of individual datum including timestamp. */
size_t bytes_per_datum; // 单个数据单元的大小(包括时间戳)

/**
* @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; // 扫描模式是否包含时间戳

/* @scan_el_dev_attr_list: List of scan element related attributes. */
struct list_head scan_el_dev_attr_list; // 与扫描元素相关的属性列表

/* @buffer_group: Attributes of the buffer group. */
struct attribute_group buffer_group; // 缓冲区组的属性

/*
* @scan_el_group: Attribute group for those attributes not
* created from the iio_chan_info array.
*/
struct attribute_group scan_el_group; // 属性组,用于那些未从 iio_chan_info 数组创建的属性

/* @attrs: Standard attributes of the buffer. */
const struct attribute **attrs; // 缓冲区的标准属性

/* @demux_bounce: Buffer for doing gather from incoming scan. */
void *demux_bounce; // 用于从传入扫描中收集数据的缓冲区

/* @buffer_list: Entry in the devices list of current buffers. */
struct list_head buffer_list; // 设备当前缓冲区列表中的条目

/* @ref: Reference count of the buffer. */
struct kref ref; // 缓冲区的引用计数,用于管理资源释放
};

iio_buffer 中的 const struct iio_buffer_access_funcs *access 定义如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
/**
* struct iio_buffer_access_funcs - access functions for buffers.
* @store_to: actually store stuff to the buffer
* @read: try to get a specified number of bytes (must exist)
* @data_available: indicates how much data is available for reading from
* the buffer.
* @request_update: if a parameter change has been marked, update underlying
* storage.
* @set_bytes_per_datum:set number of bytes per datum
* @set_length: set number of datums in buffer
* @enable: called if the buffer is attached to a device and the
* device starts sampling. Calls are balanced with
* @disable.
* @disable: called if the buffer is attached to a device and the
* device stops sampling. Calles are balanced with @enable.
* @release: called when the last reference to the buffer is dropped,
* should free all resources allocated by the buffer.
* @modes: Supported operating modes by this buffer type
* @flags: A bitmask combination of INDIO_BUFFER_FLAG_*
*
* The purpose of this structure is to make the buffer element
* modular as event for a given driver, different usecases may require
* different buffer designs (space efficiency vs speed for example).
*
* It is worth noting that a given buffer implementation may only support a
* small proportion of these functions. The core code 'should' cope fine with
* any of them not existing.
**/
struct iio_buffer_access_funcs {
// 将数据存储到缓冲区中
int (*store_to)(struct iio_buffer *buffer, const void *data);

// 从缓冲区中读取最多 n 字节的数据到用户空间缓冲区 buf
int (*read)(struct iio_buffer *buffer, size_t n, char __user *buf);

// 返回缓冲区中当前可用的数据量(以字节或数据单元为单位)
size_t (*data_available)(struct iio_buffer *buffer);

// 请求更新缓冲区配置,通常用于应用新的参数或状态
int (*request_update)(struct iio_buffer *buffer);

// 设置每个数据单元的大小(bytes per datum),用于调整缓冲区格式
int (*set_bytes_per_datum)(struct iio_buffer *buffer, size_t bpd);
// 设置缓冲区的长度(数据单元数量)
int (*set_length)(struct iio_buffer *buffer, unsigned int length);

// 启用缓冲区,通常与设备关联并开始数据采集
int (*enable)(struct iio_buffer *buffer, struct iio_dev *indio_dev);
// 禁用缓冲区,停止数据采集并清理相关资源
int (*disable)(struct iio_buffer *buffer, struct iio_dev *indio_dev);

// 释放缓冲区资源,通常在缓冲区销毁时调用
void (*release)(struct iio_buffer *buffer);

// 缓冲区支持的模式(如阻塞、非阻塞等)
unsigned int modes;
// 缓冲区的标志位,用于指示特定功能或状态
unsigned int flags;
};

该结构体定义了一组函数指针和标志位,用于实现对 IIO 缓冲区的操作接口。通过这些接口,IIO 子系统能够灵活地管理缓冲区的存储、读取、配置和生命周期,同时支持多种工作模式和功能扩展,在iio_buffer_read_outer_addr函数中,使用ret = rb->access->read(rb, n, buf);真正读数据。

iio_chrdev_release()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* iio_chrdev_release() - chrdev file close buffer access and ioctls
* @inode: Inode structure pointer for the char device
* @filp: File structure pointer for the char device
*
* Return: 0 for successful release
*/
static int iio_chrdev_release(struct inode *inode, struct file *filp)
{
// 通过 container_of 宏从 inode 中获取与字符设备关联的 IIO 设备结构体
struct iio_dev *indio_dev = container_of(inode->i_cdev,
struct iio_dev, chrdev);

// 清除 IIO 设备的 BUSY 状态标志位,表示设备不再被占用
clear_bit(IIO_BUSY_BIT_POS, &indio_dev->flags);

// 减少 IIO 设备的引用计数,释放对设备的占用
// 如果引用计数降为 0,则可能会触发设备的进一步清理或释放操作
iio_device_put(indio_dev);

// 返回 0 表示成功释放设备资源
return 0;
}

该函数的核心作用是在用户空间关闭设备文件时,清理设备的状态并释放相关资源。通过 clear_bit 函数清除了设备的 BUSY 状态标志,并减少了设备的引用计数,从而确保设备可以被其他进程安全地使用或在不再需要时进行进一步清理。

iio_buffer_poll_addr()

1
#define iio_buffer_poll_addr (&iio_buffer_poll)

iio_buffer_poll 定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
* iio_buffer_poll() - poll the buffer to find out if it has data
* @filp: File structure pointer for device access
* @wait: Poll table structure pointer for which the driver adds
* a wait queue
*
* Return: (EPOLLIN | EPOLLRDNORM) if data is available for reading
* or 0 for other cases
*/
__poll_t iio_buffer_poll(struct file *filp,
struct poll_table_struct *wait)
{
// 从文件结构体中获取与设备关联的 IIO 设备结构体
struct iio_dev *indio_dev = filp->private_data;

// 获取该设备的缓冲区结构体
struct iio_buffer *rb = indio_dev->buffer;

// 如果设备没有有效的 info 结构体,或者缓冲区未初始化,返回 0 表示无事件
if (!indio_dev->info || rb == NULL)
return 0;

// 将当前文件描述符加入到缓冲区的等待队列中,以便在事件到达时被唤醒
poll_wait(filp, &rb->pollq, wait);
if (iio_buffer_ready(indio_dev, rb, rb->watermark, 0))
return EPOLLIN | EPOLLRDNORM;// 如果有数据可读,返回 EPOLLIN | EPOLLRDNORM 表示数据可读

// 如果没有数据可读,返回 0 表示无事件
return 0;
}

该函数实现了 IIO 缓冲区的轮询机制,用于检查缓冲区是否有数据可供读取。它首先从文件结构中获取设备和缓冲区信息,并验证其有效性;然后通过 poll_wait 将当前文件描述符加入等待队列,以便在数据到达时被唤醒;最后通过 iio_buffer_ready 检查缓冲区是否满足读取条件(如达到水位值),如果满足则返回 EPOLLIN | EPOLLRDNORM 表示数据可读,否则返回 0 表示无事件。

iio_device_register_sysfs()

在讲解 IIO 设备注册流程的时候对 __iio_device_register 进行了简要的分析,在该函数中又调用了 ret = iio_device_register_sysfs(indio_dev); 函数来注册 IIO 设备的 sysfs 接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
static int iio_device_register_sysfs(struct iio_dev *indio_dev)
{
struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev);
int i, ret = 0, attrcount, attrn, attrcount_orig = 0;
struct iio_dev_attr *p;
struct attribute **attr, *clk = NULL;

/* First count elements in any existing group */
/* 首先统计现有属性组中的元素数量 */
if (indio_dev->info->attrs) {
attr = indio_dev->info->attrs->attrs;
while (*attr++ != NULL)
attrcount_orig++;
}
attrcount = attrcount_orig;
/*
* New channel registration method - relies on the fact a group does
* not need to be initialized if its name is NULL.
*/
/*
* 新的通道注册方法 - 依赖于一个事实:如果组名为空,则不需要初始化组。
*/
if (indio_dev->channels) // 如果设备有通道定义
for (i = 0; i < indio_dev->num_channels; i++) { // 遍历所有通道
const struct iio_chan_spec *chan =
&indio_dev->channels[i];

// 如果通道类型为时间戳,记录时间戳时钟属性
if (chan->type == IIO_TIMESTAMP)
clk = &dev_attr_current_timestamp_clock.attr;

// 将通道的 sysfs 属性添加到系统中
ret = iio_device_add_channel_sysfs(indio_dev, chan);
if (ret < 0) // 如果添加失败,跳转到错误处理
goto error_clear_attrs;
attrcount += ret; // 累加新增的属性数量
}

// 如果设备支持事件接口,记录时间戳时钟属性
if (iio_dev_opaque->event_interface)
clk = &dev_attr_current_timestamp_clock.attr;

// 如果设备有名称,增加属性计数
if (indio_dev->name)
attrcount++;
if (indio_dev->label)
attrcount++;

// 如果存在时间戳时钟属性,增加属性计数
if (clk)
attrcount++;

// 分配内存以存储所有属性指针
iio_dev_opaque->chan_attr_group.attrs =
kcalloc(attrcount + 1,
sizeof(iio_dev_opaque->chan_attr_group.attrs[0]),
GFP_KERNEL);
if (iio_dev_opaque->chan_attr_group.attrs == NULL) { // 如果分配失败,返回内存不足错误
ret = -ENOMEM;
goto error_clear_attrs;
}
/* Copy across original attributes */
// 复制原有的属性到新的属性数组中
if (indio_dev->info->attrs)
memcpy(iio_dev_opaque->chan_attr_group.attrs,
indio_dev->info->attrs->attrs,
sizeof(iio_dev_opaque->chan_attr_group.attrs[0])
*attrcount_orig);
attrn = attrcount_orig; // 记录当前属性数组的索引
/* Add all elements from the list. */
// 将通道属性列表中的所有属性添加到属性数组中
list_for_each_entry(p, &iio_dev_opaque->channel_attr_list, l)
iio_dev_opaque->chan_attr_group.attrs[attrn++] = &p->dev_attr.attr;
// 如果设备有名称,将名称属性添加到属性数组中
if (indio_dev->name)
iio_dev_opaque->chan_attr_group.attrs[attrn++] = &dev_attr_name.attr;
if (indio_dev->label)
iio_dev_opaque->chan_attr_group.attrs[attrn++] = &dev_attr_label.attr;
// 如果存在时间戳时钟属性,将其添加到属性数组中
if (clk)
iio_dev_opaque->chan_attr_group.attrs[attrn++] = clk;

// 将属性组添加到设备的属性组列表中
indio_dev->groups[indio_dev->groupcounter++] =
&iio_dev_opaque->chan_attr_group;

return 0; // 返回成功

error_clear_attrs:
iio_free_chan_devattr_list(&iio_dev_opaque->channel_attr_list);

return ret;
}

这个函数的核心操作在于 ret = iio_device_add_channel_sysfs(indio_dev, chan); 函数,通过该函数可以将通道的 sysfs 属性添加到系统中,该函数的具体内容如下所示:

iio_device_add_channel_sysfs()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
static int iio_device_add_channel_sysfs(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan)
{
struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev);
int ret, attrcount = 0; // 定义返回值和属性计数器
const struct iio_chan_spec_ext_info *ext_info; // 扩展信息指针

// 如果通道编号小于 0,则直接返回(表示该通道无效)
if (chan->channel < 0)
return 0;

// 添加独立类型的信息掩码属性(IIO_SEPARATE)
ret = iio_device_add_info_mask_type(indio_dev, chan,
IIO_SEPARATE,
&chan->info_mask_separate);
if (ret < 0) // 如果添加失败,返回错误码
return ret;
attrcount += ret; // 累加新增的属性数量

// 添加独立类型可用信息掩码属性(IIO_SEPARATE_AVAILABLE)
ret = iio_device_add_info_mask_type_avail(indio_dev, chan,
IIO_SEPARATE,
&chan->
info_mask_separate_available);
if (ret < 0) // 如果添加失败,返回错误码
return ret;
attrcount += ret; // 累加新增的属性数量

// 添加按类型共享的信息掩码属性(IIO_SHARED_BY_TYPE)
ret = iio_device_add_info_mask_type(indio_dev, chan,
IIO_SHARED_BY_TYPE,
&chan->info_mask_shared_by_type);
if (ret < 0) // 如果添加失败,返回错误码
return ret;
attrcount += ret; // 累加新增的属性数量

// 添加按类型共享的可用信息掩码属性(IIO_SHARED_BY_TYPE_AVAILABLE)
ret = iio_device_add_info_mask_type_avail(indio_dev, chan,
IIO_SHARED_BY_TYPE,
&chan->
info_mask_shared_by_type_available);
if (ret < 0) // 如果添加失败,返回错误码
return ret;
attrcount += ret; // 累加新增的属性数量

// 添加按方向共享的信息掩码属性(IIO_SHARED_BY_DIR)
ret = iio_device_add_info_mask_type(indio_dev, chan,
IIO_SHARED_BY_DIR,
&chan->info_mask_shared_by_dir);
if (ret < 0) // 如果添加失败,返回错误码
return ret;
attrcount += ret; // 累加新增的属性数量

// 添加按方向共享的可用信息掩码属性(IIO_SHARED_BY_DIR_AVAILABLE)
ret = iio_device_add_info_mask_type_avail(indio_dev, chan,
IIO_SHARED_BY_DIR,
&chan->info_mask_shared_by_dir_available);
if (ret < 0) // 如果添加失败,返回错误码
return ret;
attrcount += ret; // 累加新增的属性数量

// 添加全局共享的信息掩码属性(IIO_SHARED_BY_ALL)
ret = iio_device_add_info_mask_type(indio_dev, chan,
IIO_SHARED_BY_ALL,
&chan->info_mask_shared_by_all);
if (ret < 0) // 如果添加失败,返回错误码
return ret;
attrcount += ret; // 累加新增的属性数量

// 添加全局共享的可用信息掩码属性(IIO_SHARED_BY_ALL_AVAILABLE)
ret = iio_device_add_info_mask_type_avail(indio_dev, chan,
IIO_SHARED_BY_ALL,
&chan->info_mask_shared_by_all_available);
if (ret < 0) // 如果添加失败,返回错误码
return ret;
attrcount += ret; // 累加新增的属性数量

// 处理扩展信息(ext_info)
if (chan->ext_info) {
unsigned int i = 0; // 记录扩展信息的索引
for (ext_info = chan->ext_info; ext_info->name; ext_info++) { // 遍历扩展信息
// 添加扩展信息属性到 sysfs
ret = __iio_add_chan_devattr(ext_info->name, // 属性名称
chan, // 通道指针
ext_info->read ? // 是否有读回调
&iio_read_channel_ext_info : NULL,
ext_info->write ? // 是否有写回调
&iio_write_channel_ext_info : NULL,
i, // 扩展信息索引
ext_info->shared, // 是否共享
&indio_dev->dev, // 设备指针
&iio_dev_opaque->channel_attr_list); // 属性列表
i++; // 增加索引
// 如果返回 -EBUSY 且扩展信息是共享的,则跳过
if (ret == -EBUSY && ext_info->shared)
continue;

// 如果其他错误发生,直接返回错误码
if (ret)
return ret;

attrcount++; // 成功添加一个属性,累加计数
}
}

return attrcount; // 返回成功添加的属性总数
}

这些代码段通过调用 iio_device_add_info_mask_typeiio_device_add_info_mask_type_avail 函数,按照不同的共享类型逐一添加通道的属性及其可用属性,每种类型对应特定的功能集合,并在每次操作后检查返回值以确保错误能够被及时捕获和处理,同时通过累加返回值记录成功添加的属性总数,从而实现对硬件功能的灵活分类管理与动态扩展。接下来对刚刚提到的两个函数进行讲解,两个函数的函数原型如下所示:

iio_device_add_info_mask_type 函数定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
static int iio_device_add_info_mask_type(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
enum iio_shared_by shared_by,
const long *infomask)
{
struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev);
int i, ret, attrcount = 0;

for_each_set_bit(i, infomask, sizeof(*infomask)*8) {
if (i >= ARRAY_SIZE(iio_chan_info_postfix))
return -EINVAL;
ret = __iio_add_chan_devattr(iio_chan_info_postfix[i],
chan,
&iio_read_channel_info,
&iio_write_channel_info,
i,
shared_by,
&indio_dev->dev,
&iio_dev_opaque->channel_attr_list);
if ((ret == -EBUSY) && (shared_by != IIO_SEPARATE))
continue;
else if (ret < 0)
return ret;
attrcount++;
}

return attrcount;
}

iio_device_add_info_mask_type_avail 函数定义如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
static int iio_device_add_info_mask_type_avail(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
enum iio_shared_by shared_by,
const long *infomask)
{
struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev);
int i, ret, attrcount = 0;
char *avail_postfix;

for_each_set_bit(i, infomask, sizeof(*infomask) * 8) {
if (i >= ARRAY_SIZE(iio_chan_info_postfix))
return -EINVAL;
avail_postfix = kasprintf(GFP_KERNEL,
"%s_available",
iio_chan_info_postfix[i]);
if (!avail_postfix)
return -ENOMEM;

ret = __iio_add_chan_devattr(avail_postfix,
chan,
&iio_read_channel_info_avail,
NULL,
i,
shared_by,
&indio_dev->dev,
&iio_dev_opaque->channel_attr_list);
kfree(avail_postfix);
if ((ret == -EBUSY) && (shared_by != IIO_SEPARATE))
continue;
else if (ret < 0)
return ret;
attrcount++;
}

return attrcount;
}

可以看到两个函数的参数是相同的,但是他们的功能略微有些不同,iio_device_add_info_mask_type 用于添加与通道相关的信息掩码属性,iio_device_add_info_mask_type_avail 用于添加可用信息掩码属性,这些属性通常用于描述哪些功能是动态可用的。对应的参数说明如下所示:

  1. indio_dev:指向 IIO 设备的指针,表示当前操作的设备。它包含了设备的所有信息,比如通道和属性,是连接硬件与用户空间的核心。
  2. chan:指向通道的指针,表示当前要操作的具体通道。每个通道对应一个传感器或信号接口(如温度、压力等),定义了该通道的功能。
  3. shared_by:指定属性的共享类型,决定属性如何在通道间共享。在上面代码中使用的类型包括:
    • IIO_SEPARATE:独立属性,仅属于某个特定通道。
    • IIO_SHARED_BY_TYPE:按类型共享,相同类型的通道共享属性。
    • IIO_SHARED_BY_DIR:按方向共享,相同方向的通道共享属性。
    • IIO_SHARED_BY_ALL:全局共享,整个设备的所有通道共享属性。
  4. infomask:指向信息掩码的地址,定义需要暴露的属性集合。每个比特位对应一个具体的功能或特性,在 iio_device_add_info_mask_type 中,infomask 描述通道的基本功能(如读取、写入等)。在 iio_device_add_info_mask_type_avail 中,infomask 描述动态可用的功能(如某些模式下启用的额外功能)。在上述代码中用到的掩码内容介绍如下表所示:
掩码名称 核心作用 共享范围 典型应用场景
info_mask_separate 导出特定于当前通道的专属信息 独立(仅当前通道) 通道专属统计数据、队列配置、单通道链路状态
info_mask_separate_available 导出当前通道专属信息的 “可用性” 标识(指示该类信息是否可查询) 独立(仅当前通道) 标记当前通道的统计数据是否已采集完成、配置是否生效
info_mask_shared_by_type 导出所有相同类型通道共享的通用信息 按类型共享(同类型通道) 设备类型属性、驱动版本、同类型通道的全局统计(如所有 10G 网口的通用配置)
info_mask_shared_by_type_available 导出同类型通道共享信息的 “可用性” 标识 按类型共享(同类型通道) 标记同类型通道的通用配置是否加载完成、驱动版本信息是否可读取
info_mask_shared_by_dir 导出所有相同方向通道共享的信息 按方向共享(同方向通道) RX/TX 方向专属统计(如所有接收通道的总字节数)、方向相关的硬件配置
info_mask_shared_by_dir_available 导出同方向通道共享信息的 “可用性” 标识 按方向共享(同方向通道) 标记某方向通道的统计数据是否可用、方向配置是否生效
info_mask_shared_by_all 导出所有通道通用的全局信息 全局共享(所有通道) 设备 MAC 地址、固件版本、设备整体运行状态、所有通道的总收发统计
info_mask_shared_by_all_available 导出全局共享信息的 “可用性” 标识 全局共享(所有通道) 标记设备全局状态是否可查询、固件版本信息是否读取成功

这里只是对属性信息进行了添加,那关于属性的设置是在什么时候完成的呢?在前面分析 rockchip_saradc_probe 函数的时候提到过,RK3568 ADC 通道的定义使用的是 SARADC_CHANNEL 宏,该宏的具体内容如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#define SARADC_CHANNEL(_index, _id, _res) {			\
.type = IIO_VOLTAGE, \
.indexed = 1, \
.channel = _index, \
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
.datasheet_name = _id, \
.scan_index = _index, \
.scan_type = { \
.sign = 'u', \
.realbits = _res, \
.storagebits = 16, \
.endianness = IIO_CPU, \
}, \
}
  • .type = IIO_VOLTAGE 通道类型:电压测量
  • .indexed = 1 启用索引模式,表示使用 channel 字段作为索引
  • .channel = _index 主通道编号,由宏参数 _index 指定
  • .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) 单独支持的属性:原始值(RAW)
  • .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) 按类型共享的属性:缩放比例
  • .datasheet_name = _id 数据手册中的名称,由宏参数 _id 指定

可以看到info_mask_separateinfo_mask_shared_by_type 就是用来指定单独支持的属性以及共享的属性。

IIO 通道属性添加函数讲解

iio_device_add_info_mask_type()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
static int iio_device_add_info_mask_type(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
enum iio_shared_by shared_by,
const long *infomask)
{
struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev);
int i, ret, attrcount = 0;

// 遍历信息掩码中的每个有效比特位(即设置为 1 的位)
for_each_set_bit(i, infomask, sizeof(*infomask)*8) {
// 检查索引是否超出后缀数组的范围
if (i >= ARRAY_SIZE(iio_chan_info_postfix))
return -EINVAL; // 如果超出范围,返回无效参数错误

// 调用函数将属性添加到 sysfs 接口中
ret = __iio_add_chan_devattr(iio_chan_info_postfix[i], // 属性名称后缀
chan, // 通道指针
&iio_read_channel_info, // 读回调函数
&iio_write_channel_info, // 写回调函数
i, // 属性索引
shared_by, // 共享类型
&indio_dev->dev, // 设备指针
&iio_dev_opaque->channel_attr_list); // 属性列表

// 如果返回 -EBUSY 且属性不是独立类型的,则跳过该属性
if ((ret == -EBUSY) && (shared_by != IIO_SEPARATE))
continue;
// 如果发生其他错误,直接返回错误码
else if (ret < 0)
return ret;
// 成功添加一个属性,累加计数
attrcount++;
}

// 返回成功添加的属性总数
return attrcount;
}

iio_device_add_info_mask_type_avail()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
static int iio_device_add_info_mask_type_avail(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
enum iio_shared_by shared_by,
const long *infomask)
{
struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev);
int i, ret, attrcount = 0;
char *avail_postfix;

// 遍历信息掩码中的每个有效比特位(即设置为 1 的位)
for_each_set_bit(i, infomask, sizeof(*infomask) * 8) {
if (i >= ARRAY_SIZE(iio_chan_info_postfix))
return -EINVAL;
// 动态生成属性名称后缀,格式为 "属性名_available"
avail_postfix = kasprintf(GFP_KERNEL,
"%s_available",
iio_chan_info_postfix[i]);
if (!avail_postfix)
return -ENOMEM;

// 调用函数将可用属性添加到 sysfs 接口中
ret = __iio_add_chan_devattr(avail_postfix, // 属性名称后缀
chan, // 通道指针
&iio_read_channel_info_avail, // 读回调函数
NULL, // 写回调函数(不可写)
i, // 属性索引
shared_by, // 共享类型
&indio_dev->dev, // 设备指针
&iio_dev_opaque->channel_attr_list); // 属性列表
// 释放动态分配的属性名称后缀
kfree(avail_postfix);

// 如果返回 -EBUSY 且属性不是独立类型的,则跳过该属性
if ((ret == -EBUSY) && (shared_by != IIO_SEPARATE))
continue;
else if (ret < 0) // 如果发生其他错误,直接返回错误码
return ret;

// 成功添加一个属性,累加计数
attrcount++;
}

// 返回成功添加的属性总数
return attrcount;
}

这两个函数的实现逻辑是相同的,主要内容都是在 for_each_set_bit 这个 for 循环中实现的,通过 for 循环遍历掩码中每个有效比特位,解析对应的功能,并将其注册为 sysfs 属性,这里将可用属性添加到 sysfs 接口使用的是 __iio_add_chan_devattr 函数,该函数的的具体内容如下所示

__iio_add_chan_devattr()

参数如下:

  • const char *postfix 属性名称的后缀(如 “raw” 或 “available”),用于生成完整的属性名称。
  • struct iio_chan_spec const *chan 指向通道的指针,表示当前操作的通道对象。
  • ssize_t (*)(struct device *dev, struct device_attribute *attr, char *buf) readfunc 读回调函数,用于实现属性的读操作,返回值为读取的数据长度或错误码。
  • ssize_t (*)(struct device *dev, struct device_attribute *attr, const char *buf, size_t len) writefunc 写回调函数,用于实现属性的写操作,返回值为写入的数据长度或错误码。
  • u64 mask 属性的掩码地址,通常表示该属性对应的功能或特性的唯一标识。
  • enum iio_shared_by shared_by 属性的共享类型,决定属性如何在通道间共享(如独立、按类型共享等)。
  • struct device * dev 指向设备的指针,表示当前操作的目标设备。
  • struct list_head * attr_list 属性列表的头指针,用于将新创建的属性添加到链表中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
int __iio_add_chan_devattr(const char *postfix,
struct iio_chan_spec const *chan,
ssize_t (*readfunc)(struct device *dev,
struct device_attribute *attr,
char *buf),
ssize_t (*writefunc)(struct device *dev,
struct device_attribute *attr,
const char *buf,
size_t len),
u64 mask,
enum iio_shared_by shared_by,
struct device *dev,
struct list_head *attr_list)
{
int ret;
struct iio_dev_attr *iio_attr, *t;

// 分配内存以创建一个新的 iio_dev_attr 对象
iio_attr = kzalloc(sizeof(*iio_attr), GFP_KERNEL);
if (iio_attr == NULL)
return -ENOMEM;

// 初始化设备属性(包括名称、读写回调函数等)
ret = __iio_device_attr_init(&iio_attr->dev_attr,
postfix, chan,
readfunc, writefunc, shared_by);
if (ret)
goto error_iio_dev_attr_free;

// 设置通道指针和掩码地址
iio_attr->c = chan;
iio_attr->address = mask;
// 遍历属性列表,检查是否已存在相同名称的属性
list_for_each_entry(t, attr_list, l)
if (strcmp(t->dev_attr.attr.name,
iio_attr->dev_attr.attr.name) == 0) {
// 如果共享类型为独立(IIO_SEPARATE),记录错误日志
if (shared_by == IIO_SEPARATE)
dev_err(dev, "tried to double register : %s\n",
t->dev_attr.attr.name);
ret = -EBUSY;
goto error_device_attr_deinit;
}
// 将新创建的属性添加到属性列表中
list_add(&iio_attr->l, attr_list);

return 0;

error_device_attr_deinit:
// 错误处理:反初始化设备属性
__iio_device_attr_deinit(&iio_attr->dev_attr);
error_iio_dev_attr_free:
// 错误处理:释放分配的内存
kfree(iio_attr);
return ret;
}

分配内存并创建一个新的 iio_dev_attr 对象,对应的结构体内容如下所示,该结构体用来描述 IIO 设备的属性(如名称、读写回调函数)、属性的功能标识、以及所属的通道信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* struct iio_dev_attr - iio specific device attribute
* @dev_attr: underlying device attribute
* @address: associated register address
* @l: list head for maintaining list of dynamically created attrs
* @c: specification for the underlying channel
*/
struct iio_dev_attr {
struct device_attribute dev_attr; // 设备属性,包含名称和读写回调函数
u64 address; // 属性的地址或掩码值,用于标识功能
struct list_head l; // 链表节点,用于链接到属性链表中
struct iio_chan_spec const *c; // 指向所属通道的指针
};
__iio_device_attr_init()

__iio_add_chan_devattr函数中调用 ret = __iio_device_attr_init(&iio_attr->dev_attr, postfix, chan,readfunc, writefunc, shared_by); 初始化 IIO 设备属性,关于该函数的具体介绍如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
static
int __iio_device_attr_init(struct device_attribute *dev_attr,
const char *postfix,
struct iio_chan_spec const *chan,
ssize_t (*readfunc)(struct device *dev,
struct device_attribute *attr,
char *buf),
ssize_t (*writefunc)(struct device *dev,
struct device_attribute *attr,
const char *buf,
size_t len),
enum iio_shared_by shared_by)
{
int ret = 0; // 返回值,用于记录函数执行结果
char *name = NULL; // 属性名称
char *full_postfix; // 完整后缀字符串
sysfs_attr_init(&dev_attr->attr); // 初始化设备属性结构体

/* Build up postfix of <extend_name>_<modifier>_postfix */
/* 构造后缀:格式为 <extend_name>_<modifier>_postfix */
if (chan->modified && (shared_by == IIO_SEPARATE)) {
// 如果通道被修改且共享类型为独立(IIO_SEPARATE)
if (chan->extend_name)
// 如果通道有扩展名称,则生成完整后缀
full_postfix = kasprintf(GFP_KERNEL, "%s_%s_%s",
iio_modifier_names[chan
->channel2],
chan->extend_name,
postfix);
else
// 如果没有扩展名称,则只使用修饰符和后缀
full_postfix = kasprintf(GFP_KERNEL, "%s_%s",
iio_modifier_names[chan
->channel2],
postfix);
} else {
// 非修改通道或非独立共享类型
if (chan->extend_name == NULL || shared_by != IIO_SEPARATE)
// 如果没有扩展名称或共享类型不是独立,则直接复制后缀
full_postfix = kstrdup(postfix, GFP_KERNEL);
else
// 否则,将扩展名称和后缀组合
full_postfix = kasprintf(GFP_KERNEL,
"%s_%s",
chan->extend_name,
postfix);
}
// 检查后缀是否分配成功
if (full_postfix == NULL)
return -ENOMEM;

// 根据通道类型(差分或单端)和共享类型构造属性名称
if (chan->differential) { /* Differential can not have modifier */ /* 差分通道 */
switch (shared_by) {
case IIO_SHARED_BY_ALL: // 全局共享:仅使用后缀
name = kasprintf(GFP_KERNEL, "%s", full_postfix);
break;
case IIO_SHARED_BY_DIR: // 按方向共享:添加方向前缀
name = kasprintf(GFP_KERNEL, "%s_%s",
iio_direction[chan->output],
full_postfix);
break;
case IIO_SHARED_BY_TYPE: // 按类型共享:添加方向、类型信息
name = kasprintf(GFP_KERNEL, "%s_%s-%s_%s",
iio_direction[chan->output],
iio_chan_type_name_spec[chan->type],
iio_chan_type_name_spec[chan->type],
full_postfix);
break;
case IIO_SEPARATE: // 独立属性:检查是否索引化
if (!chan->indexed) {
WARN(1, "Differential channels must be indexed\n");
ret = -EINVAL;
goto error_free_full_postfix;
}
// 构造完整的差分通道名称
name = kasprintf(GFP_KERNEL,
"%s_%s%d-%s%d_%s",
iio_direction[chan->output],
iio_chan_type_name_spec[chan->type],
chan->channel,
iio_chan_type_name_spec[chan->type],
chan->channel2,
full_postfix);
break;
}
} else { /* Single ended */ /* 单端通道 */
switch (shared_by) {
case IIO_SHARED_BY_ALL: // 全局共享:仅使用后缀
name = kasprintf(GFP_KERNEL, "%s", full_postfix);
break;
case IIO_SHARED_BY_DIR: // 按方向共享:添加方向前缀
name = kasprintf(GFP_KERNEL, "%s_%s",
iio_direction[chan->output],
full_postfix);
break;
case IIO_SHARED_BY_TYPE: // 按类型共享:添加方向和类型信息
name = kasprintf(GFP_KERNEL, "%s_%s_%s",
iio_direction[chan->output],
iio_chan_type_name_spec[chan->type],
full_postfix);
break;

case IIO_SEPARATE: // 独立属性:根据是否索引化构造名称
if (chan->indexed)
name = kasprintf(GFP_KERNEL, "%s_%s%d_%s",
iio_direction[chan->output],
iio_chan_type_name_spec[chan->type],
chan->channel,
full_postfix);
else
name = kasprintf(GFP_KERNEL, "%s_%s_%s",
iio_direction[chan->output],
iio_chan_type_name_spec[chan->type],
full_postfix);
break;
}
}
if (name == NULL) { // 检查属性名称是否分配成功
ret = -ENOMEM;
goto error_free_full_postfix;
}
dev_attr->attr.name = name; // 设置属性名称

// 设置读回调函数和权限
if (readfunc) {
dev_attr->attr.mode |= S_IRUGO; // 设置可读权限
dev_attr->show = readfunc; // 绑定读回调函数
}

// 设置写回调函数和权限
if (writefunc) {
dev_attr->attr.mode |= S_IWUSR; // 设置可写权限
dev_attr->store = writefunc; // 绑定写回调函数
}

error_free_full_postfix:
kfree(full_postfix); // 释放动态分配的后缀字符串

return ret;
}

该函数的主要功能是根据通道类型、共享类型和其他参数动态构造一个 sysfs 属性名称,属性名称的设置在 123 行 dev_attr->attr.name = name; 进行的实现,而在此之前可以将程序根据 if 判断分为 3 部分,分别对应构造后缀字符串差分通道属性名称单端通道属性名称的逻辑,接下来对这三部分进行详细的讲解。

第 21-47 行用于构造后缀字符串 full_postfix,根据通道是否被修改(chan->modified) 和共享类型 (shared_by) 动态生成后缀字符串 full_postfix。如果通道被修改且共享类型为独立 (IIO_SEPARATE),则根据是否有扩展名称(chan->extend_name) 决定是否包含修饰符和扩展名称;否则,根据是否有扩展名称或共享类型直接构造后缀。

条件 格式
通道被修改 且 共享方式为 IO_SEPARATE 有扩展名称 (chan->extend_name) [修饰符名称][扩展名称][后缀]
通道被修改 且 共享方式为 IO_SEPARATE 无扩展名称 [修饰符名称][后缀]
通道未被修改 或 共享方式 ≠ IO_SEPARATE 无扩展名称 或 共享方式 ≠ IO_SEPARATE [后缀]
通道未被修改 或 共享方式 ≠ IO_SEPARATE 有扩展名称 且 共享方式为 IO_SEPARATE(仅为完整性列出) [扩展名称][后缀]

第 53-86 行针对差分通道,根据共享类型(shared_by)构造属性名称。

  • 对于全局共享(IIO_SHARED_BY_ALL),仅使用后缀;
  • 对于按方向共享 (IIO_SHARED_BY_DIR),添加方向前缀;
  • 对于按类型共享 (IIO_SHARED_BY_TYPE),进一步添加方向和类型信息;
  • 对于独立共享 (IIO_SEPARATE),要求通道必须索引化 (chan->indexed),并在名称中包含方向、类型和通道索引等详细信息,未索引化时会发出警告并返回错误。
共享方式 (shared_by) 属性名称格式
IO_SHARED_BY_ALL [full_postfix]
IO_SHARED_BY_DIR [输出方向]_[full_postfix]
IO_SHARED_BY_TYPE [输出方向][通道类型]-[通道类型][full_postfix]
IO_SEPARATE [输出方向][通道类型][通道索引]-[通道类型][通道2索引][full_postfix]

第 88-118 行针对单端通道,根据共享类型(shared_by)构造属性名称。对于全局共享(IIO_SHARED_BY_ALL),仅使用后缀;对于按方向共享 (IIO_SHARED_BY_DIR),添加方向前缀;对于按类型共享 (IIO_SHARED_BY_TYPE),添加方向和类型信息;对于独立共享(IIO_SEPARATE),根据是否索引化决定是否在名称中包含通道索引,未索引化时则省略索引部分。

共享方式 (shared_by) 属性名称格式
IO_SHARED_BY_ALL [full_postfix]
IO_SHARED_BY_DIR [输出方向]_[full_postfix]
IO_SHARED_BY_TYPE [输出方向][通道类型][full_postfix]
IO_SEPARATE(有索引,chan->indexed [输出方向][通道类型][通道索引][full_postfix]
IO_SEPARATE(无索引) [输出方向][通道类型]_[full_postfix]

接下来以 RK3568 的 ADC 通道 3 为例讲解一下 sysfs 属性名称是如何创建的,关于 RK3568 通道的具体描述如下所示:

1
2
3
4
5
6
7
8
9
10
static const struct iio_chan_spec rockchip_rk3568_saradc_iio_channels[] = {
SARADC_CHANNEL(0, "adc0", 10), // 定义 ADC 通道 0,名称为 "adc0"
SARADC_CHANNEL(1, "adc1", 10), // 定义 ADC 通道 1,名称为 "adc1"
SARADC_CHANNEL(2, "adc2", 10), // 定义 ADC 通道 2,名称为 "adc2"
SARADC_CHANNEL(3, "adc3", 10), // 定义 ADC 通道 3,名称为 "adc3"
SARADC_CHANNEL(4, "adc4", 10), // 定义 ADC 通道 4,名称为 "adc4"
SARADC_CHANNEL(5, "adc5", 10), // 定义 ADC 通道 5,名称为 "adc5"
SARADC_CHANNEL(6, "adc6", 10), // 定义 ADC 通道 6,名称为 "adc6"
SARADC_CHANNEL(7, "adc7", 10), // 定义 ADC 通道 7,名称为 "adc7"
};

该宏的具体内容如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#define SARADC_CHANNEL(_index, _id, _res) {			\
.type = IIO_VOLTAGE, \
.indexed = 1, \
.channel = _index, \
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
.datasheet_name = _id, \
.scan_index = _index, \
.scan_type = { \
.sign = 'u', \
.realbits = _res, \
.storagebits = 16, \
.endianness = IIO_CPU, \
}, \
}
  • .type = IIO_VOLTAGE 通道类型:电压测量
  • .indexed = 1 启用索引模式,表示使用 channel 字段作为索引
  • .channel = _index 主通道编号,由宏参数 _index 指定
  • .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) 单独支持的属性:原始值(RAW)
  • .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) 按类型共享的属性:缩放比例
  • .datasheet_name = _id 数据手册中的名称,由宏参数 _id 指定

带入 ADC3 之后的内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
SARADC_CHANNEL(3, "adc3"){
.type = IIO_VOLTAGE,
.indexed = 1,
.channel = 3,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),
.datasheet_name = "adc3",
.scan_index = _index,
.scan_type = {
.sign = 'u',
.realbits = _res,
.storagebits = 16,
.endianness = IIO_CPU,
},
}

可以看到info_mask_separateinfo_mask_shared_by_type 就是用来指定单独支持的属性以及共享的属性。

在第 5 行和第 6 行分别设置了 separateshared_by_type,所以在 iio_device_add_channel_sysfs 函数中会执行以下两段内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 添加独立类型的信息掩码属性(IIO_SEPARATE)
ret = iio_device_add_info_mask_type(indio_dev, chan,
IIO_SEPARATE,
&chan->info_mask_separate);
if (ret < 0) // 如果添加失败,返回错误码
return ret;
attrcount += ret; // 累加新增的属性数量

// 添加独立类型可用信息掩码属性(IIO_SEPARATE_AVAILABLE)
ret = iio_device_add_info_mask_type_avail(indio_dev, chan,
IIO_SEPARATE,
&chan->
info_mask_separate_available);
if (ret < 0) // 如果添加失败,返回错误码
return ret;
attrcount += ret; // 累加新增的属性数量

这里先来分析独立类型的信息掩码属性的添加,ADC3 的 info_mask_separate 属性被设置为了 BIT(IIO_CHAN_INFO_RAW),而 IIO_CHAN_INFO_RAW 的值为 0,所以 __iio_add_chan_devattr 函数的第一个参数就能确定了,以此确定该属性的后缀为 iio_chan_info_postfix[0]即 raw。

然后我们继续向下分析,确定该属性的全部名称,然后进入 iio_device_attr_init 函数,先来对构造后缀进行判断,由于在 ADC3 的定义中并没有 modified 属性,且 extend_name 没有被定义,所以会直接复制后缀,也就是上面确定的 RAW。

然后对下面的条件进行判断,由于 ADC3 并没有 differential 属性,所以进入的是单端通道的条件分支,然后 ADC3 的 shared_by 属性为 IIO_SEPARATEindexed 为 1,所以最终的名称确定代码为:

1
2
3
4
5
6
7
case IIO_SEPARATE: // 独立属性:根据是否索引化构造名称
if (chan->indexed)
name = kasprintf(GFP_KERNEL, "%s_%s%d_%s",
iio_direction[chan->output],
iio_chan_type_name_spec[chan->type],
chan->channel,
full_postfix);

chan->output 没有设置,所以 iio_direction 取 0,得到的值为 inchan->typeIIO_VOLTAGE,带入 iio_chan_type_name_spec 可以得到值为 voltagechan->channel 为 3,full_postfix 为后缀值 raw,所以 ADC3 的第一个属性名称为 in_voltage3_raw。然后可以用同样的方式来分析第二个属性名称,这里就不再赘述,可以得到第二个属性名为in_voltage_scale

IIO 设备节点创建分析

iio_device_add_channel_sysfs 函数确定了 ADC 通道的属性名称,然后将设备的通道属性、设备名称、时间戳属性进行收集,与 indio_dev 设备进行绑定,最终在 sysfs 中创建相应的节点,使用户可以在/sys/bus/iio/devices/iio:device0 目录下访问这些属
性。当然这段代码仅仅只是将这些属性收集到了 chan_attr_group 中,并没有在 sys 目录下创建这些属性,那创建相关属性文件的代码在哪里呢?

device_create()

创建设备节点可以有两种方式,第一种方式是通过 mknod 命令手动创建设备节点,第二种方式是自动创建设备节点,所调用的函数是 device_create,而 iio 设备节点就是自动创建的,device_create 函数内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// drivers/base/core.c
/**
* device_create - creates a device and registers it with sysfs
* @class: pointer to the struct class that this device should be registered to
* @parent: pointer to the parent struct device of this new device, if any
* @devt: the dev_t for the char device to be added
* @drvdata: the data to be added to the device for callbacks
* @fmt: string for the device's name
*
* This function can be used by char device classes. A struct device
* will be created in sysfs, registered to the specified class.
*
* A "dev" file will be created, showing the dev_t for the device, if
* the dev_t is not 0,0.
* If a pointer to a parent struct device is passed in, the newly created
* struct device will be a child of that device in sysfs.
* The pointer to the struct device will be returned from the call.
* Any further sysfs files that might be required can be created using this
* pointer.
*
* Returns &struct device pointer on success, or ERR_PTR() on error.
*
* Note: the struct class passed to this function must have previously
* been created with a call to class_create().
*/
struct device *device_create(struct class *class, struct device *parent,
dev_t devt, void *drvdata, const char *fmt, ...)
{
// 定义一个可变参数列表变量 vargs,用于存储传递给函数的可变参数
va_list vargs;

// 定义一个指向 struct device 的指针变量 dev,用于存储创建的设备对象
struct device *dev;

// 初始化可变参数列表 vargs,fmt 是最后一个固定参数,后面的参数是可变的
va_start(vargs, fmt);

// 调用 device_create_vargs 函数,传入类、父设备、设备号、驱动数据及可变参数
// 该函数会根据传入的参数创建一个设备对象,并返回指向该对象的指针
dev = device_create_groups_vargs(class, parent, devt, drvdata, NULL,
fmt, vargs);
va_end(vargs);
return dev;
}
EXPORT_SYMBOL_GPL(device_create);

该函数用于在 Linux 内核中创建一个设备对象,它的重点是 device_create_vargs 函数,device_create_vargs 函数的具体内容如下所示:

device_create_vargs()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
static __printf(6, 0) struct device *
device_create_groups_vargs(struct class *class, struct device *parent,
dev_t devt, void *drvdata,
const struct attribute_group **groups,
const char *fmt, va_list args)
{
// 定义一个指向 struct device 的指针变量 dev,初始化为 NULL
struct device *dev = NULL;

// 定义一个整型变量 retval,用于存储错误码,默认值为 -ENODEV(表示设备不存在)
int retval = -ENODEV;

// 检查传入的 class 是否为 NULL 或无效(通过 IS_ERR 检测)
if (class == NULL || IS_ERR(class))
goto error;

// 分配内存以创建一个新的设备对象,大小为 sizeof(*dev),使用 GFP_KERNEL 标志分配
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
if (!dev) { // 检查内存分配是否成功
retval = -ENOMEM;
goto error;
}

// 初始化设备对象
device_initialize(dev);

// 设置设备对象的属性
dev->devt = devt; // 设置设备号
dev->class = class; // 设置设备所属的类
dev->parent = parent; // 设置父设备
dev->groups = groups; // 设置设备的属性组
dev->release = device_create_release; // 设置设备释放时的回调函数
dev_set_drvdata(dev, drvdata); // 设置驱动程序私有数据

// 使用可变参数 args 设置设备的名称(通过格式化字符串 fmt)
retval = kobject_set_name_vargs(&dev->kobj, fmt, args);
if (retval) // 如果设置名称失败
goto error;

// 将设备添加到系统中
retval = device_add(dev);
if (retval)
goto error;

return dev;

error:
put_device(dev);
return ERR_PTR(retval);
}

最终在该函数通过device_initialize() 函数对设备进行了初始化,最后调用device_add()

device_add()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
/**
* device_add - add device to device hierarchy.
* @dev: device.
*
* This is part 2 of device_register(), though may be called
* separately _iff_ device_initialize() has been called separately.
*
* This adds @dev to the kobject hierarchy via kobject_add(), adds it
* to the global and sibling lists for the device, then
* adds it to the other relevant subsystems of the driver model.
*
* Do not call this routine or device_register() more than once for
* any device structure. The driver model core is not designed to work
* with devices that get unregistered and then spring back to life.
* (Among other things, it's very hard to guarantee that all references
* to the previous incarnation of @dev have been dropped.) Allocate
* and register a fresh new struct device instead.
*
* NOTE: _Never_ directly free @dev after calling this function, even
* if it returned an error! Always use put_device() to give up your
* reference instead.
*
* Rule of thumb is: if device_add() succeeds, you should call
* device_del() when you want to get rid of it. If device_add() has
* *not* succeeded, use *only* put_device() to drop the reference
* count.
*/
int device_add(struct device *dev)
{
struct device *parent;
struct kobject *kobj;
struct class_interface *class_intf;
int error = -EINVAL;
struct kobject *glue_dir = NULL;

// 获取设备引用,确保设备有效
dev = get_device(dev);
if (!dev)
goto done;

// 初始化设备私有数据
if (!dev->p) {
error = device_private_init(dev);
if (error)
goto done;
}

/*
* for statically allocated devices, which should all be converted
* some day, we need to initialize the name. We prevent reading back
* the name, and force the use of dev_name()
*/
// 设置设备名称
if (dev->init_name) {
dev_set_name(dev, "%s", dev->init_name);
dev->init_name = NULL;
}

/* subsystems can specify simple device enumeration */
if (!dev_name(dev) && dev->bus && dev->bus->dev_name)
dev_set_name(dev, "%s%u", dev->bus->dev_name, dev->id);

if (!dev_name(dev)) {
error = -EINVAL;
goto name_error;
}

pr_debug("device: '%s': %s\n", dev_name(dev), __func__);

// 获取父设备和 kobject 父对象
parent = get_device(dev->parent);
kobj = get_device_parent(dev, parent);
if (IS_ERR(kobj)) {
error = PTR_ERR(kobj);
goto parent_error;
}
if (kobj)
dev->kobj.parent = kobj;

/* use parent numa_node */
// 继承父设备的 NUMA 节点
if (parent && (dev_to_node(dev) == NUMA_NO_NODE))
set_dev_node(dev, dev_to_node(parent));

/* first, register with generic layer. */
/* we require the name to be set before, and pass NULL */
// 注册设备到通用层
error = kobject_add(&dev->kobj, dev->kobj.parent, NULL);
if (error) {
glue_dir = get_glue_dir(dev);
goto Error;
}

/* notify platform of device entry */
error = device_platform_notify(dev, KOBJ_ADD);
if (error)
goto platform_error;

// 创建设备属性文件和符号链接
error = device_create_file(dev, &dev_attr_uevent);
if (error)
goto attrError;

error = device_add_class_symlinks(dev);
if (error)
goto SymlinkError;
error = device_add_attrs(dev);
if (error)
goto AttrsError;

// 将设备添加到总线和电源管理子系统
error = bus_add_device(dev);
if (error)
goto BusError;
error = dpm_sysfs_add(dev);
if (error)
goto DPMError;
device_pm_add(dev);

// 如果设备号有效,创建相关文件和节点
if (MAJOR(dev->devt)) {
error = device_create_file(dev, &dev_attr_dev);
if (error)
goto DevAttrError;

error = device_create_sys_dev_entry(dev);
if (error)
goto SysEntryError;

devtmpfs_create_node(dev);
}

/* Notify clients of device addition. This call must come
* after dpm_sysfs_add() and before kobject_uevent().
*/
// 通知客户端设备已添加
if (dev->bus)
blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
BUS_NOTIFY_ADD_DEVICE, dev);

// 发送 KOBJ_ADD uevent 事件
kobject_uevent(&dev->kobj, KOBJ_ADD);

/*
* Check if any of the other devices (consumers) have been waiting for
* this device (supplier) to be added so that they can create a device
* link to it.
*
* This needs to happen after device_pm_add() because device_link_add()
* requires the supplier be registered before it's called.
*
* But this also needs to happen before bus_probe_device() to make sure
* waiting consumers can link to it before the driver is bound to the
* device and the driver sync_state callback is called for this device.
*/
// 处理设备链接(消费者-供应商关系)
if (dev->fwnode && !dev->fwnode->dev) {
dev->fwnode->dev = dev;
fw_devlink_link_device(dev);
}

// 探测设备并绑定驱动程序
bus_probe_device(dev);
if (parent)
klist_add_tail(&dev->p->knode_parent,
&parent->p->klist_children);

// 如果设备属于某个类,将设备添加到类中
if (dev->class) {
mutex_lock(&dev->class->p->mutex);
/* tie the class to the device */
klist_add_tail(&dev->p->knode_class,
&dev->class->p->klist_devices);

/* notify any interfaces that the device is here */
list_for_each_entry(class_intf,
&dev->class->p->interfaces, node)
if (class_intf->add_dev)
class_intf->add_dev(dev, class_intf);
mutex_unlock(&dev->class->p->mutex);
}
done:
put_device(dev);
return error;
SysEntryError:
if (MAJOR(dev->devt))
device_remove_file(dev, &dev_attr_dev);
DevAttrError:
device_pm_remove(dev);
dpm_sysfs_remove(dev);
DPMError:
bus_remove_device(dev);
BusError:
device_remove_attrs(dev);
AttrsError:
device_remove_class_symlinks(dev);
SymlinkError:
device_remove_file(dev, &dev_attr_uevent);
attrError:
device_platform_notify(dev, KOBJ_REMOVE);
platform_error:
kobject_uevent(&dev->kobj, KOBJ_REMOVE);
glue_dir = get_glue_dir(dev);
kobject_del(&dev->kobj);
Error:
cleanup_glue_dir(dev, glue_dir);
parent_error:
put_device(parent);
name_error:
kfree(dev->p);
dev->p = NULL;
goto done;
}
EXPORT_SYMBOL_GPL(device_add);

上面代码这部分:

1
2
3
4
5
6
7
8
9
10
11
12
// 如果设备号有效,创建相关文件和节点
if (MAJOR(dev->devt)) {
error = device_create_file(dev, &dev_attr_dev);
if (error)
goto DevAttrError;

error = device_create_sys_dev_entry(dev);
if (error)
goto SysEntryError;

devtmpfs_create_node(dev);
}

是一个条件判断,如果有设备号的话,就执行 devtmpfs_create_node 函数来创建设备节点。而如果没有设备号,就会调用 kobject_uevent 函数通过 udev 来进行创建设备节点,至此,关于设备节点创建相关的内容就回顾完成了,最终通过调用 device_add 函数来创建设备节点,那 iio 设备的设备节点是在哪里调用了该函数的呢

devm_iio_device_alloc()

drivers/iio/adc/rockchip_saradc.c 文件的 probe 函数,在 probe 函数的开头会使用 devm_iio_device_alloc 函数来申请内存,该函数的具体内容如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
* devm_iio_device_alloc - Resource-managed iio_device_alloc()
* @parent: Device to allocate iio_dev for, and parent for this IIO device
* @sizeof_priv: Space to allocate for private structure.
*
* Managed iio_device_alloc. iio_dev allocated with this function is
* automatically freed on driver detach.
*
* RETURNS:
* Pointer to allocated iio_dev on success, NULL on failure.
*/
struct iio_dev *devm_iio_device_alloc(struct device *parent, int sizeof_priv)
{
struct iio_dev **ptr, *iio_dev;
// 分配一个用于管理设备资源的指针,使用 devres 机制
ptr = devres_alloc(devm_iio_device_release, sizeof(*ptr),
GFP_KERNEL);
if (!ptr)
return NULL;

// 分配 IIO 设备结构体,并预留私有数据空间
iio_dev = iio_device_alloc(parent, sizeof_priv);
if (iio_dev) {
*ptr = iio_dev;
devres_add(parent, ptr); // 将资源添加到设备资源链表
} else {
devres_free(ptr); // 如果分配失败,释放 devres 资源
}

return iio_dev;
}
EXPORT_SYMBOL_GPL(devm_iio_device_alloc);

iio_device_alloc()

该函数调用 iio_device_alloc 函数来分配 IIO 设备结构体,iio_device_alloc 函数的具体内容如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
/**
* iio_device_alloc() - allocate an iio_dev from a driver
* @parent: Parent device.
* @sizeof_priv: Space to allocate for private structure.
**/
struct iio_dev *iio_device_alloc(struct device *parent, int sizeof_priv)
{
struct iio_dev_opaque *iio_dev_opaque;
struct iio_dev *dev;
size_t alloc_size;

// 计算需要分配的总内存大小,包括 iio_dev 结构体和私有数据
alloc_size = sizeof(struct iio_dev_opaque);
if (sizeof_priv) { // 如果需要分配私有数据空间
alloc_size = ALIGN(alloc_size, IIO_ALIGN); // 对齐到 IIO_ALIGN
alloc_size += sizeof_priv; // 加上私有数据大小
}

iio_dev_opaque = kzalloc(alloc_size, GFP_KERNEL);
if (!iio_dev_opaque)
return NULL;

dev = &iio_dev_opaque->indio_dev;
dev->priv = (char *)iio_dev_opaque +
ALIGN(sizeof(struct iio_dev_opaque), IIO_ALIGN);

// 初始化设备的基本属性
dev->dev.parent = parent;
dev->dev.groups = dev->groups; // 设置设备的默认属性组
dev->dev.type = &iio_device_type; // 设置设备类型
dev->dev.bus = &iio_bus_type; // 设置设备所属的总线类型
device_initialize(&dev->dev); // 初始化设备对象
dev_set_drvdata(&dev->dev, (void *)dev); // 设置设备私有数据
mutex_init(&dev->mlock);
mutex_init(&dev->info_exist_lock);
INIT_LIST_HEAD(&iio_dev_opaque->channel_attr_list); // 初始化通道属性链表

// 分配设备 ID
dev->id = ida_simple_get(&iio_ida, 0, 0, GFP_KERNEL);
if (dev->id < 0) {
/* cannot use a dev_err as the name isn't available */
pr_err("failed to get device id\n");
kfree(iio_dev_opaque);
return NULL;
}
// 设置设备名称
dev_set_name(&dev->dev, "iio:device%d", dev->id);
INIT_LIST_HEAD(&iio_dev_opaque->buffer_list); // 初始化缓冲区链表

return dev;
}
EXPORT_SYMBOL(iio_device_alloc);

该函数最后通过 dev_set_name 函数来设置 iio 的设备名称,也就是 dev 目录下的 iio\:device0 节点名,而在前面通过 device_initialize 对 iio 设备进行了初始化,至此,关于创建设备节点前的一系列操作就完成了。

然后重新回到 rockchip_saradc.c 文件的 probe 函数,在该函数的最后通过devm_iio_device_register 函数来注册 iio 设备,在该函数的最后调用了 cdev_device_add 用于注册字符设备并同时创建设备节点,该函数的具体内容如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/**
* cdev_device_add() - add a char device and it's corresponding
* struct device, linkink
* @dev: the device structure
* @cdev: the cdev structure
*
* cdev_device_add() adds the char device represented by @cdev to the system,
* just as cdev_add does. It then adds @dev to the system using device_add
* The dev_t for the char device will be taken from the struct device which
* needs to be initialized first. This helper function correctly takes a
* reference to the parent device so the parent will not get released until
* all references to the cdev are released.
*
* This helper uses dev->devt for the device number. If it is not set
* it will not add the cdev and it will be equivalent to device_add.
*
* This function should be used whenever the struct cdev and the
* struct device are members of the same structure whose lifetime is
* managed by the struct device.
*
* NOTE: Callers must assume that userspace was able to open the cdev and
* can call cdev fops callbacks at any time, even if this function fails.
*/
int cdev_device_add(struct cdev *cdev, struct device *dev)
{
int rc = 0;
// 如果设备 dev 具有有效的设备号 (devt)
if (dev->devt) {
// 设定 cdev 的父对象为设备 dev
cdev_set_parent(cdev, &dev->kobj);

// 注册字符设备,将 cdev 关联到设备号 dev->devt
rc = cdev_add(cdev, dev->devt, 1);
if (rc)
return rc;
}

// 注册设备对象 dev,使其在 sysfs 中可见,并创建 /dev 设备节点
rc = device_add(dev);
if (rc) // 如果 device_add 失败,则删除已注册的 cdev 进行回滚
cdev_del(cdev);

return rc;
}

可以看到该函数的调用了 device_add 函数,通过该函数对 iio 设备节点进行了创建,至此,关于 iio 设备节点是如何创建的就分析完成了

IIO 触发器

先在串口终端进入到 /sys/bus/iio/devices 目录下,具体如下图所示:

iio_sysfs_trigger

在上一个章节中对 sysfs 目录下 iio:device0 文件夹的创建进行了讲解,那相同目录下的iio_sysfs_trigger 文件夹实际上是 iio 的触发器,在内核中需要勾选 SYSFS trigger 才会出现该目录,具体路径如下所示:

1
2
3
4
-> Device Drivers
-> Industrial I/O support (IIO [=y])
-> Triggers - standalone
<*> SYSFS trigger

对应的驱动源码为 drivers/iio/trigger/iio-trig-sysfs.c

iio_sysfs_trig_init()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static int __init iio_sysfs_trig_init(void)
{
int ret;
// 初始化设备结构体
device_initialize(&iio_sysfs_trig_dev);
// 设置设备名称为 "iio_sysfs_trigger
dev_set_name(&iio_sysfs_trig_dev, "iio_sysfs_trigger");
// 将设备添加到内核中
ret = device_add(&iio_sysfs_trig_dev);
if (ret)
put_device(&iio_sysfs_trig_dev);
return ret;
}
module_init(iio_sysfs_trig_init);

static void __exit iio_sysfs_trig_exit(void)
{
device_unregister(&iio_sysfs_trig_dev);
}
module_exit(iio_sysfs_trig_exit);
  • 第 4 行,调用 device_initialize 函数初始化设备对象,确保设备结构体处于可用状态。
  • 第 7 行,调用 dev_set_name 函数为设备设置一个名称,用于标识设备,这里设置的名称为 iio_sysfs_trigger,也就是在 sysfs 子系统中看到的目录。
  • 第 9 行,调用 device_add 函数将设备注册到内核的设备模型中,使其成为系统的一部分。

这三个函数都有一个共同的参数 iio_sysfs_trig_dev,他是一个 struct device 结构体类型的变量,具体内容如下所示:

struct device iio_sysfs_trig_dev

1
2
3
4
5
static struct device iio_sysfs_trig_dev = {
.bus = &iio_bus_type,
.groups = iio_sysfs_trig_groups,
.release = &iio_trigger_sysfs_release,
};

struct bus_type iio_bus_type

1
2
3
4
struct bus_type iio_bus_type = {
.name = "iio",
};
EXPORT_SYMBOL(iio_bus_type);

struct attribute_group iio_sysfs_trig_groups

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
static ssize_t iio_sysfs_trig_add(struct device *dev,
struct device_attribute *attr,
const char *buf,
size_t len)
{
int ret;
unsigned long input;

ret = kstrtoul(buf, 10, &input);
if (ret)
return ret;
ret = iio_sysfs_trigger_probe(input);
if (ret)
return ret;
return len;
}
static DEVICE_ATTR(add_trigger, S_IWUSR, NULL, &iio_sysfs_trig_add);

static int iio_sysfs_trigger_remove(int id);
static ssize_t iio_sysfs_trig_remove(struct device *dev,
struct device_attribute *attr,
const char *buf,
size_t len)
{
int ret;
unsigned long input;

ret = kstrtoul(buf, 10, &input);
if (ret)
return ret;
ret = iio_sysfs_trigger_remove(input);
if (ret)
return ret;
return len;
}

static DEVICE_ATTR(remove_trigger, S_IWUSR, NULL, &iio_sysfs_trig_remove);

static struct attribute *iio_sysfs_trig_attrs[] = {
&dev_attr_add_trigger.attr,
&dev_attr_remove_trigger.attr,
NULL,
};

static const struct attribute_group iio_sysfs_trig_group = {
.attrs = iio_sysfs_trig_attrs,
};

static const struct attribute_group *iio_sysfs_trig_groups[] = {
&iio_sysfs_trig_group,
NULL
};

iio_trigger_sysfs_release()

1
2
3
4
/* Nothing to actually do upon release */
static void iio_trigger_sysfs_release(struct device *dev)
{
}

经过一系列的调用可以确定最终要创建的属性为 dev_attr_add_trigger.attrdev_attr_remove_trigger.attr,这两个属性就是在 sysfs 目录下的 add_triggerremove_trigger, 如下图所示:

iio_sysfs_trigger

iio_sysfs_trig_add()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static ssize_t iio_sysfs_trig_add(struct device *dev,
struct device_attribute *attr,
const char *buf,
size_t len)
{
int ret;
unsigned long input;
// 将用户输入的字符串转换为无符号长整型数字
ret = kstrtoul(buf, 10, &input);
if (ret)
return ret;
// 调用触发器探测函数,尝试添加指定编号的触发器
ret = iio_sysfs_trigger_probe(input);
if (ret)
return ret;
// 操作成功,返回输入数据的长度
return len;
}
// 定义一个设备属性文件 "add_trigger",仅允许用户写入(S_IWUSR),写入时会调用 iio_sysfs_trig_add 函数处理
static DEVICE_ATTR(add_trigger, S_IWUSR, NULL, &iio_sysfs_trig_add);

来到/sys/bus/iio/devices/iio_sysfs_trigger 目录下,向 add_trigger 写入 0,就会生成一个名为 trigger0 的文件夹,如下图所示:

echo 0 > add_trigger

iio_sysfs_trig_remove()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
static int iio_sysfs_trigger_remove(int id);
static ssize_t iio_sysfs_trig_remove(struct device *dev,
struct device_attribute *attr,
const char *buf,
size_t len)
{
int ret;
unsigned long input;

// 将用户输入的字符串转换为无符号长整型数字
ret = kstrtoul(buf, 10, &input);
if (ret)
return ret;
// 调用触发器移除函数,尝试移除指定编号的触发器
ret = iio_sysfs_trigger_remove(input);
if (ret)
return ret;
// 操作成功,返回输入数据的长度
return len;
}

static DEVICE_ATTR(remove_trigger, S_IWUSR, NULL, &iio_sysfs_trig_remove);

如果想要删掉刚刚创建的触发器,只需要向 remove_trigger 写入 0 即可,具体如下所示:

echo 0 > remove_trigger

iio_sysfs_trigger_probe()

那上面的现象是如何实现的呢,这里先来讲解添加触发文件的 iio_sysfs_trig_add 函数,该函数的核心内容在 iio_sysfs_trigger_probe 函数,函数的具体内容如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
static int iio_sysfs_trigger_probe(int id)
{
// 定义一个指向 iio_sysfs_trig 结构的指针 t,用于后续操作
struct iio_sysfs_trig *t;
int ret; // 用于存储返回值
bool foundit = false; // 标志变量,用于判断是否找到重复的触发器 ID

// 加锁,确保对共享资源 iio_sysfs_trig_list 的访问是线程安全的
mutex_lock(&iio_sysfs_trig_list_mut);
// 遍历 iio_sysfs_trig_list 链表,检查是否存在相同的 ID
list_for_each_entry(t, &iio_sysfs_trig_list, l)
if (id == t->id) { // 如果找到相同的 ID
foundit = true; // 设置标志为 true
break;
}
// 如果找到了重复的 ID,返回错误码 -EINVAL(无效参数)
if (foundit) {
ret = -EINVAL;
goto out1; // 跳转到解锁并返回的代码段
}
// 分配内存以创建一个新的 iio_sysfs_trig 结构
t = kmalloc(sizeof(*t), GFP_KERNEL);
if (t == NULL) { // 如果内存分配失败
ret = -ENOMEM;
goto out1;
}
// 初始化新触发器的 ID
t->id = id;
// 分配一个新的 IIO 触发器,并命名为 "sysfstrig%d",其中 %d 是传入的 ID
t->trig = iio_trigger_alloc("sysfstrig%d", id);
if (!t->trig) { // 如果触发器分配失败
ret = -ENOMEM;
goto free_t;
}

// 设置触发器的属性组
t->trig->dev.groups = iio_sysfs_trigger_attr_groups;
// 设置触发器的操作函数集
t->trig->ops = &iio_sysfs_trigger_ops;
// 设置触发器的父设备
t->trig->dev.parent = &iio_sysfs_trig_dev;
// 将触发器的私有数据设置为当前的 iio_sysfs_trig 结构
iio_trigger_set_drvdata(t->trig, t);

// 初始化中断工作队列,用于处理触发器的工作
init_irq_work(&t->work, iio_sysfs_trigger_work);

// 注册触发器到 IIO 子系统
ret = iio_trigger_register(t->trig);
if (ret)
goto out2;
// 将新触发器添加到全局链表 iio_sysfs_trig_list 中
list_add(&t->l, &iio_sysfs_trig_list);
// 增加模块的引用计数,防止模块被卸载
__module_get(THIS_MODULE);
// 解锁互斥锁,允许其他线程访问共享资源
mutex_unlock(&iio_sysfs_trig_list_mut);
return 0;

out2:
// 如果触发器注册失败,释放触发器资源
iio_trigger_free(t->trig);
free_t:
// 如果触发器分配失败,释放 iio_sysfs_trig 结构的内存
kfree(t);
out1:
// 解锁互斥锁,确保在任何情况下都释放锁
mutex_unlock(&iio_sysfs_trig_list_mut);
return ret;
}

该函数只有一个参数为 id,表示要创建触发器的 ID,接下来对该函数进行详细的分析。

  • 第 11 - 15 行:遍历列表,检查是否存在相同 id 的触发器。
  • 第 22 - 34 行:动态分配内存以存储新的触发器结构体,然后对触发器对象进行初始化,通过传入的 ID 值创建文件名,例如传入 ID 为 0 时,文件名为 sysfstrig0
  • 第 37 - 43 行:分别对触发器的属性组、操作函数、父设备以及私有数据进行设置。
  • 第 46 - 57 行:初始化用于处理触发器工作的中断工作队列,并调用 iio_trigger_register 函数将触发器注册到 IIO 子系统

该函数的重点在第 49 行的 iio_trigger_register 函数,该函数定义在 include/linux/iio/trigger.h 中,具体内容如下所示:

1
2
3
4
5
6
7
8
/**
* iio_trigger_register() - register a trigger with the IIO core
* @trig_info: trigger to be registered
**/
#define iio_trigger_register(trig_info) \
__iio_trigger_register((trig_info), THIS_MODULE)
int __iio_trigger_register(struct iio_trigger *trig_info,
struct module *this_mod);

__iio_trigger_register()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
int __iio_trigger_register(struct iio_trigger *trig_info,
struct module *this_mod)
{
int ret;

// 设置触发器的模块拥有者为当前模块
trig_info->owner = this_mod;

// 为触发器分配一个唯一的 ID,使用 ida_simple_get 从全局 ID 分配器中获取
trig_info->id = ida_simple_get(&iio_trigger_ida, 0, 0, GFP_KERNEL);
if (trig_info->id < 0)
return trig_info->id;

/* Set the name used for the sysfs directory etc */
// 设置触发器设备的名称,用于 sysfs 目录等,格式为 "trigger%ld"
dev_set_name(&trig_info->dev, "trigger%ld",
(unsigned long) trig_info->id);

// 将触发器设备添加到设备模型中
ret = device_add(&trig_info->dev);
if (ret) // 如果设备添加失败,跳转到错误处理标签 error_unregister_id
goto error_unregister_id;

/* Add to list of available triggers held by the IIO core */
// 锁定触发器列表,确保线程安全
mutex_lock(&iio_trigger_list_lock);
// 检查是否已经存在同名的触发器
if (__iio_trigger_find_by_name(trig_info->name)) {
// 如果存在重复名称,打印错误日志并跳转到错误处理标签 error_device_del
pr_err("Duplicate trigger name '%s'\n", trig_info->name);
ret = -EEXIST;
goto error_device_del;
}
// 将触发器添加到 IIO 核心维护的触发器列表中
list_add_tail(&trig_info->list, &iio_trigger_list);
mutex_unlock(&iio_trigger_list_lock); // 解锁触发器列表

return 0;

error_device_del:
mutex_unlock(&iio_trigger_list_lock);
device_del(&trig_info->dev);
error_unregister_id:
ida_simple_remove(&iio_trigger_ida, trig_info->id);
return ret;
}
EXPORT_SYMBOL(__iio_trigger_register);

该函数的主要功能是将一个 IIO 触发器注册到系统中,首先为触发器分配唯一ID 并设置设备名称,然后在第 20 行将触发器设备添加到 Linux 设备模型中,最后在第 35 行并将触发器加入 IIO 核心维护的全局触发器链表中。

iio_trigger_alloc()

iio_sysfs_trigger_probe()在第 30 行调用t->trig = iio_trigger_alloc("sysfstrig%d", id);,其中的iio_trigger_alloc函数定义如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct iio_trigger *iio_trigger_alloc(const char *fmt, ...)
{
struct iio_trigger *trig;
va_list vargs;

// 初始化可变参数列表
va_start(vargs, fmt);
// 调用 viio_trigger_alloc 函数,传入格式化字符串和可变参数,分配并初始化触发器
trig = viio_trigger_alloc(fmt, vargs);
// 结束可变参数列表的使用
va_end(vargs);

// 返回分配的触发器结构体指针,如果分配失败则返回 NULL
return trig;
}
EXPORT_SYMBOL(iio_trigger_alloc);

该函数的核心在第 9 行的 viio_trigger_alloc 函数,用于传入格式化字符串和可变参数,分配并初始化触发器,viio_trigger_alloc 函数的具体内容如下所示:

viio_trigger_alloc()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
static __printf(1, 0)
struct iio_trigger *viio_trigger_alloc(const char *fmt, va_list vargs)
{
struct iio_trigger *trig;
int i;

// 分配一个 iio_trigger 结构体的内存,并初始化为 0
trig = kzalloc(sizeof *trig, GFP_KERNEL);
if (!trig) // 如果分配失败,返回 NULL
return NULL;

// 设置触发器设备的类型和总线类型
trig->dev.type = &iio_trig_type;
trig->dev.bus = &iio_bus_type;
device_initialize(&trig->dev); // 初始化设备结构体

mutex_init(&trig->pool_lock); // 初始化触发器的互斥锁,用于资源保护
// 分配中断描述符,用于触发器的子中断
trig->subirq_base = irq_alloc_descs(-1, 0,
CONFIG_IIO_CONSUMERS_PER_TRIGGER,
0);
if (trig->subirq_base < 0) // 如果分配失败,跳转到错误处理
goto free_trig;

// 根据格式化字符串生成触发器名称
trig->name = kvasprintf(GFP_KERNEL, fmt, vargs);
if (trig->name == NULL)
goto free_descs;

// 配置子中断芯片的相关信息
trig->subirq_chip.name = trig->name; // 设置中断芯片名称
trig->subirq_chip.irq_mask = &iio_trig_subirqmask; // 设置中断屏蔽函数
trig->subirq_chip.irq_unmask = &iio_trig_subirqunmask; // 设置中断解除屏蔽函数
// 配置每个子中断的行为
for (i = 0; i < CONFIG_IIO_CONSUMERS_PER_TRIGGER; i++) {
irq_set_chip(trig->subirq_base + i, &trig->subirq_chip); // 设置中断芯片
irq_set_handler(trig->subirq_base + i, &handle_simple_irq); // 设置中断处理函数
irq_modify_status(trig->subirq_base + i,
IRQ_NOREQUEST | IRQ_NOAUTOEN, IRQ_NOPROBE); // 修改中断状态标志
}

return trig; // 成功返回触发器结构体

free_descs:
// 释放已分配的中断描述符
irq_free_descs(trig->subirq_base, CONFIG_IIO_CONSUMERS_PER_TRIGGER);
free_trig:
// 释放触发器结构体内存
kfree(trig);
return NULL;
}

该函数 viio_trigger_alloc 的作用是动态分配并初始化一个 IIO 触发器(iio_trigger)结构体,为其设置设备类型、总线类型、名称、中断描述符以及子中断的相关配置,那这里完善的 trig->subirq_chip 成员是在哪个地方使用的呢?在前面的章节中讲解 IIO 设备注册函数时,在__iio_device_register 函数的 55-56 行也有 IIO 触发器相关的代码,具体如下所示:

1
2
3
// 如果设备支持所有触发模式,则注册触发消费者
if (indio_dev->modes & INDIO_ALL_TRIGGERED_MODES)
iio_device_register_trigger_consumer(indio_dev);

iio_device_register_trigger_consumer 函数用来注册触发消费者,该函数定义在 drivers/iio/industrialio-trigger.c 文件中,具体内容如下所示:

1
2
3
4
5
6
void iio_device_register_trigger_consumer(struct iio_dev *indio_dev)
{
// 将触发器消费者属性组添加到 IIO 设备的属性组列表中
indio_dev->groups[indio_dev->groupcounter++] =
&iio_trigger_consumer_attr_group;
}

该函数要创建的属性为 iio_trigger_consumer_attr_group,它的结构体变量内容如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
static DEVICE_ATTR(current_trigger, S_IRUGO | S_IWUSR,
iio_trigger_read_current,
iio_trigger_write_current);

static struct attribute *iio_trigger_consumer_attrs[] = {
&dev_attr_current_trigger.attr,
NULL,
};

static const struct attribute_group iio_trigger_consumer_attr_group = {
.name = "trigger",
.attrs = iio_trigger_consumer_attrs,
};

经过一系列追踪,最终可以确定会在 sysfs 目录下创建名为 current_trigger 的属性,并且该属性有 iio_trigger_read_current 读函数,以及 iio_trigger_write_current 写函数。

iio_trigger_read_current()

iio_trigger_read_current 读函数相关的内容如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
* iio_trigger_read_current() - trigger consumer sysfs query current trigger
* @dev: device associated with an industrial I/O device
* @attr: pointer to the device_attribute structure that
* is being processed
* @buf: buffer where the current trigger name will be printed into
*
* For trigger consumers the current_trigger interface allows the trigger
* used by the device to be queried.
*
* Return: a negative number on failure, the number of characters written
* on success or 0 if no trigger is available
*/
static ssize_t iio_trigger_read_current(struct device *dev,
struct device_attribute *attr,
char *buf)
{
// 将设备结构体转换为 IIO 设备结构体
struct iio_dev *indio_dev = dev_to_iio_dev(dev);

// 如果 IIO 设备当前绑定了一个触发器(trig 不为空)
if (indio_dev->trig)
// 将触发器的名称写入缓冲区 buf,并返回写入的字符数
return sprintf(buf, "%s\n", indio_dev->trig->name);
// 如果没有绑定触发器,返回 0,表示没有数据可读
return 0;
}
iio_trigger_write_current()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
/**
* iio_trigger_write_current() - trigger consumer sysfs set current trigger
* @dev: device associated with an industrial I/O device
* @attr: device attribute that is being processed
* @buf: string buffer that holds the name of the trigger
* @len: length of the trigger name held by buf
*
* For trigger consumers the current_trigger interface allows the trigger
* used for this device to be specified at run time based on the trigger's
* name.
*
* Return: negative error code on failure or length of the buffer
* on success
*/
static ssize_t iio_trigger_write_current(struct device *dev,
struct device_attribute *attr,
const char *buf,
size_t len)
{
// 将设备结构体转换为 IIO 设备结构体
struct iio_dev *indio_dev = dev_to_iio_dev(dev);
struct iio_trigger *oldtrig = indio_dev->trig; // 保存当前绑定的触发器
struct iio_trigger *trig; // 新触发器指针
int ret;

mutex_lock(&indio_dev->mlock); // 加锁保护设备状态
if (indio_dev->currentmode == INDIO_BUFFER_TRIGGERED) { // 将设备结构体转换为 IIO 设备结构体
mutex_unlock(&indio_dev->mlock); // 解锁并返回 -EBUSY(设备忙)
return -EBUSY;
}
if (indio_dev->trig_readonly) { // 如果触发器是只读的
mutex_unlock(&indio_dev->mlock); // 解锁并返回 -EPERM(无权限)
return -EPERM;
}
mutex_unlock(&indio_dev->mlock); // 解锁

trig = iio_trigger_acquire_by_name(buf); // 根据输入名称获取新触发器
if (oldtrig == trig) { // 如果新旧触发器相同,直接返回成功
ret = len;
goto out_trigger_put;
}

if (trig && indio_dev->info->validate_trigger) { // 验证新触发器是否兼容设备
ret = indio_dev->info->validate_trigger(indio_dev, trig);
if (ret) // 验证失败,跳转到错误处理
goto out_trigger_put;
}

if (trig && trig->ops && trig->ops->validate_device) { // 验证设备是否兼容触发器
ret = trig->ops->validate_device(trig, indio_dev);
if (ret) // 验证失败,跳转到错误处理
goto out_trigger_put;
}

indio_dev->trig = trig; // 更新设备的触发器为新触发器

if (oldtrig) { // 如果存在旧触发器,解除其与设备的关联
if (indio_dev->modes & INDIO_EVENT_TRIGGERED)
iio_trigger_detach_poll_func(oldtrig,
indio_dev->pollfunc_event);
iio_trigger_put(oldtrig); // 减少旧触发器的引用计数
}
if (indio_dev->trig) { // 如果新触发器存在,将其与设备关联
if (indio_dev->modes & INDIO_EVENT_TRIGGERED)
iio_trigger_attach_poll_func(indio_dev->trig,
indio_dev->pollfunc_event);
}

return len; // 返回写入长度,表示成功

out_trigger_put:
if (trig) // 如果新触发器存在,减少其引用计数
iio_trigger_put(trig);
return ret;
}

第 65 行调用 iio_trigger_attach_poll_func 函数,该函数的具体内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
/* Complexity in here.  With certain triggers (datardy) an acknowledgement
* may be needed if the pollfuncs do not include the data read for the
* triggering device.
* This is not currently handled. Alternative of not enabling trigger unless
* the relevant function is in there may be the best option.
*/
/* Worth protecting against double additions? */
int iio_trigger_attach_poll_func(struct iio_trigger *trig,
struct iio_poll_func *pf)
{
int ret = 0;
// 检查触发器的资源池是否为空,判断触发器是否未被使用
bool notinuse
= bitmap_empty(trig->pool, CONFIG_IIO_CONSUMERS_PER_TRIGGER);

/* Prevent the module from being removed whilst attached to a trigger */
/* 防止在触发器被使用时模块被卸载 */
__module_get(pf->indio_dev->driver_module);

/* Get irq number */
/* 获取触发器的中断号 */
pf->irq = iio_trigger_get_irq(trig);
if (pf->irq < 0) {
pr_err("Could not find an available irq for trigger %s, CONFIG_IIO_CONSUMERS_PER_TRIGGER=%d limit might be exceeded\n",
trig->name, CONFIG_IIO_CONSUMERS_PER_TRIGGER);
goto out_put_module;
}

/* Request irq */
/* 请求线程化中断 */
ret = request_threaded_irq(pf->irq, pf->h, pf->thread,
pf->type, pf->name,
pf);
if (ret < 0)
goto out_put_irq;

/* Enable trigger in driver */
/* 如果触发器支持设置状态且未被使用,则启用触发器 */
if (trig->ops && trig->ops->set_trigger_state && notinuse) {
ret = trig->ops->set_trigger_state(trig, true);
if (ret < 0)
goto out_free_irq;
}

/*
* Check if we just registered to our own trigger: we determine that
* this is the case if the IIO device and the trigger device share the
* same parent device.
*/
/*
* 检查是否注册到自己的触发器:
* 判断依据是 IIO 设备和触发器设备是否有相同的父设备。
*/
if (pf->indio_dev->dev.parent == trig->dev.parent)
trig->attached_own_device = true;

return ret;

out_free_irq:
free_irq(pf->irq, pf);
out_put_irq:
iio_trigger_put_irq(trig, pf->irq);
out_put_module:
module_put(pf->indio_dev->driver_module);
return ret;
}

该函数用于将一个轮询函数附加到指定的触发器(trigger),并完成中断请求、触发器状态设置等操作,这里的中断正是上面 viio_trigger_alloc 所完善的 trig->subirq_chip 成员,IIO 触发器通过中断的方式去执行对应的事件。

IIO 数据读取分析

编写 ADC 驱动实验的时候需要调用 iio_read_channel_raw 函数来读取 ADC 通道的实际值,但并没有对该函数进行详细的讲解,在本章节中将会对 iio_read_channel_raw 读函数进行详细的讲解。该函数定义在 drivers/iio/inkern.c 文件中,具体内容如下所示:

iio_read_channel_raw()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// drivers/iio/inkern.c
int iio_read_channel_raw(struct iio_channel *chan, int *val)
{
int ret;

// 加锁保护 IIO 设备的信息结构体,防止并发访问导致数据不一致
mutex_lock(&chan->indio_dev->info_exist_lock);
// 检查 IIO 设备的信息结构体是否存在,如果为 NULL,说明设备不可用
if (chan->indio_dev->info == NULL) {
ret = -ENODEV;
goto err_unlock;
}

// 调用 iio_channel_read 函数读取通道的原始数据(IIO_CHAN_INFO_RAW)
ret = iio_channel_read(chan, val, NULL, IIO_CHAN_INFO_RAW);
err_unlock:
mutex_unlock(&chan->indio_dev->info_exist_lock);

return ret;
}
EXPORT_SYMBOL_GPL(iio_read_channel_raw);

iio_channel_read()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
static int iio_channel_read(struct iio_channel *chan, int *val, int *val2,
enum iio_chan_info_enum info)
{
int unused; // 定义一个未使用的变量,用于处理 val2 为 NULL 的情况
int vals[INDIO_MAX_RAW_ELEMENTS]; // 用于存储多值读取的结果
int ret; // 保存函数返回值
int val_len = 2; // 默认读取两个值(val 和 val2)

if (val2 == NULL)
val2 = &unused;

// 检查通道是否支持指定的信息类型(info),如果不支持则返回 -EINVAL
if (!iio_channel_has_info(chan->channel, info))
return -EINVAL;

// 如果设备支持批量读取(read_raw_multi),则调用该函数
if (chan->indio_dev->info->read_raw_multi) {
ret = chan->indio_dev->info->read_raw_multi(chan->indio_dev,
chan->channel, INDIO_MAX_RAW_ELEMENTS,
vals, &val_len, info);
*val = vals[0]; // 将第一个值赋给 val
*val2 = vals[1]; // 将第二个值赋给 val2
} else
// 否则调用单值读取函数 read_raw
ret = chan->indio_dev->info->read_raw(chan->indio_dev,
chan->channel, val, val2, info);

// 返回读取结果,成功时返回 0,失败时返回负值错误码
return ret;
}

由于并未实现批量读取函数 indio_dev->info->read_raw_multi 所以进入的是第二个分支,即通过indio_dev->info->read_raw 函数来进行读取的。

iio_device_add_info_mask_type()

那 sysfs 子系统中的属性文件是如何读取到 iio 值的呢,在前面讲解 iio_device_register_sysfs 函数时进行了分析,最终会调用 iio_device_add_info_mask_type 函数,在 iio_device_add_info_mask_type 函数中就包括读函数,iio_device_add_info_mask_type 函数如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// drivers/iio/industrialio-core.c
static int iio_device_add_info_mask_type(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
enum iio_shared_by shared_by,
const long *infomask)
{
struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev);
int i, ret, attrcount = 0;

// 遍历信息掩码中的每个有效比特位(即设置为 1 的位)
for_each_set_bit(i, infomask, sizeof(*infomask)*8) {
// 检查索引是否超出后缀数组的范围
if (i >= ARRAY_SIZE(iio_chan_info_postfix))
return -EINVAL; // 如果超出范围,返回无效参数错误

// 调用函数将属性添加到 sysfs 接口中
ret = __iio_add_chan_devattr(iio_chan_info_postfix[i], // 属性名称后缀
chan, // 通道指针
&iio_read_channel_info, // 读回调函数
&iio_write_channel_info, // 写回调函数
i, // 属性索引
shared_by, // 共享类型
&indio_dev->dev, // 设备指针
&iio_dev_opaque->channel_attr_list); // 属性列表

// 如果返回 -EBUSY 且属性不是独立类型的,则跳过该属性
if ((ret == -EBUSY) && (shared_by != IIO_SEPARATE))
continue;
// 如果发生其他错误,直接返回错误码
else if (ret < 0)
return ret;
// 成功添加一个属性,累加计数
attrcount++;
}

// 返回成功添加的属性总数
return attrcount;
}

在该函数的第 19 行指定的就是 iio 通道的读函数 iio_read_channel_info,该函数的具体内容如下所示:

iio_read_channel_info()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
static ssize_t iio_read_channel_info(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct iio_dev *indio_dev = dev_to_iio_dev(dev); // 将设备结构体转换为 IIO 设备结构体
struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); // 将设备属性转换为 IIO 属性结构体
int vals[INDIO_MAX_RAW_ELEMENTS]; // 用于存储读取的多值数据
int ret; // 存储读取操作的返回值
int val_len = 2; // 默认读取两个值

// 如果设备支持批量读取(read_raw_multi),则调用该函数
if (indio_dev->info->read_raw_multi)
ret = indio_dev->info->read_raw_multi(indio_dev, this_attr->c,
INDIO_MAX_RAW_ELEMENTS,
vals, &val_len,
this_attr->address);
else
// 否则调用单值读取函数 read_raw
ret = indio_dev->info->read_raw(indio_dev, this_attr->c,
&vals[0], &vals[1], this_attr->address);

// 如果读取失败,直接返回错误码
if (ret < 0)
return ret;

// 格式化读取的值并写入缓冲区 buf,返回写入的字节数
return iio_format_value(buf, ret, val_len, vals);
}

该函数的核心内容为第 12-20 行的逻辑判断,而由于并未实现批量读取函数 read_raw_multi 所以进入的是第二个分支,即和前面分析 iio_read_channel_raw 时相同,也是通过 indio_dev->info->read_raw 函数来进行读取的。而 indio_dev->info 是在 drivers/iio/adc/rockchip_saradc.c 文件中的 probe 函数通过indio_dev->info = &rockchip_saradc_iio_info;,而rockchip_saradc_iio_info如下:

1
2
3
static const struct iio_info rockchip_saradc_iio_info = {
.read_raw = rockchip_saradc_read_raw,
};

最终指向了 rockchip_saradc_read_raw 函数,该函数的具体内容如下所示:

rockchip_saradc_read_raw()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
static int rockchip_saradc_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int *val, int *val2, long mask)
{
struct rockchip_saradc *info = iio_priv(indio_dev); // 获取 IIO 设备的私有数据
int ret;

#ifdef CONFIG_ROCKCHIP_SARADC_TEST_CHN
if (info->test) // 如果处于测试模式,直接返回 0
return 0;
#endif
switch (mask) { // 根据 mask 参数选择读取类型
case IIO_CHAN_INFO_RAW: // 读取原始 ADC 数据
mutex_lock(&indio_dev->mlock); // 加锁保护设备状态

if (info->suspended) { // 如果设备已挂起,返回 -EBUSY(设备忙)
mutex_unlock(&indio_dev->mlock);
return -EBUSY;
}

ret = rockchip_saradc_conversion(info, chan); // 转换
if (ret) {
rockchip_saradc_power_down(info);
mutex_unlock(&indio_dev->mlock);
return ret;
}

*val = info->last_val;
mutex_unlock(&indio_dev->mlock);
return IIO_VAL_INT;
case IIO_CHAN_INFO_SCALE: // 读取缩放比例
/* It is a dummy regulator */
if (info->uv_vref < 0) /* 如果参考电压无效,直接返回错误码 */
return info->uv_vref;

*val = info->uv_vref / 1000; // 计算参考电压(单位:毫伏)
*val2 = chan->scan_type.realbits;
return IIO_VAL_FRACTIONAL_LOG2;
default:
return -EINVAL;
}
}

rockchip_saradc_isr()

在 ADC 的设备树节点中有关于中断相关的描述,当转换完成之后会生成对应的中断信号,而驱动里关于该中断的服务函数的申请定义在 probe 函数中,通过调用 ret = devm_request_irq(&pdev->dev, irq, rockchip_saradc_isr, 0, dev_name(&pdev->dev), info);通过 devm_request_irq 函数定义了一个名为 rockchip_saradc_isr 的中断服务函数,该函数的具体内容如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
static irqreturn_t rockchip_saradc_isr(int irq, void *dev_id)
{
struct rockchip_saradc *info = dev_id; // 获取设备私有数据
#ifdef CONFIG_ROCKCHIP_SARADC_TEST_CHN
unsigned long flags; // 定义用于保存中断状态的变量
#endif

/* Read value */
/* 读取 ADC 转换结果 */
info->last_val = rockchip_saradc_read(info);
#ifndef CONFIG_ROCKCHIP_SARADC_TEST_CHN
info->last_val &= GENMASK(info->last_chan->scan_type.realbits - 1, 0);
#endif

rockchip_saradc_power_down(info);

complete(&info->completion);
#ifdef CONFIG_ROCKCHIP_SARADC_TEST_CHN
spin_lock_irqsave(&info->lock, flags);
if (info->test) { // 如果处于测试模式
pr_info("chn[%d] val = %d\n", info->chn, info->last_val);
mod_delayed_work(info->wq, &info->work, msecs_to_jiffies(100));
}
spin_unlock_irqrestore(&info->lock, flags);
#endif
return IRQ_HANDLED;
}

最终读取到的数据会赋值给 rockchip_saradc_read_raw 函数的变量 val

ADC 按键驱动分析

设备树:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
//ADC 按键
adc_keys: adc-keys {
compatible = "adc-keys";
io-channels = <&saradc 0>;
io-channel-names = "buttons";
keyup-threshold-microvolt = <1800000>;
poll-interval = <100>;

vol-up-key {
label = "volume up";
linux,code = <KEY_VOLUMEUP>;
press-threshold-microvolt = <1750>;
};

vol-down-key {
label = "volume down";
linux,code = <KEY_VOLUMEDOWN>;
press-threshold-microvolt = <297500>;
};

menu-key {
label = "menu";
linux,code = <KEY_MENU>;
press-threshold-microvolt = <980000>;
};

back-key {
label = "back";
linux,code = <KEY_BACK>;
press-threshold-microvolt = <1305500>;
};
};

adc_keys_probe()

ADC 按键对应的驱动程序为 input/keyboard/adc-keys.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
static int adc_keys_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev; // 获取设备结构体
struct adc_keys_state *st; // 定义状态结构体指针
struct input_dev *input; // 定义输入设备结构体指针
enum iio_chan_type type; // 定义 IIO 通道类型
int i, value; // 定义循环变量和临时变量
int error; // 定义错误码

// 分配内存给状态结构体adc_keys_state,使用 devm_kzalloc 确保在设备卸载时自动释放
st = devm_kzalloc(dev, sizeof(*st), GFP_KERNEL);
if (!st)
return -ENOMEM;

// 获取 IIO 通道,用于读取按键的模拟信号
st->channel = devm_iio_channel_get(dev, "buttons");
if (IS_ERR(st->channel))
return PTR_ERR(st->channel);

// 检查通道是否有效
if (!st->channel->indio_dev)
return -ENXIO;

// 获取通道类型(如电压、电流等)
error = iio_get_channel_type(st->channel, &type);
if (error < 0)
return error;

// 确保通道类型为电压
if (type != IIO_VOLTAGE) {
dev_err(dev, "Incompatible channel type %d\n", type);
return -EINVAL;
}

// 从设备树中读取"keyup-threshold-microvolt"属性值,表示按键松开时的电压阈值
if (device_property_read_u32(dev, "keyup-threshold-microvolt",
&st->keyup_voltage)) {
dev_err(dev, "Invalid or missing keyup voltage\n");
return -EINVAL;
}
// 将微伏转换为毫伏
st->keyup_voltage /= 1000;

// 加载按键映射表
error = adc_keys_load_keymap(dev, st);
if (error)
return error;

// 分配一个输入设备
input = devm_input_allocate_device(dev);
if (!input) {
dev_err(dev, "failed to allocate input device\n");
return -ENOMEM;
}

input_set_drvdata(input, st);

// 设置输入设备的基本信息
input->name = pdev->name; // 设置设备名称
input->phys = "adc-keys/input0"; // 设置物理路径

// 设置输入设备的 ID 信息
input->id.bustype = BUS_HOST; // 总线类型为主机
input->id.vendor = 0x0001; // 厂商 ID
input->id.product = 0x0001; // 产品 ID
input->id.version = 0x0100; // 版本号

// 设置支持的事件类型为按键事件
__set_bit(EV_KEY, input->evbit);
for (i = 0; i < st->num_keys; i++) // 遍历按键映射表
__set_bit(st->map[i].keycode, input->keybit); // 设置支持的按键码

// 如果设备树中设置了"autorepeat"属性,则启用自动重复功能
if (device_property_read_bool(dev, "autorepeat"))
__set_bit(EV_REP, input->evbit);

// 注册轮询输入设备
error = input_setup_polling(input, adc_keys_poll);
if (error) {
dev_err(dev, "Unable to set up polling: %d\n", error);
return error;
}

// 从设备树中读取"poll-interval"属性值,设置轮询间隔
if (!device_property_read_u32(dev, "poll-interval", &value))
input_set_poll_interval(input, value);

error = input_register_device(input);
if (error) {
dev_err(dev, "Unable to register input device: %d\n", error);
return error;
}

return 0;
}

struct input_devinput_register_register()都是属于 input 子系统中的函数。