Cover image for Linux IIO

Linux IIO

Words 22.1k
Views
Visitors

Timeline

Timeline

2026-02-15

init

This article introduces the IIO (Industrial Input/Output) subsystem in the Linux kernel, which is specifically used to handle ADC, DAC, and other industrial sensor devices. The article first explains the development background and characteristics of the IIO subsystem, including providing a unified interface, supporting multiple device types, and event and trigger mechanisms. Then, taking the RK3568 platform as an example, it analyzes in detail the registration process of IIO devices: finding the ADC node from the device tree, matching the driver file, and by`struct iio_dev`describing the IIO device, and using`struct rockchip_saradc`as private data associated with the IIO device. The article also analyzes the memory allocation in the probe function, the export of the device matching table, the definition of the channel specification array, and`devm_iio_device_register()`the specific calling process of the registration function, demonstrating how the IIO subsystem uniformly manages and operates various industrial I/O devices.

Linux Driver Notes

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

Introduction to the IIO Subsystem

IIO(Industrial Input/Output, Industrial Input/Output) is a subsystem in the Linux kernel, specifically used 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 development of embedded systems and the Internet of Things (IoT) technology, more and more 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, leading to code duplication, maintenance difficulties, and performance issues. Therefore, the Linux community introduced the IIO subsystem to solve these problems.

Features of IIO

  1. Unified interface: IIO provides a standardized API and data structures, allowing different types of devices to interact using the same interface. User-space applications can access IIO devices through the standard file system interface (/sys/bus/iio/devices/) to access IIO devices, thereby achieving unified management and operation of hardware such as sensors and converters.
  2. Support for multiple device types: The IIO subsystem can support multiple device types, including ADC (analog-to-digital converter), DAC (digital-to-analog converter), temperature sensors, light sensors, pressure sensors, as well as accelerometers and gyroscopes. This broad device compatibility makes IIO an ideal choice for handling industrial and consumer-grade sensors.
  3. Event and trigger mechanisms: 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 and capable of meeting time-sensitive application requirements.

Analysis of the IIO Registration Process

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

1234567891011121314151617181920212223242526272829303132333435363738394041424344
//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, a successive approximation type analog-to-digital converter.
Through thesaradcofcompatibleThe matching value finds the corresponding driver file asdrivers/iio/adc/rockchip_saradc.c

struct iio_dev

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

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677
/** * 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; // Device unique identifier	struct module			*driver_module; // Pointer to the driver module	int				modes; // Supported operating modes (e.g., direct mode, buffered 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, protects 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; // Whether the 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 operation 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, used to store device state	void				*priv;};

Can bestruct iio_devViewed 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 the probe function’sstruct rockchip_saradcdescribed by the structure,rockchip_saradcThe specific content of the structure is as follows:

struct rockchip_saradc

1234567891011121314151617181920212223242526272829303132
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 the 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 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 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()

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
// drivers/iio/adc/rockchip_saradc.c`static int rockchip_saradc_probe(struct platform_device *pdev){	struct rockchip_saradc *info = NULL; // Define the SARADC device private data structure pointer	struct device_node *np = pdev->dev.of_node; // Get the device tree node	struct iio_dev *indio_dev = NULL; // Define the IIO device structure pointer	struct resource	*mem; // Used to obtain memory resources	const struct of_device_id *match; // Used to match device tree nodes	int ret;	int irq;        // Check whether the device tree node exists	if (!np)		return -ENODEV;        // 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        // Match the device ID in the 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 the 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 device's register address resources	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 this property to be omitted         */	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 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 the 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 the 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 the 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);	}        // If a reset controller exists, perform reset operation	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 the converter clock frequency,Use the clock frequency in device data by default         * May support user configuration 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 the 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 when the device is removed	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 the actual value of the 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 reference voltage value 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; // IIO device operation function set	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 the 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, throughdevm_iio_device_allocThe function allocates a contiguous block of memory to storeiio_devand its private data (rockchip_saradc). Then, useiio_privthe function to get a pointer to the private data area, and the twoiio_devandrockchip_saradctypes of structure variables are connected together, as shown in the following code:

1234567
       // Allocate the IIO device structure and allocate private data space for itindio_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

In the probe source code, in order to match multiple SoC chips, throughof_match_devicethe function, with the value passed from the device tree,rockchip_saradc_matchperforms matching:

12345
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 structure array variable is as follows

1234567891011121314151617181920212223242526272829
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 automatic loading mechanism (udev / modprobe) knows:

  • Which devices does this driver support?
  • When the kernel detects the corresponding device, this module can be loaded automatically.

The data corresponding to rk3568 points tork3568_saradc_dataThe 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.

1234567891011
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.

