Cover image for Linux IIO

Linux IIO


Timeline

Timeline

2026-02-15

init

This article introduces the basic concepts and main features of the Linux IIO (Industrial Input/Output) subsystem, including unified interfaces, support for multiple device types, and event-triggered mechanisms. Taking the ADC device of RK3568 as an example, it analyzes the driver matching and registration process of IIO devices in detail.

Linux Driver Notes

Table of ContentsLinks
1. Linux Driver Framework
2. Linux Driver Loading Logic
3. Character Device Basics
4. Concurrency and Competition
5. Advanced Character Device Progression
6. Interrupts
7. Platform Bus
8. Device Tree
9. Device Model
10. Hot Plug
11. pinctrl Subsystem
12. gpio Subsystem
13. Input Subsystem
14. Single Bus
15. I2C
16. SPI
17. UART
18. PWM
19. RTC
20. Watchdog
21. CAN
22. Network Device
23. ADC
24. IIO
25. USB
26. LCD

Introduction to IIO Subsystem

IIO(Industrial Input/Output, Industrial Input/Output) is a subsystem in the Linux kernel, specifically designed to handle devices related to analog-to-digital converters (ADC), digital-to-analog converters (DAC), and other industrial sensors.

The development of the IIO subsystem began in 2009. With the advancement of embedded systems and the Internet of Things (IoT) technology, an increasing number of devices need to interact with analog or digital signals. For example:

  1. Analog-to-Digital Conversion (ADC): Converts analog signals (such as temperature, pressure, light intensity, etc.) into digital signals.
  2. Digital-to-Analog Conversion (DAC): Converts digital signals into analog signals (such as controlling motor speed or volume).
  3. Other sensors: accelerometers, gyroscopes, magnetometers, etc.

These devices have different functions, but all need to interact with the operating system. Without a unified framework, each device would require a separate driver to be developed, leading to code duplication, maintenance difficulties, and performance issues. Therefore, the Linux community introduced the IIO subsystem to address these problems.

Features of IIO

  1. Unified Interface: IIO provides a standardized API and data structure, allowing different types of devices to interact using the same interface. User-space applications can access IIO devices through standard file system interfaces (/sys/bus/iio/devices/) to achieve unified management and operation of hardware such as sensors and converters.
  2. Support for Multiple Device Types: The IIO subsystem supports multiple device types, including ADC (Analog-to-Digital Converter), DAC (Digital-to-Analog Converter), temperature sensors, light sensors, pressure sensors, accelerometers, and gyroscopes. This broad device compatibility makes IIO an ideal choice for handling industrial and consumer-grade sensors.
  3. Event and Trigger Mechanism: IIO supports event-based trigger mechanisms, such as triggering an interrupt when a sensor value exceeds a threshold. It also supports hardware triggers and software triggers, suitable for real-time data acquisition scenarios, meeting time-sensitive application requirements.

IIO Registration Process Analysis

First, find the ADC device node in the RK3568 device tree, the specific content is as follows:

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 Buttons
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 refers to Successive Approximation Register Analog-to-Digital Converter
Throughsaradc’scompatiblematching value, the corresponding driver file is found asdrivers/iio/adc/rockchip_saradc.c

struct iio_dev

struct iio_devtype variable, which is used to describe the IIO device, containing all key information and resources of the device, specifically defined ininclude/linux/iio/iio.hin the file. Through this structure, the IIO subsystem can uniformly manage and operate various industrial I/O devices (such as ADC, DAC, sensors, etc.)

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; // unique identifier of the device
struct module *driver_module; // pointer to the driver module

int modes; // supported operating modes (e.g., direct mode, buffer mode)
int currentmode; // current operating mode
struct device dev; // nested Linux device model structure

struct iio_buffer *buffer; // data buffer pointer
int scan_bytes; // number of bytes of scan data
struct mutex mlock; // mutex lock, protecting shared resources

const unsigned long *available_scan_masks; // available scan mask
unsigned masklength; // mask length
const unsigned long *active_scan_mask; // currently active scan mask
bool scan_timestamp; // whether to include timestamp in scan data
unsigned scan_index_timestamp; // index of timestamp in scan data
struct iio_trigger *trig; // trigger pointer
bool trig_readonly; // Trigger is read-only
struct iio_poll_func *pollfunc; // Poll function pointer
struct iio_poll_func *pollfunc_event; // Event poll function pointer

struct iio_chan_spec const *channels; // Channel specification array
int num_channels; // Number of channels

const char *name; // Device name
const char *label;
const struct iio_info *info; // IIO device operations function set
clockid_t clock_id; // Clock ID
struct mutex info_exist_lock; // Information existence lock
const struct iio_buffer_setup_ops *setup_ops; // Buffer setup operations function set
struct cdev chrdev; // Character device structure
#define IIO_MAX_GROUPS 6
const struct attribute_group *groups[IIO_MAX_GROUPS + 1]; // Attribute group array
int groupcounter; // Attribute group counter

unsigned long flags; // Flag bits for storing device status
void *priv;
};

Can bestruct iio_devConsidered as a base class, it is used to describe IIO devices, but ADC and DAC devices both belong to IIO. IIO actually distinguishes different devices through thestruct rockchip_saradcstructure described in the probe function,rockchip_saradcThe specific content of the structure is as follows:

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; // Virtual address mapping of registers, used to access hardware registers
struct clk *pclk; // APB bus clock pointer
struct clk *clk; // SARADC converter clock pointer
struct completion completion; // Completion variable, used for synchronization operations (such as waiting for conversion to complete)
struct regulator *vref; // Reference voltage regulator pointer
int uv_vref; // Actual value of reference voltage (unit: microvolt)
struct reset_control *reset; // Reset controller pointer
const struct rockchip_saradc_data *data; // Device-specific data (such as configuration parameters)
u16 last_val; // Last ADC conversion result
const struct iio_chan_spec *last_chan;
struct notifier_block nb;
bool suspended; // Flag bit, indicating whether the device is in a suspended state
#ifdef CONFIG_ROCKCHIP_SARADC_TEST_CHN
bool test; // Timer, used for testing channels
u32 chn; // Flag bit, indicating whether it is in test mode
spinlock_t lock; // Spinlock, used to protect shared resources
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; // Define SARADC device private data structure pointer
struct device_node *np = pdev->dev.of_node; // Get device tree node
struct iio_dev *indio_dev = NULL; // Define IIO device structure pointer
struct resource *mem; // Used to get memory resources
const struct of_device_id *match; // Used to match device tree nodes
int ret;
int irq;

// Check if device tree node exists
if (!np)
return -ENODEV;

// Allocate IIO device structure and allocate private data space for it
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); // Get the private data area of the IIO device

// Match device ID in device tree
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; // Get device matching data (such as configuration parameters)

/* 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;
}

// Get the register address resource of the device
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
// Map registers to virtual address space
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
*/
/*
* Reset controller is an optional property,For compatibility with old device trees,Allow omitting this attribute
*/
info->reset = devm_reset_control_get_exclusive(&pdev->dev,
"saradc-apb");
if (IS_ERR(info->reset)) {// Failed to get reset controller
ret = PTR_ERR(info->reset);
if (ret != -ENOENT)
return ret;

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

// Initialize completion variable for synchronization operations
init_completion(&info->completion);

// Get interrupt number
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;

// Register interrupt handler
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;
}

// Get APB bus clock
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);
}

// Get SARADC converter clock
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);
}

// Get reference voltage regulator
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);
}

// Perform reset operation if reset controller exists
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.
*/
/*
* Set converter clock frequency,Use clock frequency from device data by default
* User configuration may be supported 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;
}

// Enable reference voltage regulator
ret = regulator_enable(info->vref);
if (ret < 0) {
dev_err(&pdev->dev, "failed to enable vref regulator\n");
return ret;
}
// Register a cleanup action to disable the regulator on device removal
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;
}

// Get actual value of reference voltage
ret = regulator_get_voltage(info->vref);
if (ret < 0) {
dev_err(&pdev->dev, "failed to get voltage\n");
return ret;
}
// Assign the actual value of the reference voltage to info->uv_vref
info->uv_vref = ret;

// Enable the APB bus clock
ret = clk_prepare_enable(info->pclk);
if (ret < 0) {
dev_err(&pdev->dev, "failed to enable pclk\n");
return ret;
}
// Register a cleanup action to disable the APB clock when the device is removed
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;
}

// Enable the SARADC converter clock
ret = clk_prepare_enable(info->clk);
if (ret < 0) {
dev_err(&pdev->dev, "failed to enable converter clock\n");
return ret;
}
// Register a cleanup action to disable the converter clock when the device is removed
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;
}
// Associate the IIO device with the platform device
platform_set_drvdata(pdev, indio_dev);

// Initialize the basic information of the IIO device
indio_dev->name = dev_name(&pdev->dev); // Device name
indio_dev->info = &rockchip_saradc_iio_info; // Operation function set of the IIO device
indio_dev->modes = INDIO_DIRECT_MODE; // Set the working mode to direct mode

indio_dev->channels = info->data->channels; // Channel list
indio_dev->num_channels = info->data->num_channels; // Number of 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"); // Initialize a work queue
INIT_DELAYED_WORK(&info->work, rockchip_saradc_test_work); // Initialize a delayed work
spin_lock_init(&info->lock);
ret = sysfs_create_group(&pdev->dev.kobj, &rockchip_saradc_attr_group);
if (ret)
return ret;

// Register a cleanup action to delete the sysfs attribute group when the device is removed
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
// Register IIO device
return devm_iio_device_register(&pdev->dev, indio_dev);
}

It can be seen thatstruct rockchip_saradcthere is no iio-related description in the structure, so how is it connected to the iio device?

In the probe function, first, through thedevm_iio_device_allocfunction, a contiguous block of memory is allocated to storeiio_devand its private data (rockchip_saradc). Then, using theiio_privfunction to obtain the pointer to the private data area, the twoiio_devandrockchip_saradctype structure variables are connected together, as shown in the following code:

1
2
3
4
5
6
7
       // Allocate the IIO device structure and allocate private data space for it
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); // Get the private data area of the IIO device

Probe source code, in order to match multiple SOC chips through theof_match_devicefunction, the value passed from the device tree is compared withrockchip_saradc_matchfor matching:

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_matchThe content of the struct array variable is as follows

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()is a macro in the Linux kernel used to export the device match table into the module’s ELF metadata, so that the user-space auto-loading mechanism (udev / modprobe) knows:

  • which devices this driver supports
  • when the kernel discovers a matching device, it can automatically load this module

The data corresponding to rk3568 points tork3568_saradc_data, the specific content of this variable is as follows. The main purpose of this code is to define the channel information and specific device parameters of a SARADC device

1
2
3
4
5
6
7
8
9
10
11
static const struct rockchip_saradc_data rk3568_saradc_data = {	
// points to the channel specification array
.channels = rockchip_rk3568_saradc_iio_channels,
// number of channels
.num_channels = ARRAY_SIZE(rockchip_rk3568_saradc_iio_channels),
// converter clock frequency: 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_channelsrepresents the channel specification array. The specific content of this array is as follows. It can be seen that RK3568 has 8 ADC channels

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), // define ADC channel 0, named "adc0"
SARADC_CHANNEL(1, "adc1", 10), // define ADC channel 1, named "adc1"
SARADC_CHANNEL(2, "adc2", 10), // define ADC channel 2, named "adc2"
SARADC_CHANNEL(3, "adc3", 10), // define ADC channel 3, named "adc3"
SARADC_CHANNEL(4, "adc4", 10), // define ADC channel 4, named "adc4"
SARADC_CHANNEL(5, "adc5", 10), // Define ADC channel 5 with the name "adc5"
SARADC_CHANNEL(6, "adc6", 10), // Define ADC channel 6 with the name "adc6"
SARADC_CHANNEL(7, "adc7", 10), // Define ADC channel 7 with the name "adc7"
};

The structure used to describe channel rules isstruct iio_chan_spec, which defines the channel type, number, data format, supported attributes, events, and other information, used to distinguish different types of ADC or DAC devices (such as ADC buttons, ambient light sensors, accelerometers, etc.). The specific content of this structure is as follows:

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;
}

The RK3568 ADC channel definition uses theSARADC_CHANNELmacro, and the specific content of this macro is as follows:

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_VOLTAGEChannel type: voltage measurement
  • .indexed = 1Enable index mode, indicating the use ofchannelfield as the index
  • .channel = _indexMain channel number, specified by the macro parameter_indexspecify
  • .info_mask_separate = BIT(IIO_CHAN_INFO_RAW)Individually supported attribute: raw value (RAW)
  • .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE)Attribute shared by type: scaling factor
  • .datasheet_name = _idName in the datasheet, specified by the macro parameter_idspecify

devm_iio_device_register()

the probe function finally returnsreturn devm_iio_device_register(&pdev->dev, indio_dev);, whiledevm_iio_device_registerdefined as follows

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; // define a pointer variable to store the allocated resource management structure
int ret;

// use devres_alloc allocates the device resource management structure and specifies the release function as devm_iio_device_unreg
// sizeof(*ptr) indicates the allocated size is the size of an iio_dev pointer
ptr = devres_alloc(devm_iio_device_unreg, sizeof(*ptr), GFP_KERNEL);
if (!ptr)
return -ENOMEM;

// point the allocated resource management pointer to the passed iio_dev structure
*ptr = indio_dev;

// call the underlying device registration function__iio_device_register registers iio_dev device
ret = __iio_device_register(indio_dev, this_mod);
if (!ret)
devres_add(dev, ptr);// if registration succeeds, add the allocated resource management structure to the device's resource management list
else
devres_free(ptr);// if registration fails, release the previously allocated resource management structure

// return the registration result
return ret;
}
EXPORT_SYMBOL_GPL(__devm_iio_device_register);

__iio_device_register()

__devm_iio_device_registercall in__iio_device_registerto register the IIO device

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;

// assign the current module pointer to indio_driver of dev_module field
indio_dev->driver_module = this_mod;
/* If the calling driver did not initialize of_node, do it here */
/* if the calling driver has not initialized of_node, initialize it here */
// if the device's of_node is NULL, but its parent device's of_node exists, then inherit the parent device's 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);
// check if the scan index is unique
ret = iio_check_unique_scan_index(indio_dev);
if (ret < 0)
return ret;

