设备树实例

1 基本模板

这里的设备树由 qemu 平台自动生成, 我们进行导出

加上 -machine virt,dumpdtb=qemu-riscv.dtb 导出.
再转变成 dts: dtc -I dtb -O dts -o qemu-riscv.dts qemu-riscv.dtb

1
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
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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
/dts-v1/;

/ {
#address-cells = <0x02>;
#size-cells = <0x02>;
compatible = "riscv-virtio";
model = "riscv-virtio,qemu";

poweroff {
value = <0x5555>;
offset = <0x00>;
regmap = <0x04>;
compatible = "syscon-poweroff";
};

reboot {
value = <0x7777>;
offset = <0x00>;
regmap = <0x04>;
compatible = "syscon-reboot";
};

platform-bus@4000000 {
interrupt-parent = <0x03>;
ranges = <0x00 0x00 0x4000000 0x2000000>;
#address-cells = <0x01>;
#size-cells = <0x01>;
compatible = "qemu,platform\0simple-bus";
};

memory@80000000 {
device_type = "memory";
reg = <0x00 0x80000000 0x00 0x8000000>;
};

cpus {
#address-cells = <0x01>;
#size-cells = <0x00>;
timebase-frequency = <0x989680>;

cpu@0 {
phandle = <0x01>;
device_type = "cpu";
reg = <0x00>;
status = "okay";
compatible = "riscv";
riscv,cboz-block-size = <0x40>;
riscv,cbom-block-size = <0x40>;
riscv,isa = "rv64imafdch_zicbom_zicboz_zicntr_zicsr_zifencei_zihintntl_zihintpause_zihpm_zawrs_zfa_zca_zcd_zba_zbb_zbc_zbs_sstc_svadu";
mmu-type = "riscv,sv57";

interrupt-controller {
#interrupt-cells = <0x01>;
interrupt-controller;
compatible = "riscv,cpu-intc";
phandle = <0x02>;
};
};

cpu-map {

cluster0 {

core0 {
cpu = <0x01>;
};
};
};
};

pmu {
riscv,event-to-mhpmcounters = <0x01 0x01 0x7fff9 0x02 0x02 0x7fffc 0x10019 0x10019 0x7fff8 0x1001b 0x1001b 0x7fff8 0x10021 0x10021 0x7fff8>;
compatible = "riscv,pmu";
};

fw-cfg@10100000 {
dma-coherent;
reg = <0x00 0x10100000 0x00 0x18>;
compatible = "qemu,fw-cfg-mmio";
};

flash@20000000 {
bank-width = <0x04>;
reg = <0x00 0x20000000 0x00 0x2000000 0x00 0x22000000 0x00 0x2000000>;
compatible = "cfi-flash";
};

chosen {
bootargs = "root=/dev/vda rw console=ttyS0";
stdout-path = "/soc/serial@10000000";
rng-seed = <0xd4663224 0xfdce202c 0x653f4585 0xff10145e 0x83931306 0xec91e4aa 0x8b35b70a 0x4d0f344e>;
};

soc {
#address-cells = <0x02>;
#size-cells = <0x02>;
compatible = "simple-bus";
ranges;

rtc@101000 {
interrupts = <0x0b>;
interrupt-parent = <0x03>;
reg = <0x00 0x101000 0x00 0x1000>;
compatible = "google,goldfish-rtc";
};

serial@10000000 {
interrupts = <0x0a>;
interrupt-parent = <0x03>;
clock-frequency = "\08@";
reg = <0x00 0x10000000 0x00 0x100>;
compatible = "ns16550a";
};

test@100000 {
phandle = <0x04>;
reg = <0x00 0x100000 0x00 0x1000>;
compatible = "sifive,test1\0sifive,test0\0syscon";
};

pci@30000000 {
interrupt-map-mask = <0x1800 0x00 0x00 0x07>;
interrupt-map = <0x00 0x00 0x00 0x01 0x03 0x20 0x00 0x00 0x00 0x02 0x03 0x21 0x00 0x00 0x00 0x03 0x03 0x22 0x00 0x00 0x00 0x04 0x03 0x23 0x800 0x00 0x00 0x01 0x03 0x21 0x800 0x00 0x00 0x02 0x03 0x22 0x800 0x00 0x00 0x03 0x03 0x23 0x800 0x00 0x00 0x04 0x03 0x20 0x1000 0x00 0x00 0x01 0x03 0x22 0x1000 0x00 0x00 0x02 0x03 0x23 0x1000 0x00 0x00 0x03 0x03 0x20 0x1000 0x00 0x00 0x04 0x03 0x21 0x1800 0x00 0x00 0x01 0x03 0x23 0x1800 0x00 0x00 0x02 0x03 0x20 0x1800 0x00 0x00 0x03 0x03 0x21 0x1800 0x00 0x00 0x04 0x03 0x22>;
ranges = <0x1000000 0x00 0x00 0x00 0x3000000 0x00 0x10000 0x2000000 0x00 0x40000000 0x00 0x40000000 0x00 0x40000000 0x3000000 0x04 0x00 0x04 0x00 0x04 0x00>;
reg = <0x00 0x30000000 0x00 0x10000000>;
dma-coherent;
bus-range = <0x00 0xff>;
linux,pci-domain = <0x00>;
device_type = "pci";
compatible = "pci-host-ecam-generic";
#size-cells = <0x02>;
#interrupt-cells = <0x01>;
#address-cells = <0x03>;
};

virtio_mmio@10008000 {
interrupts = <0x08>;
interrupt-parent = <0x03>;
reg = <0x00 0x10008000 0x00 0x1000>;
compatible = "virtio,mmio";
};

virtio_mmio@10007000 {
interrupts = <0x07>;
interrupt-parent = <0x03>;
reg = <0x00 0x10007000 0x00 0x1000>;
compatible = "virtio,mmio";
};

virtio_mmio@10006000 {
interrupts = <0x06>;
interrupt-parent = <0x03>;
reg = <0x00 0x10006000 0x00 0x1000>;
compatible = "virtio,mmio";
};

virtio_mmio@10005000 {
interrupts = <0x05>;
interrupt-parent = <0x03>;
reg = <0x00 0x10005000 0x00 0x1000>;
compatible = "virtio,mmio";
};

virtio_mmio@10004000 {
interrupts = <0x04>;
interrupt-parent = <0x03>;
reg = <0x00 0x10004000 0x00 0x1000>;
compatible = "virtio,mmio";
};

virtio_mmio@10003000 {
interrupts = <0x03>;
interrupt-parent = <0x03>;
reg = <0x00 0x10003000 0x00 0x1000>;
compatible = "virtio,mmio";
};

virtio_mmio@10002000 {
interrupts = <0x02>;
interrupt-parent = <0x03>;
reg = <0x00 0x10002000 0x00 0x1000>;
compatible = "virtio,mmio";
};

virtio_mmio@10001000 {
interrupts = <0x01>;
interrupt-parent = <0x03>;
reg = <0x00 0x10001000 0x00 0x1000>;
compatible = "virtio,mmio";
};

plic@c000000 {
phandle = <0x03>;
riscv,ndev = <0x5f>;
reg = <0x00 0xc000000 0x00 0x600000>;
interrupts-extended = <0x02 0x0b 0x02 0x09>;
interrupt-controller;
compatible = "sifive,plic-1.0.0\0riscv,plic0";
#address-cells = <0x00>;
#interrupt-cells = <0x01>;
};

clint@2000000 {
interrupts-extended = <0x02 0x03 0x02 0x07>;
reg = <0x00 0x2000000 0x00 0x10000>;
compatible = "sifive,clint0\0riscv,clint0";
};
};
};

