Linux Platform Device Driver Basics- Free Linux Device Drivers Course

Linux Platform Device Driver Basics

Free Linux Kernel Development Course — Chapter 5, Part 1

Kernel 6.x Ready
Original Code Example
Beginner Friendly

If you have written a Linux character driver before, you already know how to talk to user space with open(), read(), and write(). But a driver still needs to know when its hardware actually exists in the system. That is exactly what a platform device driver in the Linux kernel is built for. In this lecture, part of our free Linux device drivers course, we will build a simple, original platform driver from scratch and understand exactly how the kernel matches a driver to a device — no hand-wavy theory, just working code you can run on your own machine.

What You Will Learn

  • What the Linux “platform bus” actually is, and why it exists
  • The difference between discoverable devices (USB, PCI) and platform devices (I2C, SPI, UART controllers)
  • The two-step process every platform device driver must follow
  • How to write, build, and load your own platform driver on a real Linux machine
  • The modern void-returning remove() callback used in current kernels
  • Common mistakes beginners make with platform drivers, and how to avoid them

Prerequisites

  • Basic C programming knowledge
  • A Linux machine or VM with kernel headers installed (uname -r should match an installed linux-headers package)
  • Comfort with loading and unloading kernel modules (insmod, rmmod, dmesg)
  • Our earlier character device driver lectures are helpful but not required

Topics Covered in This Free Linux Kernel Development Course Lecture

platform device driver linux kernel
platform_driver struct
probe and remove
free linux device drivers course
module_platform_driver

What Is the Linux Platform Bus?

Most buses you already know are discoverable. Plug in a USB drive or a PCI Express card, and the kernel automatically enumerates it — it can literally ask the bus “what’s connected to you?” USB, PCI, and SATA all work this way.

But many controllers inside an SoC — I2C, SPI, UART — are not wired to any enumeration-capable bus. The kernel has no way to ask “are you there?” It simply has to be told these devices exist. That is the whole reason the pseudo platform bus exists: it is a virtual bus inside the kernel for devices that don’t sit on a real, discoverable bus.

A Common Misconception

People often assume “platform device” means “on-chip device.” That is only partly true. Devices wired into the SoC (like a UART controller) are platform devices because they are non-discoverable — not simply because they are on-chip. Meanwhile, a device connected over I2C or SPI is also a platform device, even though it is a separate external chip, because it still cannot be auto-discovered. On the other hand, an on-chip PCI or USB controller is not a platform device, because it is discoverable. The deciding factor is always discoverability, not physical location.

Discoverable Devices vs Platform Devices

Property Discoverable Devices (USB, PCI) Platform Devices (I2C, SPI, UART controllers)
Auto-detected by kernel Yes No
Must be registered manually / via Device Tree No Yes
Can be hot-plugged Usually yes No
Matched using Vendor/Product ID Driver name or compatible string

The Two-Step Process for a Platform Device Driver

Every platform device driver in the Linux kernel is built around exactly two building blocks:

  1. A platform driver — registered with a unique name, containing your probe() and remove() logic
  2. A platform device — registered with the same name, so the kernel’s driver core knows to connect the two

On real embedded boards today, the device side of this is almost always described in the Device Tree rather than registered manually in C code. For learning purposes, though, we will register a lightweight demo device directly in our module, so you can see probe and remove fire without needing any real hardware or a board with Device Tree support.

struct platform_driver Explained

Every platform driver is described by struct platform_driver. Here are the fields that matter for a beginner:

Field Purpose
probe() Called by the kernel once a matching device is found
remove() Called when the driver is unbound from the device
driver.name Unique name used to match against a device

In short: the kernel compares the platform device’s name against each registered driver’s driver.name. When "ep_platform_demo" on the device side matches "ep_platform_demo" on the driver side, the kernel calls probe() and the driver is now bound to that device.

Note on the modern kernel: in current kernels, the remove() callback returns void instead of int. Older code you may find online still shows int remove(struct platform_device *pdev) — that form is obsolete. Removal always proceeds regardless of what the old callback returned, so the return value was pointless and has been dropped kernel-wide.

Writing Your First Platform Driver

Below is an original, minimal driver called ep_platform_demo. It registers both a demo platform device and a matching platform driver in the same module, purely so you can see probe() and remove() run without needing real hardware.

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>

#define EP_DRV_NAME "ep_platform_demo"

static int ep_demo_probe(struct platform_device *pdev)
{
    dev_info(&pdev->dev, "ep_platform_demo: device probed successfully\n");
    return 0;
}

static void ep_demo_remove(struct platform_device *pdev)
{
    dev_info(&pdev->dev, "ep_platform_demo: device removed\n");
}

static struct platform_driver ep_demo_driver = {
    .probe  = ep_demo_probe,
    .remove = ep_demo_remove,
    .driver = {
        .name = EP_DRV_NAME,
    },
};

static struct platform_device *ep_demo_device;

static int __init ep_demo_init(void)
{
    int ret;

    ep_demo_device = platform_device_register_simple(EP_DRV_NAME, -1, NULL, 0);
    if (IS_ERR(ep_demo_device))
        return PTR_ERR(ep_demo_device);

    ret = platform_driver_register(&ep_demo_driver);
    if (ret)
        platform_device_unregister(ep_demo_device);

    return ret;
}