/* configure elements for the chrdev */
/* configure the relevant elements of the character device */
// generate the device number using the major and minor device numbers
indio_dev->dev.devt = MKDEV(MAJOR(iio_devt), indio_dev->id);

// register the debugfs interface
iio_device_register_debugfs(indio_dev);

// allocate buffer-related sysfs interfaces and masks
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;
}

// register the device's sysfs interface
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;
}
// Register 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 the device supports all trigger modes, register the trigger consumer
if (indio_dev->modes & INDIO_ALL_TRIGGERED_MODES)
iio_device_register_trigger_consumer(indio_dev);

// If the device supports all buffer modes and setup is not set_ops, use the default 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;

// Initialize the character device structure and bind file operation functions
cdev_init(&indio_dev->chrdev, &iio_buffer_fileops);

// Set the module owner of the character device
indio_dev->chrdev.owner = this_mod;

// Add the character device and device structure to the system
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); // Unregister event set
error_free_sysfs:
iio_device_unregister_sysfs(indio_dev); // Unregister sysfs interface
error_buffer_free_sysfs:
iio_buffer_free_sysfs_and_mask(indio_dev); // Release buffer-related sysfs interface and mask
error_unreg_debugfs:
iio_device_unregister_debugfs(indio_dev); // Unregister debugfs interface
return ret;
}
EXPORT_SYMBOL(__iio_device_register);
  • Line 6: Assign the current module pointer toindio_dev->driver_module, recording the kernel module to which the device belongs.
  • Lines 10-11: By checkingindio_dev->dev.of_nodeandindio_dev->dev.parent->of_nodeInitialize the device tree node (of_node), inheriting the device tree node of the parent device.
  • Lines 14-16: Use theiio_check_unique_scan_indexfunction to check if the channel scan index is unique, ensuring no duplicate indices.
  • Line 20: Use theMKDEVfunction to generate a device number, assigning unique identifiers to the major and minor devices.
  • Line 23: Use theiio_device_register_debugfsfunction to register a debugfs interface for debugging and monitoring device status.
  • Line 31: Use theiio_buffer_alloc_sysfs_and_maskfunction to allocate buffer-related sysfs interfaces and masks, but after jumping, it is found that the function is empty, so this part of the code has no practical significance.
  • Line 39: Use theiio_device_register_sysfsfunction to register the device’s sysfs interface, exposing device attributes to user space.
  • Line 47: Use theiio_device_register_eventsetfunction to register an event set, managing events triggered by the device.
  • Lines 55-56: Use theiio_device_register_trigger_consumerThe function registers a trigger consumer, enabling the device to respond to external trigger signals.
  • Lines 59-61: By settingindio_dev->setup_opstonoop_ring_setup_opsprovides default buffer operation functions.
  • Line 64: Through thecdev_initfunction, the character device structure is initialized and file operation functions are bound.
  • Line 67: By settingindio_dev->chrdev.ownertothis_mod, it ensures resources are released when the module is unloaded.
  • Line 70: Through thecdev_device_addfunction, the character device and device structure are added to the system.

The key point of this function is theiio_device_register_eventsetfunction on line 47, which is used to register IIO events. It is defined in thedrivers/iio/industrialio-event.cfile. The specific content of this function is as follows:

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; // Pointer used to traverse the device attribute list
int ret = 0, attrcount_orig = 0, attrcount, attrn; // Define return value, original attribute count, total attribute count, and current attribute index
struct attribute **attr; // Pointer to the attribute array

// If the device has no event attributes and no dynamic events, return directly
if (!(indio_dev->info->event_attrs ||
iio_check_for_dynamic_events(indio_dev)))
return 0;

// Allocate memory to store the event interface structure
ev_int = kzalloc(sizeof(struct iio_event_interface), GFP_KERNEL);
if (ev_int == NULL)
return -ENOMEM;

iio_dev_opaque->event_interface = ev_int;

// Initialize the device attribute linked list head of the event interface
INIT_LIST_HEAD(&ev_int->dev_attr_list);

// Set the interrupt handler function of the event interface
iio_setup_ev_int(ev_int);

// If the device has predefined event attributes, calculate their count
if (indio_dev->info->event_attrs != NULL) {
attr = indio_dev->info->event_attrs->attrs;
while (*attr++ != NULL) // Traverse the attribute array to count the number of original attributes
attrcount_orig++;
}
// Initialize the total attribute count to the number of original attributes
attrcount = attrcount_orig;
// If the device has channels, attempt to add dynamic event configuration attributes
if (indio_dev->channels) {
ret = __iio_add_event_config_attrs(indio_dev); // Add dynamic event attributes
if (ret < 0) // If adding fails, jump to error handling
goto error_free_setup_event_lines;
attrcount += ret; // Update the total attribute count
}

ev_int->group.name = iio_event_group_name;
// Allocate memory to store the attribute array of the event group
ev_int->group.attrs = kcalloc(attrcount + 1,
sizeof(ev_int->group.attrs[0]),
GFP_KERNEL);
// If the device has predefined event attributes, copy them into the attribute array of the event group
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);
// Set the current attribute index to the number of original attributes
attrn = attrcount_orig;
/* Add all elements from the list. */
// Traverse the device attribute linked list and add all dynamic attributes to the event group's attribute array
list_for_each_entry(p, &ev_int->dev_attr_list, l)
ev_int->group.attrs[attrn++] = &p->dev_attr.attr;
// Add the event group to the device's attribute group list
indio_dev->groups[indio_dev->groupcounter++] = &ev_int->group;

return 0;

error_free_setup_event_lines:
// Error handling: release the device attribute linked list
iio_free_chan_devattr_list(&ev_int->dev_attr_list);
// Free the memory of the event interface
kfree(ev_int);
// Set the pointer to null
iio_dev_opaque->event_interface = NULL;
return ret;
}

In the IIO subsystem, common events include threshold events, data ready events, state change events, etc. The structure used to describe the corresponding event in the iio subsystem isstruct iio_event_interface, and the specific content of this structure is as follows:

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, used for event notification mechanism. User space or kernel threads can wait for events to occur through this queue
wait_queue_head_t wait;

// Define a fixed-size FIFO queue (size 16) for storing detected event data.
// Each event data is a`struct iio_event_data`type structure.
DECLARE_KFIFO(det_events, struct iio_event_data, 16);

// Device attribute linked list head, used to manage event-related device attributes (such as thresholds, configurations, etc.)
struct list_head dev_attr_list;

// Flag bit, used to record the status or characteristics of the event interface. For example, whether certain functions are enabled
unsigned long flags;

// Attribute group, used to organize event-related attributes (such as thresholds, configurations, etc.) together and expose them to sysfs
struct attribute_group group;

// Mutex lock, used to protect concurrent access to event data and ensure read safety in a multi-threaded environment.
struct mutex read_lock;
};

Based on the members of this structure variable, it can be inferred that the IIO subsystem uses a FIFO queue to cache detected event data, implements an event notification mechanism via a wait queue to wake up listeners, dynamically maintains event-related attributes through a linked list and exposes them to user space via sysfs for configuration and monitoring, uses a mutex lock to ensure safe concurrent access in a multi-threaded environment, and records the status or characteristics of the event interface with flags, thereby efficiently completing event detection, configuration, storage, notification, and processing.

IIO Device Driver Analysis

IIO File Operations Set Function Analysis

In the__iio_device_registerfunction, callcdev_init(&indio_dev->chrdev, &iio_buffer_fileops);to initialize the character device.

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 function

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; // Get the IIO device instance from the file structure
int __user *ip = (int __user *)arg; // Convert the parameter passed from user space to a user space pointer
int fd; // Used to store the event file descriptor

// If the device's information structure is not initialized, return error code -ENODEV (no device)
if (!indio_dev->info)
return -ENODEV;

// Determine whether it is an IOCTL command to get the event file descriptor
if (cmd == IIO_GET_EVENT_FD_IOCTL) {
fd = iio_event_getfd(indio_dev); // Call the function to get the event file descriptor
if (fd < 0) // If the acquisition fails, directly return the error code
return fd;
// Copy the obtained file descriptor to user space
if (copy_to_user(ip, &fd, sizeof(fd)))
return -EFAULT;
return 0;
}
// If the command does not match, return error code -EINVAL (invalid argument)
return -EINVAL;
}

In the ioctl function, there is only one command, which isIIO_GET_EVENT_FD_IOCTL, and then callediio_event_getfdfunction to obtain the event file descriptor, and throughcopy_to_userfunction copies the obtained file descriptor to user space,iio_event_getfdThe specific content of the function is as follows:

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);
// Get the event interface of the device
struct iio_event_interface *ev_int = iio_dev_opaque->event_interface;
// Used to store the returned file descriptor
int fd;

// If the event interface is not initialized, return -ENODEV (no device)
if (ev_int == NULL)
return -ENODEV;

// Attempt to acquire the mutex lock to ensure mutual exclusion of device operations
fd = mutex_lock_interruptible(&indio_dev->mlock);
if (fd)
return fd;

// Check whether the event interface is already occupied (via the flag IIO_BUSY_BIT_POS judgment)
if (test_and_set_bit(IIO_BUSY_BIT_POS, &ev_int->flags)) {
fd = -EBUSY;
goto unlock;
}

// Increase the device's reference count to prevent the device from being released during use
iio_device_get(indio_dev);

// Create an anonymous inode file and return the file descriptor
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); // Clear the occupied flag
iio_device_put(indio_dev); // Decrease the device reference count
} else {
kfifo_reset_out(&ev_int->det_events); // If successful, reset the output pointer of the FIFO queue to prepare for receiving new events
}

unlock:
mutex_unlock(&indio_dev->mlock); // Release the mutex lock
return fd;
}

The return value of this function is the file descriptor fd, which is obtained throughfd = anon_inode_getfd("iio:event", &iio_event_chrdev_fileops,indio_dev, O_RDONLY | O_CLOEXEC);returned byanon_inode_getfdThe function creates an anonymous file descriptor fd. An anonymous file descriptor is a special type of file descriptor that is not associated with an actual file system path or device node, but is a virtual file dynamically created by the kernel.