1.1 手动查找现有节点

内核程序.

1
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
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
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/of.h> // core: open device tree
#include <linux/of_device.h>

static int __init dt_test_init(void)
{
struct device_node *np;
const char *model_name = NULL;
int ret;
u32 prop_val;

printk(KERN_INFO "DT-TEST: Module loaded\n");

// 1. get root node "/"
// this will add ref + 1
np = of_find_node_by_path("/");
if (!np) {
printk(KERN_ERR "DT-TEST: Failed to find root node\n");
return -ENODEV;
}

// 2. get string properity `model`
ret = of_property_read_string(np, "model", &model_name);
if (ret == 0) {
printk(KERN_INFO "DT-TEST: Machine Model: %s\n", model_name);
} else {
printk(KERN_WARNING "DT-TEST: Could not read 'model' property\n");
}

// 3. get compatible properity
ret = of_property_read_string(np, "compatible", &model_name);
if (ret == 0) {
printk(KERN_INFO "DT-TEST: Compatible: %s\n", model_name);
}

// 4. look for CPUs nodes
of_node_put(np);

np = of_find_node_by_path("/cpus");
if (np) {
// read `#address-cells`
if (of_property_read_u32(np, "#address-cells", &prop_val) == 0) {
printk(KERN_INFO "DT-TEST: /cpus #address-cells = %d\n", prop_val);
}
of_node_put(np);
}

return 0;
}