12345678910
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 structure used to describe channel rules isstruct iio_chan_specIt defines the channel type, number, data format, supported attributes and events, etc., and is 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:

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
/** * 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 definition of the RK3568 ADC channel uses theSARADC_CHANNELmacro, and the specific content of this macro is as follows:

123456789101112131415
#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 thatchannelfield is used 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: scale
  • .datasheet_name = _idThe name in the datasheet, determined by the macro parameter_idspecified

devm_iio_device_register()

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

123456789101112131415
/** * 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()

123456789101112131415161718192021222324252627
// drivers/iio/industrialio-core.cint __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 that 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-in 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 linked list.	else		devres_free(ptr);// If registration fails, release the previously allocated resource management structure.	// Return the result of registration.	return ret;}EXPORT_SYMBOL_GPL(__devm_iio_device_register);

__iio_device_register()

__devm_iio_device_registercall in__iio_device_registerto register the IIO device

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
// drivers/iio/industrialio-core.cint __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._dev's driver_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 whether 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 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, then 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 interfaces and masks.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 parent device’s device tree node.
  • Lines 14-16: Throughiio_check_unique_scan_indexfunction checks whether the channel scan index is unique, ensuring no duplicate indices.
  • Line 20: ThroughMKDEVfunction generates the device number, assigning unique identifiers to the major and minor devices.
  • Line 23: Throughiio_device_register_debugfsThe function registers the debugfs interface, used for debugging and monitoring device status.
  • Line 31: Throughiio_buffer_alloc_sysfs_and_maskThe function allocates buffer-related sysfs interfaces and masks, but after jumping to it, the function is found to be empty, so this part of the code has no practical significance.
  • Line 39: Throughiio_device_register_sysfsThe function registers the device’s sysfs interface, exposing device attributes to user space.
  • Line 47: Throughiio_device_register_eventsetThe function registers an event set to manage events triggered by the device.
  • Lines 55-56: Throughiio_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_opsisnoop_ring_setup_opsProvide default buffer operation functions.
  • Line 64: Throughcdev_initThe function initializes the character device structure and binds file operation functions.
  • Line 67: By settingindio_dev->chrdev.owneristhis_mod, ensuring resources are released when the module is unloaded.
  • Line 70: Throughcdev_device_addThe function adds the character device and device structure to the system.

The focus of this function is on line 47’siio_device_register_eventsetfunction, which is used to register IIO events, defined indrivers/iio/industrialio-event.cfile, the specific content of the function is as follows

iio_device_register_eventset()

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576
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 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, count their number	if (indio_dev->info->event_attrs != NULL) {		attr = indio_dev->info->event_attrs->attrs;		while (*attr++ != NULL) // Iterate through 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, try 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 to 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. */	// Iterate through the device attribute list and add all dynamic attributes to the attribute array of the event group	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 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. In the iio subsystem, the structure used to describe the corresponding event isstruct iio_event_interface, the specific content of this structure is as follows:

1234567891011121314151617181920212223242526272829
/** * 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 the 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) to store detected event data.	// Each event data is a `struct iio_event_data` structure of type.	DECLARE_KFIFO(det_events, struct iio_event_data, 16);	// Device attribute list head, used to manage device attributes related to events (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 features 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, 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, a wait queue to implement an event notification mechanism to wake up listeners, a linked list to dynamically maintain event-related attributes and expose them to user space via sysfs for configuration and monitoring, a mutex to ensure safe concurrent access in a multi-threaded environment, and flag bits to record the status or characteristics of the event interface, thereby efficiently completing event detection, configuration, storage, notification, and processing.

IIO device driver analysis

IIO file operation set function analysis

Before__iio_device_registerCalled in the function.cdev_init(&indio_dev->chrdev, &iio_buffer_fileops);Initialize the character device.

12345678910
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

12345678910111213141516171819202122232425
/* 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 parameters passed from user space into user space pointers	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 the IOCTL command to obtain the event file descriptor	if (cmd == IIO_GET_EVENT_FD_IOCTL) {		fd = iio_event_getfd(indio_dev); // Call the function to obtain the event file descriptor		if (fd < 0) // If acquisition fails, return the error code directly			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:

12345678910111213141516171819202122232425262728293031323334353637383940
int iio_event_getfd(struct iio_dev *indio_dev){	struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev);	// Get the device's event interface	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 to ensure mutually exclusive access to 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 check)	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 while in use.	iio_device_get(indio_dev);	// Create an anonymous inode file and return a 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 occupancy 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 isfd = anon_inode_getfd("iio:event", &iio_event_chrdev_fileops,indio_dev, O_RDONLY | O_CLOEXEC);returned,anon_inode_getfdThe function’s purpose is to create an anonymous file descriptor fd. An anonymous file descriptor is a special type of file descriptor that is not associated with an actual filesystem 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 through ioctl, thereby providing users with additional system call interfaces to interact with certain functions or data in the kernel without relying on the traditional filesystem.

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

1234567
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 operations are not supported (lseek is invalid).};

iio_event_chrdev_read()

Regarding its read function,iio_event_chrdev_readan analysis is performed. The specific contents of this function are as follows:

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556
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 the 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 the event interface.	unsigned int copied; // Record the amount of data actually copied to user space.	int ret;	if (!indio_dev->info) // Check whether 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 whether the event queue is empty			if (filep->f_flags & O_NONBLOCK) // If in non-blocking mode, return -EAGAIN directly				return -EAGAIN;			// Block waiting 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 function’s do-while loop determines whether to wait for event data by checking whether the kernel FIFO event queue is empty;
In non-blocking mode, return directly-EAGAINwhile in blocking mode, it suspends the process via a wait queue until there is data or the device is removed; then it uses a mutex to protect concurrent access to the FIFO queue, ensuring thread safety;
Finally, it copies the event data from kernel space to user space, and handles special cases in non-blocking mode based on the read result. If no data is read, it continues looping and waiting until data is successfully read, then returns the actual number of bytes read or an error code.

iio_event_poll()

12345678910111213141516171819202122232425262728293031323334
/** * 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, return an empty event (no events) directly	if (!indio_dev->info)		return events;	// Add the current file descriptor to the wait queue so that it can be woken up when an event occurs	poll_wait(filep, &ev_int->wait, wait);	// Check whether 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 usespoll_wait(filep, &ev_int->wait, wait);The function adds the current file descriptor to the wait queue so that it can be woken up when an event arrives. Then it checks whether the event FIFO queue is non-empty. If there are pending events, it setsEPOLLIN | EPOLLRDNORMThe flag indicates that data is readable, and finally returns the detected event.

iio_event_chrdev_release()

12345678910111213141516
static int iio_event_chrdev_release(struct inode *inode, struct file *filep){	// Get 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 the 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 occupation	iio_device_put(indio_dev);	return 0;}

iio_event_chrdev_releaseThe function 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 that the device can be safely used by other processes or further cleaned up when no longer needed.

In the IIO subsystem, multiple system calls need to be supported. 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, because anonymous file descriptors leave no trace in the file system, thus keeping the system clean and efficient.

iio_chrdev_open()

1234567891011121314151617181920212223242526
/** * 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){	// Get the corresponding IIO device structure from the inode.	struct iio_dev *indio_dev = container_of(inode->i_cdev,						struct iio_dev, chrdev);	// Check whether 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_openUsed to open the IIO character device, first throughcontainer_ofThe macro gets the corresponding IIO device structure from the inode, then checks whether the device is already occupied (by testing and setting the busy flag). If the device is busy, it returns-EBUSY. Then it callsiio_device_getThe function increases the device 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:

12345678910
/** * 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 will not be accidentally released during use, and return the device pointer for subsequent operations. It first checks whether the passed-instruct indio_devis NULL. If it is not NULL, then throughget_deviceincrease the device reference count, and convert the device pointer from dev back toiio_devtype and return it; if it is NULL, return NULL directly. This implementation both ensures the validity of the device and avoids operations on a NULL pointer.

iio_buffer_read_outer_addr()

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

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

iio_buffer_read_outerdefined as follows

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
// 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 = 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;	// Validity check	if (!indio_dev->info)		return -ENODEV;	if (!rb || !rb->access->read)		return -EINVAL;	datum_size = rb->bytes_per_datum; // A 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 already enough data in the buffer?		if (!iio_buffer_ready(indio_dev, rb, to_wait, n / datum_size)) { 			// If there is no signal			if (signal_pending(current)) { // Check for signals, 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.

12345678
while (没有数据) {    如果非阻塞 → EAGAIN    如果被信号打断 → 退出    睡眠等待}读数据返回

wherestruct iio_bufferdefined as follows

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859
/** * 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 read and write	/** @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 when 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; // Buffer group attributes	/*	 * @scan_el_group: Attribute group for those attributes not	 * created from the iio_chan_info array.	 */	struct attribute_group scan_el_group; // Attribute group, used 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 used to collect 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, used to manage resource release};