The anonymous file descriptor fd also has its own file operation set, so we can create virtual file descriptors via ioctl, thereby providing users with additional system call interfaces to interact with certain functions or data in the kernel without relying on the traditional file system.

The file operation set corresponding to the anonymous file descriptor fd isanon_inode_getfdThe second parameter of the functioniio_event_chrdev_fileops, and the specific content is as follows:

1
2
3
4
5
6
7
static const struct file_operations iio_event_chrdev_fileops = {
.read = iio_event_chrdev_read, // Handle the read() system call to read event data
.poll = iio_event_poll, // Handle poll() or select() calls to monitor whether events are available
.release = iio_event_chrdev_release, // Handle the close() call to release resources
.owner = THIS_MODULE, // Specify the module owner to ensure safe module unloading
.llseek = noop_llseek, // File offset operation not supported (lseek invalid)
};

iio_event_chrdev_read()

its read functioniio_event_chrdev_readis analyzed, and the specific content of this function is as follows:

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; // Get IIO device instance
struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev);
struct iio_event_interface *ev_int = iio_dev_opaque->event_interface; // Get event interface
unsigned int copied; // Record the actual amount of data copied to user space
int ret;

if (!indio_dev->info) // Check if the device has been initialized
return -ENODEV;

if (count < sizeof(struct iio_event_data)) // Ensure the buffer is large enough to hold event data
return -EINVAL;

do {
if (kfifo_is_empty(&ev_int->det_events)) { // Check if the event queue is empty
if (filep->f_flags & O_NONBLOCK) // If in non-blocking mode, directly return -EAGAIN
return -EAGAIN;

// Block and wait for data in the event queue or device removal
ret = wait_event_interruptible(ev_int->wait,
!kfifo_is_empty(&ev_int->det_events) ||
indio_dev->info == NULL);
if (ret) // If the wait is interrupted, return an error code
return ret;
if (indio_dev->info == NULL) // If the device is removed, return -ENODEV
return -ENODEV;
}

// Lock to protect access to the event queue
if (mutex_lock_interruptible(&ev_int->read_lock))
return -ERESTARTSYS;
// Copy event data from the kernel FIFO queue to user space
ret = kfifo_to_user(&ev_int->det_events, buf, count, &copied);
mutex_unlock(&ev_int->read_lock); // Unlock

if (ret) // If copying fails, return an error code
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.
*/
// If no data is read and it is non-blocking mode, return -EAGAIN
if (copied == 0 && (filep->f_flags & O_NONBLOCK))
return -EAGAIN;

} while (copied == 0); // If no data is read, continue looping and waiting

return copied; // Return the actual number of bytes read
}

The do-while loop of this function determines whether to wait for event data by checking if the kernel FIFO event queue is empty;
In non-blocking mode, return directly-EAGAIN, while in blocking mode, suspend the process via a wait queue until data arrives or the device is removed; then use a mutex to protect concurrent access to the FIFO queue, ensuring thread safety;
Finally, copy the event data from kernel space to user space, and handle special cases in non-blocking mode based on the read result; if no data is read, continue looping and waiting until successful reading and return the actual number of bytes read or an error code.

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)
{
// Get the IIO device structure associated with the file
struct iio_dev *indio_dev = filep->private_data;
struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev);
// Get the event interface structure of the device
struct iio_event_interface *ev_int = iio_dev_opaque->event_interface;
// Initialize the returned event mask to 0
__poll_t events = 0;

// If the device has no valid info structure, directly return an empty event (no event)
if (!indio_dev->info)
return events;

// Add the current file descriptor to the wait queue to be woken up when an event occurs
poll_wait(filep, &ev_int->wait, wait);

// Check if the event FIFO queue is non-empty
if (!kfifo_is_empty(&ev_int->det_events))
// If there are pending events, set the POLLIN and POLLRDNORM flags
events = EPOLLIN | EPOLLRDNORM;

// Return the event mask
return events;
}

The function uses thepoll_wait(filep, &ev_int->wait, wait);function to add the current file descriptor to the wait queue so it can be woken up when events arrive. Then it checks if the event FIFO queue is non-empty; if there are pending events, it sets theEPOLLIN | EPOLLRDNORMflag to indicate data is readable, and finally returns the detected events.

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)
{
// Retrieve the IIO device structure associated with the device from the file structure
struct iio_dev *indio_dev = filep->private_data;
struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev);
// Get the event interface structure of this device
struct iio_event_interface *ev_int = iio_dev_opaque->event_interface;

// Clear the BUSY status bit in the event interface flags (IIO_BUSY_BIT_POS)
clear_bit(IIO_BUSY_BIT_POS, &ev_int->flags);

// Decrease the reference count of the IIO device, releasing the device occupancy
iio_device_put(indio_dev);

return 0;
}

iio_event_chrdev_releasefunction is used to perform cleanup operations when the device file is closed in user space. It marks the device as available by clearing the BUSY status flag in the event interface, and decreases the reference count of the IIO device to release resources, ensuring the device can be safely used by other processes or further cleaned up when no longer needed.

In the IIO subsystem, support for multiple system calls is required. An anonymous file descriptor can be created via the ioctl function, and a separate set of file operations can be bound to this anonymous file descriptor. This approach not only extends functionality but also avoids polluting the file system, as anonymous file descriptors leave no trace in the file system, thus keeping the system clean and efficient.

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)
{
// Retrieve the corresponding IIO device structure from the inode
struct iio_dev *indio_dev = container_of(inode->i_cdev,
struct iio_dev, chrdev);

// Check if the device is already occupied; if busy, return -EBUSY
if (test_and_set_bit(IIO_BUSY_BIT_POS, &indio_dev->flags))
return -EBUSY;

// Increase the device reference count to prevent the device from being released
iio_device_get(indio_dev);

// Store the device structure pointer in the file's private data
filp->private_data = indio_dev;

return 0;
}

This functioniio_chrdev_openis used to open an IIO character device. First, through thecontainer_ofmacro, the corresponding IIO device structure is obtained from the inode. Then, it checks whether the device is already occupied (by testing and setting the busy flag). If the device is busy, it returns-EBUSY. Then, it calls theiio_device_getfunction to increase the device’s reference count to prevent the device from being released during use, and stores the device structure pointer in the file’s private data for subsequent operations. Finally, it returns 0 to indicate that the device was successfully opened.

iio_device_getThe specific content of the function is as follows:

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;
}

The purpose of this function is to increase the reference count of the IIO device, ensuring that the device is not accidentally released during use, while returning the device pointer for subsequent operations. It first checks whether the passedstruct indio_devis NULL. If it is not NULL, it usesget_deviceto increase the device’s reference count, and converts the device pointer from dev back toiio_devtype to return; if it is NULL, it directly returns NULL. This implementation ensures the validity of the device while avoiding operations on a NULL pointer.

iio_buffer_read_outer_addr()

Next, the read function in the file operation setiio_buffer_read_outer_addris analyzed:

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

iio_buffer_read_outerThe definition is as follows

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 = an IIO device
struct iio_buffer *rb = indio_dev->buffer; // rb = the bound ring buffer, rb->access->read is the actual buffer implementation (DMA / kfifo / hw buffer)
DEFINE_WAIT_FUNC(wait, woken_wake_function);
size_t datum_size;
size_t to_wait;
int ret = 0;

// Legality check
if (!indio_dev->info)
return -ENODEV;

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

datum_size = rb->bytes_per_datum; // One datum = the byte size of one sample of data.

/*
* If datum_size is 0 there will never be anything to read from the
* buffer, so signal end of file now.
*/
if (!datum_size) // Indicates that this buffer produces no data at all
return 0;

if (filp->f_flags & O_NONBLOCK) // Non-blocking mode
to_wait = 0;
else // Blocking mode
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;
}

// Is there enough data in the buffer already?
if (!iio_buffer_ready(indio_dev, rb, to_wait, n / datum_size)) {
// If there is no signal
if (signal_pending(current)) { // Check for signal, user Ctrl+C
ret = -ERESTARTSYS; // Return -ERESTARTSYS
break;
}

// Sleep, the process enters interruptible sleep
wait_woken(&wait, TASK_INTERRUPTIBLE,
MAX_SCHEDULE_TIMEOUT);
continue;
}

ret = rb->access->read(rb, n, buf); // Actually read data
if (ret == 0 && (filp->f_flags & O_NONBLOCK)) // Special handling for non-blocking mode
ret = -EAGAIN;
} while (ret == 0); // Keep waiting as long as no data is read
remove_wait_queue(&rb->pollq, &wait); // Clean up the wait queue

return ret;
}

Return value semantics:

  • > 0: number of bytes successfully read
  • 0: EOF (no data and never will be)
  • < 0: error code

This is a typical implementation of a “blocking read buffer” in the Linux kernel.

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

读数据
返回

wherestruct iio_bufferis defined as follows

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;// number of data units in the buffer

/** @bytes_per_datum: Size of individual datum including timestamp. */
size_t bytes_per_datum; // size of a single data unit (including timestamp)

/**
* @access: Buffer access functions associated with the
* implementation.
*/
const struct iio_buffer_access_funcs *access; // buffer access functions, providing operations such as reading and writing

/** @scan_mask: Bitmask used in masking scan mode elements. */
long *scan_mask; // bitmask of scan mode elements, used to select enabled channels

/** @demux_list: List of operations required to demux the scan. */
struct list_head demux_list; // list of operations required for demultiplexing scans

/** @pollq: Wait queue to allow for polling on the buffer. */
wait_queue_head_t pollq; // wait queue, used to support polling operations on the buffer

/** @watermark: Number of datums to wait for poll/read. */
unsigned int watermark; // watermark value, indicating the number of data units to wait for during polling or reading

/* private: */
/* @scan_timestamp: Does the scan mode include a timestamp. */
bool scan_timestamp; // whether the scan mode includes a timestamp

/* @scan_el_dev_attr_list: List of scan element related attributes. */
struct list_head scan_el_dev_attr_list; // list of attributes related to scan elements

/* @buffer_group: Attributes of the buffer group. */
struct attribute_group buffer_group; // Attributes of the buffer group

/*
* @scan_el_group: Attribute group for those attributes not
* created from the iio_chan_info array.
*/
struct attribute_group scan_el_group; // Attribute group for those not from iio_chan_Attributes created by the info array

/* @attrs: Standard attributes of the buffer. */
const struct attribute **attrs; // Standard attributes of the buffer

/* @demux_bounce: Buffer for doing gather from incoming scan. */
void *demux_bounce; // Buffer for collecting data from incoming scans

/* @buffer_list: Entry in the devices list of current buffers. */
struct list_head buffer_list; // Entry in the device's current buffer list

/* @ref: Reference count of the buffer. */
struct kref ref; // Reference count of the buffer for managing resource release
};

andiio_bufferinconst struct iio_buffer_access_funcs *accessis defined as follows

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 {
// Store data into the buffer
int (*store_to)(struct iio_buffer *buffer, const void *data);

// Read up to n bytes of data from the buffer into the user-space buffer buf
int (*read)(struct iio_buffer *buffer, size_t n, char __user *buf);

// Return the amount of data currently available in the buffer (in bytes or data units)
size_t (*data_available)(struct iio_buffer *buffer);

// Request to update buffer configuration, typically to apply new parameters or state
int (*request_update)(struct iio_buffer *buffer);

// Set the size of each data unit (bytes per datum) to adjust the buffer format
int (*set_bytes_per_datum)(struct iio_buffer *buffer, size_t bpd);
// Set the length of the buffer (number of data units)
int (*set_length)(struct iio_buffer *buffer, unsigned int length);

// Enable the buffer, typically associate with the device and start data acquisition
int (*enable)(struct iio_buffer *buffer, struct iio_dev *indio_dev);
// Disable the buffer, stop data acquisition and clean up related resources
int (*disable)(struct iio_buffer *buffer, struct iio_dev *indio_dev);

// Release buffer resources, typically called when the buffer is destroyed
void (*release)(struct iio_buffer *buffer);

// Modes supported by the buffer (e.g., blocking, non-blocking, etc.)
unsigned int modes;
// Buffer flags, used to indicate specific functions or states
unsigned int flags;
};