static void __exit dt_test_exit(void)
{
printk(KERN_INFO "DT-TEST: Module unloaded\n");
}

module_init(dt_test_init);
module_exit(dt_test_exit);
MODULE_LICENSE("GPL");

运行效果.

1
2
3
4
5
6
sh-5.2# insmod /lib/modules/6.18.5/updates/device_tree.ko 
[ 10.879540] device_tree: loading out-of-tree module taints kernel.
[ 10.885096] DT-TEST: Module loaded
[ 10.885463] DT-TEST: Machine Model: riscv-virtio,qemu
[ 10.885753] DT-TEST: Compatible: riscv-virtio
[ 10.886300] DT-TEST: /cpus #address-cells = 1

1.2 标准平台驱动

这是正规的驱动开发的写法, 驱动不会在 init 触发, 而是等待系统发现硬件.

我们需要先修改设备树.

1
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
/* 在设备树根节点下添加 */
// 如果不加 @10000000, 会出现 warning
// qemu-riscv.dts:36.17-42.7: Warning (unit_address_vs_reg): /jvle_device: node has a reg or ranges property, but no unit name
jvle_device@10000000 {
compatible = "jvle,my-test-device";
status = "okay";

/* 基础属性 */
my-custom-int = <123>;
my-custom-str = "Hello World";

/* 数组测试 */
my-int-array = <10 20 30 40>;

/* 布尔测试 */
my-feature-enable;

/* 字节数组 */
my-byte-array = [DE AD BE EF 00 01];

/* 中断测试 */
// 这里示例没有别名, 因此这里直接路径引用, 还有两个方法
// 1. 在前面加上 plic 标签, 然后引用 `interrupt-parent = <&plic>;`
// 2. 直接引用 phandle, `interrupt-parent = <0x03>;`
interrupt-parent = <&{/soc/plic@c000000}>;
interrupts = <10 4>;

reg = <0x0 0x10000000 0x0 0x1000>;
};

内核程序.

1
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
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
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/platform_device.h> // platform
#include <linux/of.h> // core
#include <linux/mod_devicetable.h> // device ID table

// 1. this will tell kernel its supporting device
// the string must be same with the DTS
// root node: `#address-cells = <0x02>; #size-cells=<0x02>`
/* for example:
* jvle_device@10000000 {
* compatible = "jvle,my-test-device";
* status = "okay";
* // basic types
* my-custom-int = <123>;
* my-custom-str = "Hello World";
* // 1. array of u32
* my-int-array = <10 20 30 40>;
* // 2. boolean flag
* my-feature-enable;
* // 3. byte array
* my-byte-array = [00 11 22 33 44 55];
* // 4. interrupts
* // this depends on interrupt-parent (PLIC/CLINT)
* interrupt-parent = <&plic>;
* interrupts = <10 4>; // <irq_num trigger_type>
* // format: <high 32 bits lower 32 bits length high 32 bits length lower 32 bits>
* reg = <0x0 0x10000000 0x0 0x1000>; // relate to the root node hint
* };
*/
static const struct of_device_id my_of_match[] = {
{ .compatible = "jvle,my-test-device" },
{ /* sentinel */ }
};

MODULE_DEVICE_TABLE(of, my_of_match);

