06、设备树插件驱动
dtbocfg.c 为设备树插件驱动文件。
在驱动文件中, 生成 device-tree/overlays 目录结构
一、源码
/*********************************************************************************
*
* Copyright (C) 2016-2021 Ichiro Kawazome
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
********************************************************************************/
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_fdt.h>
#include <linux/configfs.h>
#include <linux/types.h>
#include <linux/stat.h>
#include <linux/limits.h>
#include <linux/file.h>
#include <linux/version.h>
/**
* Device Tree Overlay Item Structure
*/
struct dtbocfg_overlay_item {
struct config_item item;
#if (LINUX_VERSION_CODE < 0x041100)
struct device_node* node;
#endif
int id;
void* dtbo;
int dtbo_size;
};
/**
* dtbocfg_overlay_create() - Create Device Tree Overlay
* @overlay: Pointer to Device Tree Overlay Item
* return Success(0) or Error Status.
*/
static int dtbocfg_overlay_item_create(struct dtbocfg_overlay_item *overlay)
{
int ret_val;
#if (LINUX_VERSION_CODE >= 0x041100)
{
int ovcs_id = 0;
ret_val = of_overlay_fdt_apply(overlay->dtbo,overlay->dtbo_size, &ovcs_id);
if (ret_val != 0) {
pr_err("%s: Failed to apply overlay (ret_val=%d)\n", __func__, ret_val);
goto failed;
}
overlay->id = ovcs_id;
pr_debug("%s: apply OK(id=%d)\n", __func__, ovcs_id);
}
#else
#if (LINUX_VERSION_CODE >= 0x040700)
of_fdt_unflatten_tree(overlay->dtbo, NULL, &overlay->node);
#else
of_fdt_unflatten_tree(overlay->dtbo, &overlay->node);
#endif
if (overlay->node == NULL) {
pr_err("%s: failed to unflatten tree\n", __func__);
ret_val = -EINVAL;
goto failed;
}
pr_debug("%s: unflattened OK\n", __func__);
#if (LINUX_VERSION_CODE >= 0x040F00)
{
int ovcs_id = 0;
ret_val = of_overlay_apply(overlay->node, &ovcs_id);
if (ret_val != 0) {
pr_err("%s: Failed to apply overlay (ret_val=%d)\n", __func__, ret_val);
goto failed;
}
overlay->id = ovcs_id;
pr_debug("%s: apply OK(id=%d)\n", __func__, ovcs_id);
}
#else
{
of_node_set_flag(overlay->node, OF_DETACHED);
ret_val = of_resolve_phandles(overlay->node);
if (ret_val != 0) {
pr_err("%s: Failed to resolve tree\n", __func__);
goto failed;
}
pr_debug("%s: resolved OK\n", __func__);
ret_val = of_overlay_create(overlay->node);
if (ret_val < 0) {
pr_err("%s: Failed to create overlay (ret_val=%d)\n", __func__, ret_val);
goto failed;
}
overlay->id = ret_val;
}
#endif
#endif
pr_debug("%s: create OK\n", __func__);
return 0;
failed:
return ret_val;
}
/**
* dtbocfg_overlay_item_release() - Relase Device Tree Overlay
* @overlay: Pointer to Device Tree Overlay Item
* return none
*/
static void dtbocfg_overlay_item_release(struct dtbocfg_overlay_item *overlay)
{
if (overlay->id >= 0) {
#if (LINUX_VERSION_CODE >= 0x040F00)
of_overlay_remove(&overlay->id);
#else
of_overlay_destroy(overlay->id);
#endif
overlay->id = -1;
}
}
/**
* container_of_dtbocfg_overlay_item() - Get Device Tree Overlay Item Pointer from Configuration Item
* @item: Pointer to Configuration Item
* return Pointer to Device Tree Overlay Item
*/
static inline struct dtbocfg_overlay_item* container_of_dtbocfg_overlay_item(struct config_item *item)
{
return item ? container_of(item, struct dtbocfg_overlay_item, item) : NULL;
}
/**
* dtbocfg_overlay_item_status_store() - Set Status Attibute
* @item: Pointer to Configuration Item
* @page: Pointer to Value Buffer
* @count: Size of Value Buffer Size
* return Stored Size or Error Status.
*/
static ssize_t dtbocfg_overlay_item_status_store(struct config_item *item, const char *buf, size_t count)
{
struct dtbocfg_overlay_item *overlay = container_of_dtbocfg_overlay_item(item);
ssize_t status;
unsigned long value;
if (0 != (status = kstrtoul(buf, 10, &value))) {
goto failed;
}
if (value == 0) {
if (overlay->id >= 0) {
dtbocfg_overlay_item_release(overlay);
}
} else {
if (overlay->id < 0) {
dtbocfg_overlay_item_create(overlay);
}
}
return count;
failed:
return -EPERM;
}
/**
* dtbocfg_overlay_item_status_show() - Show Status Attibute
* @item : Pointer to Configuration Item
* @page : Pointer to Value for Store
* return String Size or Error Status.
*/
static ssize_t dtbocfg_overlay_item_status_show(struct config_item *item, char *page)
{
struct dtbocfg_overlay_item *overlay = container_of_dtbocfg_overlay_item(item);
return sprintf(page, "%d\n", overlay->id >= 0 ? 1 : 0);
}
/**
* dtbocfg_overlay_item_dtbo_write() - Write Device Tree Blob to Configuration Item
* @item : Pointer to Configuration Item
* @page : Pointer to Value Buffer
* @count: Size of Value Buffer
* return Stored Size or Error Status.
*/
static ssize_t dtbocfg_overlay_item_dtbo_write(struct config_item *item, const void *buf, size_t count)
{
struct dtbocfg_overlay_item *overlay = container_of_dtbocfg_overlay_item(item);
if (overlay->dtbo_size > 0) {
if (overlay->id >= 0) {
return -EPERM;
}
kfree(overlay->dtbo);
overlay->dtbo = NULL;
overlay->dtbo_size = 0;
}
overlay->dtbo = kmemdup(buf, count, GFP_KERNEL);
if (overlay->dtbo == NULL) {
overlay->dtbo_size = 0;
return -ENOMEM;
} else {
overlay->dtbo_size = count;
return count;
}
}
/**
* dtbocfg_overlay_item_dtbo_read() - Read Device Tree Blob from Configuration Item
* @item : Pointer to Configuration Item
* @page : Pointer to Value for Store, or NULL to query the buffer size
* @size : Size of the supplied buffer
* return Read Size
*/
static ssize_t dtbocfg_overlay_item_dtbo_read(struct config_item *item, void *buf, size_t size)
{
struct dtbocfg_overlay_item *overlay = container_of_dtbocfg_overlay_item(item);
if (overlay->dtbo == NULL)
return 0;
if (buf != NULL)
memcpy(buf, overlay->dtbo, overlay->dtbo_size);
return overlay->dtbo_size;
}
/**
* Device Tree Blob Overlay Attribute Structure
*/
CONFIGFS_BIN_ATTR(dtbocfg_overlay_item_, dtbo, NULL, 1024 * 1024); // 1MiB should be way more than enough
CONFIGFS_ATTR(dtbocfg_overlay_item_, status);
static struct configfs_attribute *dtbocfg_overlay_attrs[] = {
&dtbocfg_overlay_item_attr_status,
NULL,
};
static struct configfs_bin_attribute *dtbocfg_overlay_bin_attrs[] = {
&dtbocfg_overlay_item_attr_dtbo,
NULL,
};
/**
* dtbocfg_overlay_release() - Release Device Tree Overlay Item
* @item : Pointer to Configuration Item
* Return None
*/
static void dtbocfg_overlay_release(struct config_item *item)
{
struct dtbocfg_overlay_item *overlay = container_of_dtbocfg_overlay_item(item);
pr_debug("%s\n", __func__);
dtbocfg_overlay_item_release(overlay);
if (overlay->dtbo) {
kfree(overlay->dtbo);
overlay->dtbo = NULL;
overlay->dtbo_size = 0;
}
kfree(overlay);
}
/**
* Device Tree Blob Overlay Item Structure
*/
static struct configfs_item_operations dtbocfg_overlay_item_ops = {
.release = dtbocfg_overlay_release,
};
static struct config_item_type dtbocfg_overlay_item_type = {
.ct_item_ops = &dtbocfg_overlay_item_ops,
.ct_attrs = dtbocfg_overlay_attrs,
.ct_bin_attrs = dtbocfg_overlay_bin_attrs,
.ct_owner = THIS_MODULE,
};
/**
* dtbocfg_overlay_group_make_item() - Make Device Tree Overlay Group Item
* @group: Pointer to Configuration Group
* @name : Pointer to Group Name
* Return Pointer to Device Tree Overlay Group Item
*/
static struct config_item *dtbocfg_overlay_group_make_item(struct config_group *group, const char *name)
{
struct dtbocfg_overlay_item *overlay;
pr_debug("%s\n", __func__);
overlay = kzalloc(sizeof(*overlay), GFP_KERNEL);
if (!overlay)
return ERR_PTR(-ENOMEM);
overlay->id = -1;
overlay->dtbo = NULL;
overlay->dtbo_size = 0;
config_item_init_type_name(&overlay->item, name, &dtbocfg_overlay_item_type);
return &overlay->item;
}
/**
* dtbocfg_overlay_group_drop_item() - Drop Device Tree Overlay Group Item
* @group: Pointer to Configuration Group
* @item : Pointer to Device Tree Overlay Group Item
*/
static void dtbocfg_overlay_group_drop_item(struct config_group *group, struct config_item *item)
{
struct dtbocfg_overlay_item *overlay = container_of_dtbocfg_overlay_item(item);
pr_debug("%s\n", __func__);
config_item_put(&overlay->item);
}
/**
* Device Tree Blob Overlay Sub Group Structures
*/
static struct configfs_group_operations dtbocfg_overlays_ops = {
.make_item = dtbocfg_overlay_group_make_item,
.drop_item = dtbocfg_overlay_group_drop_item,
};
static struct config_item_type dtbocfg_overlays_type = {
.ct_group_ops = &dtbocfg_overlays_ops,
.ct_owner = THIS_MODULE,
};
static struct config_group dtbocfg_overlay_group; //第二步
/**
* Device Tree Blob Overlay Root Sub System Structures
*/
static struct configfs_group_operations dtbocfg_root_ops = {
/* empty - we don't allow anything to be created */
};
static struct config_item_type dtbocfg_root_type = {
.ct_group_ops = &dtbocfg_root_ops,
.ct_owner = THIS_MODULE,
};
//第一步
static struct configfs_subsystem dtbocfg_root_subsys = {
.su_group = {
.cg_item = {
.ci_namebuf = "device-tree",
.ci_type = &dtbocfg_root_type,
},
},
.su_mutex = __MUTEX_INITIALIZER(dtbocfg_root_subsys.su_mutex),
};
/**
* dtbocfg_module_init()
*/
static int __init dtbocfg_module_init(void)
{
int retval = 0;
pr_info("%s\n", __func__);
config_group_init(&dtbocfg_root_subsys.su_group);
config_group_init_type_name(&dtbocfg_overlay_group, "overlays", &dtbocfg_overlays_type);
retval = configfs_register_subsystem(&dtbocfg_root_subsys);
if (retval != 0) {
pr_err( "%s: couldn't register subsys\n", __func__);
goto register_subsystem_failed;
}
retval = configfs_register_group(&dtbocfg_root_subsys.su_group, &dtbocfg_overlay_group);
if (retval != 0) {
pr_err( "%s: couldn't register group\n", __func__);
goto register_group_failed;
}
pr_info("%s: OK\n", __func__);
return 0;
register_group_failed:
configfs_unregister_subsystem(&dtbocfg_root_subsys);
register_subsystem_failed:
return retval;
}
/**
* dtbocfg_module_exit()
*/
static void __exit dtbocfg_module_exit(void)
{
configfs_unregister_group(&dtbocfg_overlay_group);
configfs_unregister_subsystem(&dtbocfg_root_subsys);
}
module_init(dtbocfg_module_init);
module_exit(dtbocfg_module_exit);
MODULE_AUTHOR("ikwzm");
MODULE_DESCRIPTION("Device Tree Overlay Configuration File System");
MODULE_LICENSE("Dual BSD/GPL");
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
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
二、接口说明
- 调用来创建和应用一个覆盖的变更集。返回值是一个 错误或一个识别这个覆盖的cookie。
- 调用来删除和清理先前通过调用 而创建的覆盖变更集。不允许删除一个被另一个覆盖的覆盖变化集。
最后,如果你需要一次性删除所有的覆盖,只需调用, 它将以正确的顺序删除每一个覆盖。
::: 目录:of/overlay.c
int of_overlay_fdt_apply(const void *overlay_fdt, u32 overlay_fdt_size, int *ret_ovcs_id, const struct device_node *base)
创建并应用叠加变更集
参数
const void *overlay_fdt
指向叠加设备树(FDT)的指针
u32 overlay_fdt_size
叠加设备树(FDT)的字节数
int *ret_ovcs_id
用于返回创建的变更集 ID 的指针
const struct device_node *base
用于应用叠加的目标节点的指针
描述
创建并应用一个叠加变更集。
of_overlay_apply()
以获取重要的行为信息。
如果出现错误返回,则变更集可能部分应用。特别是如果OF_OVERLAY_POST_APPLY
通知返回错误,则可能发生这种情况。在这种情况下,调用者应使用*ret_ovcs_id
中的值调用of_overlay_remove()
来移除叠加。
返回值
成功返回 0,或者负数错误代码。*ret_ovcs_id
设置为叠加变更集的 ID 值,可以传递给of_overlay_remove()
来移除叠加。 :::
- 设备树插件(dtbo) 里面的节点也要被转换成 device_node, 有的 device_node 也要被转换成platform_device。 、
- 不过在进行转换之前, of_overlay_fdt_apply 函数会先创建一个改变集。 然后根据这个改变集去进行修改。
::: 创建改变集的目的是为了方便对设备树进行修改和复原。 设备树是一种静态的数据结构,一旦被编译和加载到内核中, 就难以直接修改。 为了解决这个问题, 设备树覆盖功能引入了改变集的概念。 :::
三、改变集
- 设备树修改的"改变集"是什么?为什么需要它?
设备树就像一份固定的硬件配置说明书,一旦被系统加载后就很难直接改动。为了方便修改和恢复配置,人们发明了"改变集"这个工具。
改变集就像是设备树的修改记录本。它记录了所有修改操作:比如添加新设备、删除旧配置或调整参数。有了它,我们可以在系统运行时随时调整硬件配置,而不用去改原始配置文件。
它的优势主要有三点:
- 修改更简单:不用直接操作底层数据,只要在修改记录本里写清楚要改什么,就像用便签纸标注修改内容一样直观
- 可保存可复用:修改记录可以保存下来,方便以后重复使用,或者分享给其他设备使用
- 支持回滚:如果修改出问题,可以用"反向记录"把设备树恢复到修改前的状态,就像撤销键一样方便
这样,工程师就能安全灵活地调整硬件配置了。需要改就改,改错了也能随时恢复,让设备管理变得更可靠简单。
/**
* struct overlay_changeset
* @id: changeset identifier
* @ovcs_list: list on which we are located
* @fdt: FDT that was unflattened to create @overlay_tree
* @overlay_tree: expanded device tree that contains the fragment nodes
* @count: count of fragment structures
* @fragments: fragment nodes in the overlay expanded device tree
* @symbols_fragment: last element of @fragments[] is the __symbols__ node
* @cset: changeset to apply fragments to live device tree
*/
struct overlay_changeset {
int id;
struct list_head ovcs_list;
const void *fdt;
struct device_node *overlay_tree;
int count;
struct fragment *fragments;
bool symbols_fragment;
struct of_changeset cset;
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
以下是针对 overlay_changeset
结构说明:
id
- 这个变更集的唯一编号,在
of_overlay_fdt_apply()
函数中生成。 - 主要作用:当你需要通过
of_overlay_remove(id)
删除某个变更时,这个编号能快速定位到对应的变更集并回滚修改。
ovcs_list
- 内核用链表管理多个变更集,每个变更集都通过
list_head
连接在一起。 - 当调用
of_overlay_apply()
应用新变更时,会把新的变更集加入这个链表。
fdt
- 指向原始设备树(未展开的 FDT 数据)。
- 设备树叠加文件(如
.dtbo
文件)的内容会以 FDT 格式加载到这里。 - 这是未展开的原始数据,后续需要解析成树形结构才能使用。
overlay_tree
- 存储展开后的设备树结构(由
fdt
解析而来)。 - 这个结构包含所有需要合并到主设备树的
fragment
节点。
count
和 fragments
count
:记录当前变更集包含多少个fragment
(碎片)。fragments
:指向一个数组,每个元素是一个fragment
结构。每个
- 需要修改的设备树节点路径(比如
/soc/uart@1234
) - 需要添加、修改或删除的属性(比如设置时钟频率或删除某个中断配置)。
- 需要修改的设备树节点路径(比如
symbols_fragment
- 标记最后一个
fragment
是否是符号表节点。 - 符号表用于给设备树节点命名,方便运行时通过名称快速查找节点。
cset
这是一个变更集对象,记录了要应用到实时设备树的具体修改。
变更内容包括:
- 新增/删除节点
- 新增/修改/删除属性
最终通过
of_changeset_apply()
将这些修改合并到正在运行的设备树中。
总结流程:
- 从
.dtbo
文件加载原始 FDT 数据到fdt
。 - 解析
fdt
生成展开的overlay_tree
。 - 提取所有
fragment
存入fragments
数组,记录数量到count
。 - 根据
symbols_fragment
标记符号表位置。 - 将具体修改操作记录到
cset
。 - 应用时,
cset
的变更会被合并到实时设备树,同时整个变更集通过id
和ovcs_list
进行管理。
。