IIO Device Driver Structure
Free Linux Kernel Development Course • Free Linux Device Drivers Course • Free Embedded Linux Course
free linux kernel development course
free linux device drivers course
free embedded systems course
iio_info read_raw write_raw
indio_dev modes
In the previous lecture of this free linux device drivers course you built a minimal IIO voltage driver. Now we open up the iio_dev structure linux kernel drivers are actually built around, and look closely at operating modes and the iio_info callback table. This lecture continues our free linux kernel development course and free embedded systems course series on the Industrial I/O subsystem.
What You Will Learn
- The role and key fields of
struct iio_devon modern kernels - The difference between direct mode and buffered/triggered modes
- How
struct iio_infocallbacks connect sysfs reads/writes to your hardware code - How to extend a driver to support both read and write channels
- How to inspect and test a two-attribute IIO driver from user space
Prerequisites
- Completed Part 1 of this chapter (Linux IIO Framework Basics)
- Working kernel 6.x build environment and the
ep_iio_volt_demodriver from Part 1
Why struct iio_dev Matters
Every IIO driver revolves around one central object: struct iio_dev. It is the in-kernel representation of your chip and the driver managing it. The IIO core reads this structure to know which channels exist, which modes the device supports, and which callbacks to invoke when user space performs a read or write. You never fill this structure by hand field-by-field in one big literal — instead you allocate it with devm_iio_device_alloc() and then set only the fields your driver actually needs, as you saw in Part 1.
Key Fields You Actually Set
Rather than reproducing the entire kernel header, here are the fields a typical driver author touches directly, and what each one controls:
| Field | Purpose |
|---|---|
name |
Human-readable driver/device name, shown by the name sysfs file |
modes |
Bitmask of operating modes the device supports (see next section) |
channels |
Pointer to your static const struct iio_chan_spec array |
num_channels |
Number of entries in that channel array |
info |
Pointer to your struct iio_info callback table (read_raw, write_raw, and friends) |
currentmode |
Mode the device is actually running in right now; the core manages this once triggers/buffers are configured |
available_scan_masks |
Optional: restricts which channel combinations can be captured together in buffered mode |
Fields such as the internal chrdev, buffer, and locking structures are managed by the IIO core itself — a driver author almost never touches them directly.
IIO Operating Modes
The modes field is a bitmask built from constants defined in linux/iio/iio.h. On kernel 6.x these are:
| Mode Constant | Meaning |
|---|---|
INDIO_DIRECT_MODE |
Device supports plain sysfs-style one-shot reads/writes — what our Part 1 driver used |
INDIO_BUFFER_TRIGGERED |
Device supports software-triggered buffered capture; set automatically when you call iio_triggered_buffer_setup() |
INDIO_BUFFER_SOFTWARE |
Device pushes samples into a software ring buffer without needing an external trigger |
INDIO_BUFFER_HARDWARE |
Device has its own hardware FIFO/DMA buffer |
INDIO_EVENT_TRIGGERED |
Device can generate threshold/event style triggers |
INDIO_ALL_BUFFER_MODES |
Convenience OR of all buffer-capable modes |
A driver can combine modes with bitwise OR, for example INDIO_DIRECT_MODE | INDIO_BUFFER_TRIGGERED, to allow both a quick sysfs read and continuous buffered capture from the same device. Full triggered-buffer capture is covered later in this chapter; for now our example stays in direct mode.
struct iio_info: The Callback Table
struct iio_info is where your driver plugs its own logic into the IIO core. The two callbacks used in almost every driver are:
read_raw()— called when user space reads a channel attribute such asraworscalewrite_raw()— called when user space writes a settable attribute, such as a DAC output value or a configurable gain
Hands-On: Adding write_raw() to the Demo Driver
Let’s extend the ep_iio_volt_demo driver from Part 1 with a settable software gain, to demonstrate write_raw() alongside read_raw(). This is still original code, written for this course and not copied from any book.
// ep_iio_gain_demo.c (extends ep_iio_volt_demo with a writable gain)
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
#include <linux/mutex.h>
struct ep_iio_priv {
struct mutex lock;
int raw_value;
int gain; /* software gain multiplier, settable from user space */
};
static int ep_iio_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int *val, int *val2, long mask)
{
struct ep_iio_priv *priv = iio_priv(indio_dev);
int ret = IIO_VAL_INT;
mutex_lock(&priv->lock);
switch (mask) {
case IIO_CHAN_INFO_RAW:
*val = priv->raw_value * priv->gain;
break;
case IIO_CHAN_INFO_SCALE:
*val = 3;
*val2 = 220000;
ret = IIO_VAL_INT_PLUS_MICRO;
break;
case IIO_CHAN_INFO_HARDWAREGAIN:
*val = priv->gain;
break;
default:
ret = -EINVAL;
}
mutex_unlock(&priv->lock);
return ret;
}
static int ep_iio_write_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int val, int val2, long mask)
{
struct ep_iio_priv *priv = iio_priv(indio_dev);
if (mask != IIO_CHAN_INFO_HARDWAREGAIN)
return -EINVAL;
if (val < 1 || val > 16)
return -EINVAL;
mutex_lock(&priv->lock);
priv->gain = val;
mutex_unlock(&priv->lock);
return 0;
}
static const struct iio_info ep_iio_info = {
.read_raw = ep_iio_read_raw,
.write_raw = ep_iio_write_raw,
};
static const struct iio_chan_spec ep_iio_channels[] = {
{
.type = IIO_VOLTAGE,
.indexed = 1,
.channel = 0,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
BIT(IIO_CHAN_INFO_SCALE) |
BIT(IIO_CHAN_INFO_HARDWAREGAIN),
},
};
static int ep_iio_probe(struct platform_device *pdev)
{
struct iio_dev *indio_dev;
struct ep_iio_priv *priv;
indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*priv));
if (!indio_dev)
return -ENOMEM;
priv = iio_priv(indio_dev);
mutex_init(&priv->lock);
priv->raw_value = 512;
priv->gain = 1;
indio_dev->name = "ep_iio_gain_demo";
indio_dev->info = &ep_iio_info;
indio_dev->modes = INDIO_DIRECT_MODE;
indio_dev->channels = ep_iio_channels;
indio_dev->num_channels = ARRAY_SIZE(ep_iio_channels);
platform_set_drvdata(pdev, indio_dev);
return devm_iio_device_register(&pdev->dev, indio_dev);
}
static struct platform_driver ep_iio_driver = {
.driver = {
.name = "ep_iio_gain_demo",
},
.probe = ep_iio_probe,
};
static struct platform_device *ep_iio_pdev;
static int __init ep_iio_init(void)
{
ep_iio_pdev = platform_device_register_simple("ep_iio_gain_demo", -1, NULL, 0);
if (IS_ERR(ep_iio_pdev))
return PTR_ERR(ep_iio_pdev);
return platform_driver_register(&ep_iio_driver);
}
static void __exit ep_iio_exit(void)
{
platform_driver_unregister(&ep_iio_driver);
platform_device_unregister(ep_iio_pdev);
}
module_init(ep_iio_init);
module_exit(ep_iio_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("EmbeddedPathashala");
MODULE_DESCRIPTION("IIO driver demonstrating read_raw and write_raw with a settable gain");
Build, Load, and Test
make -C /lib/modules/$(uname -r)/build M=$(pwd) modules
sudo insmod ep_iio_gain_demo.ko
# Read the current raw value (gain = 1 by default)
cat /sys/bus/iio/devices/iio:device0/in_voltage0_raw
512
# Increase the gain
echo 4 | sudo tee /sys/bus/iio/devices/iio:device0/in_voltage0_hardwaregain
# Raw value now reflects the new gain
cat /sys/bus/iio/devices/iio:device0/in_voltage0_raw
2048
Notice how the sysfs file name in_voltage0_hardwaregain is derived automatically from the channel type, index, and the IIO_CHAN_INFO_HARDWAREGAIN mask — the same naming convention introduced in Part 1.
Performance Considerations
- Keep
read_raw()/write_raw()fast: they run in process context but any real hardware transaction (I2C/SPI) will block the caller. - Use a mutex, as shown above, to protect shared private state instead of disabling interrupts — these callbacks always run in a context that can sleep.
- For high-rate continuous sampling, plan to move to triggered buffers rather than polling
rawin a tight user-space loop.
Security Considerations
- Always validate written values (as done for
gainabove) before applying them to hardware registers. - Sysfs files created by IIO default to root-writable permissions unless you customize them; review who should be allowed to change gain or output channels on your target system.
- Never trust a raw value coming back from hardware without range-checking it before exposing it further up the stack.
Common Mistakes and Troubleshooting
| Mistake | Fix |
|---|---|
Declaring write_raw() but forgetting the matching BIT() flag in info_mask_separate |
The attribute will be read-only in sysfs even though your callback exists — always match mask and callback |
Not locking shared state between read_raw() and write_raw() |
Add a mutex in your private struct, as shown in ep_iio_gain_demo |
Setting modes to a buffered mode without actually setting up a buffer |
Only set INDIO_BUFFER_* once you have called the corresponding buffer setup function, covered later in this chapter |
Best Practices
- Treat
iio_infoas your driver’s public API surface — keep it small and only implement the callbacks you truly need. - Validate every value in
write_raw()before touching hardware or shared state. - Document which
IIO_CHAN_INFO_*masks each channel supports directly above the channel array, for the next engineer reading your code.
Summary and Key Takeaways
struct iio_devties together your channels, callback table, and operating mode; the IIO core manages the rest.modestells the core whether your device supports direct sysfs access, buffered capture, or both.struct iio_info‘sread_raw()andwrite_raw()are where hardware access actually happens.- Sysfs attribute file names are generated automatically from channel type, index, and info mask — never hard-coded by the driver author.
Conclusion
With the iio_dev structure linux kernel internals and the iio_info callback pattern covered, you now understand how any IIO driver — whether it is a simple voltage sensor or a full accelerometer with buffered output — is put together. This completes the core building blocks for direct-mode IIO drivers in this free linux kernel development course. The next lecture in this free linux device drivers course moves on to iio_chan_spec in depth, followed by triggered buffer support for continuous, high-rate capture.
Frequently Asked Questions
What is struct iio_dev used for?
It is the central kernel object representing an IIO device, holding its channels, callback table, and operating mode.
What does the modes field in iio_dev control?
It is a bitmask declaring which operating modes the device supports, such as INDIO_DIRECT_MODE or INDIO_BUFFER_TRIGGERED.
What is the difference between read_raw and write_raw in iio_info?
read_raw handles user space reads of a channel attribute, while write_raw handles writes, such as setting a gain or DAC output value.
Can one IIO channel support both direct mode and buffered mode?
Yes, by OR-ing INDIO_DIRECT_MODE with a buffer mode constant in the modes field, once the corresponding buffer is set up.
Why did the sysfs attribute name change to in_voltage0_hardwaregain?
The IIO core derives sysfs file names from the channel’s type, index, and the IIO_CHAN_INFO_HARDWAREGAIN mask automatically.
Do I need a mutex inside my IIO driver’s private data?
Yes, whenever read_raw and write_raw can run concurrently and touch shared state, as shown in the ep_iio_gain_demo example.
Is triggered buffering covered in this lecture?
No, this lecture stays in direct mode; triggered buffer support is covered in a later lecture of this free linux kernel development course.
Continue the Free Linux Device Drivers Course
Next: iio_chan_spec in depth and triggered buffer capture.