This structure defines a set of function pointers and flags to implement the operation interface for the IIO buffer. Through these interfaces, the IIO subsystem can flexibly manage buffer storage, reading, configuration, and lifecycle, while supporting multiple working modes and functional extensions. Iniio_buffer_read_outer_addrthe function, useret = rb->access->read(rb, n, buf);to actually read data.

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)
{
// Obtain the IIO device structure associated with the character device from the inode using the container_of macro
struct iio_dev *indio_dev = container_of(inode->i_cdev,
struct iio_dev, chrdev);

// Clear the BUSY status flag of the IIO device, indicating the device is no longer occupied
clear_bit(IIO_BUSY_BIT_POS, &indio_dev->flags);

// Decrease the reference count of the IIO device, releasing the occupation of the device
// If the reference count drops to 0, it may trigger further cleanup or release operations of the device
iio_device_put(indio_dev);

// Return 0 indicates successful release of device resources
return 0;
}

The core function of this function is to clean up the device state and release related resources when the device file is closed in user space. Throughclear_bitThe function clears the BUSY status flag of the device and decrements the device’s reference count, ensuring that the device can be safely used by other processes or further cleaned up when no longer needed.

iio_buffer_poll_addr()

1
#define iio_buffer_poll_addr (&iio_buffer_poll)

iio_buffer_pollDefined as follows:

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)
{
// Obtain the IIO device structure associated with the device from the file structure
struct iio_dev *indio_dev = filp->private_data;

// Obtain the buffer structure of this device
struct iio_buffer *rb = indio_dev->buffer;

// If the device has no valid info structure, or the buffer is not initialized, return 0 to indicate no event
if (!indio_dev->info || rb == NULL)
return 0;

// Add the current file descriptor to the buffer's wait queue so that it can be woken up when an event arrives
poll_wait(filp, &rb->pollq, wait);
if (iio_buffer_ready(indio_dev, rb, rb->watermark, 0))
return EPOLLIN | EPOLLRDNORM;// If there is data to read, return EPOLLIN | EPOLLRDNORM to indicate data is readable

// If there is no data to read, return 0 to indicate no event
return 0;
}

This function implements the polling mechanism of the IIO buffer, used to check if there is data available for reading in the buffer. It first obtains the device and buffer information from the file structure and verifies their validity; then throughpoll_waitadds the current file descriptor to the wait queue so that it can be woken up when data arrives; finally throughiio_buffer_readychecks whether the buffer meets the read condition (such as reaching the watermark value), and if so, returnsEPOLLIN | EPOLLRDNORMto indicate data is readable, otherwise returns 0 to indicate no event.

iio_device_register_sysfs()

When explaining the IIO device registration process, a brief analysis was performed on__iio_device_registerwas briefly analyzed, and within this function,ret = iio_device_register_sysfs(indio_dev);function is called to register the sysfs interface of the IIO device.

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 */
/* First, count the number of elements in the existing attribute 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.
*/
/*
* New channel registration method. - Depends on a fact.:If the group name is empty,,then the group does not need to be initialized.。
*/
if (indio_dev->channels) // If the device has channel definitions,
for (i = 0; i < indio_dev->num_channels; i++) { // iterate through all channels.
const struct iio_chan_spec *chan =
&indio_dev->channels[i];

// If the channel type is timestamp, record the timestamp clock attribute.
if (chan->type == IIO_TIMESTAMP)
clk = &dev_attr_current_timestamp_clock.attr;

// Add the channel's sysfs attribute to the system.
ret = iio_device_add_channel_sysfs(indio_dev, chan);
if (ret < 0) // If the addition fails, jump to error handling.
goto error_clear_attrs;
attrcount += ret; // Accumulate the count of newly added attributes.
}

// If the device supports the event interface, record the timestamp clock attribute.
if (iio_dev_opaque->event_interface)
clk = &dev_attr_current_timestamp_clock.attr;

// If the device has a name, increment the attribute count.
if (indio_dev->name)
attrcount++;
if (indio_dev->label)
attrcount++;

// If a timestamp clock attribute exists, increment the attribute count.
if (clk)
attrcount++;

// Allocate memory to store all attribute pointers.
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) { // If allocation fails, return an out-of-memory error
ret = -ENOMEM;
goto error_clear_attrs;
}
/* Copy across original attributes */
// Copy the original attributes to the new attribute array
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; // Record the index of the current attribute array
/* Add all elements from the list. */
// Add all attributes from the channel attribute list to the attribute array
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 the device has a name, add the name attribute to the attribute array
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 a timestamp clock attribute exists, add it to the attribute array
if (clk)
iio_dev_opaque->chan_attr_group.attrs[attrn++] = clk;

// Add the attribute group to the device's attribute group list
indio_dev->groups[indio_dev->groupcounter++] =
&iio_dev_opaque->chan_attr_group;

return 0; // Return success

error_clear_attrs:
iio_free_chan_devattr_list(&iio_dev_opaque->channel_attr_list);

return ret;
}

The core operation of this function lies inret = iio_device_add_channel_sysfs(indio_dev, chan);function, through which the channel’s sysfs attributes can be added to the system. The specific content of this function is as follows:

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; // Define the return value and attribute counter
const struct iio_chan_spec_ext_info *ext_info; // Extended information pointer

// If the channel number is less than 0, return directly (indicating the channel is invalid)
if (chan->channel < 0)
return 0;

// Add an information mask attribute of independent type (IIO_SEPARATE)
ret = iio_device_add_info_mask_type(indio_dev, chan,
IIO_SEPARATE,
&chan->info_mask_separate);
if (ret < 0) // If adding fails, return an error code
return ret;
attrcount += ret; // Accumulate the number of newly added attributes

// Add independent type available information mask attribute (IIO_SEPARATE_AVAILABLE)
ret = iio_device_add_info_mask_type_avail(indio_dev, chan,
IIO_SEPARATE,
&chan->
info_mask_separate_available);
if (ret < 0) // If adding fails, return an error code
return ret;
attrcount += ret; // Accumulate the number of newly added attributes

// Add type-shared information mask attribute (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) // If adding fails, return an error code
return ret;
attrcount += ret; // Accumulate the number of newly added attributes

// Add type-shared available information mask attribute (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) // If adding fails, return an error code
return ret;
attrcount += ret; // Accumulate the number of newly added attributes

// Add direction-shared information mask attribute (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) // If adding fails, return an error code
return ret;
attrcount += ret; // Accumulate the number of newly added attributes

// Add direction-shared available information mask attribute (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) // If adding fails, return an error code
return ret;
attrcount += ret; // Accumulate the number of newly added attributes

// Add globally shared information mask attribute (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 error code if addition fails
return ret;
attrcount += ret; // Accumulate the number of newly added attributes

// Add globally shared available information mask attribute (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 error code if addition fails
return ret;
attrcount += ret; // Accumulate the number of newly added attributes

// Process extended information (ext_info)
if (chan->ext_info) {
unsigned int i = 0; // Record the index of extended information
for (ext_info = chan->ext_info; ext_info->name; ext_info++) { // Traverse extended information
// Add extended information attributes to sysfs
ret = __iio_add_chan_devattr(ext_info->name, // Attribute name
chan, // Channel pointer
ext_info->read ? // Whether there is a read callback
&iio_read_channel_ext_info : NULL,
ext_info->write ? // Whether there is a write callback
&iio_write_channel_ext_info : NULL,
i, // Extended information index
ext_info->shared, // Whether shared
&indio_dev->dev, // Device pointer
&iio_dev_opaque->channel_attr_list); // Attribute list
i++; // Add index
// If -EBUSY is returned and the extended information is shared, skip
if (ret == -EBUSY && ext_info->shared)
continue;

// If other errors occur, directly return the error code
if (ret)
return ret;

attrcount++; // Successfully add an attribute, accumulate count
}
}

return attrcount; // Return the total number of successfully added attributes
}

These code segments call theiio_device_add_info_mask_typeandiio_device_add_info_mask_type_availfunctions to add channel attributes and their available attributes one by one according to different sharing types. Each type corresponds to a specific set of functions, and the return value is checked after each operation to ensure errors can be captured and handled in a timely manner. Meanwhile, the total number of successfully added attributes is recorded by accumulating return values, thereby achieving flexible classification management and dynamic expansion of hardware functions. Next, the two functions just mentioned will be explained, and their function prototypes are as follows:

iio_device_add_info_mask_typeThe function definition is as follows:

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_availThe function definition is as follows

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;
}

It can be seen that the parameters of the two functions are the same, but their functions are slightly different,iio_device_add_info_mask_typeUsed to add information mask attributes related to channels,iio_device_add_info_mask_type_availUsed to add available information mask attributes, which are typically used to describe which functions are dynamically available. The corresponding parameter descriptions are as follows:

  1. indio_dev: Pointer to the IIO device, representing the device currently being operated. It contains all information about the device, such as channels and attributes, and is the core connecting hardware to user space.
  2. chan: Pointer to the channel, indicating the specific channel to be operated on. Each channel corresponds to a sensor or signal interface (such as temperature, pressure, etc.), defining the function of that channel.
  3. shared_by: Specifies the sharing type of the attribute, determining how attributes are shared among channels. The types used in the above code include:
    • IIO_SEPARATE: Independent attribute, belonging only to a specific channel.
    • IIO_SHARED_BY_TYPE: Shared by type, channels of the same type share the attribute.
    • IIO_SHARED_BY_DIR: Shared by direction, channels of the same direction share the attribute.
    • IIO_SHARED_BY_ALL: Globally shared, all channels of the entire device share the attribute.
  4. infomask: Pointer to the address of the information mask, defining the set of attributes to be exposed. Each bit corresponds to a specific function or feature. Iniio_device_add_info_mask_type,infomaskdescribes the basic functions of the channel (such as reading, writing, etc.). Iniio_device_add_info_mask_type_avail, infomask describes dynamically available functions (such as additional functions enabled in certain modes). The mask contents used in the above code are introduced in the following table:
Mask NameCore FunctionSharing ScopeTypical Application Scenarios
info_mask_separateExport exclusive information specific to the current channelIndependent (Current Channel Only)Channel-specific statistics, queue configuration, single-channel link status
info_mask_separate_availableExport the ‘availability’ indicator of exclusive information for the current channel (indicating whether such information is queryable)Independent (Current Channel Only)Mark whether the statistics of the current channel have been collected and whether the configuration has taken effect
info_mask_shared_by_typeExport common information shared by all channels of the same typeShared by Type (Same Type Channels)Device type attributes, driver version, global statistics for same-type channels (e.g., common configuration for all 10G network ports)
info_mask_shared_by_type_availableExport the ‘availability’ indicator of shared information for same-type channelsShared by Type (Same Type Channels)Mark whether the common configuration for same-type channels has been loaded and whether driver version information is readable
info_mask_shared_by_dirExport information shared by all channels in the same directionShared by direction (same-direction channels)Direction-specific statistics for RX/TX (e.g., total bytes of all receive channels) and direction-related hardware configuration
info_mask_shared_by_dir_availableExport the “availability” flag of shared information for same-direction channelsShared by direction (same-direction channels)Indicate whether the statistics of a direction channel are available and whether the direction configuration is effective
info_mask_shared_by_allExport global information common to all channelsGlobally shared (all channels)Device MAC address, firmware version, overall device operation status, and total transmit/receive statistics for all channels
info_mask_shared_by_all_availableExport the “availability” flag of globally shared informationGlobally shared (all channels)Indicate whether the device global status is queryable and whether the firmware version information is read successfully

Here, only the attribute information has been added. So when is the attribute setting completed? In the previous analysis,rockchip_saradc_probewhen discussing the function, it was mentioned that the definition of the RK3568 ADC channel uses theSARADC_CHANNELmacro, and the specific content of this macro is as follows:

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_VOLTAGEChannel type: Voltage measurement
  • .indexed = 1Enable index mode, indicating the use ofchannelfield as the index
  • .channel = _indexMain channel number, specified by the macro parameter_indexspecified
  • .info_mask_separate = BIT(IIO_CHAN_INFO_RAW)Individually supported attribute: raw value (RAW)
  • .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE)Attribute shared by type: scaling factor
  • .datasheet_name = _idName in the datasheet, specified by the macro parameter_idspecified

It can be seen thatinfo_mask_separateandinfo_mask_shared_by_typeare used to specify individually supported attributes and shared attributes.

Explanation of IIO channel attribute addition function

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;

