1. DT和FDT
设备树(Device Tree,DT)是一种层次结构的文本数据表示方式,用于描述硬件设备的配置信息。扁平设备树(Flattened Device Tree,FDT),也叫平坦设备树,是设备树的一种二进制表示形式,提高了在嵌入式系统中的传输和解析效率。
DT和FDT在使用方式、传输方式和效率方面存在一些差异,但都是为了帮助操作系统内核正确地初始化和管理硬件设备。下面是DT和FDT的区别:
数据表示方式:
- 设备树(DT):设备树使用的是一种层次结构的数据表示方式,通过树状结构的节点和子节点描述设备之间的关系。每个节点包含设备的属性、寄存器、中断等信息。
- 扁平设备树(FDT):扁平设备树是设备树的一种二进制表示形式,通过将设备树的层次结构转换为线性的、平坦的二进制数据来实现。FDT更易于在嵌入式系统中传输和解析。
传输和解析效率:
- 设备树(DT):设备树作为一种文本格式,相对容易编写和阅读。然而,设备树的传输和解析可能相对较慢,特别是在资源受限的环境中。
- 扁平设备树(FDT):FDT以二进制形式存储设备树,提高了传输和解析的效率。FDT在嵌入式系统中更受欢迎,特别是在引导加载程序和设备驱动程序中。
使用场景:
- 设备树(DT):设备树通常在内核启动过程中使用,用于传递硬件设备的信息给操作系统内核。设备树描述了硬件设备的配置和布局,使内核能够正确地初始化和管理这些设备。
- 扁平设备树(FDT):FDT作为设备树的序列化形式,主要用于传输和解析设备树数据。FDT更方便在引导加载程序和嵌入式系统中的传输和解析操作。
2. FDT的反扁平化(unflatten)
在ARM和ARM64的setup_arch中,会调用unflatten_device_tree来是FDT反扁平化,就是将FDT转换为以struct device_node
为节点的树状结构。其他架构可能会调用unflatten_and_copy_device_tree来实现FDT的反扁平化。
2.1. unflatten_device_tree
initial_boot_params记录从Bootloader传入的fdt的指针,在early_init_dt_verify中赋值。
1/**
2 * unflatten_device_tree - create tree of device_nodes from flat blob
3 *
4 * unflattens the device-tree passed by the firmware, creating the
5 * tree of struct device_node. It also fills the "name" and "type"
6 * pointers of the nodes so the normal device-tree walking functions
7 * can be used.
8 */
9void __init unflatten_device_tree(void)
10{
11 __unflatten_device_tree(initial_boot_params, NULL, &of_root,
12 early_init_dt_alloc_memory_arch, false);
13
14 /* Get pointer to "/chosen" and "/aliases" nodes for use everywhere */
15 of_alias_scan(early_init_dt_alloc_memory_arch);
16
17 unittest_unflatten_overlay_base();
18}
2.2. __unflatten_device_tree
__unflatten_device_tree
先调用一次unflatten_dt_nodes
来获取需要占用的内存区域的大小。
然后为其申请内存,再次调用unflatten_dt_nodes
,进行真正的反扁平化。
unflatten_dt_nodes
针对fdt
中的每个节点调用populate_node
,来创建device_node
并生成树状结构。最后再通过reverse_nodes
来反转子节点。
unflatten_dt_nodes代码在drivers/of/fdt.c
,感兴趣的可以自己分析。
1/**
2 * __unflatten_device_tree - create tree of device_nodes from flat blob
3 * @blob: The blob to expand
4 * @dad: Parent device node
5 * @mynodes: The device_node tree created by the call
6 * @dt_alloc: An allocator that provides a virtual address to memory
7 * for the resulting tree
8 * @detached: if true set OF_DETACHED on @mynodes
9 *
10 * unflattens a device-tree, creating the tree of struct device_node. It also
11 * fills the "name" and "type" pointers of the nodes so the normal device-tree
12 * walking functions can be used.
13 *
14 * Return: NULL on failure or the memory chunk containing the unflattened
15 * device tree on success.
16 */
17void *__unflatten_device_tree(const void *blob,
18 struct device_node *dad,
19 struct device_node **mynodes,
20 void *(*dt_alloc)(u64 size, u64 align),
21 bool detached)
22{
23 /// ... ...
24
25 /* First pass, scan for size */
26 size = unflatten_dt_nodes(blob, NULL, dad, NULL);
27 if (size <= 0)
28 return NULL;
29
30 size = ALIGN(size, 4);
31 pr_debug(" size is %d, allocating...\n", size);
32
33 /* Allocate memory for the expanded device tree */
34 mem = dt_alloc(size + 4, __alignof__(struct device_node));
35 if (!mem)
36 return NULL;
37
38 memset(mem, 0, size);
39
40 *(__be32 *)(mem + size) = cpu_to_be32(0xdeadbeef);
41
42 pr_debug(" unflattening %p...\n", mem);
43
44 /* Second pass, do actual unflattening */
45 ret = unflatten_dt_nodes(blob, mem, dad, mynodes);
46
47 /// ... ...
48}
3. device_node的层次关系
经过反扁平化后,device_node间的关系如下图:
of_root是根节点,其child指向子节点,形成一个单链表。子节点的parent指向父节点。兄弟节点使用slibling关联。
每个device_node都会有struct property
数组,proterty间形成单链表,每个proterty都包含属性名、长度、值。
每个device_node内嵌的fwnode的ops指向全局变量of_fwnode_ops。
of_fwnode_ops中的函数指针会通过如下宏来调用,这些宏被调用的地方主要在drivers/base/property.c
和drivers/base/core.c
,具体函数可以通过git grep -In -p fwnode_call_ drivers/
查看。
1/// include/linux/fwnode.h
2#define fwnode_has_op(fwnode, op) \
3 (!IS_ERR_OR_NULL(fwnode) && (fwnode)->ops && (fwnode)->ops->op)
4
5#define fwnode_call_int_op(fwnode, op, ...) \
6 (fwnode_has_op(fwnode, op) ? \
7 (fwnode)->ops->op(fwnode, ## __VA_ARGS__) : (IS_ERR_OR_NULL(fwnode) ? -EINVAL : -ENXIO))
8
9#define fwnode_call_bool_op(fwnode, op, ...) \
10 (fwnode_has_op(fwnode, op) ? \
11 (fwnode)->ops->op(fwnode, ## __VA_ARGS__) : false)
12
13#define fwnode_call_ptr_op(fwnode, op, ...) \
14 (fwnode_has_op(fwnode, op) ? \
15 (fwnode)->ops->op(fwnode, ## __VA_ARGS__) : NULL)
16#define fwnode_call_void_op(fwnode, op, ...) \
17 do { \
18 if (fwnode_has_op(fwnode, op)) \
19 (fwnode)->ops->op(fwnode, ## __VA_ARGS__); \
20 } while (false)