Andiio_bufferinconst struct iio_buffer_access_funcs *accessdefined as follows

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859
/** * 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, usually 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, usually associated 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, usually 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;	// Flags of the buffer, 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 the storage, reading, configuration, and lifecycle of the buffer, while supporting multiple working modes and functional extensions, iniio_buffer_read_outer_addrIn the function, useret = rb->access->read(rb, n, buf);Actually read data.

iio_chrdev_release()

1234567891011121314151617181920212223
/** * 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 via 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 that 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 device occupation	// If the reference count drops to 0, further cleanup or release operations of the device may be triggered	iio_device_put(indio_dev);	// Return 0 indicates that the device resources were successfully released.	return 0;}

The core role 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 device’s BUSY status flag and reduces the device’s reference count, thereby 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:

123456789101112131415161718192021222324252627282930
/** * 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){	// Get the IIO device structure associated with the device from the file structure.	struct iio_dev *indio_dev = filp->private_data;	// Get the buffer structure of the 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 that 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 to check whether there is data available to read in the buffer. It first obtains the device and buffer information from the file structure and verifies its validity; then throughpoll_waitadding the current file descriptor to the wait queue so that it can be woken up when data arrives; finally throughiio_buffer_readycheck whether the buffer meets the read condition (such as reaching the watermark value), and if so, returnEPOLLIN | EPOLLRDNORMindicating that data is readable, otherwise return 0 to indicate no event.

iio_device_register_sysfs()

When explaining the IIO device registration process, we__iio_device_registerconducted a brief analysis, and in that function it also callsret = iio_device_register_sysfs(indio_dev);function to register the sysfs interface of the IIO device.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
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 - relies on a fact:If the group name is empty,then there is no need to initialize the group。	 */	if (indio_dev->channels) // If the device has channel definitions		for (i = 0; i < indio_dev->num_channels; i++) { // iterate over 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 attributes to the system.			ret = iio_device_add_channel_sysfs(indio_dev, chan);			if (ret < 0) // If adding fails, jump to error handling.				goto error_clear_attrs;			attrcount += ret; // Accumulate the number 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 successerror_clear_attrs:	iio_free_chan_devattr_list(&iio_dev_opaque->channel_attr_list);	return ret;}

