IIO Device Driver Structure- Free Linux Device Drivers Course

IIO Device Driver Structure

Free Linux Kernel Development Course • Free Linux Device Drivers Course • Free Embedded Linux Course

Chapter 10 • Part 2
Kernel 6.x Verified
Builds On Part 1
iio_dev structure linux kernel
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_dev on modern kernels
  • The difference between direct mode and buffered/triggered modes
  • How struct iio_info callbacks 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_demo driver 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.

Where iio_info Fits
User reads/writes a channel’s sysfs file (e.g. in_voltage0_raw)
IIO core matches the file to a channel + info mask
Calls indio_dev->info->read_raw() or ->write_raw()
Your driver talks to hardware and returns the value

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 as raw or scale
  • write_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 raw in a tight user-space loop.

Security Considerations

  • Always validate written values (as done for gain above) 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_info as 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_dev ties together your channels, callback table, and operating mode; the IIO core manages the rest.
  • modes tells the core whether your device supports direct sysfs access, buffered capture, or both.
  • struct iio_info‘s read_raw() and write_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.

 

Leave a Reply

Your email address will not be published. Required fields are marked *