// Iterate over each valid bit in the information mask (i.e., bits set to 1)
for_each_set_bit(i, infomask, sizeof(*infomask)*8) {
// Check if the index exceeds the range of the suffix array
if (i >= ARRAY_SIZE(iio_chan_info_postfix))
return -EINVAL; // If out of range, return an invalid parameter error

// Call the function to add attributes to the sysfs interface
ret = __iio_add_chan_devattr(iio_chan_info_postfix[i], // Attribute name suffix
chan, // Channel pointer
&iio_read_channel_info, // Read callback function
&iio_write_channel_info, // Write callback function
i, // Attribute index
shared_by, // Shared type
&indio_dev->dev, // Device pointer
&iio_dev_opaque->channel_attr_list); // Attribute list

// If -EBUSY is returned and the attribute is not of an independent type, skip this attribute
if ((ret == -EBUSY) && (shared_by != IIO_SEPARATE))
continue;
// If any other error occurs, return the error code directly
else if (ret < 0)
return ret;
// Successfully added one attribute, increment the count
attrcount++;
}

// Return the total number of successfully added attributes
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;

// Iterate over each valid bit in the information mask (i.e., bits set to 1)
for_each_set_bit(i, infomask, sizeof(*infomask) * 8) {
if (i >= ARRAY_SIZE(iio_chan_info_postfix))
return -EINVAL;
// Dynamically generate the attribute name suffix in the format "attribute_name_available"
avail_postfix = kasprintf(GFP_KERNEL,
"%s_available",
iio_chan_info_postfix[i]);
if (!avail_postfix)
return -ENOMEM;

// Call the function to add available attributes to the sysfs interface
ret = __iio_add_chan_devattr(avail_postfix, // Attribute name suffix
chan, // Channel pointer
&iio_read_channel_info_avail, // Read callback function
NULL, // Write callback function (not writable)
i, // Attribute index
shared_by, // Shared type
&indio_dev->dev, // Device pointer
&iio_dev_opaque->channel_attr_list); // Attribute list
// Release dynamically allocated attribute name suffix
kfree(avail_postfix);

// If -EBUSY is returned and the attribute is not of independent type, skip the attribute
if ((ret == -EBUSY) && (shared_by != IIO_SEPARATE))
continue;
else if (ret < 0) // If other errors occur, return the error code directly
return ret;

// Successfully add an attribute, increment the count
attrcount++;
}

// Return the total number of successfully added attributes
return attrcount;
}

The implementation logic of these two functions is the same, with the main content being infor_each_set_bitImplemented in this for loop, it traverses each valid bit in the mask, parses the corresponding function, and registers it as a sysfs attribute. Here, the available attributes are added to the sysfs interface using__iio_add_chan_devattrfunction, the specific content of which is as follows

__iio_add_chan_devattr()

The parameters are as follows:

  • const char *postfixThe suffix of the attribute name (such as “raw” or “available”), used to generate the complete attribute name.
  • struct iio_chan_spec const *chanA pointer to the channel, representing the channel object for the current operation.
  • ssize_t (*)(struct device *dev, struct device_attribute *attr, char *buf) readfuncThe read callback function, used to implement the read operation of the attribute, with the return value being the length of data read or an error code.
  • ssize_t (*)(struct device *dev, struct device_attribute *attr, const char *buf, size_t len) writefuncThe write callback function, used to implement the write operation of the attribute, with the return value being the length of data written or an error code.
  • u64 maskThe mask address of the attribute, usually representing the unique identifier of the function or feature corresponding to the attribute.
  • enum iio_shared_by shared_byThe sharing type of the attribute, determining how the attribute is shared among channels (e.g., independent, shared by type, etc.).
  • struct device * devA pointer to the device, representing the target device for the current operation.
  • struct list_head * attr_listThe head pointer of the attribute list, used to add the newly created attribute to the linked 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;

// Allocate memory to create a new iio_dev_attr object
iio_attr = kzalloc(sizeof(*iio_attr), GFP_KERNEL);
if (iio_attr == NULL)
return -ENOMEM;

// Initialize device attributes (including name, read/write callback functions, etc.)
ret = __iio_device_attr_init(&iio_attr->dev_attr,
postfix, chan,
readfunc, writefunc, shared_by);
if (ret)
goto error_iio_dev_attr_free;

// Set the channel pointer and mask address
iio_attr->c = chan;
iio_attr->address = mask;
// Traverse the attribute list to check if an attribute with the same name already exists
list_for_each_entry(t, attr_list, l)
if (strcmp(t->dev_attr.attr.name,
iio_attr->dev_attr.attr.name) == 0) {
// If the shared type is independent (IIO_SEPARATE), log an error
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;
}
// Add the newly created attribute to the attribute list
list_add(&iio_attr->l, attr_list);

return 0;

error_device_attr_deinit:
// Error handling: deinitialize device attributes
__iio_device_attr_deinit(&iio_attr->dev_attr);
error_iio_dev_attr_free:
// Error handling: free allocated memory
kfree(iio_attr);
return ret;
}

Allocate memory and create a newiio_dev_attrobject. The corresponding structure content is as follows. This structure is used to describe the attributes of an IIO device (such as name, read/write callback functions), the functional identifier of the attribute, and the channel information it belongs to.

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; // Device attribute, including name and read/write callback functions
u64 address; // Address or mask value of the attribute, used to identify the function
struct list_head l; // Linked list node, used to link into the attribute list
struct iio_chan_spec const *c; // Pointer to the channel it belongs to
};
__iio_device_attr_init()

__iio_add_chan_devattrCalled in the functionret = __iio_device_attr_init(&iio_attr->dev_attr, postfix, chan,readfunc, writefunc, shared_by);to initialize IIO device attributes. The detailed introduction of this function is as follows:

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; // Return value, used to record the function execution result
char *name = NULL; // Attribute name
char *full_postfix; // Complete suffix string
sysfs_attr_init(&dev_attr->attr); // Initialize device attribute structure

/* Build up postfix of <extend_name>_<modifier>_postfix */
/* Construct suffix: format is <extend_name>_<modifier>_postfix */
if (chan->modified && (shared_by == IIO_SEPARATE)) {
// If the channel is modified and the sharing type is independent (IIO_SEPARATE)
if (chan->extend_name)
// If the channel has an extended name, generate the complete suffix
full_postfix = kasprintf(GFP_KERNEL, "%s_%s_%s",
iio_modifier_names[chan
->channel2],
chan->extend_name,
postfix);
else
// If there is no extended name, only use the modifier and suffix
full_postfix = kasprintf(GFP_KERNEL, "%s_%s",
iio_modifier_names[chan
->channel2],
postfix);
} else {
// Non-modified channel or non-independent sharing type
if (chan->extend_name == NULL || shared_by != IIO_SEPARATE)
// If there is no extended name or the sharing type is not independent, directly copy the suffix
full_postfix = kstrdup(postfix, GFP_KERNEL);
else
// Otherwise, combine the extended name and suffix
full_postfix = kasprintf(GFP_KERNEL,
"%s_%s",
chan->extend_name,
postfix);
}
// Check if the suffix is allocated successfully
if (full_postfix == NULL)
return -ENOMEM;

// Construct attribute name based on channel type (differential or single-ended) and sharing type
if (chan->differential) { /* Differential can not have modifier */ /* 差分通道 */
switch (shared_by) {
case IIO_SHARED_BY_ALL: // Global sharing: only use suffix
name = kasprintf(GFP_KERNEL, "%s", full_postfix);
break;
case IIO_SHARED_BY_DIR: // Sharing by direction: add direction prefix
name = kasprintf(GFP_KERNEL, "%s_%s",
iio_direction[chan->output],
full_postfix);
break;
case IIO_SHARED_BY_TYPE: // Sharing by type: add direction and type information
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: // Independent attribute: check if indexed
if (!chan->indexed) {
WARN(1, "Differential channels must be indexed\n");
ret = -EINVAL;
goto error_free_full_postfix;
}
// Construct the complete differential channel name
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: // Global sharing: use only suffix
name = kasprintf(GFP_KERNEL, "%s", full_postfix);
break;
case IIO_SHARED_BY_DIR: // Sharing by direction: add direction prefix
name = kasprintf(GFP_KERNEL, "%s_%s",
iio_direction[chan->output],
full_postfix);
break;
case IIO_SHARED_BY_TYPE: // Sharing by type: add direction and type information
name = kasprintf(GFP_KERNEL, "%s_%s_%s",
iio_direction[chan->output],
iio_chan_type_name_spec[chan->type],
full_postfix);
break;

case IIO_SEPARATE: // Independent attribute: construct name based on whether indexed
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) { // Check if attribute name allocation is successful
ret = -ENOMEM;
goto error_free_full_postfix;
}
dev_attr->attr.name = name; // Set attribute name

// Set read callback function and permissions
if (readfunc) {
dev_attr->attr.mode |= S_IRUGO; // Set readable permission
dev_attr->show = readfunc; // Bind read callback function
}

// Set write callback function and permissions
if (writefunc) {
dev_attr->attr.mode |= S_IWUSR; // Set writable permission
dev_attr->store = writefunc; // Bind write callback function
}

error_free_full_postfix:
kfree(full_postfix); // Release dynamically allocated suffix string

return ret;
}

The main function of this function is to dynamically construct a sysfs attribute name based on channel type, sharing type, and other parameters. The attribute name is set at line 123dev_attr->attr.name = name;implementation, and before that, the program can be divided into 3 parts based on if judgments, corresponding toconstructing suffix stringsdifferential channel attribute namesingle-ended channel attribute namelogic. Next, these three parts will be explained in detail.

Lines 21-47 are used to construct suffix stringsfull_postfix, based on whether the channel is modified (chan->modified) and sharing type (shared_by) dynamically generate suffix stringsfull_postfix. If the channel is modified and the sharing type is independent (IIO_SEPARATE), then based on whether there is an extension name (chan->extend_name) decide whether to include modifiers and extension name; otherwise, directly construct the suffix based on whether there is an extension name or sharing type.

ConditionFormat
Channel is modified and sharing mode isIO_SEPARATEhas extension name (chan->extend_name)[修饰符名称][扩展名称][后缀]
channel is modified and sharing mode isIO_SEPARATEno extension name[修饰符名称][后缀]
channel is not modified or sharing mode ≠IO_SEPARATEno extension name or sharing mode ≠IO_SEPARATE[后缀]
channel is not modified or sharing mode ≠IO_SEPARATEhas extension name and sharing mode isIO_SEPARATE(listed for completeness only)[扩展名称][后缀]

Lines 53-86 target differential channels, based on sharing type (shared_by) construct attribute names.

  • For global sharing (IIO_SHARED_BY_ALL), only the suffix is used;
  • For direction-based sharing (IIO_SHARED_BY_DIR), add a direction prefix;
  • For type-based sharing (IIO_SHARED_BY_TYPE), further add direction and type information;
  • For independent sharing (IIO_SEPARATE), channels must be indexed (chan->indexed), and include details such as direction, type, and channel index in the name; if not indexed, a warning is issued and an error is returned.
Sharing method (shared_by)Attribute name format
IO_SHARED_BY_ALL[full_postfix]
IO_SHARED_BY_DIR[输出方向]_[full_postfix]
IO_SHARED_BY_TYPE[输出方向][通道类型]-[通道类型][full_postfix]
IO_SEPARATE[输出方向][通道类型][通道索引]-[通道类型][通道2索引][full_postfix]

Lines 88-118, for single-ended channels, construct attribute names based on the sharing type (shared_by). For global sharing (IIO_SHARED_BY_ALL), only the suffix is used; for sharing by direction (IIO_SHARED_BY_DIR), a direction prefix is added; for sharing by type (IIO_SHARED_BY_TYPE), direction and type information are added; for independent sharing (IIO_SEPARATE), whether to include the channel index in the name depends on whether it is indexed; if not indexed, the index part is omitted.

