If you have been following this free Linux kernel development course, the last two
lectures in this chapter introduced the V4L2 async interface and graph binding, and then
walked through the fwnode_graph_* API used to discover a device’s connections
inside a media pipeline. Before we can go any further into V4L2’s own graph helpers, it is
worth stopping to properly understand the Linux kernel fwnode API itself,
because it is the foundation that almost every modern platform, I2C, SPI, and V4L2 sub-device
driver now builds on. This lecture is a standalone, self-contained deep dive into
struct fwnode_handle — what problem it solves, how to walk a firmware node tree,
how to read properties from it, and how it relates to the older Device Tree-only and
ACPI-only APIs. By the end, you will be able to write drivers that work identically on
Device Tree boards and ACPI-based systems without writing separate code paths for each.
Why the Kernel Needed a Generic Firmware Node API
Every embedded Linux driver eventually needs to answer a simple question: “what hardware
am I attached to, and what are its properties?” On a typical ARM SBC that information comes
from Device Tree, represented internally by struct device_node. On x86 laptops,
servers, and many industrial boards, the same information comes from ACPI tables, represented
by struct acpi_device. For years the kernel had two completely separate sets of
APIs for reading this data — one prefixed of_, the other prefixed
acpi_ — which meant every driver author had to either pick one platform or
write and maintain two parallel code paths.
The Linux kernel fwnode API was introduced to remove that duplication. It defines a single
opaque handle, struct fwnode_handle, together with a matching set of
fwnode_* functions, that transparently works whether the underlying firmware
description came from Device Tree, ACPI, or even a purely software-defined node created at
runtime (a “software node”, used when there is no firmware description at all). A driver
written against the fwnode API never needs to know or care which backend produced the data —
it just calls fwnode_property_read_u32() or
fwnode_for_each_child_node() and gets the same behaviour everywhere. This is
exactly why every subsystem covered so far in this free linux device drivers course — platform
drivers, I2C client drivers, and now V4L2 sub-devices — has steadily migrated its public APIs
toward fwnode-based variants.
What You Will Learn
- What
struct fwnode_handlerepresents and why it replaced rawstruct device_nodepointers in new drivers - How to obtain a device’s fwnode and walk its child nodes
- How to read integer, string, and boolean properties through the fwnode API
- The difference between
fwnode_for_each_child_node()andfwnode_for_each_available_child_node() - How to convert between
fwnode_handle,device_node, andacpi_devicewhen you must interoperate with older subsystem code - How to build and test an original platform driver that walks child fwnodes and reads their properties
Prerequisites
- Comfort writing a basic platform driver (probe/remove,
of_device_idtable) — covered earlier in this free linux kernel development course - Basic Device Tree syntax — nodes, properties,
compatiblestrings - A Linux kernel source tree (6.x) to build an out-of-tree module against, or a QEMU/board setup where you can load a Device Tree overlay
struct fwnode_handle and dev_fwnode()
struct fwnode_handle is deliberately a thin, largely opaque structure. Drivers
almost never touch its internal fields directly — they obtain a handle and then pass it into
fwnode_* accessor functions. The most common way to get a device’s own fwnode is:
struct fwnode_handle *fwnode = dev_fwnode(dev);
if (!fwnode) {
dev_err(dev, "device has no firmware node\n");
return -ENODEV;
}
dev_fwnode() looks at struct device and returns whichever backend
is actually populated for that device — the Device Tree node wrapped as a fwnode on an ARM
board, or the ACPI node wrapped as a fwnode on an ACPI system. This single call is what lets a
driver’s probe() function stay completely platform-agnostic.
Walking a Firmware Node Tree
Many devices — audio codecs with multiple channels, sensor hubs, connector/port graphs — describe several logical sub-units as child nodes underneath one parent node. The fwnode API gives you a small family of functions to walk these children without caring whether the backing description is Device Tree or ACPI:
struct fwnode_handle *fwnode_get_parent(const struct fwnode_handle *fwnode);
struct fwnode_handle *fwnode_get_next_child_node(const struct fwnode_handle *fwnode,
struct fwnode_handle *child);
struct fwnode_handle *fwnode_get_next_available_child_node(const struct fwnode_handle *fwnode,
struct fwnode_handle *child);
struct fwnode_handle *fwnode_get_named_child_node(const struct fwnode_handle *fwnode,
const char *childname);
struct fwnode_handle *fwnode_handle_get(struct fwnode_handle *fwnode);
void fwnode_handle_put(struct fwnode_handle *fwnode);
fwnode_get_next_child_node() returns the first child when you pass
NULL as the current child, and the next sibling after that when you pass a
previous child back in. fwnode_get_next_available_child_node() behaves the same
way but skips over any child node whose backing device has not actually been probed
successfully yet — useful when a node might be present in the description but disabled or not
yet bound to a driver. fwnode_get_named_child_node() looks a child up directly by
its node name instead of iterating. Every fwnode you obtain through these calls holds a
reference that must eventually be released with fwnode_handle_put() — this is the
same reference-counting discipline you already know from of_node_get() /
of_node_put() in the Device Tree lectures.
Rather than calling these functions one at a time, the kernel provides convenience iterator macros that handle the get/put and the loop bookkeeping for you:
#define fwnode_for_each_child_node(fwnode, child) \
for (child = fwnode_get_next_child_node(fwnode, NULL); \
child; \
child = fwnode_get_next_child_node(fwnode, child))
#define fwnode_for_each_available_child_node(fwnode, child) \
for (child = fwnode_get_next_available_child_node(fwnode, NULL); \
child; \
child = fwnode_get_next_available_child_node(fwnode, child))
In almost every real driver you should reach for
fwnode_for_each_available_child_node() rather than the plain version, since it
naturally skips child nodes that are disabled with status = "disabled"; in Device
Tree, or the ACPI equivalent — exactly the child nodes you do not want your driver acting on.
Reading Properties Through the fwnode API
Once you have a fwnode — whether it is the device’s own node or one of its children — you
read its properties with a family of fwnode_property_* functions, all declared in
include/linux/property.h:
bool fwnode_device_is_available(const struct fwnode_handle *fwnode);
bool fwnode_property_present(const struct fwnode_handle *fwnode,
const char *propname);
int fwnode_property_read_u32(const struct fwnode_handle *fwnode,
const char *propname, u32 *val);
int fwnode_property_read_string(const struct fwnode_handle *fwnode,
const char *propname,
const char **val);
int fwnode_property_match_string(const struct fwnode_handle *fwnode,
const char *propname,
const char *string);
fwnode_device_is_available() tells you whether the node’s own device has been
marked enabled/present by firmware. fwnode_property_present() is a simple boolean
check for whether a property exists at all, useful for optional flags. The
fwnode_property_read_* family (there are equivalents for u8/u16/u32/u64, arrays of
each, and strings) reads a typed value out of the property and returns a negative errno if the
property is missing or the wrong type — always check the return value before trusting the
output parameter. All of these functions work identically whether the property came from a
Device Tree reg/custom property or an ACPI _DSD entry, which is the
entire point of routing your driver through the fwnode layer instead of calling
of_property_read_u32() directly.
Converting Between fwnode, Device Tree, and ACPI
Most new drivers never need to leave the fwnode API at all. But when you are working with an
older subsystem, or a helper function that still expects a raw struct device_node *,
the kernel provides small conversion helpers so you are not forced to rewrite that code path:
/* fwnode -> device_node, when the backend is Device Tree */
struct device_node *np = to_of_node(fwnode);
/* device_node -> fwnode */
struct fwnode_handle *fwnode = of_fwnode_handle(np);
/* fwnode -> acpi_device, when the backend is ACPI */
struct acpi_device *adev = to_acpi_device_node(fwnode);
/* acpi_device -> fwnode */
struct fwnode_handle *fwnode = acpi_fwnode_handle(adev);
to_of_node() and to_acpi_device_node() both safely return
NULL if the fwnode you pass in is not actually backed by that type, so it is safe
to call the “wrong” one defensively while writing portable code — just always check the result
before dereferencing it. These conversions exist purely as an escape hatch; treat reaching for
one as a sign that a piece of legacy code has not yet been migrated to accept a fwnode
directly, not as a pattern to build new drivers around.
Comparison: device_node vs acpi_device vs fwnode_handle
| Aspect | struct device_node | struct acpi_device | struct fwnode_handle |
|---|---|---|---|
| Backing firmware | Device Tree only | ACPI only | Device Tree, ACPI, or software node |
| Property read API | of_property_read_*() |
ACPI-specific accessors | fwnode_property_read_*() |
| Child node walking | for_each_child_of_node() |
ACPI-specific walk APIs | fwnode_for_each_child_node() |
| Recommended for new drivers | No — Device Tree-only code paths only | No — ACPI-only code paths only | Yes — this is the current best practice |
Building an Original fwnode Demo Driver
Let’s put this together in a small, original platform driver called
ep_fwnodedemo. It walks every available child node under itself and prints the
custom properties it finds — the same pattern a real audio, sensor-array, or connector driver
would use to discover its logical sub-units.
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/property.h>
#include <linux/mod_devicetable.h>
static int ep_fwnodedemo_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct fwnode_handle *fwnode = dev_fwnode(dev);
struct fwnode_handle *child;
const char *label;
u32 chan_id;
int ret;
if (!fwnode) {
dev_err(dev, "ep_fwnodedemo: no firmware node found\n");
return -ENODEV;
}
dev_info(dev, "ep_fwnodedemo: walking child nodes\n");
fwnode_for_each_available_child_node(fwnode, child) {
ret = fwnode_property_read_u32(child, "ep,channel-id", &chan_id);
if (ret) {
dev_warn(dev, "ep_fwnodedemo: child missing ep,channel-id (%d)\n", ret);
continue;
}
ret = fwnode_property_read_string(child, "ep,channel-label", &label);
if (ret)
label = "unnamed";
dev_info(dev, "ep_fwnodedemo: channel %u label=\"%s\"\n", chan_id, label);
}
return 0;
}
static void ep_fwnodedemo_remove(struct platform_device *pdev)
{
dev_info(&pdev->dev, "ep_fwnodedemo: removed\n");
}
static const struct of_device_id ep_fwnodedemo_of_match[] = {
{ .compatible = "ep,fwnode-demo" },
{ }
};
MODULE_DEVICE_TABLE(of, ep_fwnodedemo_of_match);
static struct platform_driver ep_fwnodedemo_driver = {
.probe = ep_fwnodedemo_probe,
.remove = ep_fwnodedemo_remove,
.driver = {
.name = "ep_fwnodedemo",
.of_match_table = ep_fwnodedemo_of_match,
},
};
module_platform_driver(ep_fwnodedemo_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("EmbeddedPathashala");
MODULE_DESCRIPTION("Demo driver walking fwnode child nodes and properties");
Note that .remove is declared to return void here — recent kernel
releases changed the platform driver remove callback from an int return to
void, since the core never actually acted on a non-zero return value anyway.
Always check include/linux/platform_device.h in the kernel version you are
targeting if you see a build warning about this.
An original Device Tree snippet to exercise this driver, using our own
ep,fwnode-demo compatible string and two child channel nodes:
ep_fwnode_demo: fwnode-demo {
compatible = "ep,fwnode-demo";
status = "okay";
channel@0 {
ep,channel-id = <0>;
ep,channel-label = "left-channel";
};
channel@1 {
ep,channel-id = <1>;
ep,channel-label = "right-channel";
};
};
Build and load it against your running kernel:
$ make -C /lib/modules/$(uname -r)/build M=$(pwd) modules
$ sudo insmod ep_fwnodedemo.ko
Expected output in dmesg once the overlay is applied and the driver binds:
$ dmesg | tail
[ 12.401223] ep_fwnodedemo: walking child nodes
[ 12.401247] ep_fwnodedemo: channel 0 label="left-channel"
[ 12.401259] ep_fwnodedemo: channel 1 label="right-channel"
Unload it and confirm the remove path runs cleanly:
$ sudo rmmod ep_fwnodedemo
$ dmesg | tail -1
[ 40.118842] ep_fwnodedemo: removed
Real-World Use Cases
- Multi-channel audio codecs describing several audio channels or DAI links as child nodes, each read through fwnode at probe time
- V4L2 sensor arrays and connector graphs, where port/endpoint nodes are
themselves walked and parsed as fwnodes — the direct foundation for the
fwnode_graph_*API from the previous lecture in this chapter - Drivers shipped for both ARM Device Tree boards and x86 ACPI-based industrial PCs from a single source tree, with zero platform-specific branches
- GPIO/pinctrl and regulator consumers that need to enumerate a variable number of child resources described in firmware
Common Mistakes and Troubleshooting
- Forgetting to call
fwnode_handle_put()on nodes obtained outside the iterator macros — this leaks a reference and can eventually pin memory or prevent clean unbind. Thefwnode_for_each_*macros already do this for you internally as the loop advances, but any earlybreakout of the loop still needs an explicitfwnode_handle_put()on the current child. - Using
fwnode_for_each_child_node()when you meant the “available” variant — this will happily hand you disabled nodes and your driver may try to configure hardware that firmware has explicitly marked as absent. - Ignoring the return value of
fwnode_property_read_*()— a missing or mistyped property leaves the output parameter untouched, so skipping the error check silently uses uninitialized data. - Reaching for
to_of_node()as a first instinct instead of the matchingfwnode_*call — this quietly reintroduces the Device-Tree-only assumption the fwnode API exists to remove.
Best Practices, Performance, and Security Considerations
- Always prefer the
fwnode_*entry point over the olderof_*or ACPI-specific one in any new driver code, even on a board you know is Device-Tree-only today — it keeps the driver portable and matches upstream review expectations. - Property reads themselves are cheap and happen only during probe, so there is no runtime performance concern — the cost that matters is getting reference counting right so probe/ remove cycles do not leak memory over repeated bind/unbind, which matters a great deal on systems that hot-plug or rebind drivers frequently.
- Never trust property values read from firmware as implicitly safe bounds — a malformed or malicious Device Tree overlay or ACPI table can hand your driver an out-of-range channel ID or an oversized string length; validate every value the same way you would validate user-space input before using it to index an array or size a buffer.
- Use
fwnode_property_present()to make optional properties genuinely optional in your driver logic, rather than treating every missing property as a hard probe failure.
Summary and Key Takeaways
The Linux kernel fwnode API, centered on struct fwnode_handle, gives every
subsystem a single, backend-agnostic way to discover devices, walk child nodes, and read
properties, regardless of whether the underlying firmware description is Device Tree, ACPI, or
a runtime-constructed software node. You obtain a device’s fwnode with dev_fwnode(),
walk children with fwnode_for_each_available_child_node(), read typed properties
with the fwnode_property_read_*() family, and fall back to
to_of_node()/to_acpi_device_node() only when interoperating with
legacy code that has not yet been converted. This is the exact mechanism the V4L2 async and
graph-binding code from the earlier lectures in this chapter builds on internally, so
understanding it here will make the rest of the V4L2 media controller material much easier to
follow. As always in this free linux device drivers course, we built and tested every example
as an original driver rather than copying vendor code, which is the habit that will serve you
best once you start reading and contributing to real upstream drivers.
Frequently Asked Questions
What is the Linux kernel fwnode API and why was it introduced?
The fwnode API is a generic layer, centered on struct fwnode_handle, that lets
drivers read device properties and walk child nodes the same way regardless of whether the
underlying firmware description is Device Tree, ACPI, or a software node. It was introduced so
driver authors would not need to write and maintain separate of_* and ACPI code
paths for the same logic.
What is the difference between struct device_node and struct fwnode_handle?
struct device_node represents a Device Tree node only. struct
fwnode_handle is a generic wrapper that can represent a Device Tree node, an ACPI
device, or a software node, all accessed through the same set of fwnode_*
functions.
Does the fwnode API work on ACPI-based systems, not just Device Tree boards?
Yes. That is the entire purpose of the API — the same driver code compiled once will read
properties correctly whether it runs on a Device Tree ARM board or an ACPI-based x86 system,
because dev_fwnode() returns the correct backend automatically.
How do I read a property value using the fwnode API?
Call the appropriate typed accessor, such as fwnode_property_read_u32() or
fwnode_property_read_string(), passing the fwnode handle and property name. Always
check the returned error code, since a missing or mistyped property leaves the output
parameter untouched.
What is the difference between fwnode_for_each_child_node and fwnode_for_each_available_child_node?
The plain version walks every child node regardless of status. The “available” version skips child nodes whose device has been marked disabled or has not been probed successfully, which is usually what you actually want in driver code.
Can I still use the older of_* or ACPI-specific APIs in new drivers?
You can, but it is discouraged for new code. Reaching for of_property_read_*()
or ACPI-specific accessors directly reintroduces the platform-specific branching that the
fwnode API was built to eliminate. Reserve the conversion helpers like
to_of_node() for interoperating with legacy code only.
How do I convert between fwnode_handle, device_node, and acpi_device?
Use to_of_node() to get a device_node from a fwnode,
of_fwnode_handle() to go the other way, and to_acpi_device_node() /
acpi_fwnode_handle() for the ACPI equivalents. Each conversion function safely
returns NULL if the fwnode is not backed by that type.
Where can I learn V4L2 and Linux kernel driver development for free?
This lecture is part of EmbeddedPathashala’s free Linux kernel development course, which also covers platform drivers, I2C/SPI client drivers, Device Tree, memory management, and V4L2 video capture drivers as a free linux device drivers course and free embedded systems course, built entirely around original, from-scratch driver examples.
Continue the Free Linux Kernel Development Course
Next in this chapter: parsing V4L2-specific properties on top of the fwnode graph, and
wiring a discovered graph into v4l2_async_notifier.
