Skip to content

Add Device Tree section #329

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ obj-m += vkbd.o
obj-m += static_key.o
obj-m += led.o
obj-m += dht11.o
obj-m += devicetree.o

PWD := $(CURDIR)

Expand Down
151 changes: 151 additions & 0 deletions examples/devicetree.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
/* devicetree.c - Demonstrates device tree interaction with kernel modules */

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/version.h>

#define DRIVER_NAME "lkmpg_devicetree"

/* Structure to hold device-specific data */
struct dt_device_data {
const char *label;
u32 reg_value;
u32 custom_value;
bool has_clock;
};

/* Probe function - called when device tree node matches */
static int dt_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
struct dt_device_data *data;
const char *string_prop;
u32 value;
int ret;

pr_info("%s: Device tree probe called for %s\n", DRIVER_NAME,
np->full_name);

/* Allocate memory for device data */
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;

/* Read a string property */
ret = of_property_read_string(np, "label", &string_prop);
if (ret == 0) {
data->label = string_prop;
pr_info("%s: Found label property: %s\n", DRIVER_NAME, data->label);
} else {
data->label = "unnamed";
pr_info("%s: No label property found, using default\n", DRIVER_NAME);
}

/* Read a u32 property */
ret = of_property_read_u32(np, "reg", &value);
if (ret == 0) {
data->reg_value = value;
pr_info("%s: Found reg property: 0x%x\n", DRIVER_NAME, data->reg_value);
}

/* Read a custom u32 property */
ret = of_property_read_u32(np, "lkmpg,custom-value", &value);
if (ret == 0) {
data->custom_value = value;
pr_info("%s: Found custom-value property: %u\n", DRIVER_NAME,
data->custom_value);
} else {
data->custom_value = 42; /* Default value */
pr_info("%s: No custom-value found, using default: %u\n", DRIVER_NAME,
data->custom_value);
}

/* Check for presence of a property */
data->has_clock = of_property_read_bool(np, "lkmpg,has-clock");
pr_info("%s: has-clock property: %s\n", DRIVER_NAME,
data->has_clock ? "present" : "absent");

/* Store device data for later use */
platform_set_drvdata(pdev, data);

pr_info("%s: Device probe successful\n", DRIVER_NAME);
return 0;
}

/* Remove function - called when device is removed */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 11, 0)
static void dt_remove(struct platform_device *pdev)
{
struct dt_device_data *data = platform_get_drvdata(pdev);

pr_info("%s: Removing device %s\n", DRIVER_NAME, data->label);
/* Cleanup is handled automatically by devm_* functions */
}
#else
static int dt_remove(struct platform_device *pdev)
{
struct dt_device_data *data = platform_get_drvdata(pdev);

pr_info("%s: Removing device %s\n", DRIVER_NAME, data->label);
/* Cleanup is handled automatically by devm_* functions */
return 0;
}
#endif

/* Device tree match table - defines compatible strings this driver supports */
static const struct of_device_id dt_match_table[] = {
{
.compatible = "lkmpg,example-device",
},
{
.compatible = "lkmpg,another-device",
},
{} /* Sentinel */
};
MODULE_DEVICE_TABLE(of, dt_match_table);

/* Platform driver structure */
static struct platform_driver dt_driver = {
.probe = dt_probe,
.remove = dt_remove,
.driver = {
.name = DRIVER_NAME,
.of_match_table = dt_match_table,
},
};

/* Module initialization */
static int __init dt_init(void)
{
int ret;

pr_info("%s: Initializing device tree example module\n", DRIVER_NAME);

/* Register the platform driver */
ret = platform_driver_register(&dt_driver);
if (ret) {
pr_err("%s: Failed to register platform driver\n", DRIVER_NAME);
return ret;
}

pr_info("%s: Module loaded successfully\n", DRIVER_NAME);
return 0;
}

/* Module cleanup */
static void __exit dt_exit(void)
{
pr_info("%s: Cleaning up device tree example module\n", DRIVER_NAME);
platform_driver_unregister(&dt_driver);
}

module_init(dt_init);
module_exit(dt_exit);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Device tree interaction example for LKMPG");
42 changes: 42 additions & 0 deletions examples/dt-overlay.dts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Device Tree Overlay for LKMPG Device Tree Example
*
* This overlay can be compiled and loaded on systems that support
* runtime device tree overlays (like Raspberry Pi).
*
* Compile with:
* dtc -@ -I dts -O dtb -o dt-overlay.dtbo dt-overlay.dts
*
* Load with (on Raspberry Pi):
* sudo dtoverlay dt-overlay.dtbo
*/

/dts-v1/;
/plugin/;