Sharing method (shared_by)Attribute name format
IO_SHARED_BY_ALL[full_postfix]
IO_SHARED_BY_DIR[输出方向]_[full_postfix]
IO_SHARED_BY_TYPE[输出方向][通道类型][full_postfix]
IO_SEPARATE(with index,chan->indexed[输出方向][通道类型][通道索引][full_postfix]
IO_SEPARATE(No index)[输出方向][通道类型]_[full_postfix]

Next, take ADC channel 3 of RK3568 as an example to explain how the sysfs attribute name is created. The specific description of RK3568 channels is as follows:

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), // Define ADC channel 0, named "adc0"
SARADC_CHANNEL(1, "adc1", 10), // Define ADC channel 1, named "adc1"
SARADC_CHANNEL(2, "adc2", 10), // Define ADC channel 2, named "adc2"
SARADC_CHANNEL(3, "adc3", 10), // Define ADC channel 3, named "adc3"
SARADC_CHANNEL(4, "adc4", 10), // Define ADC channel 4, named "adc4"
SARADC_CHANNEL(5, "adc5", 10), // Define ADC channel 5, named "adc5"
SARADC_CHANNEL(6, "adc6", 10), // Define ADC channel 6, named "adc6"
SARADC_CHANNEL(7, "adc7", 10), // Define ADC channel 7, named "adc7"
};

The specific content of this macro is as follows:

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_VOLTAGEChannel type: voltage measurement
  • .indexed = 1Enable index mode, indicating the use ofchannelfield as the index
  • .channel = _indexMain channel number, determined by the macro parameter_indexSpecify
  • .info_mask_separate = BIT(IIO_CHAN_INFO_RAW)Individually supported attribute: Raw value (RAW)
  • .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE)Attribute shared by type: Scaling factor
  • .datasheet_name = _idName in the datasheet, determined by the macro parameter_idSpecify

The content after being brought into ADC3 is as follows:

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,
},
}

It can be seen thatinfo_mask_separateandinfo_mask_shared_by_typeare used to specify individually supported attributes and shared attributes.

In lines 5 and 6, respectively, setseparateandshared_by_type, so in theiio_device_add_channel_sysfsfunction, the following two sections will be executed

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Add the information mask attribute of independent type (IIO_SEPARATE)
ret = iio_device_add_info_mask_type(indio_dev, chan,
IIO_SEPARATE,
&chan->info_mask_separate);
if (ret < 0) // If the addition fails, return an error code
return ret;
attrcount += ret; // Accumulate the number of newly added attributes

// Add independent type available information mask attribute (IIO_SEPARATE_AVAILABLE)
ret = iio_device_add_info_mask_type_avail(indio_dev, chan,
IIO_SEPARATE,
&chan->
info_mask_separate_available);
if (ret < 0) // If adding fails, return an error code
return ret;
attrcount += ret; // Accumulate the number of newly added attributes

Here, first analyze the addition of the independent type information mask attribute, ADC3’sinfo_mask_separateattribute is set toBIT(IIO_CHAN_INFO_RAW), andIIO_CHAN_INFO_RAWhas a value of 0, so__iio_add_chan_devattrthe first parameter of the function can be determined, thereby determining that the suffix of this attribute isiio_chan_info_postfix[0]namely raw.

Then we continue to analyze downwards, determine the full name of the attribute, and then enteriio_device_attr_initfunction, first judge the constructed suffix. Since there is nomodifiedattribute in the definition of ADC3, andextend_nameis not defined, the suffix will be directly copied, which is the RAW determined above.

Then judge the following conditions. Since ADC3 does not havedifferentialattribute, so it enters the conditional branch for single-ended channels, then ADC3’sshared_byattribute isIIO_SEPARATEandindexedis 1, so the final name determination code is:

1
2
3
4
5
6
7
case IIO_SEPARATE: // Independent attribute: construct name based on whether it is indexed
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->outputis not set, soiio_directiontakes 0, the obtained value isinchan->typeisIIO_VOLTAGE, substitute intoiio_chan_type_name_specwe can get the value asvoltagechan->channelis 3,full_postfixis the suffix value raw, so the first attribute name of ADC3 isin_voltage3_raw. Then the same method can be used to analyze the second attribute name, which will not be repeated here, and the second attribute name can be obtained asin_voltage_scale

IIO device node creation analysis

iio_device_add_channel_sysfsfunction determines the attribute names of the ADC channels, then collects the device’s channel attributes, device name, and timestamp attributes, and withindio_devbind the device, and finally create corresponding nodes in sysfs, so that users can/sys/bus/iio/devices/iio:device0access these attributes in the directory
. Of course, this code only collects these attributes intochan_attr_groupand does not create these attributes in the sys directory. So where is the code that creates the relevant attribute files?

device_create()

There are two ways to create device nodes. The first way is through themknodcommand to manually create device nodes. The second way is to automatically create device nodes, and the function called isdevice_create, and the iio device node is automatically created,device_createThe function content is as follows:

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, ...)
{
// Define a variable argument list variable vargs to store the variable arguments passed to the function
va_list vargs;

// Define a pointer variable dev pointing to struct device to store the created device object
struct device *dev;

// Initialize the variable argument list vargs, where fmt is the last fixed parameter, and the following parameters are variable
va_start(vargs, fmt);

// Call the device_create_vargs function, passing in the class, parent device, device number, driver data, and variable arguments
// This function creates a device object based on the passed parameters and returns a pointer to the object
dev = device_create_groups_vargs(class, parent, devt, drvdata, NULL,
fmt, vargs);
va_end(vargs);
return dev;
}
EXPORT_SYMBOL_GPL(device_create);

This function is used to create a device object in the Linux kernel, and its focus is ondevice_create_vargsfunction,device_create_vargsThe specific content of the function is as follows:

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)
{
// Define a pointer variable dev pointing to struct device, initialized to NULL
struct device *dev = NULL;

// Define an integer variable retval to store the error code, with a default value of -ENODEV (indicating the device does not exist)
int retval = -ENODEV;

// Check whether the incoming class is NULL or invalid (detected via IS_ERR)
if (class == NULL || IS_ERR(class))
goto error;

// Allocate memory to create a new device object, with size sizeof(*dev), using the GFP_KERNEL flag
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
if (!dev) { // Check whether the memory allocation was successful
retval = -ENOMEM;
goto error;
}

// Initialize the device object
device_initialize(dev);

// Set the attributes of the device object
dev->devt = devt; // Set the device number
dev->class = class; // Set the class to which the device belongs
dev->parent = parent; // Set the parent device
dev->groups = groups; // Set the attribute group of the device
dev->release = device_create_release; // Set the callback function for device release
dev_set_drvdata(dev, drvdata); // Set the driver private data

// Use variable arguments args to set the device name (via format string fmt)
retval = kobject_set_name_vargs(&dev->kobj, fmt, args);
if (retval) // If setting the name fails
goto error;

// Add the device to the system
retval = device_add(dev);
if (retval)
goto error;

return dev;

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

Finally, in this function, throughdevice_initialize()function, the device is initialized, and finally calldevice_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;

// Get the device reference to ensure the device is valid
dev = get_device(dev);
if (!dev)
goto done;

// Initialize device private data
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()
*/
// Set the device 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__);

// Get the parent device and kobject parent object
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 */
// Inherit the NUMA node of the parent device
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 */
// Register the device to the generic layer
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;

// Create device attribute files and symbolic links
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;

// Add the device to the bus and power management subsystem
error = bus_add_device(dev);
if (error)
goto BusError;
error = dpm_sysfs_add(dev);
if (error)
goto DPMError;
device_pm_add(dev);

// If the device number is valid, create related files and nodes
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().
*/
// Notify clients that the device has been added
if (dev->bus)
blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
BUS_NOTIFY_ADD_DEVICE, dev);

// Send KOBJ_ADD uevent event
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.
*/
// Handle device links (consumer-supplier relationships)
if (dev->fwnode && !dev->fwnode->dev) {
dev->fwnode->dev = dev;
fw_devlink_link_device(dev);
}

// Probe device and bind driver
bus_probe_device(dev);
if (parent)
klist_add_tail(&dev->p->knode_parent,
&parent->p->klist_children);

// If the device belongs to a class, add the device to the class
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);

This part of the code above:

1
2
3
4
5
6
7
8
9
10
11
12
// If the device number is valid, create related files and nodes
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);
}

Is a conditional check; if there is a device number, executedevtmpfs_create_nodefunction to create device nodes. If there is no device number, it will callkobject_ueventfunction to create device nodes via udev. At this point, the review of device node creation is complete, and finally by callingdevice_addfunction to create device nodes. So where is this function called for the device nodes of IIO devices?

devm_iio_device_alloc()

drivers/iio/adc/rockchip_saradc.cfile’s probe function, at the beginning of the probe function, it usesdevm_iio_device_allocfunction to allocate memory. The specific content of this function is as follows:

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;
// Allocate a pointer for managing device resources, using the devres mechanism
ptr = devres_alloc(devm_iio_device_release, sizeof(*ptr),
GFP_KERNEL);
if (!ptr)
return NULL;

// Allocate IIO device structure and reserve private data space
iio_dev = iio_device_alloc(parent, sizeof_priv);
if (iio_dev) {
*ptr = iio_dev;
devres_add(parent, ptr); // Add the resource to the device resource list
} else {
devres_free(ptr); // If allocation fails, release devres resources
}

return iio_dev;
}
EXPORT_SYMBOL_GPL(devm_iio_device_alloc);

iio_device_alloc()

This function callsiio_device_allocfunction to allocate the IIO device structure,iio_device_allocThe specific content of the function is as follows:

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;

// Calculate the total memory size to be allocated, including the iio_dev structure and private data
alloc_size = sizeof(struct iio_dev_opaque);
if (sizeof_priv) { // If private data space needs to be allocated
alloc_size = ALIGN(alloc_size, IIO_ALIGN); // Align to IIO_ALIGN
alloc_size += sizeof_priv; // Add the private data size
}

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);

// Initialize the basic attributes of the device
dev->dev.parent = parent;
dev->dev.groups = dev->groups; // Set the default attribute group of the device
dev->dev.type = &iio_device_type; // Set the device type
dev->dev.bus = &iio_bus_type; // Set the bus type to which the device belongs
device_initialize(&dev->dev); // Initialize the device object
dev_set_drvdata(&dev->dev, (void *)dev); // Set the device private data
mutex_init(&dev->mlock);
mutex_init(&dev->info_exist_lock);
INIT_LIST_HEAD(&iio_dev_opaque->channel_attr_list); // Initialize the channel attribute linked list

// Allocate device 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;
}
// Set device name
dev_set_name(&dev->dev, "iio:device%d", dev->id);
INIT_LIST_HEAD(&iio_dev_opaque->buffer_list); // Initialize buffer linked list

return dev;
}
EXPORT_SYMBOL(iio_device_alloc);

This function finally usesdev_set_namefunction to set the iio device name, which is theiio\:device0node name in the dev directory, and earlier usesdevice_initializeto initialize the iio device. At this point, the series of operations before creating the device node is complete.

Then return torockchip_saradc.cthe probe function of the file, and at the end of this function, usedevm_iio_device_registerfunction to register the iio device. At the end of this function, it callscdev_device_addto register the character device and simultaneously create the device node. The specific content of this function is as follows:

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;
// If the device dev has a valid device number (devt)
if (dev->devt) {
// Set the parent object of cdev to device dev
cdev_set_parent(cdev, &dev->kobj);

// Register the character device, associating cdev with the device number dev->devt
rc = cdev_add(cdev, dev->devt, 1);
if (rc)
return rc;
}

// Register the device object dev, making it visible in sysfs, and create the /dev device node
rc = device_add(dev);
if (rc) // If device_add fails, roll back by deleting the registered cdev
cdev_del(cdev);

return rc;
}

You can see the call to this functiondevice_addfunction, through which the iio device node is created. At this point, the analysis of how the iio device node is created is complete

IIO Trigger

First, enter the serial terminal into/sys/bus/iio/devicesdirectory, as shown in the figure below:

iio_sysfs_trigger
iio_sysfs_trigger

In the previous chapter, the creation of theiio:device0folder under the sysfs directory was explained. In the same directory,iio_sysfs_triggerfolder is actually the iio trigger. In the kernel, you need to enableSYSFS triggerfor this directory to appear. The specific path is as follows:

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

