| ← PREV_LEC | NEXT_LEC → |
Sometimes you don’t want to write a kernel driver at all — you just want an application to talk to an SPI device directly. That is exactly what spidev userspace spi driver access gives you: a generic kernel driver that exposes any SPI device as a character device under /dev, so a normal C program can read, write, and configure the bus with plain system calls. This lecture in our free Linux device drivers course covers both the simple read/write path and the more powerful ioctl-based full-duplex path, updated for modern kernel 6.x behaviour.
What You Will Learn
Modern device tree binding rules
Half-duplex read()/write()
Full-duplex ioctl() transfers
Configuring mode and speed from userspace
Prerequisites
Device tree fundamentals
Basic C and Linux ioctl experience
This lecture assumes you have already been through the SPI driver-writing lectures earlier in this chapter. Use the PREV_LEC link above if you need a refresher.
What is Spidev?
The spidev kernel driver is a generic SPI client driver that does not know anything about the specific device on the other end of the bus. Instead of parsing sensor registers or display commands itself, it simply forwards raw bytes between userspace and the SPI controller. Once bound, it creates a character device such as /dev/spidev0.0, which any application can open and use.
It is meant mainly for prototyping, testing, and devices without a dedicated in-tree driver. For production hardware with a real driver available, writing (or using) a proper kernel driver like the ones built earlier in this chapter is still the better long-term choice.
Enabling Spidev the Modern Way
Older tutorials show a device tree node with compatible = "spidev"; directly. On current mainline kernels this no longer works: the spidev driver now fails the probe early if it sees the literal string "spidev" in a device tree compatible property, because that string was never meant to describe real hardware.
compatible = “spidev”;
Use a real compatible string already listed in spidev’s match table, or bind at runtime with driver_override
There are two supported ways to bind spidev today:
1. A recognised compatible string in the device tree
spidev@0 {
compatible = "rohm,dh2228fv";
reg = <0>;
spi-max-frequency = <8000000>;
};
Any string already present in the kernel’s spidev_dt_ids match table works, even if your actual chip is unrelated — the string is only used for driver matching, not device identification.
2. Runtime binding with driver_override
# bind spidev to an already-registered SPI device at runtime
echo spidev > /sys/bus/spi/devices/spi0.0/driver_override
echo spi0.0 > /sys/bus/spi/drivers/spidev/bind
# confirm the character device appeared
ls /dev/spidev*
Half-Duplex Access: read() and write()
Once /dev/spidev0.0 exists, the simplest way to use it is with the standard open(), read(), and write() calls. Each call is a separate half-duplex transfer — you can only send or only receive in a single call, never both at once.
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
int main(void)
{
int fd;
unsigned char cmd[2] = { 0x9F, 0x00 }; /* example: read ID command */
unsigned char resp[3] = { 0 };
fd = open("/dev/spidev0.0", O_RDWR);
if (fd < 0) {
perror("open");
return 1;
}
if (write(fd, cmd, sizeof(cmd)) != (ssize_t)sizeof(cmd)) {
perror("write");
close(fd);
return 1;
}
if (read(fd, resp, sizeof(resp)) != (ssize_t)sizeof(resp)) {
perror("read");
close(fd);
return 1;
}
printf("Response: %02X %02X %02X\n", resp[0], resp[1], resp[2]);
close(fd);
return 0;
}
Build and run it like any userspace program:
gcc -o spi_rw_test spi_rw_test.c
sudo ./spi_rw_test
Expected output for a device that answers with a 3-byte ID:
Response: EF 40 18
Full-Duplex Access with ioctl()
Plain read()/write() cannot do full-duplex transfers, and cannot change bus settings like clock speed or SPI mode. For that, spidev exposes a set of ioctl() commands defined in linux/spi/spidev.h.
| Capability | read()/write() | ioctl() |
|---|---|---|
| Half-duplex transfer | Yes | Yes |
| Full-duplex transfer | No | Yes |
| Change SPI mode / speed / bits-per-word | No | Yes |
Setting Bus Configuration
#include <stdint.h>
#include <sys/ioctl.h>
#include <linux/spi/spidev.h>
static int ep_spidev_configure(int fd)
{
uint8_t mode = SPI_MODE_0;
uint8_t bits = 8;
uint32_t speed = 8000000; /* 8 MHz */
if (ioctl(fd, SPI_IOC_WR_MODE, &mode) < 0)
return -1;
if (ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits) < 0)
return -1;
if (ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed) < 0)
return -1;
return 0;
}
A Full-Duplex Transfer
#include <stdint.h>
#include <string.h>
#include <sys/ioctl.h>
#include <linux/spi/spidev.h>
static int ep_spidev_full_duplex(int fd, uint8_t *tx, uint8_t *rx, size_t len)
{
struct spi_ioc_transfer tr = { 0 };
tr.tx_buf = (unsigned long)tx;
tr.rx_buf = (unsigned long)rx;
tr.len = len;
tr.speed_hz = 8000000;
tr.bits_per_word = 8;
return ioctl(fd, SPI_IOC_MESSAGE(1), &tr);
}
SPI_IOC_MESSAGE(1) sends one struct spi_ioc_transfer; you can pass an array and use SPI_IOC_MESSAGE(N) to submit several chained transfers in one call, mirroring the kernel-side spi_message chaining you learned in the previous lecture — only this time it is driven entirely from userspace.
Build and Test Workflow
gcc -o spidev_ioctl_test spidev_ioctl_test.c
sudo ./spidev_ioctl_test /dev/spidev0.0
# watch the bus with logic-analyzer style debugging if available
dmesg | grep spi
Common Mistakes
| Mistake | Why it breaks |
|---|---|
Writing compatible = "spidev" literally in device tree |
Modern kernels reject this and the probe fails; use a recognised string or driver_override instead. |
| Expecting read()/write() to do full-duplex | Only ioctl() with SPI_IOC_MESSAGE can transmit and receive at the same time. |
Forgetting to cast buffer pointers to unsigned long in spi_ioc_transfer |
The struct stores pointers as 64-bit integers for a stable ABI; skipping the cast causes compiler warnings and, on some setups, truncated pointers. |
Best Practices and Security Considerations
- Restrict
/dev/spidevB.Cpermissions to trusted users or groups — any process that can open it can drive the SPI bus directly, bypassing any kernel-side validation a dedicated driver would normally provide. - Prefer a proper kernel driver for production devices; reserve spidev for prototyping, bring-up, and testing.
- Always check every
ioctl()return value — a failed mode or speed change can silently leave the bus in an unexpected state. - Batch related operations into one
SPI_IOC_MESSAGE(N)call instead of many single-transfer ioctls, for both performance and atomicity.
Summary and Key Takeaways
- Spidev exposes any SPI device as
/dev/spidevB.Cfor direct userspace access, without writing a kernel driver. - Modern kernels reject the literal
"spidev"compatible string; use a real compatible string ordriver_overrideto bind it. - read()/write() give simple half-duplex access; ioctl() with SPI_IOC_MESSAGE gives full-duplex access and bus configuration.
- Spidev is best for prototyping and testing, not as a substitute for a dedicated kernel driver in production.
Conclusion
With this lecture, you now understand both sides of SPI communication on Linux — writing a proper kernel client driver and talking to hardware directly from userspace with spidev. Together with the earlier lectures on spi_transfer, spi_message, and device tree registration, you have a complete, modern picture of the SPI subsystem in kernel 6.x. This wraps up the SPI Device Drivers chapter of this free Linux kernel development course.
Frequently Asked Questions
Why does compatible = “spidev” no longer work in device tree?
The spidev driver name describes a Linux implementation detail, not real hardware, so mainline kernels now fail the probe early if that literal string is used directly.
Can I use spidev without any device tree changes at all?
Yes, if the SPI device is already registered another way, you can bind spidev at runtime using the driver_override sysfs mechanism shown in this lecture.
Is spidev suitable for production products?
It can work for simple cases, but a dedicated kernel driver is generally preferred in production because it can validate input and expose a safer, purpose-built interface.
How do I change SPI mode from a spidev application?
Use the SPI_IOC_WR_MODE ioctl with one of the SPI_MODE_0 through SPI_MODE_3 constants, as shown in the configuration example.
What is the difference between SPI_IOC_MESSAGE(1) and multiple write() calls?
SPI_IOC_MESSAGE can chain several transfers with CS held across them in one atomic call, while separate write() calls each open and close their own transaction.
Does spidev support multiple chip selects on the same bus?
Yes, each chip select shows up as its own device node, for example /dev/spidev0.0 and /dev/spidev0.1.
Pls visit this lecture SPI DRIVER REGISTRATION
Continue the Free Linux Kernel Development Course
Explore more free lectures on SPI, I2C, device tree, and Linux device drivers on EmbeddedPathashala.
| ← PREV_LEC | NEXT_LEC → |
