Updated October 8, 2026. This tutorial explains the Linux platform-driver model and shows a minimal driver that can bind to a matching firmware-described device. It is a learning example, not a driver for real hardware: the compatible string and binding must be supplied by a board or device-tree binding that actually describes the device.
A platform driver does not create or discover arbitrary hardware by itself. The kernel enumerates a platform device—commonly from Device Tree, ACPI, or board setup—and the driver core matches it to a registered platform_driver. Only then does the core call probe().
When to use a platform driver
The platform bus is commonly used for SoC-integrated peripherals and some legacy devices with directly addressable resources. Devices on buses with their own discovery and driver models, such as PCI, USB, I2C, or SPI, should normally use that bus’s driver model instead. See the current Linux platform-device documentation before choosing the bus.
1. Prepare a safe development environment
Build against the headers and configuration for the target kernel. A virtual machine or QEMU test system is preferable while learning: an incorrect kernel module can crash or corrupt the running system. Do not load experimental modules on a production device.
sudo apt update
sudo apt install build-essential linux-headers-$(uname -r)
ls /lib/modules/$(uname -r)/build
On a cross-compiled embedded target, use that target’s kernel source, configuration, and toolchain rather than the headers for your development PC. Kernel APIs change over time, so the target kernel tree is the final authority.
2. Write a minimal matching platform driver
This example only logs when the driver binds and unbinds; it does not access registers or control hardware. The placeholder compatible value must be replaced with a real binding string already used by the target device tree. Without a matching enumerated device, the module can load successfully and probe() will not run.
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
static int example_probe(struct platform_device *pdev)
{
dev_info(&pdev->dev, "matched example platform device\n");
return 0;
}
static void example_remove(struct platform_device *pdev)
{
dev_info(&pdev->dev, "example platform device removed\n");
}
static const struct of_device_id example_of_match[] = {
{ .compatible = "example,platform-device" },
{ }
};
MODULE_DEVICE_TABLE(of, example_of_match);
static struct platform_driver example_driver = {
.probe = example_probe,
.remove = example_remove,
.driver = {
.name = "example-platform",
.of_match_table = example_of_match,
},
};
module_platform_driver(example_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Minimal platform driver example");
The empty final entry in the match table is required. MODULE_DEVICE_TABLE() makes the match information available to module tooling. Current upstream kernels use a void remove() callback; older kernel branches may use a different signature, so compile against the exact target headers and follow that tree’s platform_driver definition.
3. Build the external module
Save the source as example_platform.c and create a Makefile in the same directory:
obj-m += example_platform.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
Run make using the matching build tree. A successful build produces example_platform.ko. A module may still be rejected at load time if its vermagic, signing requirements, architecture, or kernel configuration do not match the running target.
4. Describe real hardware with a binding
For a Device Tree system, the device node is part of the board description and must follow the device’s documented binding. A node may describe resources such as registers, interrupts, clocks, resets, and power domains, but their addresses and specifiers are specific to the SoC and board. Do not copy fabricated addresses or interrupt numbers from a generic tutorial.
For upstreamable drivers, add or update a YAML schema under Documentation/devicetree/bindings/ and validate it with the kernel’s Device Tree checks. Read the Device Tree usage model and the binding documentation for the target subsystem.
Platform devices may also be enumerated through ACPI or board setup. An OF match table alone only matches Device Tree devices; add ACPI or platform-ID matching only when the hardware and target kernel require it.
5. Acquire resources in probe()
A real probe function should validate the device and acquire each resource through the device-managed APIs where appropriate. Common helpers include platform_get_resource() with devm_ioremap_resource() for a memory resource, platform_get_irq() for an IRQ, and the clock, reset, regulator, GPIO, and DMA frameworks for their respective resources. Check every return value, propagate errors, and use -EPROBE_DEFER when a required supplier is not ready yet.
Do not dereference a missing resource, invent register layouts, or access MMIO before the resource has been reserved and mapped. Use the subsystem’s established helpers rather than raw addresses. Keep interrupt handlers short and move work that may sleep into a threaded handler or workqueue.
6. Build, bind, and debug
- Confirm that the device exists in the target’s firmware description and that its compatible string matches the driver table.
- Check
/sys/bus/platform/devices/and/sys/bus/platform/drivers/to inspect enumerated devices and registered drivers. - Read the exact kernel log message with
dmesgor the system journal; distinguish a module-load failure from a device-match or probe failure. - Check the probe return value and resource errors. A deferred probe is different from a failed match.
- Use a VM, QEMU, or a dedicated development board for module-load tests; remove the module and restore the test system if it becomes unstable.
The official external-module build guide and platform bus documentation describe the supported build and driver-model interfaces.
Common mistakes
- Expecting probe() to run without an enumerated device that matches the driver.
- Registering a fake platform device from the same driver module as a shortcut; device enumeration normally belongs to firmware or board setup.
- Copying Device Tree register addresses, interrupt cells, or clock names from another board.
- Ignoring errors from resource helpers or suppressing deferred-probe errors.
- Using a remove() signature from a different kernel version or adding manual cleanup for resources already managed by devm APIs.
- Treating a character device and a platform driver as alternatives: a platform driver binds hardware to a bus; a character-device interface is a separate userspace API that may be added when the hardware requires one.
Further reading
Start with the Linux platform driver model, the Device Tree usage model, and the Kbuild external-module documentation. For register-level work, continue with the documentation for the specific subsystem and board; a generic example cannot safely define hardware resources for you.
For broader interview preparation, see our Linux device-driver interview roadmap.