SPI Driver Probe and Remove Free Linux Device Drivers Course

SPI Driver Probe and Remove
Free Linux Device Drivers Course — SPI Protocol Driver Lifecycle on Kernel 6.x
Chapter 8 • Part 3
Free Linux Kernel Development Course
Beginner to Intermediate

spi driver probe function
free embedded systems course
free linux device drivers course
free linux kernel development course
spi_set_drvdata spi_get_drvdata
module_spi_driver linux kernel

This lecture is part of our free Linux device drivers course and continues the SPI Device Drivers chapter. In the previous lecture we looked at struct spi_device, SPI clock modes (CPOL/CPHA), and how a basic SPI protocol driver registers itself. In this lecture we go deeper into the spi driver probe function, the matching remove function, how a driver stores its own per-device data, and how the driver is finally wired into the kernel with module_spi_driver(). Every code example here is written fresh for kernel 6.x and tested for the concepts it teaches — nothing is copy-pasted from any book.

What You Will Learn

  • A quick recap of the SPI protocol so the driver code makes sense
  • Every field of struct spi_driver explained in plain language
  • How the probe() function configures an SPI device with spi_setup()
  • How to save and retrieve per-device private data with spi_set_drvdata() and spi_get_drvdata()
  • Writing a correct remove() function that frees every resource
  • Registering a driver the modern way with module_spi_driver()
  • Using struct spi_device_id and MODULE_DEVICE_TABLE() for module autoloading
  • A complete, original SPI sensor driver example you can build and test

Prerequisites

  • Basic C programming knowledge
  • Completion of Part 1 and Part 2 of this SPI chapter (SPI bus signals, CPOL/CPHA, struct spi_device)
  • A Linux machine with kernel headers installed for building kernel modules
  • Familiarity with device tree basics is helpful but not required

Quick SPI Protocol Recap

SPI Master and Slave Communication

SPI Master

Generates the clock on SCK
Drives MOSI
Reads MISO
Toggles CS to select a slave

MOSI →
← MISO
SCK →
CS →

SPI Slave

Never generates the clock
Only active while CS is asserted
Shifts data in step with SCK

SPI is a full-duplex, synchronous, master-driven bus. The kernel splits the job into two driver types: a controller driver that talks to the physical SPI hardware, and a protocol driver that talks to one chip sitting on that bus. The spi_driver we are covering today is a protocol driver — it never touches SPI registers directly, it only sends spi_message objects and lets the controller driver worry about the electrical details.

struct spi_driver Explained

Every SPI protocol driver is described to the kernel using struct spi_driver. You do not write the struct definition yourself — it already exists in the kernel headers — you only fill in the fields that matter for your device.

Field Purpose
id_table Array of spi_device_id entries used for legacy/modalias matching
probe Called when the kernel binds your driver to a matching SPI device
remove Called when the device is unbound or the module is removed; returns void on kernel 5.18 and newer
shutdown Optional callback run on system shutdown/reboot/kexec
driver Embedded struct device_driver holding the driver name and, usually, an of_match_table

Modernization note: on current kernels the remove() callback in spi_driver returns void instead of int, because the driver core cannot do anything useful with a remove failure anyway. If you are reading an older tutorial or a driver written for kernel 5.17 or earlier, you will still see int my_remove(struct spi_device *spi) — that form still compiles today but new drivers should return void.

The probe() Function In Detail

The probe() function is where your driver takes ownership of a matched SPI device. Unlike an I2C client, an SPI device lets the protocol driver adjust bus parameters — clock speed, mode, and bits-per-word — at runtime through spi_setup(). A typical probe function follows this shape:

static int ep_sensor_probe(struct spi_device *spi)
{
    struct ep_sensor_data *sensor;
    int ret;

    /* configure the bus parameters this chip needs */
    spi->mode = SPI_MODE_0;
    spi->max_speed_hz = 4000000;   /* 4 MHz */
    spi->bits_per_word = 8;

    ret = spi_setup(spi);
    if (ret)
        return ret;

    sensor = devm_kzalloc(&spi->dev, sizeof(*sensor), GFP_KERNEL);
    if (!sensor)
        return -ENOMEM;

    mutex_init(&sensor->lock);
    sensor->spi = spi;

    spi_set_drvdata(spi, sensor);

    dev_info(&spi->dev, "ep_sensor driver probed successfully\n");
    return 0;
}

Notice that spi_setup() is called before any transfer happens, and that memory is allocated with devm_kzalloc() so it is automatically freed when the device is removed — there is no need to manually free it in remove().

Storing Per-Device Private Data

A real SPI chip driver usually manages more than one device instance, so you need a way to keep data specific to each spi_device. The kernel gives you a pair of helper functions for exactly this:

/* attach your private structure to this spi_device */
void spi_set_drvdata(struct spi_device *spi, void *data);

/* get it back later, in remove() or any other callback */
void *spi_get_drvdata(const struct spi_device *spi);

This is the same pattern you already saw for I2C client drivers — the bus-specific wrapper simply calls dev_set_drvdata()/dev_get_drvdata() under the hood on the embedded struct device.

The remove() Function

The job of remove() is simple: undo everything probe() registered that is not already handled by devm_* managed resources. On kernel 5.18+ it must return void.

static void ep_sensor_remove(struct spi_device *spi)
{
    struct ep_sensor_data *sensor = spi_get_drvdata(spi);

    if (!sensor)
        return;

    mutex_destroy(&sensor->lock);
    dev_info(&spi->dev, "ep_sensor driver removed\n");
}

Because sensor itself was allocated with devm_kzalloc(), we do not call kfree() on it — the device-managed core frees it automatically once remove() returns.

spi_device_id and MODULE_DEVICE_TABLE

To let the driver core and user-space module loading tools know which chips your driver supports, you declare a table of spi_device_id entries:

struct spi_device_id {
    char name[SPI_NAME_SIZE];
    kernel_ulong_t driver_data;   /* private per-entry data */
};

A typical table for a driver that supports two chip variants looks like this:

static const struct spi_device_id ep_sensor_idtable[] = {
    { "ep-sensor-basic",    0 },
    { "ep-sensor-advanced", 1 },
    { }
};
MODULE_DEVICE_TABLE(spi, ep_sensor_idtable);

MODULE_DEVICE_TABLE() exports this table into the module’s metadata so that modprobe can auto-load the driver when a matching device shows up. On kernel 6.x, most SPI drivers combine this table with an of_device_id table for device tree matching, and read a chip variant back using spi_get_device_id() or the more modern device_get_match_data(), which works uniformly across device tree, ACPI, and legacy id-table matching:

const struct spi_device_id *id = spi_get_device_id(spi);
int variant = id->driver_data;

Driver Initialization and Registration

The final piece wires everything together. In older code you would see explicit module_init()/module_exit() functions that only call spi_register_driver() and spi_unregister_driver():

static int __init ep_sensor_init(void)
{
    return spi_register_driver(&ep_sensor_driver);
}
module_init(ep_sensor_init);

static void __exit ep_sensor_exit(void)
{
    spi_unregister_driver(&ep_sensor_driver);
}
module_exit(ep_sensor_exit);

Since your driver does nothing extra at init/exit time, the kernel gives you a shortcut macro that does exactly the same thing in one line:

module_spi_driver(ep_sensor_driver);

Use module_spi_driver() unless you genuinely need custom logic in your init or exit path.

Complete Example: ep_sensor_demo Driver

Here is the full, original driver combining everything covered above — device tree matching, per-device data, probe, remove, and the registration macro.

#include <linux/module.h>
#include <linux/spi/spi.h>
#include <linux/mutex.h>
#include <linux/of.h>

struct ep_sensor_data {
    struct spi_device *spi;
    struct mutex lock;
};

static const struct of_device_id ep_sensor_of_match[] = {
    { .compatible = "ep,sensor-demo" },
    { }
};
MODULE_DEVICE_TABLE(of, ep_sensor_of_match);

static const struct spi_device_id ep_sensor_idtable[] = {
    { "ep-sensor-demo", 0 },
    { }
};
MODULE_DEVICE_TABLE(spi, ep_sensor_idtable);

static int ep_sensor_probe(struct spi_device *spi)
{
    struct ep_sensor_data *sensor;
    int ret;

    spi->mode = SPI_MODE_0;
    spi->max_speed_hz = 4000000;
    spi->bits_per_word = 8;

    ret = spi_setup(spi);
    if (ret)
        return ret;

    sensor = devm_kzalloc(&spi->dev, sizeof(*sensor), GFP_KERNEL);
    if (!sensor)
        return -ENOMEM;

    mutex_init(&sensor->lock);
    sensor->spi = spi;
    spi_set_drvdata(spi, sensor);

    dev_info(&spi->dev, "ep_sensor_demo probed, mode=%d speed=%d\n",
             spi->mode, spi->max_speed_hz);
    return 0;
}

static void ep_sensor_remove(struct spi_device *spi)
{
    struct ep_sensor_data *sensor = spi_get_drvdata(spi);

    if (!sensor)
        return;

    mutex_destroy(&sensor->lock);
    dev_info(&spi->dev, "ep_sensor_demo removed\n");
}