The corresponding driver source code isdrivers/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;
// Initialize the device structure
device_initialize(&iio_sysfs_trig_dev);
// Set the device name to "iio_sysfs_trigger
dev_set_name(&iio_sysfs_trig_dev, "iio_sysfs_trigger");
// Add the device to the kernel
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);
  • Line 4, calldevice_initializeThe function initializes the device object, ensuring the device structure is in a usable state.
  • Line 7 calls thedev_set_namefunction to set a name for the device to identify it. The name set here isiio_sysfs_trigger, which is the directory seen in the sysfs subsystem.
  • Line 9 calls thedevice_addfunction to register the device into the kernel’s device model, making it part of the system.

These three functions share a common parameteriio_sysfs_trig_dev, which is astruct devicestructure type variable, with the specific content as follows:

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)
{
}

After a series of calls, it can be determined that the attributes to be created aredev_attr_add_trigger.attranddev_attr_remove_trigger.attr. These two attributes are theadd_triggerandremove_triggerin the sysfs directory, as shown in the following figure:

iio_sysfs_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;
// Convert the user-input string to an unsigned long integer number
ret = kstrtoul(buf, 10, &input);
if (ret)
return ret;
// Call the trigger probe function to attempt adding a trigger with the specified ID.
ret = iio_sysfs_trigger_probe(input);
if (ret)
return ret;
// Operation successful, returns the length of the input data.
return len;
}
// Define a device attribute file "add_trigger", allowing only user write (S_IWUSR), writing will invoke the iio_sysfs_trig_add function for processing.
static DEVICE_ATTR(add_trigger, S_IWUSR, NULL, &iio_sysfs_trig_add);

Navigate to/sys/bus/iio/devices/iio_sysfs_triggerthe directory, writeadd_trigger0 to it, and a folder namedtrigger0will be generated, as shown in the figure below:

echo 0 > add_trigger
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;

// Convert the user-input string to an unsigned long integer.
ret = kstrtoul(buf, 10, &input);
if (ret)
return ret;
// Call the trigger removal function to attempt removing a trigger with the specified ID.
ret = iio_sysfs_trigger_remove(input);
if (ret)
return ret;
// Operation successful, returns the length of the input data.
return len;
}

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

To delete the trigger just created, simply writeremove_trigger0 to it, as shown below:

echo 0 > remove_trigger
echo 0 > remove_trigger

iio_sysfs_trigger_probe()

How is the above phenomenon achieved? Let’s first explain the addition of the trigger file.iio_sysfs_trig_addfunction, the core content of which is in theiio_sysfs_trigger_probefunction, the specific content of the function is as follows:

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)
{
// Define a pointer to the iio_sysfs_trig structure pointer t, for subsequent operations
struct iio_sysfs_trig *t;
int ret; // Used to store the return value
bool foundit = false; // Flag variable, used to determine if a duplicate trigger ID is found

// Lock to ensure that access to the shared resource iio_sysfs_trig_list is thread-safe
mutex_lock(&iio_sysfs_trig_list_mut);
// Traverse the iio_sysfs_trig_list linked list to check if the same ID exists
list_for_each_entry(t, &iio_sysfs_trig_list, l)
if (id == t->id) { // If the same ID is found
foundit = true; // Set the flag to true
break;
}
// If a duplicate ID is found, return error code -EINVAL (invalid argument)
if (foundit) {
ret = -EINVAL;
goto out1; // Jump to the code segment that unlocks and returns
}
// Allocate memory to create a new iio_sysfs_trig structure
t = kmalloc(sizeof(*t), GFP_KERNEL);
if (t == NULL) { // If memory allocation fails
ret = -ENOMEM;
goto out1;
}
// Initialize the ID of the new trigger
t->id = id;
// Allocate a new IIO trigger and name it "sysfstrig%d", where %d is the passed ID
t->trig = iio_trigger_alloc("sysfstrig%d", id);
if (!t->trig) { // If trigger allocation fails
ret = -ENOMEM;
goto free_t;
}

// Set the attribute group of the trigger
t->trig->dev.groups = iio_sysfs_trigger_attr_groups;
// Set the operation function set of the trigger
t->trig->ops = &iio_sysfs_trigger_ops;
// Set the parent device of the trigger
t->trig->dev.parent = &iio_sysfs_trig_dev;
// Set the private data of the trigger to the current iio_sysfs_trig structure
iio_trigger_set_drvdata(t->trig, t);

// Initialize the interrupt work queue for handling trigger work
init_irq_work(&t->work, iio_sysfs_trigger_work);

// Register the trigger to the IIO subsystem
ret = iio_trigger_register(t->trig);
if (ret)
goto out2;
// Add the new trigger to the global linked list iio_sysfs_trig_list
list_add(&t->l, &iio_sysfs_trig_list);
// Increment the module's reference count to prevent the module from being unloaded
__module_get(THIS_MODULE);
// Unlock the mutex to allow other threads to access the shared resource
mutex_unlock(&iio_sysfs_trig_list_mut);
return 0;

out2:
// If the trigger registration fails, release the trigger resources
iio_trigger_free(t->trig);
free_t:
// If the trigger allocation fails, release the iio_sysfs_memory of the trig structure
kfree(t);
out1:
// Unlock the mutex to ensure the lock is released under all circumstances
mutex_unlock(&iio_sysfs_trig_list_mut);
return ret;
}

This function has only one parameter, id, which indicates the ID of the trigger to be created. Next, a detailed analysis of this function is provided.

  • Lines 11-15: Traverse the list to check if a trigger with the same ID exists.
  • Lines 22-34: Dynamically allocate memory to store the new trigger structure, then initialize the trigger object, creating the file name based on the passed ID value. For example, when the ID is 0, the file name issysfstrig0
  • Lines 37-43: Set the trigger’s attribute group, operation functions, parent device, and private data respectively.
  • Lines 46-57: Initialize the interrupt work queue for handling trigger work, and calliio_trigger_registerfunction to register the trigger with the IIO subsystem

The key point of this function is on line 49, theiio_trigger_registerfunction, which is defined ininclude/linux/iio/trigger.hand its specific content is as follows:

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;

// Set the module owner of the trigger to the current module
trig_info->owner = this_mod;

// Allocate a unique ID for the trigger using ida_simple_Get from the global ID allocator
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 */
// Set the name of the trigger device, used for sysfs directories, etc., in the format "trigger%ld"
dev_set_name(&trig_info->dev, "trigger%ld",
(unsigned long) trig_info->id);

// Add the trigger device to the device model
ret = device_add(&trig_info->dev);
if (ret) // If device addition fails, jump to the error handling label error_unregister_id
goto error_unregister_id;

/* Add to list of available triggers held by the IIO core */
// Lock the trigger list to ensure thread safety
mutex_lock(&iio_trigger_list_lock);
// Check if a trigger with the same name already exists
if (__iio_trigger_find_by_name(trig_info->name)) {
// If a duplicate name exists, print an error log and jump to the error handling label error_device_del
pr_err("Duplicate trigger name '%s'\n", trig_info->name);
ret = -EEXIST;
goto error_device_del;
}
// Add the trigger to the trigger list maintained by the IIO core
list_add_tail(&trig_info->list, &iio_trigger_list);
mutex_unlock(&iio_trigger_list_lock); // Unlock the trigger list

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);

The main function of this function is to register an IIO trigger into the system. First, it allocates a unique ID for the trigger and sets the device name, then on line 20 it adds the trigger device to the Linux device model, and finally on line 35 it adds the trigger to the global trigger linked list maintained by the IIO core.

iio_trigger_alloc()

iio_sysfs_trigger_probe()Called on line 30t->trig = iio_trigger_alloc("sysfstrig%d", id);, where theiio_trigger_allocfunction is defined as follows

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;

// Initialize variable argument list
va_start(vargs, fmt);
// Call viio_trigger_alloc function, passing format string and variable arguments, allocate and initialize trigger
trig = viio_trigger_alloc(fmt, vargs);
// End usage of variable argument list
va_end(vargs);

// Return pointer to allocated trigger structure, or NULL if allocation fails
return trig;
}
EXPORT_SYMBOL(iio_trigger_alloc);

The core of this function is at line 9viio_trigger_allocfunction, used to pass format string and variable arguments, allocate and initialize trigger,viio_trigger_allocThe specific content of the function is as follows:

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;

// Allocate memory for an iio_trigger structure and initialize to 0
trig = kzalloc(sizeof *trig, GFP_KERNEL);
if (!trig) // If allocation fails, return NULL
return NULL;

// Set trigger device type and bus type
trig->dev.type = &iio_trig_type;
trig->dev.bus = &iio_bus_type;
device_initialize(&trig->dev); // Initialize device structure

mutex_init(&trig->pool_lock); // Initialize trigger mutex for resource protection
// Allocate interrupt descriptor for trigger's sub-interrupt
trig->subirq_base = irq_alloc_descs(-1, 0,
CONFIG_IIO_CONSUMERS_PER_TRIGGER,
0);
if (trig->subirq_base < 0) // If allocation fails, jump to error handling
goto free_trig;

// Generate trigger name based on format string
trig->name = kvasprintf(GFP_KERNEL, fmt, vargs);
if (trig->name == NULL)
goto free_descs;

// Configure information related to sub-interrupt chip
trig->subirq_chip.name = trig->name; // Set interrupt chip name
trig->subirq_chip.irq_mask = &iio_trig_subirqmask; // Set interrupt mask function
trig->subirq_chip.irq_unmask = &iio_trig_subirqunmask; // Set interrupt unmask function
// Configure behavior of each sub-interrupt
for (i = 0; i < CONFIG_IIO_CONSUMERS_PER_TRIGGER; i++) {
irq_set_chip(trig->subirq_base + i, &trig->subirq_chip); // Set interrupt chip
irq_set_handler(trig->subirq_base + i, &handle_simple_irq); // Set interrupt handler function
irq_modify_status(trig->subirq_base + i,
IRQ_NOREQUEST | IRQ_NOAUTOEN, IRQ_NOPROBE); // Modify interrupt status flag
}

return trig; // Return trigger structure on success

free_descs:
// Release allocated interrupt descriptor
irq_free_descs(trig->subirq_base, CONFIG_IIO_CONSUMERS_PER_TRIGGER);
free_trig:
// Free trigger structure memory
kfree(trig);
return NULL;
}

This functionviio_trigger_allocis used to dynamically allocate and initialize an IIO trigger (iio_trigger) structure, setting its device type, bus type, name, interrupt descriptor, and related configuration of sub-interrupts. So the well-establishedtrig->subirq_chipWhere is the member used? In the previous chapter, when explaining the IIO device registration function, in__iio_device_registerLines 55-56 of the function also contain IIO trigger-related code, as shown below:

1
2
3
// If the device supports all trigger modes, register the trigger consumer
if (indio_dev->modes & INDIO_ALL_TRIGGERED_MODES)
iio_device_register_trigger_consumer(indio_dev);

iio_device_register_trigger_consumerThe function is used to register the trigger consumer, and it is defined indrivers/iio/industrialio-trigger.cthe file, with the specific content as follows:

1
2
3
4
5
6
void iio_device_register_trigger_consumer(struct iio_dev *indio_dev)
{
// Add the trigger consumer attribute group to the attribute group list of the IIO device
indio_dev->groups[indio_dev->groupcounter++] =
&iio_trigger_consumer_attr_group;
}

The attribute to be created by this function isiio_trigger_consumer_attr_group, and its structure variable content is as follows:

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,
};

After a series of tracing, it can be determined that an attribute namedcurrent_triggerwill be created in the sysfs directory, and this attribute hasiio_trigger_read_currenta read function, andiio_trigger_write_currenta write function.

iio_trigger_read_current()

