platform_get_resource and Device Tree in Linux Drivers: Finding Hardware Resources the Modern Way
Free Linux Kernel Programming Course — Chapter 3: Working with Hardware I/O Memory
← Previous Lecture | Next Lecture →
Before any driver can map a peripheral’s registers, it first has to answer a simple-sounding question: where in physical memory does this device actually live? On a modern embedded Linux system that answer almost never comes from hard-coded numbers in the driver source; it comes from the Device Tree, and the driver reads it out using the platform_get_resource Linux driver API and its modern successors. This lesson walks through how the kernel discovers hardware at boot, how that information reaches your probe() function, and how to write a clean, current-kernel driver that consumes it correctly.
Key APIs covered in this lesson
What You Will Learn
- Why hard-coded, board-specific files fell out of favor for describing hardware
- What the Device Tree is, and how a .dts source file becomes a .dtb the kernel can read
- How the kernel turns Device Tree nodes into
platform_devicestructures with populatedstruct resourceentries - How to read those resources in a driver with
platform_get_resource()and its modern, managed successors - A complete example driver, matching device tree snippet, and common pitfalls
Prerequisites
- Comfort with the material from the earlier lesson on
devm_ioremapand managed I/O memory mapping - A recent kernel source tree (5.x or 6.x) and a board that boots from a Device Tree, such as a Raspberry Pi or BeagleBone
- Basic familiarity with C structs and pointers
The Problem: How Does a Driver Know Where Its Hardware Is?
A driver’s source code is generic — the same UART driver binary might run on dozens of different boards, each wiring that UART controller at a different physical address, with a different interrupt line. Something has to tell the driver, at run time, “on this particular board, your registers start at this address, and your interrupt is this number.” Historically this information was hard-coded directly into per-board C files compiled into the kernel image itself. That approach worked when there were only a handful of ARM boards to support, but it does not scale: every new board variant meant a new C file, and the kernel’s board-file count grew into the thousands before the community moved to a better model.
The Modern Answer: The Device Tree
The Device Tree is a data structure — not code — that describes a board’s hardware topology: its SoC, memory ranges, buses, and every peripheral wired to it, along with each peripheral’s address range, interrupt line, clocks, and other properties. It is written in a human-readable text format (a .dts or .dtsi file), compiled by the Device Tree Compiler (dtc) into a compact binary blob (the DTB), and handed to the kernel by the bootloader at boot time. During early boot the kernel parses the DTB and automatically creates the corresponding platform_device structures, each one carrying the resource information pulled straight out of that node.
| .dts source human-readable |
→ | dtc compiler | → | .dtb binary loaded by bootloader |
→ | kernel parses DTB creates platform_device |
→ |
|
driver probe() matched via of_match_table — resources ready to read
| |||||||
A minimal Device Tree node for a hypothetical peripheral might look like this — this is an original example written for this lesson, not copied from any book or driver source:
mydevice: mydevice@fe201000 {
compatible = "example,mydev";
reg = <0xfe201000 0x100>;
interrupts = <GIC_SPI 87 IRQ_TYPE_LEVEL_HIGH>;
status = "okay";
};
The reg property here encodes the resource’s start address and size, and compatible is the string the kernel uses to match this node against a driver’s of_match_table. Everything downstream — the struct resource your driver reads — is derived from properties exactly like these.
Reading the Resource: platform_get_resource()
Once the kernel has bound your driver’s probe() function to a matching platform device, that device already carries an array of struct resource entries describing its memory ranges, IRQs, and other resource types. The classic way to fetch one of them is platform_get_resource():
struct resource *platform_get_resource(struct platform_device *pdev,
unsigned int type,
unsigned int num);
type is a resource class such as IORESOURCE_MEM or IORESOURCE_IRQ, and num is the zero-based index within that class — so index 0 of IORESOURCE_MEM corresponds to the first reg entry in the Device Tree node. A traditional driver would call this, check for a NULL return, and then hand the resulting struct resource * to devm_ioremap_resource() for the actual mapping — exactly the two-step pattern the previous lesson touched on.
The Streamlined, Current Approach
Modern kernels let you skip the intermediate struct resource * variable entirely for the common case of “get resource, then map it.” The table below compares the three levels of convenience available today:
| Style | What you write | Notes |
|---|---|---|
| Classic, two calls | platform_get_resource() then devm_ioremap_resource() |
Still valid, useful when you need the raw struct resource for something else, like reading its size. |
| Combined by index | devm_platform_ioremap_resource(pdev, index) |
One call, looked up by numeric index. Best default for most drivers. |
| Combined, resource kept | devm_platform_get_and_ioremap_resource(pdev, index, &res) |
Same convenience, but also hands back the struct resource * in case you still need its start/end fields afterwards. |
Practical Example: A Complete Driver Reading Its Device Tree Resource
Putting it together, here is an original example driver that matches the Device Tree node shown earlier and reads its resource using the modern, managed style:
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/io.h>
#include <linux/module.h>
struct mydev_priv {
void __iomem *base;
int irq;
};
static int mydev_probe(struct platform_device *pdev)
{
struct mydev_priv *priv;
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
/* Index 0 corresponds to the first "reg" entry in the DT node */
priv->base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(priv->base))
return PTR_ERR(priv->base);
/* platform_get_irq() reads the "interrupts" property the same way */
priv->irq = platform_get_irq(pdev, 0);
if (priv->irq < 0)
return priv->irq;
platform_set_drvdata(pdev, priv);
dev_info(&pdev->dev, "mydev bound, irq=%d\n", priv->irq);
return 0;
}
static const struct of_device_id mydev_of_match[] = {
{ .compatible = "example,mydev" },
{ }
};
MODULE_DEVICE_TABLE(of, mydev_of_match);
static struct platform_driver mydev_driver = {
.probe = mydev_probe,
.driver = {
.name = "mydev",
.of_match_table = mydev_of_match,
},
};
module_platform_driver(mydev_driver);
MODULE_LICENSE("GPL");
The compatible string in mydev_of_match is the link back to the Device Tree node — it is exactly what lets the kernel decide which driver to bind to which hardware node at boot.
Common Mistakes and Troubleshooting
- Wrong resource index. If a node has multiple
regentries, using index 1 when you meant index 0 gives you the wrong register block silently — always cross-check against the Device Tree source. - Mismatched compatible strings. A single typo between the
.dtsnode and the driver’sof_match_tablemeansprobe()never runs at all, and there is no obvious error — checkdmesgfor “no driver found” style messages and confirm with/proc/device-treeordtc -I fs /proc/device-tree. - Forgetting status = “disabled”. A node with
status = "disabled"in the Device Tree will never be instantiated, which looks identical to a missing compatible match from the driver’s point of view. - Assuming platform_get_resource() returns a mapped pointer. It only returns resource metadata (addresses, flags); you still need an ioremap step to actually access the registers.
Best Practices
- Keep Device Tree bindings documented alongside the driver, even informally, so the mapping between properties and driver behavior stays clear.
- Prefer
devm_platform_ioremap_resource()over the older two-step pattern unless you specifically need the rawstruct resource. - Always check
platform_get_irq()‘s return value for a negative number rather than assuming it succeeded. - Use
MODULE_DEVICE_TABLE(of, ...)so user-space tools and module auto-loading can see which Device Tree nodes your module supports.
Performance Considerations
Device Tree parsing happens once, early in boot, before any driver code runs — it has no ongoing runtime cost for your driver. Resource lookups through platform_get_resource() and friends are simple array/list lookups on already-parsed data, so calling them in probe() is effectively free; there is no reason to cache or avoid them for performance purposes.
Security Considerations
Because peripheral addresses and interrupt numbers come from a boot-time-supplied blob, a corrupted or malicious DTB is a genuine attack surface on some boot chains — this is one of the reasons secure boot implementations increasingly verify the DTB alongside the kernel image itself. From the driver author’s side, always validate resource values (non-NULL, expected size) rather than trusting them blindly, since a misconfigured or tampered Device Tree can hand a driver a resource pointing at unexpected memory.
Real-World Use Cases
Every mainline ARM and RISC-V platform driver in the kernel today — from GPIO expanders to display controllers to network MACs — is instantiated this way: a Device Tree node describes it, the kernel creates a platform_device, and the driver’s probe() reads its resources with exactly the APIs covered in this lesson. If you build a custom driver for a peripheral on a Raspberry Pi, BeagleBone, or any other DT-based SBC, this is the pattern you will use.
Summary / Key Takeaways
- The Device Tree replaced hard-coded board files as the standard way to describe embedded hardware to Linux.
- A
.dtssource compiles into a.dtbblob, which the bootloader hands to the kernel at boot. - The kernel turns each matching DT node into a
platform_devicewith populatedstruct resourceentries. platform_get_resource()reads those resources; modern helpers likedevm_platform_ioremap_resource()combine lookup and mapping into one managed call.compatiblestrings are the link between a DT node and the driver’sof_match_table.
Conclusion
Understanding how the platform_get_resource Linux driver mechanism connects to the Device Tree closes the loop on the earlier lesson about managed I/O memory mapping: the Device Tree describes what hardware exists and where, the kernel turns that into resource structures on a platform_device, and your driver reads those structures — ideally through the modern, managed helper functions — to get a usable, automatically-cleaned-up pointer to the hardware. Once this pipeline clicks, writing a new platform driver for any DT-based board becomes a fairly mechanical, predictable process.
Frequently Asked Questions
1. What is the difference between a .dts file and a .dtb file?
A .dts file is the human-readable Device Tree source. The Device Tree Compiler (dtc) turns it into a compact binary .dtb file that the bootloader passes to the kernel at boot.
2. Does every Linux system use a Device Tree?
No. x86 and many server-class systems use ACPI tables instead, which serve a broadly similar purpose but come from firmware rather than a bootloader-supplied blob.
3. What does the index argument in platform_get_resource() actually mean?
It selects which entry of a given resource type (for example, the second reg range, or the third interrupt) to fetch, using zero-based counting that matches the order the properties appear in the Device Tree node.
4. How does the kernel decide which driver binds to which Device Tree node?
It matches the node’s compatible string against the strings listed in a driver’s of_match_table. The first driver with a matching entry gets bound.
5. What happens if a Device Tree node has status = “disabled”?
The kernel skips creating a platform device for that node entirely, so no driver will ever bind to it, even if the compatible string matches perfectly.
6. Can I read Device Tree properties other than reg and interrupts?
Yes, arbitrary custom properties can be read with the of_property_read_*() family of functions, which is a broader topic covered elsewhere in this course.
7. Is platform_get_resource() deprecated in favor of the devm_platform_* helpers?
It is not deprecated outright — it is still useful when you need the raw struct resource for something beyond mapping, such as computing its size. For the common “map it and use it” case, the combined helpers are simply less code.
8. How can I inspect the live Device Tree on a running board?
The kernel exposes the parsed tree under /proc/device-tree, and it can be dumped back into readable form with dtc -I fs -O dts /proc/device-tree.
9. What is the GIC_SPI reference in the interrupts property?
It identifies the interrupt as a Shared Peripheral Interrupt on an ARM Generic Interrupt Controller, as opposed to a private, per-CPU interrupt.
10. Do overlays change how platform_get_resource() works?
No — a Device Tree overlay simply adds or modifies nodes before or after boot; once a node exists and a platform device is created from it, resource lookup in the driver works exactly the same way regardless of whether the base tree or an overlay supplied that node.

2 Comments