static struct spi_driver ep_sensor_driver = {
    .driver = {
        .name = "ep_sensor_demo",
        .of_match_table = ep_sensor_of_match,
    },
    .id_table = ep_sensor_idtable,
    .probe = ep_sensor_probe,
    .remove = ep_sensor_remove,
};
module_spi_driver(ep_sensor_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("EmbeddedPathashala");
MODULE_DESCRIPTION("EP demo SPI protocol driver for the free Linux device drivers course");

Matching Device Tree Node

&spi0 {
    status = "okay";

    ep_sensor: sensor@0 {
        compatible = "ep,sensor-demo";
        reg = <0>;
        spi-max-frequency = <4000000>;
    };
};

Build and Test Workflow

Build the module against your running kernel’s headers:

make -C /lib/modules/$(uname -r)/build M=$(pwd) modules

Load it and check that the probe function ran:

sudo insmod ep_sensor_demo.ko
dmesg | tail -5

Expected output:

[ 1234.567890] ep_sensor_demo spi0.0: ep_sensor_demo probed, mode=0 speed=4000000

Confirm the driver is bound to the device:

ls /sys/bus/spi/drivers/ep_sensor_demo/

Unload the module and confirm the remove function ran:

sudo rmmod ep_sensor_demo
dmesg | tail -3
[ 1240.112233] ep_sensor_demo spi0.0: ep_sensor_demo removed

Common Mistakes

Mistake Consequence
Returning int from remove() on kernel 5.18+ Compile error — the function pointer type no longer matches
Forgetting spi_setup() after changing mode/speed Bus parameters never actually get applied to the controller
Manually kfree()-ing a devm_kzalloc() pointer Double-free when the device-managed core frees it again
Missing MODULE_DEVICE_TABLE() Module never auto-loads via udev/modprobe hotplug

Best Practices

  • Prefer devm_* managed allocations so remove() stays minimal
  • Always check the return value of spi_setup()
  • Combine an of_device_id table with your spi_device_id table so the driver matches both device tree and legacy boardinfo setups
  • Keep the private data struct’s mutex initialized in probe() and destroyed in remove()

Performance Considerations

Setting max_speed_hz too high for the physical wiring causes bit errors that show up as garbled reads rather than obvious kernel errors. Start conservative and raise the clock only after confirming reliable transfers on a scope or logic analyzer.

Security Considerations

Never trust raw values read back from an SPI peripheral without range-checking them before exposing them to user space through sysfs or a character device — a misbehaving or spoofed chip on a shared bus can otherwise feed malformed data straight into kernel logic.

Summary / Key Takeaways

  • struct spi_driver ties your probe/remove callbacks to matching SPI devices
  • probe() configures the bus with spi_setup() and stores private data with spi_set_drvdata()
  • remove() returns void on kernel 5.18+ and only needs to clean up what devm_* did not already handle
  • spi_device_id plus MODULE_DEVICE_TABLE(spi, ...) enables module autoloading
  • module_spi_driver() replaces manual module_init()/module_exit() boilerplate

Conclusion

You now know how a real SPI protocol driver comes to life on kernel 6.x: from matching, through probing and configuring the bus, to safely tearing everything down in remove. This closes the core lifecycle of an SPI driver in our free Linux kernel development course. The next lecture in this free embedded systems course moves on to declaring SPI devices through the device tree in more depth and performing full-duplex transfers with spi_sync().

FAQ

What does the SPI driver probe function actually do?

It runs when the kernel matches your spi_driver to a physical spi_device. Inside it you configure bus parameters with spi_setup(), allocate any private data, and register with whatever subsystem your device belongs to.

Why does remove() return void on newer kernels?

Because the driver core cannot meaningfully recover from a failed remove — the device is going away regardless — so kernel 5.18 changed the SPI remove callback signature to return void.

What is the difference between spi_set_drvdata and devm_kzalloc?

devm_kzalloc() allocates memory that is automatically freed when the device is removed. spi_set_drvdata() simply attaches a pointer to that allocated struct to the spi_device so you can retrieve it later with spi_get_drvdata().

Do I need both spi_device_id and of_device_id tables?

Not strictly, but combining both is standard practice on kernel 6.x. The of_device_id table matches via device tree, while spi_device_id supports legacy boardinfo-based matching and module autoloading.

What does module_spi_driver() do?

It is a macro that expands into the standard module_init()/module_exit() pair, calling spi_register_driver() and spi_unregister_driver() for you when your driver has no other init/exit work to do.

Can an SPI protocol driver change the clock speed at runtime?

Yes. Unlike I2C, SPI lets a protocol driver adjust mode, max_speed_hz, and bits_per_word on the spi_device and apply them with spi_setup(), even after the initial bus setup.

Where can I practice this for free?

This lecture is part of the EmbeddedPathashala free Linux device drivers course, which covers character devices, platform devices, device tree, I2C, and SPI drivers end to end at no cost.

Continue the Free Linux Kernel Development Course

More SPI, I2C, and device tree lectures are on the way.

Leave a Reply

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