1. How Does the Driver Access Device Tree?
Linux kernel provides a series of OpenFirmware (OF) API functions, allowing drivers to access and parse content in the Device Tree.
As long as you know the compatible or node name characteristics of the Device Tree node, you can manually find the node and extract its properties (such as u32, string, array, etc.).
2. Common OF API Introduction
of_find_node_by_name(NULL, "node_name")
- Find Device Tree node by name (system-wide traversal, may have duplicates)
of_find_node_by_path("/mychardev@0")
- Find unique node by full path
of_find_compatible_node(NULL, NULL, "compatible-name")
- Find node by compatible string (most recommended)
of_property_read_u32(np, "property_name", &val)
- Read u32 property
of_property_read_string(np, "property_name", &ptr)
- Read string property
3. Character Device Driver Parsing Device Tree
Create an 11_driver_devicetree_get/ directory, enter the directory and create a driver_devicetree_get.c driver file. We will reuse the code from the Implementing a Character Device chapter, copy the content from 05_char_device/mychardev.c and modify it.
1. Header File Preparation
Add at the beginning of the driver code:
#include <linux/of.h> // Device Tree interface core header file
#include <linux/of_address.h> // Header file used for reading memory-mapped resources2
2. Find Node and Read Properties
In the mychardev_init function, write the code:
static int __init mychardev_init(void)
{
// ...original code...
struct device_node *np; // Device Tree node pointer
u32 buffer_len = 0, dev_id = 0; // Used to store Device Tree property values
/* Get device node from Device Tree (find node using compatible) */
// Must match the compatible name set in Device Tree!
np = of_find_compatible_node(NULL, NULL, "lckfb,mychardev");
if (!np) {
printk(KERN_ERR "mychardev: device tree node not found!\n");
return -ENODEV;
}
// Read buffer-len
if (of_property_read_u32(np, "buffer-len", &buffer_len)) {
// Print error message
printk(KERN_ERR "mychardev: failed to get buffer-len!\n");
}
// Read dev-id
if (of_property_read_u32(np, "dev-id", &dev_id)) {
// Print error message
printk(KERN_ERR "mychardev: failed to get dev-id!\n");
}
// Print the read information
printk(KERN_INFO "mychardev: from dtb buffer-len=%u, dev-id=0x%X\n", buffer_len, dev_id);
// ...original code...
return 0;
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
3. Code Explanation
- Get Device Tree Node
np = of_find_compatible_node(NULL, NULL, "lckfb,mychardev");
if (!np) {
printk(KERN_ERR "mychardev: device tree node not found!\n");
return -ENODEV;
}2
3
4
5
- Use
of_find_compatible_nodefunction to find and get the device node pointer (struct device_node *np) from the Device Tree using the"lckfb,mychardev"compatible property. - If the node cannot be found, the kernel log reports an error and returns
-ENODEV, initialization fails.
- Read Device Tree Properties
if (of_property_read_u32(np, "buffer-len", &buffer_len)) {
printk(KERN_ERR "mychardev: failed to get buffer-len!\n");
}
if (of_property_read_u32(np, "dev-id", &dev_id)) {
printk(KERN_ERR "mychardev: failed to get dev-id!\n");
}2
3
4
5
6
- Use
of_property_read_u32to read the"buffer-len"and"dev-id"properties (32-bit unsigned integers) under the Device Tree node. - If reading fails, output error logs respectively.
- Print Read Results
printk(KERN_INFO "mychardev: from dtb buffer-len=%u, dev-id=0x%X\n", buffer_len, dev_id);Whether or not reading succeeds, the property values obtained (or default value 0) will be printed in the kernel log.
4. Complete Code
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h> // Device Tree interface core header file
#include <linux/of_address.h> // Header file used for reading memory-mapped resources
// Device name and class name macro definitions
#define DEV_NAME "mychardev" // Device node name
#define CLASS_NAME "class_mychardev" // Device class name
static dev_t dev_num; // Stores device number
static struct cdev cdev_test; // Character device structure
static struct class *class_test; // Device class pointer
// Function called when opening the device
// inode: pointer to file inode structure
// file: file structure pointer
static int chrdev_open(struct inode *inode, struct file *file)
{
printk(KERN_INFO "mychardev: chrdev_open called\n"); // Print open info to kernel log
return 0; // Return 0 indicates success
}
// Function called when reading the device
// file: file structure pointer
// buf: user space buffer pointer
// size: expected number of bytes to read
// off: offset pointer
static ssize_t chrdev_read(struct file *file, char __user *buf, size_t size, loff_t *off)
{
printk(KERN_INFO "mychardev: chrdev_read called\n"); // Print read operation info
return 0; // Return 0 indicates no data to read
}
// Function called when writing to the device
// file: file structure pointer
// buf: user space buffer pointer
// size: number of bytes to write
// off: offset pointer
static ssize_t chrdev_write(struct file *file, const char __user *buf, size_t size, loff_t *off)
{
printk(KERN_INFO "mychardev: chrdev_write called\n"); // Print write operation info
return size; // Return number of bytes written, indicating success
}
// Function called when closing the device
// inode: pointer to file inode structure
// file: file structure pointer
static int chrdev_release(struct inode *inode, struct file *file)
{
printk(KERN_INFO "mychardev: chrdev_release called\n"); // Print close info
return 0; // Return 0 indicates success
}
// file_operations structure, specifying operations supported by this device
static struct file_operations cdev_fops_test = {
.owner = THIS_MODULE, // Owner, generally THIS_MODULE
.open = chrdev_open, // open operation
.read = chrdev_read, // read operation
.write = chrdev_write, // write operation
.release = chrdev_release, // release operation
};
// Initialization function automatically called when module is loaded
static int __init mychardev_init(void)
{
int ret;
int major, minor;
struct device_node *np; // Device Tree node pointer
u32 buffer_len = 0, dev_id = 0; // Used to store Device Tree property values
/* Get device node from Device Tree (find node using compatible) */
np = of_find_compatible_node(NULL, NULL, "lckfb,mychardev");
if (!np) {
printk(KERN_ERR "mychardev: device tree node not found!\n");
return -ENODEV;
}
// Read buffer-len
if (of_property_read_u32(np, "buffer-len", &buffer_len)) {
// Print error message
printk(KERN_ERR "mychardev: failed to get buffer-len!\n");
}
// Read dev-id
if (of_property_read_u32(np, "dev-id", &dev_id)) {
// Print error message
printk(KERN_ERR "mychardev: failed to get dev-id!\n");
}
// Print the read information
printk(KERN_INFO "mychardev: from dtb buffer-len=%u, dev-id=0x%X\n", buffer_len, dev_id);
// 1. Automatically apply for device number, major and minor numbers allocated by kernel
ret = alloc_chrdev_region(&dev_num, 0, 1, DEV_NAME);
if (ret < 0) {
printk(KERN_ERR "mychardev: alloc_chrdev_region failed\n"); // Allocation failed
return ret;
}
major = MAJOR(dev_num); // Get major device number
minor = MINOR(dev_num); // Get minor device number
printk(KERN_INFO "mychardev: alloc_chrdev_region ok: major=%d, minor=%d\n", major, minor);
// 2. Initialize cdev structure and add to kernel
cdev_init(&cdev_test, &cdev_fops_test); // Initialize cdev
ret = cdev_add(&cdev_test, dev_num, 1); // Register cdev to kernel
if (ret < 0) {
printk(KERN_ERR "mychardev: cdev_add failed\n");
unregister_chrdev_region(dev_num,1); // Release device number on failure
return ret;
}
// 3. Create device class for automatic device node creation
class_test = class_create(THIS_MODULE, CLASS_NAME);
if (IS_ERR(class_test)) {
printk(KERN_ERR "mychardev: class_create failed\n");
cdev_del(&cdev_test);
unregister_chrdev_region(dev_num,1);
return PTR_ERR(class_test);
}
// 4. Create device node /dev/device_test
if (device_create(class_test, NULL, dev_num, NULL, DEV_NAME) == NULL) {
printk(KERN_ERR "mychardev: device_create failed\n");
class_destroy(class_test);
cdev_del(&cdev_test);
unregister_chrdev_region(dev_num,1);
return -ENOMEM;
}
printk(KERN_INFO "mychardev: chrdev driver loaded successfully\n"); // Driver loaded successfully
return 0;
}
// Cleanup function automatically called when module is unloaded
static void __exit mychardev_exit(void)
{
device_destroy(class_test, dev_num); // Delete device node
class_destroy(class_test); // Delete device class
cdev_del(&cdev_test); // Unregister cdev
unregister_chrdev_region(dev_num, 1); // Release device number
printk(KERN_INFO "mychardev: chrdev driver unloaded\n"); // Unload info
}
// Specify module initialization and exit functions
module_init(mychardev_init); // Call when loading module
module_exit(mychardev_exit); // Call when unloading module
MODULE_LICENSE("GPL"); // Module license declaration2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
5. Makefile and Compilation
The Makefile is almost exactly the same as in 05_char_device/Makefile, as follows:
export ARCH=arm64
# Cross compiler absolute path prefix
export CROSS_COMPILE=/home/lckfb/TaishanPi-3-Linux/prebuilts/gcc/linux-x86/aarch64/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-
# Consistent with source file name
obj-m += driver_devicetree_get.o
# Kernel source directory
KDIR := /home/lckfb/TaishanPi-3-Linux/kernel-6.1
PWD ?= $(shell pwd)
all:
make -C $(KDIR) M=$(PWD) modules
clean:
make -C $(KDIR) M=$(PWD) clean2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
CROSS_COMPILE: still the compiler path prefix in SDK.KDIR: still the kernel source directory.- Need to change what follows
obj-m +=todriver_devicetree_get.o.
Then execute make to compile and generate the .ko file:
6. Running Tests
We must flash the kernel with the modified Device Tree to the development board, then copy the just-compiled driver_devicetree_get.ko file to the development board for mounting:
sudo insmod driver_devicetree_get.koThen we can see that the values we set in the Device Tree are read by the driver and printed:
4. Multiple Device Support
If you need to find multiple similar nodes (e.g., multiple mychardevs), you can use of_find_compatible_node with loops/linked lists.
struct device_node *np = NULL;
while ((np = of_find_compatible_node(np, NULL, "lckfb,mychardev"))) {
// Each time np points to the next matching node
// Can continue reading properties of each node
}2
3
4
5
However, if only one node was added, this usage is sufficient.
5. Notes
With Device Tree parsing, we can abstract many parameters that were originally hardcoded in the driver to the Device Tree, making modifications extremely convenient.
Combined with dynamic Device Tree, the effect is even more powerful.