iio_trigger_read_currentThe content related to the read function is as follows:

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)
{
// Convert the device structure to the IIO device structure
struct iio_dev *indio_dev = dev_to_iio_dev(dev);

// If the IIO device is currently bound to a trigger (trig is not null)
if (indio_dev->trig)
// Write the trigger name into the buffer buf and return the number of characters written
return sprintf(buf, "%s\n", indio_dev->trig->name);
// If no trigger is bound, return 0, indicating no data to read
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)
{
// Convert the device structure to an IIO device structure
struct iio_dev *indio_dev = dev_to_iio_dev(dev);
struct iio_trigger *oldtrig = indio_dev->trig; // Save the currently bound trigger
struct iio_trigger *trig; // New trigger pointer
int ret;

mutex_lock(&indio_dev->mlock); // Lock to protect device state
if (indio_dev->currentmode == INDIO_BUFFER_TRIGGERED) { // Convert the device structure to an IIO device structure
mutex_unlock(&indio_dev->mlock); // Unlock and return -EBUSY (device busy)
return -EBUSY;
}
if (indio_dev->trig_readonly) { // If the trigger is read-only
mutex_unlock(&indio_dev->mlock); // Unlock and return -EPERM (no permission)
return -EPERM;
}
mutex_unlock(&indio_dev->mlock); // Unlock

trig = iio_trigger_acquire_by_name(buf); // Get a new trigger based on the input name
if (oldtrig == trig) { // If the new and old triggers are the same, return success directly
ret = len;
goto out_trigger_put;
}

if (trig && indio_dev->info->validate_trigger) { // Verify whether the new trigger is compatible with the device
ret = indio_dev->info->validate_trigger(indio_dev, trig);
if (ret) // Verification fails, jump to error handling
goto out_trigger_put;
}

if (trig && trig->ops && trig->ops->validate_device) { // Verify whether the device is compatible with the trigger
ret = trig->ops->validate_device(trig, indio_dev);
if (ret) // Verification failed, jump to error handling
goto out_trigger_put;
}

indio_dev->trig = trig; // Update the device's trigger to the new trigger

if (oldtrig) { // If an old trigger exists, disassociate it from the device
if (indio_dev->modes & INDIO_EVENT_TRIGGERED)
iio_trigger_detach_poll_func(oldtrig,
indio_dev->pollfunc_event);
iio_trigger_put(oldtrig); // Decrease the reference count of the old trigger
}
if (indio_dev->trig) { // If the new trigger exists, associate it with the device
if (indio_dev->modes & INDIO_EVENT_TRIGGERED)
iio_trigger_attach_poll_func(indio_dev->trig,
indio_dev->pollfunc_event);
}

return len; // Return the write length, indicating success

out_trigger_put:
if (trig) // If the new trigger exists, decrease its reference count
iio_trigger_put(trig);
return ret;
}

Line 65 callsiio_trigger_attach_poll_funcfunction, the specific content of which is as follows:

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;
// Check whether the trigger's resource pool is empty to determine if the trigger is unused
bool notinuse
= bitmap_empty(trig->pool, CONFIG_IIO_CONSUMERS_PER_TRIGGER);

/* Prevent the module from being removed whilst attached to a trigger */
/* Prevent the module from being unloaded while the trigger is in use */
__module_get(pf->indio_dev->driver_module);

/* Get irq number */
/* Get the interrupt number of the trigger */
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 */
/* Request a threaded interrupt */
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 the trigger supports setting state and is unused, enable the trigger */
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.
*/
/*
* Check if registered to its own trigger:
* The judgment basis is IIO Whether the device and the trigger device have the same parent device。
*/
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;
}

This function is used to attach a polling function to a specified trigger, and complete operations such as interrupt requests and trigger state settings. The interrupt here is exactly theviio_trigger_allocimproved bytrig->subirq_chipmember. The IIO trigger executes corresponding events through interrupts.

IIO Data Read Analysis

When writing the ADC driver experiment, it is necessary to call theiio_read_channel_rawfunction to read the actual value of the ADC channel, but no detailed explanation was given for this function. In this chapter, we will provide a detailed explanation of theiio_read_channel_rawread function. This function is defined in thedrivers/iio/inkern.cfile, and the specific content is as follows:

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;

// Lock the information structure of the IIO device to prevent data inconsistency caused by concurrent access
mutex_lock(&chan->indio_dev->info_exist_lock);
// Check whether the information structure of the IIO device exists; if it is NULL, the device is unavailable
if (chan->indio_dev->info == NULL) {
ret = -ENODEV;
goto err_unlock;
}

// Call the iio_channel_read function to read the raw data of the channel (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; // Define an unused variable to handle the case where val2 is NULL
int vals[INDIO_MAX_RAW_ELEMENTS]; // Used to store the result of multi-value reading
int ret; // Save the function return value
int val_len = 2; // Read two values by default (val and val2)

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

// Check if the channel supports the specified information type (info); if not, return -EINVAL
if (!iio_channel_has_info(chan->channel, info))
return -EINVAL;

// If the device supports batch reading (read_raw_multi), call this function
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]; // Assign the first value to val
*val2 = vals[1]; // Assign the second value to val2
} else
// Otherwise, call the single-value read function read_raw
ret = chan->indio_dev->info->read_raw(chan->indio_dev,
chan->channel, val, val2, info);

// Return the read result; return 0 on success, or a negative error code on failure
return ret;
}

Since the batch read function is not implementedindio_dev->info->read_raw_multithe second branch is entered, i.e., throughindio_dev->info->read_rawthe function to perform the reading.

iio_device_add_info_mask_type()

How are the attribute files in the sysfs subsystem read to obtain the iio value? As explained earlieriio_device_register_sysfsWhen analyzing the function, it will ultimately calliio_device_add_info_mask_typefunction, iniio_device_add_info_mask_typethe function includes the read function,iio_device_add_info_mask_typeThe function is as follows:

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;

// Traverse each valid bit in the information mask (i.e., bits set to 1)
for_each_set_bit(i, infomask, sizeof(*infomask)*8) {
// Check if the index exceeds the range of the suffix array
if (i >= ARRAY_SIZE(iio_chan_info_postfix))
return -EINVAL; // If out of range, return an invalid parameter error

// Call the function to add the attribute to the sysfs interface
ret = __iio_add_chan_devattr(iio_chan_info_postfix[i], // Attribute name suffix
chan, // Channel pointer
&iio_read_channel_info, // Read callback function
&iio_write_channel_info, // Write callback function
i, // Attribute index
shared_by, // Shared type
&indio_dev->dev, // Device pointer
&iio_dev_opaque->channel_attr_list); // Attribute list

// If -EBUSY is returned and the attribute is not of an independent type, skip the attribute
if ((ret == -EBUSY) && (shared_by != IIO_SEPARATE))
continue;
// If other errors occur, return the error code directly
else if (ret < 0)
return ret;
// Successfully added an attribute, increment the count
attrcount++;
}

// Return the total number of successfully added attributes
return attrcount;
}

Line 19 of this function specifies the read function for the iio channeliio_read_channel_info, the specific content of this function is as follows:

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); // Convert the device structure to an IIO device structure
struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); // Convert the device attribute to an IIO attribute structure
int vals[INDIO_MAX_RAW_ELEMENTS]; // Used to store the read multi-value data
int ret; // Store the return value of the read operation
int val_len = 2; // Read two values by default

// If the device supports batch reading (read_raw_multi), call this function
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
// Otherwise, call the single-value read function read_raw
ret = indio_dev->info->read_raw(indio_dev, this_attr->c,
&vals[0], &vals[1], this_attr->address);

// If reading fails, directly return the error code
if (ret < 0)
return ret;

// Format the read value and write it into the buffer buf, returning the number of bytes written
return iio_format_value(buf, ret, val_len, vals);
}

The core content of this function is the logical judgment in lines 12-20, and since the batch read function is not implementedread_raw_multiit enters the second branch, which is the same as the previous analysisiio_read_channel_rawand is also performed via theindio_dev->info->read_rawfunction for reading. Andindio_dev->infois in thedrivers/iio/adc/rockchip_saradc.cprobe function in the file viaindio_dev->info = &rockchip_saradc_iio_info;, androckchip_saradc_iio_infoas follows:

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

Ultimately points to therockchip_saradc_read_rawfunction, the specific content of which is as follows:

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); // Get the private data of the IIO device
int ret;

#ifdef CONFIG_ROCKCHIP_SARADC_TEST_CHN
if (info->test) // If in test mode, directly return 0
return 0;
#endif
switch (mask) { // Select the read type based on the mask parameter
case IIO_CHAN_INFO_RAW: // Read raw ADC data
mutex_lock(&indio_dev->mlock); // Lock to protect device state

if (info->suspended) { // If the device is suspended, return -EBUSY (device busy)
mutex_unlock(&indio_dev->mlock);
return -EBUSY;
}

ret = rockchip_saradc_conversion(info, chan); // Conversion
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: // Read scaling factor
/* It is a dummy regulator */
if (info->uv_vref < 0) /* If the reference voltage is invalid, directly return an error code */
return info->uv_vref;

*val = info->uv_vref / 1000; // Calculate reference voltage (in millivolts)
*val2 = chan->scan_type.realbits;
return IIO_VAL_FRACTIONAL_LOG2;
default:
return -EINVAL;
}
}

rockchip_saradc_isr()

In the ADC device tree node, there is a description related to interrupts. When the conversion is complete, a corresponding interrupt signal is generated. The driver’s interrupt service function is requested and defined in the probe function by callingret = devm_request_irq(&pdev->dev, irq, rockchip_saradc_isr, 0, dev_name(&pdev->dev), info);Throughdevm_request_irqfunction, an interrupt service function namedrockchip_saradc_isris defined. The specific content of this function is as follows:

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; // Get device private data
#ifdef CONFIG_ROCKCHIP_SARADC_TEST_CHN
unsigned long flags; // Define a variable to save the interrupt status
#endif

/* Read value */
/* Read ADC conversion result */
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) { // If in test mode
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;
}

The final read data will be assigned torockchip_saradc_read_rawthe variable val of the function

ADC Button Driver Analysis

Device Tree:

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 Button
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()

The driver corresponding to the ADC button isinput/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; // Get Device Structure
struct adc_keys_state *st; // Define State Structure Pointer
struct input_dev *input; // Define Input Device Structure Pointer
enum iio_chan_type type; // Define IIO Channel Type
int i, value; // Define Loop Variables and Temporary Variables
int error; // Define Error Code

// Allocate Memory for State Structure adc_keys_state, using devm_kzalloc to ensure automatic release when the device is unloaded
st = devm_kzalloc(dev, sizeof(*st), GFP_KERNEL);
if (!st)
return -ENOMEM;

// Get IIO Channel for Reading the Analog Signal of the Button
st->channel = devm_iio_channel_get(dev, "buttons");
if (IS_ERR(st->channel))
return PTR_ERR(st->channel);

// Check if the channel is valid
if (!st->channel->indio_dev)
return -ENXIO;

// Get the channel type (e.g., voltage, current, etc.)
error = iio_get_channel_type(st->channel, &type);
if (error < 0)
return error;

// Ensure the channel type is voltage
if (type != IIO_VOLTAGE) {
dev_err(dev, "Incompatible channel type %d\n", type);
return -EINVAL;
}

// Read the "keyup-threshold-microvolt" property value from the device tree, which indicates the voltage threshold when the key is released
if (device_property_read_u32(dev, "keyup-threshold-microvolt",
&st->keyup_voltage)) {
dev_err(dev, "Invalid or missing keyup voltage\n");
return -EINVAL;
}
// Convert microvolts to millivolts
st->keyup_voltage /= 1000;

// Load the key mapping table
error = adc_keys_load_keymap(dev, st);
if (error)
return error;

// Allocate an input device
input = devm_input_allocate_device(dev);
if (!input) {
dev_err(dev, "failed to allocate input device\n");
return -ENOMEM;
}

input_set_drvdata(input, st);

// Set the basic information of the input device
input->name = pdev->name; // Set the device name
input->phys = "adc-keys/input0"; // Set the physical path

// Set the ID information of the input device
input->id.bustype = BUS_HOST; // Bus type is host
input->id.vendor = 0x0001; // Vendor ID
input->id.product = 0x0001; // Product ID
input->id.version = 0x0100; // Version number

// Set the supported event type to key event
__set_bit(EV_KEY, input->evbit);
for (i = 0; i < st->num_keys; i++) // Traverse the key mapping table
__set_bit(st->map[i].keycode, input->keybit); // Set the supported key codes

// If the "autorepeat" property is set in the device tree, enable the auto-repeat function
if (device_property_read_bool(dev, "autorepeat"))
__set_bit(EV_REP, input->evbit);

// Register the polling input device
error = input_setup_polling(input, adc_keys_poll);
if (error) {
dev_err(dev, "Unable to set up polling: %d\n", error);
return error;
}

// Read the "poll-interval" property value from the device tree and set the polling 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_devandinput_register_register()are both functions in the input subsystem.