The core operation of this function isret = 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()

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
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 the independent type information mask attribute (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 the 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 the info mask attribute shared by type (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 the available information mask attribute shared by type (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 the information mask attribute shared by direction (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 the available information mask attribute shared by direction (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 the 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) // If adding fails, return an error code.		return ret;	attrcount += ret; // Accumulate the number of newly added attributes.	// Add the 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) // If adding fails, return an error code.		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 it.			if (ret == -EBUSY && ext_info->shared)				continue;						// If any other error occurs, return the error code directly.			if (ret)				return ret;			attrcount++; // Successfully add an attribute and increment the count		}	}	return attrcount; // Return the total number of attributes successfully added}

These code snippets, by callingiio_device_add_info_mask_typeandiio_device_add_info_mask_type_availThe function adds channel attributes and their available attributes one by one according to different sharing types. Each type corresponds to a specific set of functions, and after each operation, the return value is checked to ensure errors can be captured and handled in a timely manner. At the same time, 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 are explained. The function prototypes of the two functions are as follows:

iio_device_add_info_mask_typeThe function is defined as follows:

12345678910111213141516171819202122232425262728
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 is defined as follows

123456789101112131415161718192021222324252627282930313233343536
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 the channel,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 on. It contains all information about the device, such as channels and attributes, and is the core connecting hardware and user space.
  2. chan: Pointer to the channel, representing the specific channel to be operated on. Each channel corresponds to a sensor or signal interface (such as temperature, pressure, etc.) and defines the functionality 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 attributes.
    • IIO_SHARED_BY_DIR: Shared by direction, channels with the same direction share attributes.
    • IIO_SHARED_BY_ALL: Globally shared, all channels of the entire device share attributes.
  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_typein,infomaskDescribes the basic functions of the channel (such as read, write, etc.). Iniio_device_add_info_mask_type_availAmong them, infomask describes dynamically available functions (such as additional functions enabled in certain modes). The mask contents used in the above code are described in the following table:
Mask nameCore functionShareabilityTypical application scenarios
info_mask_separateExport information specific to the current channelIndependent (current channel only)Channel-specific statistics, queue configuration, single-channel link status
info_mask_separate_availableExport the ‘availability’ flag for current channel-specific information (indicating whether such information can be queried)Independent (current channel only)Indicate whether the current channel’s statistics have been collected and whether the configuration is effective
info_mask_shared_by_typeExport common information shared by all channels of the same typeShared by type (channels of the same type)Device type attributes, driver version, global statistics for channels of the same type (e.g., common configuration for all 10G network ports)
info_mask_shared_by_type_availableExport the ‘availability’ flag for information shared by channels of the same typeShared by type (channels of the same type)Indicate whether the common configuration for channels of the same type 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 (channels in the same direction)RX/TX direction-specific statistics (e.g., total bytes of all receive channels), direction-related hardware configuration
info_mask_shared_by_dir_availableExport the ‘availability’ flag for information shared by channels in the same directionShared by direction (channels in the same direction)Indicate whether statistics for channels in a certain direction 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, total transmit/receive statistics for all channels
info_mask_shared_by_all_availableExport the ‘availability’ flag for globally shared informationGlobally shared (all channels)Indicate whether the device global status can be queried and whether firmware version information was read successfully

Here we only added the attribute information. So when is the attribute setting completed? In the earlier analysisrockchip_saradc_probeof the function, it was mentioned that the RK3568 ADC channel definition usesSARADC_CHANNELmacro, and the specific content of this macro is as follows:

123456789101112131415
#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 thatchannelfield is used 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: scale
  • .datasheet_name = _idThe name in the datasheet, determined by the macro parameter_idspecified

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

Explanation of the IIO channel attribute addition function

iio_device_add_info_mask_type()

12345678910111213141516171819202122232425262728293031323334353637
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 whether the index exceeds the range of the suffix array		if (i >= ARRAY_SIZE(iio_chan_info_postfix))			return -EINVAL; // If it exceeds the range, return an invalid argument 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 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 add an attribute and increment the count		attrcount++;	}		// Return the total number of attributes successfully added	return attrcount;}

iio_device_add_info_mask_type_avail()

123456789101112131415161718192021222324252627282930313233343536373839404142434445
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 an 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 the available attribute 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		// Free the dynamically allocated attribute name suffix.		kfree(avail_postfix);		// If -EBUSY is returned and the attribute is not of independent type, skip this attribute		if ((ret == -EBUSY) && (shared_by != IIO_SEPARATE))			continue;		else if (ret < 0) // If any other error occurs, return the error code directly			return ret;				// Successfully add an attribute and increment the count		attrcount++;	}		// Return the total number of attributes successfully added	return attrcount;}

The implementation logic of these two functions is the same, and the main content is infor_each_set_bitIt is implemented in this for loop. The for loop iterates over each valid bit in the mask, parses the corresponding function, and registers it as a sysfs attribute. Here, the function used to add the available attribute to the sysfs interface is__iio_add_chan_devattrfunction, whose specific content 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 *chanPointer to the channel, representing the channel object currently being operated on.
  • ssize_t (*)(struct device *dev, struct device_attribute *attr, char *buf) readfuncRead callback function, used to implement the read operation of the attribute. The return value is the length of data read or an error code.
  • ssize_t (*)(struct device *dev, struct device_attribute *attr, const char *buf, size_t len) writefuncWrite callback function, used to implement the write operation of the attribute. The return value is 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, which determines how the attribute is shared among channels (e.g., independent, shared by type, etc.).
  • struct device * devPointer to the device, indicating the target device of the current operation.
  • struct list_head * attr_listHead pointer of the attribute list, used to add newly created attributes to the linked list.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556
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 whether 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 sharing 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 the allocated memory	kfree(iio_attr);	return ret;}

Allocate memory and create a newiio_dev_attrobject. The corresponding structure content is shown below. 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 to which it belongs.

12345678910111213
/** * 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;			  // The 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 linked list	struct iio_chan_spec const *c;    // Pointer to the channel to which it belongs};
__iio_device_attr_init()

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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
staticint __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 execution result of the function	char *name = NULL; // Attribute name	char *full_postfix; // Full 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 shared type is separate (IIO_SEPARATE)		if (chan->extend_name)			// If the channel has an extended name, generate the full 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-separate shared type		if (chan->extend_name == NULL || shared_by != IIO_SEPARATE)			// If there is no extended name or the shared type is not separate, copy the suffix directly			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 whether the suffix was allocated successfully	if (full_postfix == NULL)		return -ENOMEM;	// Construct the attribute name based on the channel type (differential or single-ended) and shared type	if (chan->differential) { /* Differential can not have modifier */ /* 差分通道 */		switch (shared_by) {		case IIO_SHARED_BY_ALL:  // Global shared: use only the suffix			name = kasprintf(GFP_KERNEL, "%s", full_postfix);			break;		case IIO_SHARED_BY_DIR:  // Shared by direction: add direction prefix			name = kasprintf(GFP_KERNEL, "%s_%s",						iio_direction[chan->output],						full_postfix);			break;		case IIO_SHARED_BY_TYPE:  // Shared 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:  // Separate attribute: check whether it is indexed			if (!chan->indexed) {				WARN(1, "Differential channels must be indexed\n");				ret = -EINVAL;				goto error_free_full_postfix;			}			// Construct the full 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 shared: use only the suffix			name = kasprintf(GFP_KERNEL, "%s", full_postfix);			break;		case IIO_SHARED_BY_DIR: // Shared by direction: add direction prefix			name = kasprintf(GFP_KERNEL, "%s_%s",						iio_direction[chan->output],						full_postfix);			break;		case IIO_SHARED_BY_TYPE: // Shared 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 the name according to 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);			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 whether the attribute name was allocated successfully		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 read 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); // Free the dynamically allocated suffix string	return ret;}

The main function of this function is to dynamically construct a sysfs attribute name based on the channel type, sharing type, and other parameters. The attribute name is set at line 123.dev_attr->attr.name = name;The implementation is carried out there, and before that, the program can be divided into three parts based on if statements, corresponding to:Constructing the suffix stringDifferential channel attribute nameSingle-ended channel attribute namelogic. Next, we will explain these three parts in detail.

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

ConditionFormat
Channel is modified and sharing mode isIIO_SEPARATEHas extended name (chan->extend_name)[modifier name][extended name][suffix]
Channel is modified and sharing mode isIIO_SEPARATENo extended name[modifier name][suffix]
Channel not modified or shared_by ≠IIO_SEPARATENo extended name or sharing mode ≠IIO_SEPARATE[suffix]
Channel not modified or shared_by ≠IIO_SEPARATEHas extended name and shared_by isIIO_SEPARATE(listed for completeness only)[extended name][suffix]

Lines 53-86, for 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), the channel must be indexed (chan->indexed), and include detailed information 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
IIO_SHARED_BY_ALL[full_postfix]
IIO_SHARED_BY_DIR[output direction]_[full_postfix]
IIO_SHARED_BY_TYPE[output direction][channel type]-[channel type][full_postfix]
IIO_SEPARATE[output direction][channel type][channel index]-[channel type][channel2 index][full_postfix]

Lines 88-118, for single-ended 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), add direction and type information; for independent shared (IIO_SEPARATE), decide whether to include the channel index in the name based on whether it is indexed; when not indexed, omit the index part.

Sharing method (shared_by)Attribute name format
IIO_SHARED_BY_ALL[full_postfix]
IIO_SHARED_BY_DIR[output direction]_[full_postfix]
IIO_SHARED_BY_TYPE[Output direction][Channel type][full_postfix]
IIO_SEPARATE(with index,chan->indexed[Output direction][Channel type][Channel index][full_postfix]
IIO_SEPARATE(without index)[Output direction][Channel type]_[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 the RK3568 channels is as follows:

12345678910
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:

123456789101112131415
#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 thatchannelfield is used 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: scale
  • .datasheet_name = _idThe name in the datasheet, determined by the macro parameter_idspecified

After substituting into ADC3, the content is as follows:

123456789101112131415
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 = 3,	.scan_type = {		.sign = 'u',		.realbits = 10,		.storagebits = 16,		.endianness = IIO_CPU,	},}

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

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

12345678910111213141516
// Add the independent type information mask attribute (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 the 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]i.e., raw.

Then we continue to analyze further, determine the full name of the attribute, and then enteriio_device_attr_initfunction, first judge the constructed suffix. Since in the definition of ADC3 there is nomodifiedattribute, andextend_nameIt is not defined, so the suffix is directly copied, that is, the RAW determined above.

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

1234567
case IIO_SEPARATE: // Independent attribute: construct the name according to 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, and the obtained value isinchan->typeisIIO_VOLTAGE, substituting intoiio_chan_type_name_specwe can get the valuevoltagechan->channelas 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. The second attribute name can be obtained asin_voltage_scale

IIO device node creation analysis

iio_device_add_channel_sysfsThe function determines the attribute names of the ADC channel, then collects the device’s channel attributes, device name, and timestamp attributes, and binds them withindio_devthe device, and finally creates 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_groupbut does not create these attributes in the sys directory. So where is the code that creates the related attribute files?

device_create()

There are two ways to create device nodes. The first way is throughmknodcommand 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:

123456789101112131415161718192021222324252627282930313233343536373839404142434445
// 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, used to store the variable arguments passed to the function.	va_list vargs;	// Define a pointer variable dev to struct device, used to store the created device object.	struct device *dev;	// Initialize the variable argument list vargs; fmt is the last fixed parameter, and the following parameters are variable.	va_start(vargs, fmt);	// Call 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; its key point isdevice_create_vargsfunction,device_create_vargsThe specific content of the function is as follows:

device_create_vargs()

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950
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 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 passed 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 succeeded.		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's private data.	// Use the variable arguments args to set the device name (via the 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()The function initializes the device and finally callsdevice_add()

device_add()

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
/** * 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 a reference to the device 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 the 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 relationship)	if (dev->fwnode && !dev->fwnode->dev) {		dev->fwnode->dev = dev;		fw_devlink_link_device(dev);	}	// Probe the device and bind the 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 above code:

123456789101112
// If the device number is valid, create related files and nodesif (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 a device number exists, executedevtmpfs_create_nodefunction to create the device node. If there is no device number, it callskobject_ueventfunction creates 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 IIO device’s device node?

devm_iio_device_alloc()

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

1234567891011121314151617181920212223242526272829303132
/** * 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 the 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 linked 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:

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152
/** * 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 allocate, 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 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 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 the device name.	dev_set_name(&dev->dev, "iio:device%d", dev->id);	INIT_LIST_HEAD(&iio_dev_opaque->buffer_list); // Initialize the buffer linked list	return dev;}EXPORT_SYMBOL(iio_device_alloc);

Finally, this function usesdev_set_namea function to set the iio device name, which is under the dev directoryiio\:device0node name, and earlier throughdevice_initializethe iio device was initialized, and at this point, the series of operations before creating the device node is completed.

Then return torockchip_saradc.cthe probe function of the file, and at the end of this function, throughdevm_iio_device_registera function to register the iio device, and at the end of this function, it callscdev_device_addused to register the character device and create the device node at the same time. The specific content of this function is as follows:

1234567891011121314151617181920212223242526272829303132333435363738394041424344
/** * 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 the 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, make it visible in sysfs, and create the /dev device node	rc = device_add(dev);	if (rc) // If device_add fails, delete the registered cdev to roll back		cdev_del(cdev);	return rc;}

You can see the call to this function.device_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, in the serial terminal, enter/sys/bus/iio/devicesdirectory, as shown in the figure below:

iio_sysfs_trigger
iio_sysfs_trigger

In the previous chapter, under the sysfs directoryiio:device0the creation of the folder was explained, and the one in the same directoryiio_sysfs_triggerthe folder is actually an iio trigger, and in the kernel you need to checkSYSFS triggeronly then will this directory appear, and the specific path is as follows:

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

1234567891011121314151617181920
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_initializefunction to initialize the device object, ensuring the device structure is in a usable state.
  • Line 7, calldev_set_namefunction to set a name for the device, used to identify the device; the name set here isiio_sysfs_trigger, which is the directory seen in the sysfs subsystem.
  • Line 9, calldevice_addfunction to register the device into the kernel’s device model, making it part of the system.

These three functions all have a common parameteriio_sysfs_trig_dev, which is astruct devicevariable of structure type, and its specific content is as follows:

struct device iio_sysfs_trig_dev

12345
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

1234
struct bus_type iio_bus_type = {	.name = "iio",};EXPORT_SYMBOL(iio_bus_type);

struct attribute_group iio_sysfs_trig_groups

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

1234
/* 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 attribute to be created ultimately isdev_attr_add_trigger.attranddev_attr_remove_trigger.attr, these two attributes are exactly the ones under the sysfs directoryadd_triggerandremove_trigger, as shown in the figure below:

iio_sysfs_trigger
iio_sysfs_trigger

iio_sysfs_trig_add()

1234567891011121314151617181920
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 string entered by the user to an unsigned long integer	ret = kstrtoul(buf, 10, &input);	if (ret)		return ret;    	// Call the trigger probe function to attempt to add a trigger with the specified number	ret = iio_sysfs_trigger_probe(input);	if (ret)		return ret;    	// On success, return the length of the input data	return len;}// Define a device attribute file "add_trigger", write-only for users (S_IWUSR), and when written, it calls the iio_sysfs_trig_add function to handle itstatic DEVICE_ATTR(add_trigger, S_IWUSR, NULL, &iio_sysfs_trig_add);

Go to/sys/bus/iio/devices/iio_sysfs_triggerdirectory, and write toadd_triggerwrite 0, and a folder namedtrigger0folder, as shown in the figure below:

echo 0 > add_trigger
echo 0 > add_trigger

iio_sysfs_trig_remove()

12345678910111213141516171819202122
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 string entered by the user to an unsigned long integer	ret = kstrtoul(buf, 10, &input);	if (ret)		return ret;    	// Call the trigger remove function to attempt to remove the trigger with the specified number	ret = iio_sysfs_trigger_remove(input);	if (ret)		return ret;    	// On success, return the length of the input data	return len;}static DEVICE_ATTR(remove_trigger, S_IWUSR, NULL, &iio_sysfs_trig_remove);

If you want to delete the trigger just created, simply write toremove_triggerwrite 0, as shown below:

echo 0 > remove_trigger
echo 0 > remove_trigger

iio_sysfs_trigger_probe()

So how is the above phenomenon implemented? Here we first explain theiio_sysfs_trig_addfunction. The core content of this function is iniio_sysfs_trigger_probefunction. The detailed content of the function is as follows:

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970
static int iio_sysfs_trigger_probe(int id){    	// Define a pointer to the iio_sysfs_trig structure pointer t, used for subsequent operations	struct iio_sysfs_trig *t;	int ret; // Used to store the return value	bool foundit = false; // Flag variable, used to determine whether a duplicate trigger ID is found    	// Lock to ensure that the shared resource iio_sysfs_Access to trig_list is thread-safe	mutex_lock(&iio_sysfs_trig_list_mut);    	// Traverse iio_sysfs_the trig_list linked list, check whether 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 section 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 new trigger's ID	t->id = id;    	// Allocate a new IIO trigger and name it "sysfstrig%d", where %d is the passed-in ID	t->trig = iio_trigger_alloc("sysfstrig%d", id);	if (!t->trig) { // If trigger allocation fails		ret = -ENOMEM;		goto free_t;	}    	// Set the trigger's attribute group	t->trig->dev.groups = iio_sysfs_trigger_attr_groups;    	// Set the trigger's operation function set	t->trig->ops = &iio_sysfs_trigger_ops;    	// Set the trigger's parent device	t->trig->dev.parent = &iio_sysfs_trig_dev;    	// Set the trigger's private data to the current iio_sysfs_trig structure	iio_trigger_set_drvdata(t->trig, t);    	// Initialize the interrupt work queue to handle the trigger's work	init_irq_work(&t->work, iio_sysfs_trigger_work);    	// Register the trigger with the IIO subsystem	ret = iio_trigger_register(t->trig);	if (ret)		goto out2;    	// Add the new trigger to the global linked list iio_sysfs_in 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 shared resources	mutex_unlock(&iio_sysfs_trig_list_mut);	return 0;out2:    	// If trigger registration fails, release the trigger resources	iio_trigger_free(t->trig);free_t:    	// If trigger allocation fails, release iio_sysfs_the memory of the trig structure	kfree(t);out1:    	// Unlock the mutex to ensure the lock is released in any case	mutex_unlock(&iio_sysfs_trig_list_mut);	return ret;}

This function has only one parameter, id, which represents the ID of the trigger to be created. Next, we analyze this function in detail.

  • Lines 11-15: Traverse the list to check whether a trigger with the same id exists.
  • Lines 22-34: Dynamically allocate memory to store the new trigger structure, then initialize the trigger object and create the file name based on the passed ID value. For example, when the passed 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 workqueue used to handle trigger work, and calliio_trigger_registerThe function registers the trigger to the IIO subsystem.

The key point of this function is at line 49iio_trigger_registerfunction, which is defined ininclude/linux/iio/trigger.hin, and its specific content is as follows:

12345678
/** * 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()

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647
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 obtains it 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., with 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 adding the device 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 whether 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 to the system. First, it allocates a unique ID for the trigger and sets the device name. Then, at line 20, it adds the trigger device to the Linux device model. Finally, at line 35, it adds the trigger to the global trigger linked list maintained by the IIO core.

iio_trigger_alloc()

iio_sysfs_trigger_probe()At line 30, callt->trig = iio_trigger_alloc("sysfstrig%d", id);, in whichiio_trigger_allocthe function is defined as follows

12345678910111213141516
struct iio_trigger *iio_trigger_alloc(const char *fmt, ...){	struct iio_trigger *trig;	va_list vargs;    	// initialize the variable argument list	va_start(vargs, fmt);    	// call viio_trigger_alloc function, passing the format string and variable arguments, to allocate and initialize the trigger	trig = viio_trigger_alloc(fmt, vargs);    	// end the use of the variable argument list	va_end(vargs);    	// return the pointer to the allocated trigger structure, or NULL if allocation fails	return trig;}EXPORT_SYMBOL(iio_trigger_alloc);

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

viio_trigger_alloc()
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051
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 it to 0	trig = kzalloc(sizeof *trig, GFP_KERNEL);	if (!trig) // if allocation fails, return NULL		return NULL;	// set the type and bus type of the trigger device	trig->dev.type = &iio_trig_type;	trig->dev.bus = &iio_bus_type;	device_initialize(&trig->dev); // Initialize the device structure.	mutex_init(&trig->pool_lock); // initialize the trigger's mutex for resource protection	// allocate interrupt descriptors for the trigger's child interrupts	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 the trigger name based on the format string	trig->name = kvasprintf(GFP_KERNEL, fmt, vargs);	if (trig->name == NULL)		goto free_descs;		// configure the relevant information of the child interrupt chip	trig->subirq_chip.name = trig->name; // set the interrupt chip name	trig->subirq_chip.irq_mask = &iio_trig_subirqmask; // set the interrupt mask function	trig->subirq_chip.irq_unmask = &iio_trig_subirqunmask; // set the interrupt unmask function	// configure the behavior of each child interrupt	for (i = 0; i < CONFIG_IIO_CONSUMERS_PER_TRIGGER; i++) {		irq_set_chip(trig->subirq_base + i, &trig->subirq_chip); // set the interrupt chip		irq_set_handler(trig->subirq_base + i, &handle_simple_irq); // Set interrupt handler		irq_modify_status(trig->subirq_base + i,				  IRQ_NOREQUEST | IRQ_NOAUTOEN, IRQ_NOPROBE); // modify the interrupt status flag	}	return trig; // return the trigger structure on successfree_descs:	// Release the allocated interrupt descriptor	irq_free_descs(trig->subirq_base, CONFIG_IIO_CONSUMERS_PER_TRIGGER);free_trig:	// Free the trigger structure memory	kfree(trig);	return NULL;}

This functionviio_trigger_allocis to dynamically allocate and initialize an IIO trigger (iio_trigger) structure, set its device type, bus type, name, interrupt descriptor, and sub-interrupt related configuration, the here-refinedtrig->subirq_chipmember is used where? In the previous chapter, when explaining the IIO device registration function, in__iio_device_registerlines 55-56 of the function also have IIO trigger-related code, as shown below:

123
// If the device supports all trigger modes, register the trigger consumerif (indio_dev->modes & INDIO_ALL_TRIGGERED_MODES)	iio_device_register_trigger_consumer(indio_dev);

iio_device_register_trigger_consumerfunction is used to register the trigger consumer, which is defined indrivers/iio/industrialio-trigger.cfile, and the specific content is as follows:

123456
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:

12345678910111213
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 finally 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:

123456789101112131415161718192021222324252627
/** * 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 to 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 there is no data to read	return 0;}
iio_trigger_write_current()
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475
/** * 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 the 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 the 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 the 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) // If 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) // If verification fails, 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 successout_trigger_put:	if (trig) // If the new trigger exists, decrease its reference count		iio_trigger_put(trig);	return ret;}

Called at line 65iio_trigger_attach_poll_funcfunction, whose specific content is as follows:

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566
/* 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 whether 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 not in use, 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 whether it is registered to its own trigger:	 * The basis for the judgment 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 perform operations such as interrupt request and trigger state setting. The interrupt here is exactly the one mentioned aboveviio_trigger_allocthat was perfectedtrig->subirq_chipmember. The IIO trigger executes the corresponding event by means of an interrupt.

IIO Data Read Analysis

When writing the ADC driver experiment, you need to calliio_read_channel_rawfunction to read the actual value of the ADC channel, but that function was not explained in detail. In this chapter, we williio_read_channel_rawexplain the read function in detail. This function is defined indrivers/iio/inkern.cfile, and the specific content is as follows:

iio_read_channel_raw()

123456789101112131415161718192021
// drivers/iio/inkern.cint iio_read_channel_raw(struct iio_channel *chan, int *val){	int ret;		// Lock to protect the IIO device information structure to prevent data inconsistency caused by concurrent access.	mutex_lock(&chan->indio_dev->info_exist_lock);	// Check whether the IIO device information structure exists. If it is NULL, the device is unavailable.	if (chan->indio_dev->info == NULL) {		ret = -ENODEV;		goto err_unlock;	}	// Call 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()

123456789101112131415161718192021222324252627282930
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 whether 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 read (read_raw_multi), then 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, and a negative error code on failure.	return ret;}

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

iio_device_add_info_mask_type()

So how do attribute files in the sysfs subsystem read IIO values? In the previous explanationiio_device_register_sysfsof the function, we analyzed that it ultimately callsiio_device_add_info_mask_typefunction, in whichiio_device_add_info_mask_typethe read function is included.iio_device_add_info_mask_typeThe function is as follows:

1234567891011121314151617181920212223242526272829303132333435363738
// drivers/iio/industrialio-core.cstatic 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 whether the index exceeds the range of the suffix array		if (i >= ARRAY_SIZE(iio_chan_info_postfix))			return -EINVAL; // If it exceeds the range, return an invalid argument 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 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 add an attribute and increment the count		attrcount++;	}		// Return the total number of attributes successfully added	return attrcount;}

In line 19 of this function, the read function of the IIO channel is specifiediio_read_channel_infoThe specific content of this function is as follows:

iio_read_channel_info()

12345678910111213141516171819202122232425262728
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 the IIO device structure	struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); // Convert the device attribute to the IIO attribute structure	int vals[INDIO_MAX_RAW_ELEMENTS]; // Used to store the multi-value data read	int ret; // Store the return value of the read operation	int val_len = 2; // Read two values by default    	// If the device supports batch read (read_raw_multi), then 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 the read fails, return the error code directly	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 logic judgment in lines 12-20, and since the batch read function is not implementedread_raw_multiSo it enters the second branch, which is the same as the previous analysisiio_read_channel_rawthe same, also throughindio_dev->info->read_rawfunction to perform the read. Andindio_dev->infois indrivers/iio/adc/rockchip_saradc.cthe probe function in the file, throughindio_dev->info = &rockchip_saradc_iio_info;, androckchip_saradc_iio_infoAs follows:

123
static const struct iio_info rockchip_saradc_iio_info = {	.read_raw = rockchip_saradc_read_raw,};

ultimately points torockchip_saradc_read_rawFunction, the specific content of which is as follows:

rockchip_saradc_read_raw()

123456789101112131415161718192021222324252627282930313233343536373839404142
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, return 0 directly		return 0;#endif	switch (mask) { // Select the read type according to 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 the scaling factor		/* It is a dummy regulator */		if (info->uv_vref < 0) /* If the reference voltage is invalid, return the error code directly */			return info->uv_vref;		*val = info->uv_vref / 1000; // Calculate the reference voltage (unit: 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 completed, a corresponding interrupt signal is generated. In the driver, the request for the interrupt service function is defined in the probe function, by callingret = devm_request_irq(&pdev->dev, irq, rockchip_saradc_isr, 0, dev_name(&pdev->dev), info);Through thedevm_request_irqThe function defines one namedrockchip_saradc_isrthe interrupt service function, the specific content of which is as follows:

123456789101112131415161718192021222324252627
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 state#endif	/* Read value */    	/* Read the 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 function’s variable val

ADC key driver analysis

Device tree:

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

adc_keys_probe()

The driver corresponding to the ADC key isinput/keyboard/adc-keys.c

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
static int adc_keys_probe(struct platform_device *pdev){	struct device *dev = &pdev->dev; // Get the device structure	struct adc_keys_state *st; // Define a state structure pointer	struct input_dev *input; // Define an input device structure pointer	enum iio_chan_type type; // Define the IIO channel type	int i, value; // Define loop variables and temporary variables	int error;  // Define an error code	// Allocate memory for the 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 the IIO channel to read the analog signal of the key	st->channel = devm_iio_channel_get(dev, "buttons");	if (IS_ERR(st->channel))		return PTR_ERR(st->channel);		// Check whether the channel is valid	if (!st->channel->indio_dev)		return -ENXIO;		// Get the channel type (such as 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" attribute value from the device tree, which represents 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; // The 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++) // Iterate through 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()They are all functions in the input subsystem.

Loading comments…