Linux Platform Driver Probe Function
A hands-on lesson in our free Linux device drivers course: how probe() finds its hardware, pulls memory and IRQ resources, and reads optional device tree properties on a modern kernel.
If you are working through this free Linux device drivers course, you already know that a platform driver does nothing until the kernel decides it has found a matching piece of hardware. The moment that match happens, control passes to one function: probe(). Everything the driver needs to talk to real silicon — its register window, its interrupt line, and any board-specific tuning values — has to be pulled out of the kernel’s data structures inside that single callback. This lecture is a full, from-scratch walkthrough of what happens inside probe(), written against the current mainline API rather than the deprecated calls that older books still teach. By the end you will be able to write a probe() that correctly maps memory, requests an IRQ, and reads optional device tree properties, and you will understand why the “modern” helper functions exist in the first place.
What You Will Learn
Prerequisites
From Match to Probe: A Quick Recap
In the previous lecture in this free Linux kernel development course we covered how a platform driver registers an of_device_id table so the kernel can match it against a device tree node’s compatible string, or alternatively matches on driver.name against platform data supplied at boot. Once a match is found, the kernel calls the driver’s probe() function exactly once per matched device, handing it a pointer to the corresponding struct platform_device. That single pointer is the driver’s entire window into the hardware it is about to manage — no globals, no guessing, everything is discovered through it.
Extracting Memory Resources the Modern Way
Older code you will find in books and vendor trees calls platform_get_resource() to fetch a struct resource *, checks it for NULL, and then calls ioremap() by hand. That pattern still compiles, but current kernels give you a single helper that does all three steps and also ties the mapping’s lifetime to the device, so it is automatically undone if probe fails or the driver unbinds:
#include <linux/platform_device.h>
#include <linux/io.h>
static int ep_fanctl_probe(struct platform_device *pdev)
{
void __iomem *base;
base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(base))
return PTR_ERR(base);
/* base now points at the mapped register window */
return 0;
}
The index passed in, 0, refers to the first memory resource listed for this device — either the first reg entry in its device tree node, or the first IORESOURCE_MEM entry supplied via platform data. If a device exposes more than one register bank, you request additional banks by incrementing that index, or by name using devm_platform_ioremap_resource_byname() when the device tree node labels its reg entries with reg-names.
Requesting the Interrupt Line
The older approach reads an IRQ the same way it reads memory — via platform_get_resource(pdev, IORESOURCE_IRQ, 0) and then dereferencing ->start. That still works, but it silently returns a resource struct even when the platform has no working IRQ mapping, which has caused real bugs. The recommended call today is platform_get_irq(), which returns the IRQ number directly, or a negative error code you can propagate straight out of probe:
#include <linux/interrupt.h>
static irqreturn_t ep_fanctl_irq(int irq, void *data)
{
struct ep_fanctl_dev *fdev = data;
/* handle the tachometer pulse / fault line here */
return IRQ_HANDLED;
}
static int ep_fanctl_probe(struct platform_device *pdev)
{
struct ep_fanctl_dev *fdev;
int irq, ret;
fdev = devm_kzalloc(&pdev->dev, sizeof(*fdev), GFP_KERNEL);
if (!fdev)
return -ENOMEM;
fdev->base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(fdev->base))
return PTR_ERR(fdev->base);
irq = platform_get_irq(pdev, 0);
if (irq dev, irq, ep_fanctl_irq, 0,
"ep_fanctl", fdev);
if (ret)
return ret;
platform_set_drvdata(pdev, fdev);
dev_info(&pdev->dev, "ep_fanctl bound, irq=%d\n", irq);
return 0;
}
Notice the use of devm_kzalloc() and devm_request_irq(). Every devm_* allocation and registration is torn down automatically by the driver core when the device is removed, which is why a well-written modern probe function on this free embedded Linux course rarely needs an explicit remove() at all — the kernel does the cleanup for you.
Reading Optional Device Tree Properties
Memory and interrupts are the two resources almost every platform device needs, but device trees can describe far more: clock frequencies, GPIO polarity, thresholds, calibration offsets. These show up as ordinary properties inside the node, and the driver reads them only if it needs them — absence of an optional property is not an error. The classic call is of_property_read_u32(), which only works when the firmware description is a device tree:
#include <linux/of.h>
u32 pwm_freq = 25000; /* sane default */
of_property_read_u32(pdev->dev.of_node, "ep,pwm-freq-hz", &pwm_freq);
A more future-proof choice is the firmware-agnostic device_property_read_u32(), which reads the same value whether the platform describes hardware through a device tree or through ACPI, without the driver needing to know which one is in use:
#include <linux/property.h>
u32 pwm_freq = 25000;
device_property_read_u32(&pdev->dev, "ep,pwm-freq-hz", &pwm_freq);
Both calls return 0 on success and a negative error code — typically -EINVAL — when the property is missing, which is exactly why you initialize the variable with a default beforehand rather than treating a missing optional property as fatal.
Where Bindings Are Documented Today
Every custom property a driver reads from a device tree node must be documented as a binding, so board authors know what values are legal. In current kernels, new bindings are written as YAML files under Documentation/devicetree/bindings/ and validated automatically at build time with make dtbs_check against the dt-schema tooling — the older free-form .txt binding documents are being phased out in favor of these machine-checkable schemas. If you are adding a custom property like ep,pwm-freq-hz to a real driver, the YAML binding is not optional paperwork; it is how you keep your device tree source valid as the schema tooling gets stricter with every release.
Legacy vs Modern API — Comparison
| Task | Older Pattern | Current Recommended Call |
|---|---|---|
| Map register window | platform_get_resource() + ioremap() | devm_platform_ioremap_resource() |
| Get interrupt number | platform_get_resource(IORESOURCE_IRQ) + ->start | platform_get_irq() |
| Read optional DT value | of_property_read_u32() | device_property_read_u32() (DT + ACPI) |
| Binding documentation | Plain-text .txt under Documentation/devicetree/bindings | Validated YAML schema under the same directory |
Building and Testing ep_fanctl
To see this in action, build the driver above against your kernel headers with a minimal Kbuild file, exactly as covered earlier in this free Linux kernel development course:
obj-m += ep_fanctl.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
Load it and check the kernel log for the message the probe function prints once it has successfully mapped memory and armed the interrupt:
$ make
$ sudo insmod ep_fanctl.ko
$ dmesg | tail -n 3
[ 1234.567890] ep_fanctl 3f000000.fanctl: ep_fanctl bound, irq=42
If platform_get_irq() returns a negative value instead, dmesg will show the module load failing at probe — that is exactly the fail-fast behavior you want, instead of a driver that silently runs with no working interrupt.
Common Mistakes and Troubleshooting
Best Practices, Performance, and Security
Prefer the devm_ family of calls throughout probe() so that a failure halfway through cleanly unwinds everything already allocated — this is the single biggest source of clean, leak-free error handling in modern platform drivers. Validate every optional property you read against a sane range before trusting it; a device tree can come from a bootloader or an overlay you do not fully control, so a corrupted ep,pwm-freq-hz value should be clamped or rejected rather than fed straight into hardware registers. Keep the interrupt handler itself short — do the real work in a threaded IRQ handler or workqueue if it needs to sleep or take locks, which is a topic this free embedded systems course covers in more depth in the interrupt handling chapter.
Summary and Key Takeaways
The probe() function is where a platform driver turns an abstract “match found” event into working access to real hardware. Modern kernels give you devm_platform_ioremap_resource() and platform_get_irq() to replace the older, more error-prone platform_get_resource() pattern, and device_property_read_u32() to read optional configuration in a way that works for both device tree and ACPI systems. Documenting any custom property you introduce as a validated YAML binding keeps your driver correct as the rest of the kernel tightens its schema checking. Understanding this flow end to end is one of the most practical skills you will build in this free Linux device drivers course, because nearly every real embedded driver you touch professionally will follow the same shape.
Frequently Asked Questions
Why does probe() sometimes get called more than once?
It is called once per matched device instance, so a board with two identical peripherals described by two device tree nodes will trigger probe() twice, once per instance, each with its own platform_device pointer.
Is ioremap() itself deprecated?
No, but calling it directly inside probe() without the devm_ wrapper is discouraged because you become responsible for unmapping it manually on every error path and on remove().
What happens if platform_get_irq() finds no IRQ resource at all?
It returns a negative error code such as -ENXIO, which your probe() should check and propagate immediately rather than continuing with an invalid IRQ number.
Do I need device_property_read_u32() if my hardware only ever uses device trees?
of_property_read_u32() is perfectly fine in that case; device_property_read_u32() only matters if the same driver needs to run unmodified on ACPI-described platforms too.
Where do I find existing bindings to copy the style of before writing a new one?
Browse existing YAML files under Documentation/devicetree/bindings/ in the kernel source tree for a device in the same subsystem as yours, and follow its structure.
Can a driver mix platform_get_resource() and the newer devm_ helpers?
Yes, but it is best practice to pick one style consistently within a driver so error handling stays predictable.
What is the difference between request_irq() and devm_request_irq()?
They behave the same at request time, but devm_request_irq() automatically frees the interrupt when the device is unbound, removing the need for an explicit free_irq() call in most drivers.
Continue This Free Linux Device Drivers Course
Practice by giving ep_fanctl a real device tree overlay with an ep,pwm-freq-hz property and confirming your driver reads it correctly.
Next Lecture Course Index
2 Comments