IIO Driver Probe Function
Free Linux Kernel Development Course — IIO Framework, Lecture 10
Intermediate
6.x (current)
18-20 min
If you have been following this free linux kernel development course, you already know how read_raw() and write_raw() work in isolation. This lecture closes the loop: it walks through the iio driver probe function that ties struct iio_info, your channel array, and the IIO core together into a device that actually shows up under /sys/bus/iio/devices/. We build an original driver, ep_iio_probe_demo, from an empty probe() to a fully working sysfs interface, on a modern 6.x kernel.
What You Will Learn
- How
struct iio_infoconnects sysfs reads/writes to your driver code - The exact meaning of the
maskparameter inread_raw()andwrite_raw() - Step-by-step assembly of an iio driver probe function using
devm_iio_device_alloc()anddevm_iio_device_register() - Why modern IIO drivers no longer need a manual
remove()callback - What
available_scan_masksdoes and when you actually need it - How to test a freshly probed IIO device from the command line
Prerequisites
- Lecture 1-2 of this chapter: the IIO channel model and
iio_chan_specbasics - Comfort reading basic SPI or I2C client driver code
- A Linux kernel tree at version 6.1 or newer for building and testing
free linux device drivers course
free linux kernel development course
free embedded linux course
Why the Probe Function Matters
Every earlier lecture in this chapter dealt with one piece of an IIO driver: a channel spec here, a read hook there. None of those pieces do anything on their own. The probe() function is where a driver author allocates the iio_dev object, fills in its fields, and hands it to the IIO core with a single registration call. Get the field values wrong and either the device never appears, or it appears with missing sysfs files.
SPI/I2C core calls probe()
reserve iio_dev + private data
name, info, channels, modes
sysfs files created
struct iio_info: The Hook Table
struct iio_info is a small table of function pointers that the IIO core calls whenever user space touches a sysfs attribute. You only implement the hooks your device actually needs.
| Field | Called when… | Required? |
|---|---|---|
read_raw |
user space reads a *_raw, *_scale, or similar attribute |
Almost always |
write_raw |
user space writes to a settable attribute such as *_scale |
Only if a channel is settable |
attrs |
the driver exposes custom, non-channel sysfs attributes | Optional |
read_avail |
user space reads an *_available list (valid scale/frequency values) |
Recommended if write_raw is settable |
Note the field you will not find here on a current kernel: driver_module. Older reference material still shows it, but it was removed from struct iio_info years ago because the core now derives ownership from the parent device. Setting it today is a compile error, not a warning.
Reading the mask Parameter Correctly
Both hooks receive a long mask argument. This single value tells you which sysfs attribute triggered the call, so one function can serve every attribute of a channel.
int (*read_raw)(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int *val, int *val2, long mask);
| mask value | Sysfs attribute | Output convention |
|---|---|---|
IIO_CHAN_INFO_RAW |
in_voltage0_raw |
*val = raw ADC/register count |
IIO_CHAN_INFO_SCALE |
in_voltage0_scale |
*val, *val2 = integer + micro-fraction multiplier |
IIO_CHAN_INFO_OFFSET |
in_voltage0_offset |
value added before applying scale |
The physical reading is always (raw + offset) * scale. Splitting raw and scale keeps the fast path (a raw register read) cheap, while scale conversion stays in floating-point-free integer math that user space or a library like libiio can apply.
Building ep_iio_probe_demo
To keep the example focused, ep_iio_probe_demo models a small SPI voltage sensor with one channel and a settable gain. It is original code written for this course, not copied from any book or repository.
struct ep_probe_data {
struct spi_device *spi;
struct mutex lock;
int gain_idx;
};
static const int ep_gain_table[4][2] = {
{ 1, 0 }, /* x1 */
{ 2, 0 }, /* x2 */
{ 4, 0 }, /* x4 */
{ 8, 0 }, /* x8 */
};
static const struct iio_chan_spec ep_probe_channels[] = {
{
.type = IIO_VOLTAGE,
.indexed = 1,
.channel = 0,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
BIT(IIO_CHAN_INFO_SCALE),
},
};
Next, the read_raw() and write_raw() hooks. read_raw() answers both IIO_CHAN_INFO_RAW and IIO_CHAN_INFO_SCALE; write_raw() only handles IIO_CHAN_INFO_SCALE, letting user space pick a gain step.
static int ep_probe_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int *val, int *val2, long mask)
{
struct ep_probe_data *data = iio_priv(indio_dev);
u8 tx = 0x80, rx[2];
int ret;
switch (mask) {
case IIO_CHAN_INFO_RAW:
mutex_lock(&data->lock);
ret = spi_write_then_read(data->spi, &tx, 1, rx, 2);
mutex_unlock(&data->lock);
if (ret < 0)
return ret;
*val = (rx[0] << 8) | rx[1]; return IIO_VAL_INT; case IIO_CHAN_INFO_SCALE: *val = ep_gain_table[data->gain_idx][0];
*val2 = ep_gain_table[data->gain_idx][1];
return IIO_VAL_INT;
}
return -EINVAL;
}
static int ep_probe_write_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int val, int val2, long mask)
{
struct ep_probe_data *data = iio_priv(indio_dev);
int i;
if (mask != IIO_CHAN_INFO_SCALE)
return -EINVAL;
for (i = 0; i < ARRAY_SIZE(ep_gain_table); i++) { if (val == ep_gain_table[i][0] && val2 == ep_gain_table[i][1]) { mutex_lock(&data->lock);
data->gain_idx = i;
mutex_unlock(&data->lock);
return 0;
}
}
return -EINVAL;
}
static const struct iio_info ep_probe_info = {
.read_raw = ep_probe_read_raw,
.write_raw = ep_probe_write_raw,
};
Assembling iio_dev in probe()
This is the part earlier lectures only touched briefly. Every field you set here has a direct, visible effect on the sysfs directory the IIO core creates.
static int ep_probe(struct spi_device *spi)
{
struct iio_dev *indio_dev;
struct ep_probe_data *data;
indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*data));
if (!indio_dev)
return -ENOMEM;
data = iio_priv(indio_dev);
data->spi = spi;
mutex_init(&data->lock);
data->gain_idx = 0;
indio_dev->name = "ep_iio_probe_demo";
indio_dev->info = &ep_probe_info;
indio_dev->modes = INDIO_DIRECT_MODE;
indio_dev->channels = ep_probe_channels;
indio_dev->num_channels = ARRAY_SIZE(ep_probe_channels);
return devm_iio_device_register(&spi->dev, indio_dev);
}
static const struct spi_device_id ep_probe_id[] = {
{ "ep-iio-probe-demo", 0 },
{ }
};
MODULE_DEVICE_TABLE(spi, ep_probe_id);
static struct spi_driver ep_probe_driver = {
.driver = {
.name = "ep_iio_probe_demo",
},
.probe = ep_probe,
.id_table = ep_probe_id,
};
module_spi_driver(ep_probe_driver);
| iio_dev field | What it controls |
|---|---|
name |
text you see in cat /sys/bus/iio/devices/iio:deviceN/name |
info |
which read_raw/write_raw pair services this device |
modes |
whether direct sysfs reads, buffered capture, or both are allowed |
channels / num_channels |
which sysfs attribute files get generated |
Modernization note: notice there is no ep_probe_remove() function and no call to iio_device_unregister(). Because the driver used devm_iio_device_alloc() and devm_iio_device_register(), the device-managed (devm_) core automatically unregisters and frees everything when the SPI device is removed. Older reference material built around iio_device_alloc() / iio_device_register() still needs a manual remove() with the matching free/unregister calls — write new drivers with the devm_ variants instead.
A Note on available_scan_masks
If your device supports buffered capture across multiple channels (covered in earlier lectures of this chapter), iio_dev also has an available_scan_masks field: an array of bitmasks describing which channel combinations the hardware can sample together. A single-channel, non-buffered driver like ep_iio_probe_demo can leave it unset. A three-axis sensor that can only be read all-at-once or not-at-all would set it once, in probe, as a small static array — the same idea you saw applied to buffer setup in the earlier trigger lectures.
Testing the Driver
Build and load the module on a target or QEMU instance with SPI support, then inspect the generated sysfs tree.
$ sudo insmod ep_iio_probe_demo.ko
$ ls /sys/bus/iio/devices/iio:device0/
in_voltage0_raw in_voltage0_scale name ...
$ cat /sys/bus/iio/devices/iio:device0/name
ep_iio_probe_demo
$ cat /sys/bus/iio/devices/iio:device0/in_voltage0_raw
1832
$ cat /sys/bus/iio/devices/iio:device0/in_voltage0_scale
2.000000
$ echo "4 0" | sudo tee /sys/bus/iio/devices/iio:device0/in_voltage0_scale
$ cat /sys/bus/iio/devices/iio:device0/in_voltage0_scale
4.000000
The real voltage is raw * scale in millivolts (or whatever unit your datasheet defines). Changing the scale here changes the gain register on the next read.
Common Mistakes
| Mistake | Symptom | Fix |
|---|---|---|
Forgetting indio_dev->info |
Device registers but every sysfs read returns -ENODEV |
Always set info before calling register |
Mismatched info_mask_separate bits |
Expected sysfs file is missing | Add the matching IIO_CHAN_INFO_* bit for every attribute you implement |
Using iio_device_register() with a manual remove() that forgets to unregister |
Kernel warning or crash on module unload | Prefer the devm_ alloc/register pair |
Setting driver_module in iio_info |
Build fails on kernel 5.x/6.x | Remove the field entirely — it no longer exists |
Best Practices
- Always take a lock around hardware transactions inside
read_raw/write_raw— sysfs reads can happen concurrently - Return
-EINVALfor any unhandledmaskvalue instead of silently ignoring it - Prefer
devm_allocation and registration functions for new drivers on kernel 6.x - Keep
probe()focused on wiring fields together; push hardware setup into small helper functions
Performance and Security Considerations
Every sysfs read triggers a fresh call into read_raw() — there is no built-in caching, so a driver that talks to slow hardware over I2C should keep the bus transaction as short as possible and consider buffered/triggered capture (covered earlier in this chapter) for high sample-rate use cases. On the security side, always validate values arriving through write_raw() against a known table (as ep_probe_write_raw does) rather than writing raw user-supplied numbers straight to a hardware register.
Summary / Key Takeaways
struct iio_infois the hook table;read_raw/write_raware dispatched by themaskparameter- The iio driver probe function allocates with
devm_iio_device_alloc(), fillsname/info/modes/channels, and finishes withdevm_iio_device_register() devm_variants remove the need for a manualremove()callbackdriver_moduleno longer exists instruct iio_infoon current kernels
Conclusion
With this lecture, you can now read an IIO driver end to end: channel specs describe what exists, iio_info describes how to read and write it, and probe() is the glue that registers the whole thing with the kernel. That is the complete mental model behind every IIO client driver you will encounter in mainline Linux, and the same pattern ep_iio_probe_demo uses here scales up cleanly to the multi-channel and buffered drivers built earlier in this chapter.
FAQ
What is the difference between read_raw and write_raw in an IIO driver?
read_raw() answers sysfs reads (raw values, scale, offset); write_raw() handles writes to settable attributes such as scale or calibration bias. Both share the same mask convention.
Do I need to implement write_raw for every IIO driver?
No. Only implement it for channels where a value is meant to be user-adjustable, such as a settable gain or sampling frequency. A read-only sensor only needs read_raw.
Why did my driver fail to build with an error about driver_module?
driver_module was removed from struct iio_info. Reference material written against older kernels still shows it; delete that line from your driver and it will build cleanly on kernel 6.x.
Do I still need a remove() function in a modern IIO driver?
Not if you used devm_iio_device_alloc() and devm_iio_device_register(). The device-managed core unregisters and frees the device automatically when the parent device is removed.
What does the mask parameter in read_raw actually represent?
It identifies which sysfs attribute triggered the call, using constants like IIO_CHAN_INFO_RAW or IIO_CHAN_INFO_SCALE, so a single function can serve multiple attributes of the same channel.
When do I need to set available_scan_masks in iio_dev?
Only when the device supports buffered, multi-channel capture and hardware constraints limit which channel combinations can be sampled together. Single-channel, direct-mode drivers can leave it unset.
How is the real-world value calculated from raw and scale?
The IIO convention is (raw + offset) * scale. Not every channel implements offset; when it is absent, treat it as zero.
Continue the Free Linux Kernel Development Course
More IIO framework lectures, character device drivers, and platform driver tutorials are available on EmbeddedPathashala.
