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的反扁平化。

fdt_func

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间的关系如下图:

dev_fw_node_struct

of_root是根节点,其child指向子节点,形成一个单链表。子节点的parent指向父节点。兄弟节点使用slibling关联。

每个device_node都会有struct property数组,proterty间形成单链表,每个proterty都包含属性名、长度、值。

每个device_node内嵌的fwnode的ops指向全局变量of_fwnode_ops。 of_fwnode_ops中的函数指针会通过如下宏来调用,这些宏被调用的地方主要在drivers/base/property.cdrivers/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)