static void __exit ep_demo_exit(void)
{
    platform_driver_unregister(&ep_demo_driver);
    platform_device_unregister(ep_demo_device);
}

module_init(ep_demo_init);
module_exit(ep_demo_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("EmbeddedPathashala");
MODULE_DESCRIPTION("Simple platform device and driver demo for Linux 6.x");

Two things worth noticing:

  • We used platform_device_register_simple() — a convenience helper that creates and registers a platform device in a single call, ideal for a demo like this.
  • The names in EP_DRV_NAME match on both the device and the driver side. That match is exactly what triggers probe().

Building and Testing the Driver

Create a Makefile alongside ep_platform_demo.c:

obj-m += ep_platform_demo.o

KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)

all:
	make -C $(KDIR) M=$(PWD) modules

clean:
	make -C $(KDIR) M=$(PWD) clean

Now build and load it:

make
sudo insmod ep_platform_demo.ko
dmesg | tail -5

Expected output:

[12345.678901] ep_platform_demo ep_platform_demo.0: ep_platform_demo: device probed successfully

Now unload it:

sudo rmmod ep_platform_demo
dmesg | tail -5

Expected output:

[12350.123456] ep_platform_demo ep_platform_demo.0: ep_platform_demo: device removed

If you see both lines, your platform device and platform driver matched correctly, and probe()/remove() fired exactly as designed.

platform_driver_register() vs platform_driver_probe()

There are two ways to register a platform driver, and beginners often mix them up:

Function Behavior
platform_driver_register() Adds the driver to the kernel’s driver list. probe() can run later if a matching device shows up at any point (deferred probing supported).
platform_driver_probe() Immediately checks for a matching device and calls probe() right away. If no match exists yet, the driver is discarded. Use this only when you are certain the device is already present.

The module_platform_driver() Macro

If your module does nothing in init/exit besides registering and unregistering the driver, you can skip module_init()/module_exit() entirely and use one line instead:

module_platform_driver(ep_demo_driver);

This expands into the register/unregister boilerplate for you. It only works cleanly when you are not also manually registering a demo device the way we did above — which is exactly why many real-world platform drivers, bound purely through the Device Tree, use this macro instead of writing out init/exit by hand.

Common Mistakes and Troubleshooting

  • Name mismatch: the single most common bug. If the platform device name and driver.name don’t match exactly (including case), probe() never runs.
  • Forgetting to unregister the device: leaving a demo platform device registered after module unload will crash the next insmod. Always unregister in the reverse order of registration.
  • Using the old int-returning remove() signature: this will fail to compile on current kernels. Use void remove().
  • Assuming platform_driver_probe() supports deferred probing: it does not — if your device isn’t present at registration time, the driver simply won’t bind.

Best Practices

  • Prefer Device Tree-based device description over manually written platform_device structs for real hardware.
  • Always check return values from registration functions and unwind cleanly on failure.
  • Use dev_info()/dev_err() instead of plain pr_info() — they automatically prefix your messages with the device name, which makes multi-device debugging far easier.
  • Keep probe() fast; defer any heavy initialization work if possible.

Performance and Security Considerations

Platform drivers themselves are lightweight — the real performance impact comes from what you do inside probe(), such as requesting IRQs or mapping memory regions (covered in later lectures). From a security standpoint, always validate any resources you request in probe() and release everything you acquire in remove() to avoid resource leaks that persist across module reloads.

Summary and Key Takeaways

  • The platform bus is a virtual bus for non-discoverable devices like I2C, SPI, and UART controllers.
  • A platform device driver needs two matching pieces: a platform driver and a platform device, connected by a shared name.
  • probe() runs when a match is found; remove() runs when the driver unbinds — and remove() is void in current kernels.
  • module_platform_driver() removes boilerplate for drivers with no custom init/exit logic.

Frequently Asked Questions

What is a platform device driver in the Linux kernel?

It is a driver for hardware that sits on the kernel’s virtual “platform bus” rather than a discoverable bus like USB or PCI — typically I2C, SPI, or UART controllers built into an SoC.

Why can’t platform devices be auto-detected like USB devices?

Because the buses they sit on (I2C, SPI, UART) have no enumeration protocol the kernel can query. The kernel has to be told these devices exist, usually via the Device Tree.

Do I need real hardware to learn platform drivers?

No. As shown in this lecture, you can register a demo platform device directly in your own module and see probe/remove run on any Linux machine.

Why does remove() return void in modern kernels?

Because a non-zero return value from remove() never actually stopped device removal — it was ignored. Recent kernels dropped the unused return value kernel-wide.

What is the difference between platform_driver_register() and platform_driver_probe()?

register() adds the driver to a list and allows matching to happen later (deferred probing). probe() checks for a match immediately and discards the driver if none exists.

Is Device Tree required for platform drivers?

Not strictly — you can register a platform_device manually in code, as we did here. But on real embedded boards, Device Tree is the standard way to describe platform devices.

What does module_platform_driver() do?

It replaces manual module_init()/module_exit() calls with a single macro that registers and unregisters your platform driver automatically.

Are I2C and SPI devices platform devices?

The I2C/SPI controllers themselves are platform devices. Devices connected on those buses are matched using I2C/SPI-specific mechanisms, not the plain platform bus.

 

Leave a Reply

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