// 2. probe function:when the kernel finds a device tree node
// that matches the `compatible`, it calls the function
static int my_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
const char *str_val;
u32 int_val;
u32 array_val[4];
u8 byte_arr[6];
struct resource *res;
int ret;
int irq;

printk(KERN_INFO "PLATFORM-TEST: Probe triggered!\n");

// read basic properties
ret = of_property_read_u32(np, "my-custom-int", &int_val);
if (!ret) {
printk(KERN_INFO "PLATFORM-TEST: my-custom-int = %d\n", int_val);
}

ret = of_property_read_string(np, "my-custom-str", &str_val);
if (!ret) {
printk(KERN_INFO "PLATFORM-TEST: my-custom-str = %s\n", str_val);
}

// read array property
// read 4 u32 values into array
ret = of_property_read_u32_array(np, "my-int-array", array_val, 4);
if (!ret) {
printk(KERN_INFO "PLATFORM-TEST: my-int-array = [%d, %d, %d, %d]\n",
array_val[0], array_val[1], array_val[2], array_val[3]);
} else {
printk(KERN_INFO "PLATFORM-TEST: my-int-array not found\n");
}

// read boolean property
// returns true if property exists, false otherwise
if (of_property_read_bool(np, "my-feature-enable")) {
printk(KERN_INFO "PLATFORM-TEST: my-feature-enable is SET\n");
} else {
printk(KERN_INFO "PLATFORM-TEST: my-feature-enable is UNSET\n");
}

// read byte array
ret = of_property_read_u8_array(np, "my-byte-array", byte_arr, 6);
if (!ret) {
printk(KERN_INFO "PLATFORM-TEST: my-byte-array = %02x:%02x:%02x:%02x:%02x:%02x\n",
byte_arr[0], byte_arr[1], byte_arr[2],
byte_arr[3], byte_arr[4], byte_arr[5]);
}

// get interrupt resource
// platform_get_irq handles the parsing of "interrupts" property
irq = platform_get_irq(pdev, 0); // Get the 0-th interrupt
if (irq >= 0) {
printk(KERN_INFO "PLATFORM-TEST: IRQ number = %d\n", irq);
}

// get memory resource
// IORESOURCE_MEM: mem resources
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (res) {
printk(KERN_INFO "PLATFORM-TEST: reg start = 0x%llx, size = 0x%llx\n",
(unsigned long long)res->start,
(unsigned long long)resource_size(res));
}

return 0;
}

// 3. remove function: it triggerd when device is removed
static void my_remove(struct platform_device *pdev)
{
printk(KERN_INFO "PLATFORM-TEST: Device removed\n");
}

// 4. platform driver struct
static struct platform_driver my_driver = {
.probe = my_probe,
.remove = my_remove,
.driver = {
.name = "my_dt_test_driver",
.of_match_table = my_of_match, // match table
.owner = THIS_MODULE,
},
};

// 5. replace init/exit
module_platform_driver(my_driver);
MODULE_LICENSE("GPL");

先将 dts 变成 dtb.

dtc -I dts -O dtb -o my-custom.dtb qemu-riscv.dts

启动时指定 dtb, -dtb my-custom.dtb

运行效果.

1
2
3
4
5
6
7
8
9
10
11
12
sh-5.2# insmod /lib/modules/6.18.5/updates/device_tree_new.ko 
[ 13.007946] device_tree_new: loading out-of-tree module taints kernel.
[ 13.013327] PLATFORM-TEST: Probe triggered!
[ 13.013592] PLATFORM-TEST: my-custom-int = 123
[ 13.013863] PLATFORM-TEST: my-custom-str = Hello World
[ 13.014199] PLATFORM-TEST: my-int-array = [10, 20, 30, 40]
[ 13.015183] PLATFORM-TEST: my-feature-enable is SET
[ 13.015772] PLATFORM-TEST: my-byte-array = de:ad:be:ef:00:01
[ 13.016288] PLATFORM-TEST: IRQ number = 12
[ 13.016616] PLATFORM-TEST: reg start = 0x10000000, size = 0x1000
sh-5.2# rmmod /lib/modules/6.18.5/updates/device_tree_new.ko
[ 19.945078] PLATFORM-TEST: Device removed