/ {
compatible = "brcm,bcm2835";

fragment@0 {
target-path = "/";
__overlay__ {
lkmpg_device@0 {
compatible = "lkmpg,example-device";
reg = <0x40000000 0x1000>;
label = "LKMPG Test Device";
lkmpg,custom-value = <100>;
lkmpg,has-clock;
status = "okay";
};

lkmpg_device@1 {
compatible = "lkmpg,another-device";
reg = <0x40001000 0x1000>;
label = "LKMPG Secondary Device";
lkmpg,custom-value = <200>;
/* no has-clock property for this one */
status = "okay";
};
};
};
};
91 changes: 91 additions & 0 deletions lkmpg.tex
Original file line number Diff line number Diff line change
Expand Up @@ -2236,6 +2236,97 @@ \section{Standardizing the interfaces: The Device Model}

\samplec{examples/devicemodel.c}

\section{Device Tree}
\label{sec:device_tree}
\subsection{Introduction to Device Tree}
\label{sec:dt_intro}
Device Tree is a data structure that describes hardware components in a system, particularly in embedded systems and ARM-based platforms.
Instead of hard-coding hardware details in the kernel source, Device Tree provides a separate, human-readable description that the kernel can parse at boot time.
This separation allows the same kernel binary to support multiple hardware platforms,
making development and maintenance significantly easier.

Device Tree files (with \verb|.dts| extension for source files and \verb|.dtb| for compiled binary files) use a hierarchical structure similar to a filesystem to represent the hardware topology.
Each hardware component is represented as a node with properties that describe its characteristics,
such as memory addresses, interrupt numbers, and device-specific parameters.

\subsection{Device Tree and Kernel Modules}
\label{sec:dt_modules}
While Device Tree is primarily used during kernel initialization, kernel modules can also interact with Device Tree nodes through the platform device framework.
When the kernel parses the Device Tree at boot, it creates platform devices for nodes that have compatible strings.
Kernel modules can then register platform drivers that match these compatible strings, allowing them to be automatically probed when the corresponding hardware is detected.

The key concepts for Device Tree interaction in kernel modules include:
\begin{itemize}
\item \textbf{Compatible strings}: Unique identifiers that match Device Tree nodes to their drivers
\item \textbf{Property reading}: Functions to extract configuration data from Device Tree nodes
\item \textbf{Platform driver framework}: Infrastructure for binding drivers to devices described in Device Tree
\item \textbf{Device-specific data}: Custom properties that can be defined for specific hardware
\end{itemize}

\subsection{Example: Device Tree Module}
\label{sec:dt_example}
The following example demonstrates how a kernel module can interact with Device Tree nodes.
This module registers a platform driver that matches specific compatible strings and extracts properties from the matched Device Tree nodes.

\samplec{examples/devicetree.c}

\subsection{Device Tree Source Example}
\label{sec:dt_source}
To use the above module, you would need a Device Tree entry like this:

\begin{code}
/* Example device tree fragment */
lkmpg_device@0 {
compatible = "lkmpg,example-device";
reg = <0x40000000 0x1000>;
label = "LKMPG Test Device";
lkmpg,custom-value = <100>;
lkmpg,has-clock;
};
\end{code}

The properties in this Device Tree node would be read by the module's probe function when the device is matched.
The \verb|compatible| property is used to match the device with the driver, while other properties provide device-specific configuration.

\subsection{Testing Device Tree Modules}
\label{sec:dt_testing}
Testing Device Tree modules can be done in several ways:

\begin{enumerate}
\item \textbf{Using Device Tree overlays}: On systems that support it (like Raspberry Pi), you can load Device Tree overlays at runtime to add new devices without rebooting.

\item \textbf{Modifying the main Device Tree}: Add your device nodes to the system's main Device Tree source file and recompile it.

\item \textbf{Using QEMU}: For development and testing, QEMU can emulate systems with custom Device Trees, allowing you to test your modules without physical hardware.
\end{enumerate}

To check if your device was properly detected, you can examine the sysfs filesystem:

\begin{codebash}
# List all platform devices
ls /sys/bus/platform/devices/

# Check device tree nodes
ls /proc/device-tree/
\end{codebash}

\subsection{Common Device Tree Functions}
\label{sec:dt_functions}
Here are some commonly used Device Tree functions in kernel modules:

\begin{itemize}
\item \cpp|of_property_read_string()| - Read a string property
\item \cpp|of_property_read_u32()| - Read a 32-bit integer property
\item \cpp|of_property_read_bool()| - Check if a boolean property exists
\item \cpp|of_find_property()| - Find a property by name
\item \cpp|of_get_property()| - Get a property's raw value
\item \cpp|of_match_device()| - Match a device against a match table
\item \cpp|of_parse_phandle()| - Parse a phandle reference to another node
\end{itemize}

These functions provide a robust interface for extracting configuration data from Device Tree nodes,
allowing modules to be highly configurable without code changes.

\section{Optimizations}
\label{sec:optimization}
\subsection{Likely and Unlikely conditions}
Expand Down