IIO Scan Elements Type Format
If you are building a free linux device drivers course mindset around sensor drivers, the iio scan_elements type attribute is one of the most misunderstood parts of the Linux Industrial I/O framework. It is the small sysfs file that tells user space exactly how a raw sample is packed inside a triggered buffer — its sign, its bit width, its padding, its shift, and its endianness. Get this wrong in your driver and every buffered reading a user space tool decodes will be garbage, even though your hardware registers are perfectly correct.
This lecture is part of our free embedded linux course and continues the IIO framework chapter of the free linux kernel development course hosted on EmbeddedPathashala. We build an original triaxial sensor driver, expose its scan elements correctly, and verify the buffer output byte by byte.
Free Linux Development Course
Free Linux Device Drivers Course
Free Linux Kernel Development Course
What You Will Learn
Lecture Outcomes
iio scan_elements type attribute format
scan_type structure fields
en attribute and buffer enable logic
building an original triaxial IIO driver
reading and decoding raw buffer bytes
Prerequisites
Before You Start
- Basic understanding of the IIO device/channel model — see our earlier IIO framework introduction lecture.
- Familiarity with
iio_chan_spec,info_mask_separate, and channel indexing — covered in our iio_dev structure lecture. - Working knowledge of triggered buffers — see our IIO triggered buffer support lecture.
- A Linux system running kernel 6.x with IIO support (
CONFIG_IIO=y) for testing.
What Is a Scan Element in the IIO Framework?
Every channel that can push a sample into a triggered buffer is called a scan element. Not every IIO channel needs to be a scan element — a channel that is only ever read through the direct sysfs _raw attribute does not need one. But the moment a channel participates in buffered capture, the kernel needs a way to describe, precisely, how that channel’s value is laid out inside the buffer, and user space needs a way to read that description before it can decode anything.
That description lives under /sys/bus/iio/iio:deviceX/scan_elements/, and this is exactly where the iio scan_elements type attribute comes in. Two attributes matter most here:
*_en— a boolean switch. Writing 1 enables the channel for the next triggered capture; writing 0 disables it. If it reads 0, that channel’s bytes simply will not appear in the buffer.*_type— a read-only string describing exactly how the raw value is stored: its endianness, its sign, its valid bit count, its storage size, and any bit shift needed to extract it.
Decoding the IIO scan_elements Type Attribute Format
The type string follows a fixed pattern:
[be|le]:[s|u]<realbits>/<storagebits>X<repeat>[>>shift]
Each component maps directly onto a field of the scan_type sub-structure inside iio_chan_spec. The table below breaks each piece down so you can read any iio scan_elements type attribute string at a glance.
| Symbol | Meaning | struct field |
|---|---|---|
| be / le | Big-endian or little-endian byte order | endianness |
| s / u | Signed (two’s complement) or unsigned value | sign |
| realbits | Number of valid data bits the hardware actually produces | realbits |
| storagebits | Total bits this channel occupies in the buffer, including padding | storagebits |
| repeat | Number of back-to-back repetitions of this element (omitted if 0 or 1) | repeat |
| shift | Bits to shift right before masking out realbits | shift |
A Worked Diagram: 10-Bit Data Inside a 16-Bit Register Pair
To make the iio scan_elements type attribute concrete, imagine an original triaxial sensor design, which we’ll call the EP-ACCEL200, that reports 10 valid bits of data per axis but stores each sample across two 8-bit registers (16 bits total), left-justified. Here is how the two bytes look, built as an inline HTML diagram rather than text art:
Reading this diagram: 10 valid bits (D0–D9, blue and green) are packed into the top of a 16-bit little-endian field. The 6 bottom bits (grey X) are padding. The resulting iio scan_elements type attribute string for this layout is:
le:s10/16>>6
That single line means: little-endian, signed, 10 real bits, stored in 16 bits, shift right by 6 before masking. Once you can build this diagram for any register map, you can write the scan_type field for almost any sensor.
The scan_type Field Inside iio_chan_spec
Inside the kernel, this exact pattern is captured by the scan_type sub-structure of struct iio_chan_spec, which every IIO driver author fills in per channel:
struct iio_chan_spec {
/* ... other members such as type, channel, scan_index ... */
struct {
char sign; /* 's' or 'u' */
u8 realbits; /* valid data bits */
u8 storagebits; /* total bits in buffer */
u8 shift; /* right-shift before masking */
u8 repeat; /* repetitions, 0/1 = omitted */
enum iio_endian endianness;
} scan_type;
};
This structure has stayed stable across recent kernel releases, so drivers written against 6.x kernels use the same six fields you see above. What has changed over time is how drivers around it are written — modern IIO drivers favour devm_iio_device_register(), regmap-backed register access, and resource-managed triggered buffer helpers instead of manual allocate/register/unregister/free sequences.
Building an Original Driver With Scan Element Support
Let’s put this into a complete, original example driver — ep_iio_scanattr_demo — for our fictional EP-ACCEL200 triaxial sensor over SPI. This extends the triggered-buffer pattern from our earlier lecture with correct scan_type values for every axis.
// ep_iio_scanattr_demo.c
// Original EmbeddedPathashala example driver — EP-ACCEL200 style sensor
#include <linux/module.h>
#include <linux/spi/spi.h>
#include <linux/iio/iio.h>
#include <linux/iio/buffer.h>
#include <linux/iio/triggered_buffer.h>
#include <linux/iio/trigger_consumer.h>
#define EP_ACCEL200_REG_X_L 0x2A
#define EP_ACCEL200_DATA_SHIFT 6
struct ep_scanattr_priv {
struct spi_device *spi;
struct mutex lock;
/* raw sample cache: 3 x s16 + timestamp, 8-byte aligned */
s16 buf[4] __aligned(8);
};
#define EP_ACCEL_CHANNEL(idx, reg, axis) { \
.type = IIO_ACCEL, \
.address = (reg), \
.modified = 1, \
.channel2 = IIO_MOD_##axis, \
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
.scan_index = (idx), \
.scan_type = { \
.sign = 's', \
.realbits = 10, \
.storagebits = 16, \
.shift = EP_ACCEL200_DATA_SHIFT, \
.endianness = IIO_LE, \
}, \
}
static const struct iio_chan_spec ep_scanattr_channels[] = {
EP_ACCEL_CHANNEL(0, EP_ACCEL200_REG_X_L, X),
EP_ACCEL_CHANNEL(1, EP_ACCEL200_REG_X_L + 2, Y),
EP_ACCEL_CHANNEL(2, EP_ACCEL200_REG_X_L + 4, Z),
IIO_CHAN_SOFT_TIMESTAMP(3),
};
static irqreturn_t ep_scanattr_trigger_handler(int irq, void *p)
{
struct iio_poll_func *pf = p;
struct iio_dev *indio_dev = pf->indio_dev;
struct ep_scanattr_priv *priv = iio_priv(indio_dev);
mutex_lock(&priv->lock);
/* real driver: spi_read of 6 bytes into priv->buf here */
iio_push_to_buffers_with_timestamp(indio_dev, priv->buf,
iio_get_time_ns(indio_dev));
mutex_unlock(&priv->lock);
iio_trigger_notify_done(indio_dev->trig);
return IRQ_HANDLED;
}
static const struct iio_info ep_scanattr_info = {
/* read_raw / write_raw callbacks go here */
};
static int ep_scanattr_probe(struct spi_device *spi)
{
struct iio_dev *indio_dev;
struct ep_scanattr_priv *priv;
int ret;
indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*priv));
if (!indio_dev)
return -ENOMEM;
priv = iio_priv(indio_dev);
priv->spi = spi;
mutex_init(&priv->lock);
indio_dev->name = "ep_iio_scanattr_demo";
indio_dev->info = &ep_scanattr_info;
indio_dev->modes = INDIO_DIRECT_MODE | INDIO_BUFFER_TRIGGERED;
indio_dev->channels = ep_scanattr_channels;
indio_dev->num_channels = ARRAY_SIZE(ep_scanattr_channels);
ret = devm_iio_triggered_buffer_setup(&spi->dev, indio_dev, NULL,
ep_scanattr_trigger_handler, NULL);
if (ret)
return ret;
return devm_iio_device_register(&spi->dev, indio_dev);
}
static const struct spi_device_id ep_scanattr_id[] = {
{ "ep-accel200", 0 },
{ }
};
MODULE_DEVICE_TABLE(spi, ep_scanattr_id);
static struct spi_driver ep_scanattr_driver = {
.driver = {
.name = "ep_iio_scanattr_demo",
},
.probe = ep_scanattr_probe,
.id_table = ep_scanattr_id,
};
module_spi_driver(ep_scanattr_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("EmbeddedPathashala");
MODULE_DESCRIPTION("Original example driver demonstrating IIO scan_elements type attribute");
Notice the EP_ACCEL_CHANNEL() macro sets .shift = 6 and .realbits = 10 for every axis, exactly matching our diagram above. Because we did not need any manual iio_device_unregister() or iio_triggered_buffer_cleanup() calls, both use devm_* resource-managed variants that the kernel cleans up automatically on driver removal.
Testing and Expected Output
Once ep_iio_scanattr_demo is loaded and bound to a device named ep-accel200, verify the scan element attributes from user space:
$ cd /sys/bus/iio/devices/iio:device0/scan_elements
$ cat in_accel_x_type
le:s10/16>>6
$ cat in_accel_x_en
0
$ echo 1 > in_accel_x_en
$ echo 1 > in_accel_y_en
$ echo 1 > in_accel_z_en
$ cat in_accel_x_en
1
With all three axes enabled and a trigger attached (see our earlier IIO trigger sysfs interface lecture for current_trigger setup), enable the buffer and read raw bytes:
$ echo 1 > buffer0/enable
$ cat /dev/iio:device0 | od -An -tx1 -N 8
00 04 10 08 20 0c 00 00
Decoding this: each axis occupies 2 bytes (16 storagebits), little-endian. Take the X pair 00 04 → word 0x0400 → shift right 6 → 0x10 (16 decimal), which is a valid signed 10-bit sample. The same masking-and-shift logic applies identically to Y and Z.
Real-World Use Cases
- Accelerometers and gyroscopes that store fewer valid bits than their register width (very common — 10, 12, and 14-bit ADCs inside 16-bit registers).
- Environmental sensors (pressure, humidity) using repeat fields to pack multiple identical-format readings per sample.
- High-speed ADC front ends where correct
storagebits/shiftvalues directly affect DMA buffer alignment and throughput. - Any sensor fusion pipeline (robotics, drones, wearables) that reads raw IIO buffers directly instead of the per-sample sysfs interface, for lower CPU overhead.
Common Mistakes and Troubleshooting
| Symptom | Likely Cause | Fix |
|---|---|---|
| Buffer values look randomly scaled | Wrong shift value in scan_type |
Recompute shift as storagebits − realbits (for left-justified data) |
| Negative readings appear as huge positive numbers | sign set to ‘u’ instead of ‘s’ |
Match sign to the sensor’s actual two’s-complement output |
| Channels overlap or corrupt each other in the buffer | Duplicate scan_index values |
Ensure every channel has a unique scan_index |
| Enabling a channel has no effect on buffer size | Missing _en write, or channel not in available_scan_masks |
Confirm *_en was written 1 and mask logic allows this combination |
Best Practices
- Always derive
shiftmathematically from the datasheet’s bit layout rather than guessing — draw the register diagram first, as we did above. - Keep
storagebitsa multiple of 8 for straightforward byte-level debugging withodorxxd. - Use
IIO_CHAN_SOFT_TIMESTAMP()and let the core handle 8-byte alignment for you rather than hand-rolling timestamp packing. - Prefer
devm_iio_triggered_buffer_setup()over the legacy manual setup/cleanup pair in all new 6.x drivers.
Performance Considerations
The storagebits and shift values directly affect how efficiently the buffer packs samples. Byte-aligned storage sizes (8, 16, 32 bits) let the kernel and DMA-capable buffer implementations copy data in natural word sizes, avoiding expensive bit-level repacking on every sample. Mis-aligned or unnecessarily wide storagebits wastes buffer memory and increases the number of bytes user space must read per sample, which matters at high sample rates.
Security Considerations
The scan_elements directory and the raw character device node are typically root-owned by default; review your udev rules before relaxing permissions, since raw sensor buffers can leak timing and environmental information. Never trust the *_en and *_type values as user-controlled input inside kernel code beyond what the IIO core already validates — always let the core’s mask-checking logic (available_scan_masks) decide which channel combinations are legal.
Summary and Key Takeaways
- The iio scan_elements type attribute tells user space exactly how to decode each channel’s raw bytes inside a triggered buffer.
- The type string format is
[be|le]:[s|u]realbits/storagebitsXrepeat[>>shift], matching thescan_typesub-structure ofiio_chan_spec. - Getting
shift,sign, andstoragebitsright is the difference between correct sensor data and silent corruption. - Modern 6.x drivers pair this with
devm_iio_device_register()anddevm_iio_triggered_buffer_setup()for clean, leak-free resource management.
Conclusion
Understanding the iio scan_elements type attribute turns triggered-buffer IIO drivers from guesswork into a precise, diagram-driven exercise. Once you can sketch a register’s bit layout and translate it directly into sign, realbits, storagebits, and shift, you can support almost any sensor’s buffered capture path correctly on the first attempt. This lecture continues our free linux kernel development course and free linux device drivers course series — keep following along as we move next into IIO events and threshold-based notifications.
Frequently Asked Questions
What is the difference between realbits and storagebits?
realbits is how many bits actually carry sensor data; storagebits is the total bit width the value occupies in the buffer, including any padding bits.
Why does the iio scan_elements type attribute include a shift value?
Many sensors left-justify their data within a wider register. The shift value tells user space how many bits to shift right before masking out the valid realbits.
Is the scan_type structure the same across kernel versions?
Yes — the six fields (sign, realbits, storagebits, shift, repeat, endianness) inside iio_chan_spec.scan_type have remained stable through recent 6.x kernel releases.
When is the repeat field used in scan_type?
When a device presents multiple identical-format repetitions of the same channel back-to-back, such as burst-mode ADC samples. It is omitted from the type string when it is 0 or 1.
Do I need to manually unregister an IIO device in modern drivers?
No — using devm_iio_device_alloc() and devm_iio_device_register() lets the kernel automatically clean up on driver removal, avoiding manual unregister/free calls.
How do I verify my scan_type values are correct?
Enable the channel, enable the buffer, and read raw bytes with od -An -tx1. Manually shift and mask the bytes per your type string and compare against a known reference reading.
Can the type attribute differ between channels on the same device?
Yes — each channel has its own scan_type, so a device can mix, for example, 10-bit accelerometer channels with a 32-bit timestamp channel.
Continue the Free Linux Kernel Development Course
More original driver lectures on IIO, Device Tree, I2C, SPI, and kernel synchronization — all free.
