[TOC]

OF

在 Linux 内核中,of 通常是 Open Firmware 的缩写。Open Firmware 是一种硬件设备描述标准,用于描述设备树(Device Tree)。设备树是一种数据结构,用于以树状层次结构描述硬件的布局和配置,特别是在嵌入式系统和 ARM 架构中广泛使用。

  • of 表示与设备树(Device Tree)相关的驱动代码。
  • fdt.c 表示与 Flat Device Tree(FDT,扁平设备树)相关的实现。

设备树的作用

设备树的主要作用是将硬件信息从内核代码中分离出来,使得内核可以通过解析设备树来获取硬件配置,而无需硬编码。这种方式提高了内核的可移植性和灵活性。

在 Linux 内核中,fwnodenode 是两种不同的结构体,分别用于描述硬件设备的固件信息和设备树节点信息。它们的主要区别在于用途和适用场景。


fwnode和node的区别

1. fwnode_handle

fwnode_handle 是一个通用的固件节点(firmware node)抽象,用于描述硬件设备的固件信息。它可以适配多种固件接口,例如设备树(Device Tree)、ACPI(Advanced Configuration and Power Interface)等。

特点

  • 通用性:
    fwnode_handle 是一个抽象层,能够统一表示不同固件接口(如设备树和 ACPI)的节点信息。
  • 适配性:
    内核通过 fwnode_handle 提供了一组通用的操作接口,屏蔽了底层固件实现的差异。
  • 灵活性:
    适用于需要支持多种固件接口的驱动程序。

常见字段

fwnode_handle 是一个结构体,通常包含以下字段:

  • ops: 指向操作函数的指针,用于实现具体固件接口的操作。
  • secondary: 指向辅助固件节点的指针。
  • data: 用于存储与固件节点相关的私有数据。

适用场景

  • ACPI 和设备树的统一处理。
  • 驱动程序中需要与固件节点交互时使用,例如中断映射、GPIO 配置等。

2. device_node

device_node 是设备树(Device Tree)中用于描述硬件设备的节点结构。设备树是一种硬件描述语言,广泛用于嵌入式系统中。

特点

  • 专用性:
    device_node 专门用于设备树,不能直接适配其他固件接口(如 ACPI)。
  • 层次性:
    设备树节点具有父子关系,device_node 通过指针字段表示这种层次结构。
  • 静态性:
    设备树通常在系统启动时加载,节点信息在运行时不会动态变化。

常见字段

device_node 是一个结构体,通常包含以下字段:

  • name: 节点名称。
  • type: 节点类型。
  • parent: 指向父节点的指针。
  • childsibling: 用于表示子节点和兄弟节点的指针。
  • properties: 节点的属性列表。

适用场景

  • 嵌入式系统中基于设备树的硬件描述。
  • 驱动程序中解析设备树节点以获取硬件配置信息。

3. 区别总结

特性 fwnode_handle device_node
用途 通用固件节点抽象 专用于设备树节点
适用固件接口 支持设备树、ACPI 等多种固件接口 仅支持设备树
灵活性 高,适配多种固件实现 低,仅适用于设备树
层次结构 无直接层次关系 支持父子关系和兄弟关系
使用场景 驱动程序中统一处理固件节点 嵌入式系统中解析设备树

4. 关联关系

  • fwnode_handle 可以通过适配器与 device_node 关联。例如,of_node_to_fwnode() 函数可以将 device_node 转换为 fwnode_handle
  • 在设备树场景中,fwnode_handle 的实现通常基于 device_node

5. 示例代码

device_node 转换为 fwnode_handle

1
2
struct device_node *node = ...; // 获取设备树节点
struct fwnode_handle *fwnode = of_node_to_fwnode(node);

fwnode_handle 转换为 device_node

1
2
struct fwnode_handle *fwnode = ...; // 获取固件节点
struct device_node *node = to_of_node(fwnode);

6. 总结

  • fwnode_handle 是一个通用的固件节点抽象,适用于需要支持多种固件接口的场景。
  • device_node 是设备树的专用节点结构,适用于嵌入式系统中基于设备树的硬件描述。
  • 在设备树场景中,fwnode_handledevice_node 可以通过适配器互相转换。

include/linux/of.h

_OF_DECLARE 设备树兼容性表的宏定义

1
2
3
4
5
6
7
8
9
10
11
#if defined(CONFIG_OF) && !defined(MODULE)
#define _OF_DECLARE(table, name, compat, fn, fn_type) \
static const struct of_device_id __of_table_##name \
__used __section("__" #table "_of_table") \
__aligned(__alignof__(struct of_device_id)) \
= { .compatible = compat, \
.data = (fn == (fn_type)NULL) ? fn : fn }
#else
#define _OF_DECLARE(table, name, compat, fn, fn_type) \
_OF_DECLARE_STUB(table, name, compat, fn, fn_type)
#endif

of_node_init 初始化设备节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* of_node_init - 初始化 DeviceTree 节点
* @node:指向 kzalloc() 创建的设备节点的指针
*
* 返回时,device_node refcount 设置为 1。
* 完成后,对 @node 使用 of_node_put() 以释放为其分配的内存。
* 如果节点不是动态节点,则不会释放内存。是否释放内存的决定将由 node->release() 完成,即 of_node_release()。
*/
static inline void of_node_init(struct device_node *node)
{
#if defined(CONFIG_OF_KOBJ)
kobject_init(&node->kobj, &of_node_ktype);
#endif
fwnode_init(&node->fwnode, &of_fwnode_ops);
}

for_each_matching_node_and_match 遍历匹配的节点和匹配项

  • 注意按照FDT的顺序遍历,每个节点都会调用一次匹配函数,matches可以有多个匹配项
1
2
3
#define for_each_matching_node_and_match(dn, matches, match) \
for (dn = of_find_matching_node_and_match(NULL, matches, match); \
dn; dn = of_find_matching_node_and_match(dn, matches, match))

for_each_of_allnodes_from 遍历所有节点

1
2
#define for_each_of_allnodes_from(from, dn) \
for (dn = __of_find_all_nodes(from); dn; dn = __of_find_all_nodes(dn))

of_for_each_phandle 遍历 phandle 句柄

1
2
3
4
5
#define of_for_each_phandle(it, err, np, ln, cn, cc)			\
for (of_phandle_iterator_init((it), (np), (ln), (cn), (cc)), \
err = of_phandle_iterator_next(it); \
err == 0; \
err = of_phandle_iterator_next(it))

DEFINE_FREE(device_node)

1
DEFINE_FREE(device_node, struct device_node *, if (_T) of_node_put(_T))

of_read_number 读取大数字

1
2
3
4
5
6
7
8
/* 读取大数的 Helper;大小以单元格为单位(而不是字节)*/
static inline u64 of_read_number(const __be32 *cell, int size)
{
u64 r = 0;
for (; size--; cell++)
r = (r << 32) | be32_to_cpu(*cell);
return r;
}

include/linux/of_reserved_mem.h

RESERVEDMEM_OF_DECLARE 预留内存区域的设备树兼容性表的宏定义

1
2
3
4
5
#ifdef CONFIG_OF_RESERVED_MEM

#define RESERVEDMEM_OF_DECLARE(name, compat, init) \
_OF_DECLARE(reservedmem, name, compat, init, reservedmem_of_init_fn)
#endif

drivers/of/address.c

of_match_bus 匹配设备树总线

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
/*
* 总线特定转换器阵列
*/

static const struct of_bus of_busses[] = {
#ifdef CONFIG_PCI
/* PCI */
{
.name = "pci",
.addresses = "assigned-addresses",
.match = of_bus_pci_match,
.count_cells = of_bus_pci_count_cells,
.map = of_bus_pci_map,
.translate = of_bus_default_flags_translate,
.flag_cells = 1,
.get_flags = of_bus_pci_get_flags,
},
#endif /* CONFIG_PCI */
/* ISA */
{
.name = "isa",
.addresses = "reg",
.match = of_bus_isa_match,
.count_cells = of_bus_isa_count_cells,
.map = of_bus_isa_map,
.translate = of_bus_default_flags_translate,
.flag_cells = 1,
.get_flags = of_bus_isa_get_flags,
},
/* Default with flags cell */
{
.name = "default-flags",
.addresses = "reg",
.match = of_bus_default_flags_match,
.count_cells = of_bus_default_count_cells,
.map = of_bus_default_flags_map,
.translate = of_bus_default_flags_translate,
.flag_cells = 1,
.get_flags = of_bus_default_flags_get_flags,
},
/* Default */
{
.name = "default",
.addresses = "reg",
.match = of_bus_default_match,
.count_cells = of_bus_default_count_cells,
.map = of_bus_default_map,
.translate = of_bus_default_translate,
.get_flags = of_bus_default_get_flags,
},
};

static unsigned int of_bus_default_get_flags(const __be32 *addr)
{
return IORESOURCE_MEM;
}

/*
* Default translator (generic bus)
*/

static void of_bus_default_count_cells(struct device_node *dev,
int *addrc, int *sizec)
{
if (addrc)
*addrc = of_n_addr_cells(dev);
if (sizec)
*sizec = of_n_size_cells(dev);
}

static int of_bus_default_match(struct device_node *np)
{
/*
* Check for presence first since of_bus_n_addr_cells() will warn when
* walking parent nodes.
*/
return of_property_present(np, "#address-cells");
}

static const struct of_bus *of_match_bus(struct device_node *np)
{
int i;

for (i = 0; i < ARRAY_SIZE(of_busses); i++)
if (!of_busses[i].match || of_busses[i].match(np))
return &of_busses[i];
return NULL;
}

__of_get_address 获取设备树地址

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
const __be32 *__of_get_address(struct device_node *dev, int index, int bar_no,
u64 *size, unsigned int *flags)
{
const __be32 *prop;
unsigned int psize;
struct device_node *parent __free(device_node) = of_get_parent(dev);
const struct of_bus *bus;
int onesize, i, na, ns;

if (parent == NULL)
return NULL;

/* 匹配父级的总线类型 */
bus = of_match_bus(parent);
if (!bus || (strcmp(bus->name, "pci") && (bar_no >= 0)))
return NULL;

/* 获取 “reg” 或 “assigned-addresses” 属性*/
prop = of_get_property(dev, bus->addresses, &psize);
if (prop == NULL)
return NULL;
psize /= 4;

bus->count_cells(dev, &na, &ns);
if (!OF_CHECK_ADDR_COUNT(na))
return NULL;

onesize = na + ns;
for (i = 0; psize >= onesize; psize -= onesize, prop += onesize, i++) {
u32 val = be32_to_cpu(prop[0]);
/* PCI 总线匹配在 BAR 编号上,而不是 inde 上x */
if (((bar_no >= 0) && ((val & 0xff) == ((bar_no * 4) + PCI_BASE_ADDRESS_0))) ||
((index >= 0) && (i == index))) {
if (size)
*size = of_read_number(prop + na, ns);
if (flags)
*flags = bus->get_flags(prop);
return prop;
}
}
return NULL;
}
EXPORT_SYMBOL(__of_get_address);

of_translate_one 将设备树地址转换为 CPU 物理地址

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
static int of_translate_one(const struct device_node *parent, const struct of_bus *bus,
const struct of_bus *pbus, __be32 *addr,
int na, int ns, int pna, const char *rprop)
{
const __be32 *ranges;
unsigned int rlen;
int rone;
u64 offset = OF_BAD_ADDR;

/*
* 通常,缺少 “ranges” 属性意味着我们正在跨越一个不可转换的边界,因此低于 current 的地址不能转换为 CPU 物理地址。
* 不幸的是,虽然这在规范中非常清楚,但这不是 Apple 所理解的,他们确实有像 /uni-n 或 /ht 节点这样的东西,没有“范围”属性,
* 并且它们下面有很多完美可用的映射设备。因此,我们将缺少 “ranges” 视为等同于空的 “ranges” 属性,这意味着在该级别进行 1:1 转换。
* 调用方可以不尝试转换本来就不应该转换的地址。--本 H.
*
* 据我们所知,此损坏仅存在于 Apple 机器上,因此此代码仅在 powerpc 上启用。--协鑫
*
* 此怪癖也适用于 'dma-ranges',它经常存在于子节点中,而父节点中没有 'dma-ranges'。--罗布
*/
ranges = of_get_property(parent, rprop, &rlen);
if (ranges == NULL && !of_empty_ranges_quirk(parent) &&
strcmp(rprop, "dma-ranges")) {
pr_debug("no ranges; cannot translate\n");
return 1;
}
if (ranges == NULL || rlen == 0) {
offset = of_read_number(addr, na);
/* set address to zero, pass flags through */
memset(addr + pbus->flag_cells, 0, (pna - pbus->flag_cells) * 4);
pr_debug("empty ranges; 1:1 translation\n");
goto finish;
}

pr_debug("walking ranges...\n");

/* Now walk through the ranges */
rlen /= 4;
rone = na + pna + ns;
for (; rlen >= rone; rlen -= rone, ranges += rone) {
offset = bus->map(addr, ranges, na, ns, pna, bus->flag_cells);
if (offset != OF_BAD_ADDR)
break;
}
if (offset == OF_BAD_ADDR) {
pr_debug("not found !\n");
return 1;
}
memcpy(addr, ranges + na, 4 * pna);

finish:
of_dump_addr("parent translation for:", addr, pna);
pr_debug("with offset: %llx\n", offset);

/* Translate it into parent bus space */
return pbus->translate(addr, offset, pna);
}

of_translate_address 将设备树地址转换为 CPU 物理地址

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
/*
* 将 device-tree 中的地址转换为 CPU 物理地址,这将沿着树向上移动,并在途中应用各种总线映射。
*
* 注意: 我们认为用 #size-cells == 0 跨越任何级别都意味着不可能进行转换(也就是说,我们没有处理可以映射到 cpu 物理地址的值)。
* 这并没有真正以这种方式指定,但这是传统上至少 IBM 做事的方式
*
* 每当转换失败时,*host 指针将被设置为已注册逻辑 PIO 映射的设备,并且返回代码是相对于该节点的。
*/
static u64 __of_translate_address(struct device_node *node,
struct device_node *(*get_parent)(const struct device_node *),
const __be32 *in_addr, const char *rprop,
struct device_node **host)
{
struct device_node *dev __free(device_node) = of_node_get(node);
struct device_node *parent __free(device_node) = get_parent(dev);
const struct of_bus *bus, *pbus;
__be32 addr[OF_MAX_ADDR_CELLS];
int na, ns, pna, pns;

pr_debug("** translation for device %pOF **\n", dev);

*host = NULL;

if (parent == NULL)
return OF_BAD_ADDR;
bus = of_match_bus(parent);
if (!bus)
return OF_BAD_ADDR;

/* 计算地址单元格并在本地复制地址*/
bus->count_cells(dev, &na, &ns);
if (!OF_CHECK_COUNTS(na, ns)) {
pr_debug("Bad cell count for %pOF\n", dev);
return OF_BAD_ADDR;
}
memcpy(addr, in_addr, na * 4);

pr_debug("bus is %s (na=%d, ns=%d) on %pOF\n",
bus->name, na, ns, parent);
of_dump_addr("translating address:", addr, na);

/* Translate */
for (;;) {
struct logic_pio_hwaddr *iorange;

/* 切换到父总线 */
of_node_put(dev);
dev = parent;
parent = get_parent(dev);

/* If root, we have finished */
if (parent == NULL) {
pr_debug("reached root node\n");
return of_read_number(addr, na);
}

/*
* 对于没有 ranges 属性的 indirect IO 设备,直接从 reg 获取地址。
*/
iorange = find_io_range_by_fwnode(&dev->fwnode);
if (iorange && (iorange->flags != LOGIC_PIO_CPU_MMIO)) {
u64 result = of_read_number(addr + 1, na - 1);
pr_debug("indirectIO matched(%pOF) 0x%llx\n",
dev, result);
*host = no_free_ptr(dev);
return result;
}

/* Get new parent bus and counts */
pbus = of_match_bus(parent);
if (!pbus)
return OF_BAD_ADDR;
pbus->count_cells(dev, &pna, &pns);
if (!OF_CHECK_COUNTS(pna, pns)) {
pr_err("Bad cell count for %pOF\n", dev);
return OF_BAD_ADDR;
}

pr_debug("parent bus is %s (na=%d, ns=%d) on %pOF\n",
pbus->name, pna, pns, parent);

/* Apply bus translation */
if (of_translate_one(dev, bus, pbus, addr, na, ns, pna, rprop))
return OF_BAD_ADDR;

/* Complete the move up one level */
na = pna;
ns = pns;
bus = pbus;

of_dump_addr("one level translation:", addr, na);
}

unreachable();
}

u64 of_translate_address(struct device_node *dev, const __be32 *in_addr)
{
struct device_node *host;
u64 ret;

ret = __of_translate_address(dev, of_get_parent,
in_addr, "ranges", &host);
if (host) {
of_node_put(host);
return OF_BAD_ADDR;
}

return ret;
}
EXPORT_SYMBOL(of_translate_address);

__of_address_resource_bounds 计算地址资源边界

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
VISIBLE_IF_KUNIT int __of_address_resource_bounds(struct resource *r, u64 start, u64 size)
{
if (overflows_type(start, r->start))
return -EOVERFLOW;

r->start = start;

if (!size)
r->end = wrapping_sub(typeof(r->end), r->start, 1);
else if (size && check_add_overflow(r->start, size - 1地址资源边界 &r->end))
return -EOVERFLOW;

return 0;
}
EXPORT_SYMBOL_IF_KUNIT(__of_address_resource_bounds);

of_address_to_resource 将设备树地址转换为资源

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
static int __of_address_to_resource(struct device_node *dev, int index, int bar_no,
struct resource *r)
{
u64 taddr;
const __be32 *addrp;
u64 size;
unsigned int flags;
const char *name = NULL;

addrp = __of_get_address(dev, index, bar_no, &size, &flags);
if (addrp == NULL)
return -EINVAL;

/*获取可选的 “reg-names” 属性以向资源添加名称 */
if (index >= 0)
of_property_read_string_index(dev, "reg-names", index, &name);

if (flags & IORESOURCE_MEM)
taddr = of_translate_address(dev, addrp);
else if (flags & IORESOURCE_IO)
taddr = of_translate_ioport(dev, addrp, size);
else
return -EINVAL;

if (taddr == OF_BAD_ADDR)
return -EINVAL;
memset(r, 0, sizeof(struct resource));

if (of_mmio_is_nonposted(dev))
flags |= IORESOURCE_MEM_NONPOSTED;

r->flags = flags;
r->name = name ? name : dev->full_name;

return __of_address_resource_bounds(r, taddr, size);
}

/**
* of_address_to_resource - 转换设备树地址并作为资源返回
* @dev:调用方的设备节点
* @index:索引到数组中
* @r:指向资源数组的指针
*
* 如果范围无法转换为资源,则返回 -EINVAL。
*
* 请注意,如果您的地址是 PIO 地址,则如果物理地址无法使用 pci_address_to_pio() 在内部转换为 IO 令牌,
* 则转换将失败,这是因为它调用得太早,或者无法与任何主机桥 IO 空间匹配
*/
int of_address_to_resource(struct device_node *dev, int index,
struct resource *r)
{
return __of_address_to_resource(dev, index, -1, r);
}
EXPORT_SYMBOL_GPL(of_address_to_resource);

of_iomap 映射给定device_node的内存映射 IO

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* of_iomap - 映射给定device_node的内存映射 IO
* @np:将映射其 IO 范围的设备
* @index:IO 范围的索引
*
* 返回指向映射内存的指针
*/
void __iomem *of_iomap(struct device_node *np, int index)
{
struct resource res;

if (of_address_to_resource(np, index, &res))
return NULL;

if (res.flags & IORESOURCE_MEM_NONPOSTED)
return ioremap_np(res.start, resource_size(&res));
else
return ioremap(res.start, resource_size(&res));/* 直接返回start地址 */
}
EXPORT_SYMBOL(of_iomap);

drivers/of/base.c

__of_find_node_by_path 根据路径查找节点

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
static struct device_node *__of_get_next_child(const struct device_node *node,
struct device_node *prev)
{
struct device_node *next;

if (!node)
return NULL;

next = prev ? prev->sibling : node->child;
of_node_get(next);
of_node_put(prev);
return next;
}

#define __for_each_child_of_node(parent, child) \
for (child = __of_get_next_child(parent, NULL); child != NULL; \
child = __of_get_next_child(parent, child))

struct device_node *__of_find_node_by_path(const struct device_node *parent,
const char *path)
{
struct device_node *child;
int len;

len = strcspn(path, "/:");
if (!len)
return NULL;

__for_each_child_of_node(parent, child) {
const char *name = kbasename(child->full_name);
if (strncmp(path, name, len) == 0 && (strlen(name) == len))
return child;
}
return NULL;
}

__of_find_node_by_full_path 根据完整路径返回节点

  • 对于节点来说,节点和其父路径节点都会of_node_put
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct device_node *__of_find_node_by_full_path(struct device_node *node,
const char *path)
{
const char *separator = strchr(path, ':');

while (node && *path == '/') {
struct device_node *tmp = node;

path++; /* Increment past '/' delimiter */
node = __of_find_node_by_path(node, path); //这里get了node
of_node_put(tmp); //这里put了上一个node
path = strchrnul(path, '/');
if (separator && separator < path)
break;
}
return node;
}

of_find_node_opts_by_path 查找与完整 OF 路径匹配的节点

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
/**
* of_find_node_opts_by_path - 查找与完整 OF 路径匹配的节点
* @path:要匹配的完整路径,或者如果路径不以 '/' 开头,则为 /aliases 节点的属性名称(别名)。 如果是别名,则将返回与 alias 值匹配的节点。
* @opts:一个指针的地址,用于存储选项字符串的开头,该字符串附加到路径末尾,并带有 ':' 分隔符。
*
* 有效路径:
* * /foo/bar 完整路径
* * foo 有效别名
* * foo/bar 有效的别名相对路径
*
* 返回:一个 refcount 递增的节点指针,完成后对它使用 of_node_put()。
*/
struct device_node *of_find_node_opts_by_path(const char *path, const char **opts)
{
struct device_node *np = NULL;
const struct property *pp;
unsigned long flags;
const char *separator = strchr(path, ':');

if (opts)
*opts = separator ? separator + 1 : NULL;

if (strcmp(path, "/") == 0)
return of_node_get(of_root);

/* The path could begin with an alias */
if (*path != '/') {
int len;
const char *p = strchrnul(path, '/');

if (separator && separator < p)
p = separator;
len = p - path;

/* of_aliases must not be NULL */
if (!of_aliases)
return NULL;

for_each_property_of_node(of_aliases, pp) {
if (strlen(pp->name) == len && !strncmp(pp->name, path, len)) {
np = of_find_node_by_path(pp->value);
break;
}
}
if (!np)
return NULL;
path = p;
}

/* Step down the tree matching path components */
raw_spin_lock_irqsave(&devtree_lock, flags);
if (!np)
np = of_node_get(of_root);
np = __of_find_node_by_full_path(np, path);
raw_spin_unlock_irqrestore(&devtree_lock, flags);
return np;
}
EXPORT_SYMBOL(of_find_node_opts_by_path);

of_find_matching_node_and_match 查找与设备树匹配的节点

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
/**
* of_find_matching_node_and_match - 根据 of_device_id 匹配表查找节点。
.* from:
指向起始节点的指针。如果为 NULL,表示从设备树的根节点开始搜索。
该节点本身不会被搜索,搜索从它的下一个节点开始。
函数会在结束时调用 of_node_put() 释放该节点的引用。
* matches:
一个 of_device_id 数组,包含匹配规则。每个条目定义了一个匹配条件,用于与设备树节点的属性进行比较。
* match:
一个指向 of_device_id 指针的指针,用于返回匹配的 of_device_id 条目。如果不需要此信息,可以传递 NULL。
* Return: 一个 refcount 递增的节点指针,完成后对它使用 of_node_put()。
*/
struct device_node *of_find_matching_node_and_match(struct device_node *from,
const struct of_device_id *matches,
const struct of_device_id **match)
{
struct device_node *np;
const struct of_device_id *m;
unsigned long flags;

if (match)
*match = NULL;

raw_spin_lock_irqsave(&devtree_lock, flags);
//从指定的起始节点开始遍历设备树的所有节点
for_each_of_allnodes_from(from, np) {
m = __of_match_node(matches, np);
if (m && of_node_get(np)) {
if (match)
*match = m;
break;
}
}
of_node_put(from);
raw_spin_unlock_irqrestore(&devtree_lock, flags);
return np;
}
EXPORT_SYMBOL(of_find_matching_node_and_match);

__of_find_all_nodes 遍历所有节点

  • 该函数用于遍历设备树中的所有节点,并返回下一个节点的指针。
  1. 如果 prev 为 NULL,则返回根节点。
  2. 如果 prev 有子节点,则返回第一个子节点。
  3. 如果 prev 没有子节点,则向上遍历父节点,直到找到一个有兄弟节点的节点,并返回该兄弟节点。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct device_node *__of_find_all_nodes(struct device_node *prev)
{
struct device_node *np;
if (!prev) {
np = of_root;
} else if (prev->child) {
np = prev->child;
} else {
/* Walk back up looking for a sibling, or the end of the structure */
np = prev;
while (np->parent && !np->sibling)
np = np->parent;
np = np->sibling; /* Might be null at the end of the tree */
}
return np;
}

of_device_is_compatible 检查节点是否与给定的约束匹配

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
/**
* __of_device_is_compatible() - 检查节点是否与给定的约束匹配
* @device: 指向节点的指针
* @compat: 任何匹配项都需要兼容的字符串 NULL 或 “”
* @type: 任何匹配项所需的 device_type 值、NULL 或 “”
* @name: 任何匹配项都需要的节点名称、NULL 或 “”
*
* 检查给定的 @compat、@type 和 @name 字符串是否与给定@device的属性匹配。可以通过传递 NULL 或空字符串作为约束来跳过 A 约束。
*
* 对于不匹配,则返回 0,匹配时返回一个正整数。返回值是一个相对分数,值越大表示匹配越好。该分数针对最具体的兼容值进行加权,以获得最高分数。Matching type (匹配类型) 是 next (匹配类型),后跟 matching name (匹配名称)。实际上,这会导致 matches 的优先级顺序如下:
*
* 1. specific compatible && type && name
* 2. specific compatible && type
* 3. specific compatible && name
* 4. specific compatible
* 5. general compatible && type && name
* 6. general compatible && type
* 7. general compatible && name
* 8. general compatible
* 9. type && name
* 10. type
* 11. name
*/
static int __of_device_is_compatible(const struct device_node *device,
const char *compat, const char *type, const char *name)
{
const struct property *prop;
const char *cp;
int index = 0, score = 0;

/* 兼容匹配具有最高优先级 */
if (compat && compat[0]) {
prop = __of_find_property(device, "compatible", NULL);
for (cp = of_prop_next_string(prop, NULL); cp;
cp = of_prop_next_string(prop, cp), index++) {
if (of_compat_cmp(cp, compat, strlen(compat)) == 0) {
/* 如果找到匹配项,分数基于匹配的具体性和属性中的索引位置计算(索引越小,分数越高)。
分数的计算公式为 INT_MAX/2 - (index << 2),确保更具体的匹配获得更高的分数 */
score = INT_MAX/2 - (index << 2);
break;
}
}
if (!score)
return 0;
}

/* 匹配类型优于匹配名称
如果指定了 type,函数调用 __of_node_is_type 检查节点的类型是否匹配*/
if (type && type[0]) {
if (!__of_node_is_type(device, type))
return 0;
/* 如果匹配,分数增加 2;如果不匹配,函数返回 0 */
score += 2;
}

/* 如果匹配,分数增加 1;如果不匹配,函数返回 0*/
if (name && name[0]) {
if (!of_node_name_eq(device, name))
return 0;
score++;
}
/* 如果所有指定的约束条件都匹配,函数返回计算出的分数。分数越高,表示匹配程度越高 */
return score;
}

/** 检查给定的 “compat” 字符串是否与设备的 “compatible” 属性中的字符串之一匹配
*/
int of_device_is_compatible(const struct device_node *device,
const char *compat)
{
unsigned long flags;
int res;

raw_spin_lock_irqsave(&devtree_lock, flags);
res = __of_device_is_compatible(device, compat, NULL, NULL);
raw_spin_unlock_irqrestore(&devtree_lock, flags);
return res;
}
EXPORT_SYMBOL(of_device_is_compatible);

__of_match_node 查找与设备树匹配的节点

  • 该函数用于查找与设备树匹配的节点,并返回最佳匹配的节点。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
static
const struct of_device_id *__of_match_node(const struct of_device_id *matches,
const struct device_node *node)
{
const struct of_device_id *best_match = NULL;
int score, best_score = 0;

if (!matches)
return NULL;

for (; matches->name[0] || matches->type[0] || matches->compatible[0]; matches++) {
score = __of_device_is_compatible(node, matches->compatible,
matches->type, matches->name);
if (score > best_score) {
best_match = matches;
best_score = score;
}
}

return best_match;
}

__of_find_property 查找设备树属性

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
#define of_prop_cmp(s1, s2)		strcmp((s1), (s2))
static struct property *__of_find_property(const struct device_node *np,
const char *name, int *lenp)
{
struct property *pp;

if (!np)
return NULL;

for (pp = np->properties; pp; pp = pp->next) {
if (of_prop_cmp(pp->name, name) == 0) {
if (lenp)
*lenp = pp->length;
break;
}
}

return pp;
}

struct property *of_find_property(const struct device_node *np,
const char *name,
int *lenp)
{
struct property *pp;
unsigned long flags;

raw_spin_lock_irqsave(&devtree_lock, flags);
pp = __of_find_property(np, name, lenp);
raw_spin_unlock_irqrestore(&devtree_lock, flags);

return pp;
}
EXPORT_SYMBOL(of_find_property);

of_get_property 查找设备树属性

1
2
3
4
5
6
7
8
9
10
11
/*
* 查找具有给定节点的给定名称的属性并返回该值。
*/
const void *of_get_property(const struct device_node *np, const char *name,
int *lenp)
{
const struct property *pp = of_find_property(np, name, lenp);

return pp ? pp->value : NULL;
}
EXPORT_SYMBOL(of_get_property);

of_phandle_iterator_args 获取 phandle 迭代器参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int of_phandle_iterator_args(struct of_phandle_iterator *it,
uint32_t *args,
int size)
{
int i, count;

count = it->cur_count;

if (WARN_ON(size < count))
count = size;

for (i = 0; i < count; i++)
args[i] = be32_to_cpup(it->cur++);

return count;
}

of_phandle_iterator_init 初始化 phandle 迭代器

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
int of_phandle_iterator_init(struct of_phandle_iterator *it,
const struct device_node *np,
const char *list_name,
const char *cells_name,
int cell_count)
{
const __be32 *list;
int size;

memset(it, 0, sizeof(*it));

/*
* one of cell_count or cells_name must be provided to determine the
* argument length.
*/
if (cell_count < 0 && !cells_name)
return -EINVAL;

list = of_get_property(np, list_name, &size);
if (!list)
return -ENOENT;

it->cells_name = cells_name;
it->cell_count = cell_count;
it->parent = np;
it->list_end = list + size / sizeof(*list);
it->phandle_end = list;
it->cur = list;

return 0;
}
EXPORT_SYMBOL_GPL(of_phandle_iterator_init);

of_phandle_iterator_next 迭代器的下一个 phandle

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
int of_phandle_iterator_next(struct of_phandle_iterator *it)
{
uint32_t count = 0;

if (it->node) {
of_node_put(it->node);
it->node = NULL;
}

if (!it->cur || it->phandle_end >= it->list_end)
return -ENOENT;

it->cur = it->phandle_end;

/* 如果 phandle 为 0,则它是一个没有参数的空条目. */
it->phandle = be32_to_cpup(it->cur++);

if (it->phandle) {

/*
* 找到 provider 节点并解析 #*-cells 属性以确定参数长度。
*/
it->node = of_find_node_by_phandle(it->phandle);

if (it->cells_name) {
if (!it->node) {
pr_err("%pOF: could not find phandle %d\n",
it->parent, it->phandle);
goto err;
}

if (of_property_read_u32(it->node, it->cells_name,
&count)) {
/*
* 如果同时给出了 cell_count 和 cells_name,则在没有 cells_name 属性的情况下回退到 cell_count
*/
if (it->cell_count >= 0) {
count = it->cell_count;
} else {
pr_err("%pOF: could not get %s for %pOF\n",
it->parent,
it->cells_name,
it->node);
goto err;
}
}
} else {
count = it->cell_count;
}

/*
* 确保参数实际上适合剩余的属性数据长度
*/
if (it->cur + count > it->list_end) {
if (it->cells_name)
pr_err("%pOF: %s = %d found %td\n",
it->parent, it->cells_name,
count, it->list_end - it->cur);
else
pr_err("%pOF: phandle %s needs %d, found %td\n",
it->parent, of_node_full_name(it->node),
count, it->list_end - it->cur);
goto err;
}
}

it->phandle_end = it->cur + count;
it->cur_count = count;

return 0;

err:
if (it->node) {
of_node_put(it->node);
it->node = NULL;
}

return -EINVAL;
}
EXPORT_SYMBOL_GPL(of_phandle_iterator_next);

__of_parse_phandle_with_args 解析设备树句柄

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
int __of_parse_phandle_with_args(const struct device_node *np,
const char *list_name,
const char *cells_name,
int cell_count, int index,
struct of_phandle_args *out_args)
{
struct of_phandle_iterator it;
int rc, cur_index = 0;

if (index < 0)
return -EINVAL;

/* 遍历 phandles,直到找到所有请求的条目 */
of_for_each_phandle(&it, rc, np, list_name, cells_name, cell_count) {
/*
*所有错误情况都退出了循环,因此此时解析成功。如果请求的索引匹配,则填充 out_args 结构并返回,或为空条目返回 -ENOENT。
*/
rc = -ENOENT;
if (cur_index == index) {
if (!it.phandle)
goto err;

if (out_args) {
int c;

c = of_phandle_iterator_args(&it,
out_args->args,
MAX_PHANDLE_ARGS);
out_args->np = it.node;
out_args->args_count = c;
} else {
of_node_put(it.node);
}

/* 找到了!返回成功 */
return 0;
}

cur_index++;
}

/*
* 返回 result 前解锁 node;将是以下之一:
* -ENOENT : 索引用于空 phandle
* -EINVAL : 数据解析错误
*/

err:
of_node_put(it.node);
return rc;
}
EXPORT_SYMBOL(__of_parse_phandle_with_args);

/**
* of_parse_phandle_with_args() - 在列表中查找 phandle 指向的节点
* @np:指向包含列表的设备树节点的指针
* @list_name:包含列表的属性名称
* @cells_name:指定 pHandles 参数计数的属性名称
* @index:要解析的 phandle 的索引
* @out_args:指向输出参数结构的可选指针(将被填充)
*
* 此函数可用于解析 phandle 及其参数的列表。成功时返回 0 并填充 out_args,出错时返回适当的 errno 值。
*
* 调用方负责对返回的 out_args->np 指针调用 of_node_put()。
*
*例::
*
* phandle1: node1 {
* #list 单元格 = <2>;
* };
*
* phandle2: node2 {
* #list 单元格 = <1>;
* };
*
* 节点 3 {
* 列表 = <&phandle1 1 2 &phandle2 3>;
* };
*
* 要获取 ''node2'' 节点的device_node,您可以调用以下命令: of_parse_phandle_with_args(node3, “list”, “#list-cells”, 1, &args);
*/
static inline int of_parse_phandle_with_args(const struct device_node *np,
const char *list_name,
const char *cells_name,
int index,
struct of_phandle_args *out_args)
{
int cell_count = -1;

/* 如果 cells_name 为 NULL,则我们假设 cell count 为 0 */
if (!cells_name)
cell_count = 0;

return __of_parse_phandle_with_args(np, list_name, cells_name,
cell_count, index, out_args);
}

/**
* of_parse_phandle - 将 phandle 属性解析为 device_node 指针
* @np:指向持有 phandle 属性的设备节点的指针
* @phandle_name:保存 phandle 值的属性名称
* @index:对于保存 phandle 表的属性,这是表中的索引
*
* Return:refcount 递增的 device_node 指针。 完成后对它使用 of_node_put()。
*/
static inline struct device_node *of_parse_phandle(const struct device_node *np,
const char *phandle_name,
int index)
{
struct of_phandle_args args;

if (__of_parse_phandle_with_args(np, phandle_name, NULL, 0,
index, &args))
return NULL;

return args.np;
}

of_phandle_cache_hash 计算 phandle 哈希值

1
2
3
4
5
6
static struct device_node *phandle_cache[OF_PHANDLE_CACHE_SZ];

static u32 of_phandle_cache_hash(phandle handle)
{
return hash_32(handle, OF_PHANDLE_CACHE_BITS);
}

of_find_node_by_phandle 查找节点

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
/**
* of_find_node_by_phandle - 查找给定 phandle 的节点
* @handle: phandle 来查找
*
* Return: 一个 refcount 递增的节点指针,完成后对它使用 of_node_put()。
*/
struct device_node *of_find_node_by_phandle(phandle handle)
{
struct device_node *np = NULL;
unsigned long flags;
u32 handle_hash;

if (!handle)
return NULL;

handle_hash = of_phandle_cache_hash(handle);

raw_spin_lock_irqsave(&devtree_lock, flags);

if (phandle_cache[handle_hash] &&
handle == phandle_cache[handle_hash]->phandle)
np = phandle_cache[handle_hash];
/* 早期阶段of_core_init()还未调用初始化phandle_cache */
if (!np) {
for_each_of_allnodes(np)
if (np->phandle == handle &&
/* OF_DETACHED : 已从设备树中分离 */
!of_node_check_flag(np, OF_DETACHED)) {
phandle_cache[handle_hash] = np;
break;
}
}

of_node_get(np);
raw_spin_unlock_irqrestore(&devtree_lock, flags);
return np;
}
EXPORT_SYMBOL(of_find_node_by_phandle);

of_get_parent 获取节点的父节点(如果有)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* of_get_parent - 获取节点的父节点(如果有)
* @node:要获取父节点的节点
*
* 返回:一个 refcount 递增的节点指针,完成后对它使用 of_node_put()。
*/
struct device_node *of_get_parent(const struct device_node *node)
{
struct device_node *np;
unsigned long flags;

if (!node)
return NULL;

raw_spin_lock_irqsave(&devtree_lock, flags);
np = of_node_get(node->parent);
raw_spin_unlock_irqrestore(&devtree_lock, flags);
return np;
}
EXPORT_SYMBOL(of_get_parent);

of_bus_n_addr_cells of_bus_n_size_cells 获取地址单元数 获取大小单元数

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
int of_bus_n_addr_cells(struct device_node *np)
{
u32 cells;

for (; np; np = np->parent) {
if (!of_property_read_u32(np, "#address-cells", &cells))
return cells;
/*
* Default root value and walking parent nodes for "#address-cells"
* is deprecated. Any platforms which hit this warning should
* be added to the excluded list.
*/
WARN_ONCE(!EXCLUDED_DEFAULT_CELLS_PLATFORMS,
"Missing '#address-cells' in %pOF\n", np);
}
return OF_ROOT_NODE_ADDR_CELLS_DEFAULT;
}

int of_n_addr_cells(struct device_node *np)
{
if (np->parent)
np = np->parent;

return of_bus_n_addr_cells(np);
}
EXPORT_SYMBOL(of_n_addr_cells);

int of_bus_n_size_cells(struct device_node *np)
{
u32 cells;

for (; np; np = np->parent) {
if (!of_property_read_u32(np, "#size-cells", &cells))
return cells;
/*
* Default root value and walking parent nodes for "#size-cells"
* is deprecated. Any platforms which hit this warning should
* be added to the excluded list.
*/
WARN_ONCE(!EXCLUDED_DEFAULT_CELLS_PLATFORMS,
"Missing '#size-cells' in %pOF\n", np);
}
return OF_ROOT_NODE_SIZE_CELLS_DEFAULT;
}

int of_n_size_cells(struct device_node *np)
{
if (np->parent)
np = np->parent;

return of_bus_n_size_cells(np);
}
EXPORT_SYMBOL(of_n_size_cells);

of_core_init: 初始化设备树核心并创建sysfs表示

此函数在Linux内核启动初期被调用, 它的核心作用是解析已经加载到内存中的设备树 (Device Tree), 并在 sysfs 文件系统中 (通常位于 /sys/firmware/devicetree/) 创建一个与之对应的目录结构。这使得用户空间程序和内核其他部分可以通过标准的文件接口来访问设备树的节点和属性。

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
#define OF_PHANDLE_CACHE_BITS	7
#define OF_PHANDLE_CACHE_SZ BIT(OF_PHANDLE_CACHE_BITS)

static struct device_node *phandle_cache[OF_PHANDLE_CACHE_SZ];

static u32 of_phandle_cache_hash(phandle handle)
{
return hash_32(handle, OF_PHANDLE_CACHE_BITS);
}

/*
* 函数 of_core_init
* __init 标记表示该函数是一个初始化函数.
* 在内核启动过程完成后, 该函数所占用的内存会被释放, 以节约系统资源.
* void 表示该函数没有参数, 也无返回值.
*/
void __init of_core_init(void)
{
/*
* 定义一个指向 struct device_node 的指针 np.
* struct device_node 是设备树在内存中的节点表示形式.
* np 将被用作迭代器, 遍历设备树中的所有节点.
*/
struct device_node *np;

/*
* 调用 of_platform_register_reconfig_notifier 函数.
* 该函数用于注册一个通知器 (notifier), 以便在设备树发生动态重配置时 (例如通过overlay) 接收通知.
* 在像STM32H750这样设备树通常是静态的嵌入式系统中, 此功能较少使用, 但为保持通用性而存在.
*/
of_platform_register_reconfig_notifier();

/*
* 对 of_mutex 全局互斥锁进行加锁.
* 即使在单核系统中, 如果内核是抢占式的, 这个锁也是必要的.
* 它可以防止在操作全局的设备树数据结构时被其他内核任务抢占, 从而保证操作的原子性和数据一致性.
*/
mutex_lock(&of_mutex);
/*
* 调用 kset_create_and_add 函数创建一个名为 "devicetree" 的 kset (kobject集合).
* kset 在 sysfs 中表现为一个目录.
* 此 kset 将被添加到 firmware_kobj (代表/sys/firmware/目录) 之下.
* 因此, 这行代码的作用是创建 /sys/firmware/devicetree/ 目录.
* 返回的 kset 指针被保存在全局变量 of_kset 中, 作为所有设备树 sysfs 条目的父容器.
*/
of_kset = kset_create_and_add("devicetree", NULL, firmware_kobj);
/*
* 检查 kset 是否创建成功. 如果 kset_create_and_add 失败, 它将返回 NULL.
*/
if (!of_kset) {
/*
* 如果创建失败, 必须在返回前解锁互斥锁.
*/
mutex_unlock(&of_mutex);
/*
* 使用 pr_err 打印一条内核错误日志.
*/
pr_err("failed to register existing nodes\n");
/*
* 退出函数.
*/
return;
}
/*
* for_each_of_allnodes 是一个宏, 用于遍历内存中设备树的所有节点.
* 从根节点开始, np 指针在每次循环中都会指向下一个设备节点.
*/
for_each_of_allnodes(np) {
/*
* 对当前遍历到的节点 np, 调用 __of_attach_node_sysfs 函数.
* 此函数负责为该节点在之前创建的 /sys/firmware/devicetree/ 目录下创建对应的子目录和属性文件.
*/
__of_attach_node_sysfs(np);
/*
* 检查当前节点是否有一个非零的 phandle (节点在设备树源文件中的唯一标识符),
* 并且检查用于快速查找的 phandle_cache 缓存中, 对应此 phandle 的条目是否为空.
* of_phandle_cache_hash 是一个简单的哈希函数 (通常是直接取模), 用于计算 phandle 在缓存数组中的索引.
*/
if (np->phandle && !phandle_cache[of_phandle_cache_hash(np->phandle)])
/*
* 如果上述条件满足, 就将当前节点 np 的指针存入 phandle_cache 缓存数组.
* 这样后续可以通过 phandle 值快速地查找到对应的 device_node, 而无需再次遍历整个设备树.
*/
phandle_cache[of_phandle_cache_hash(np->phandle)] = np;
}
/*
* 所有节点都已在 sysfs 中注册完毕, 解锁互斥锁.
*/
mutex_unlock(&of_mutex);

/*
* 检查 of_root (指向设备树根节点的全局指针) 是否有效.
* 如果设备树被正确加载, of_root 应该不为 NULL.
*/
if (of_root)
/*
* 调用 proc_symlink 在 /proc 目录下创建一个符号链接 (软链接).
* 链接名为 "device-tree", 它指向 "/sys/firmware/devicetree/base" (即设备树根节点在sysfs中的路径).
* 这样做是为了兼容需要通过 /proc/device-tree 访问设备树的旧版用户空间工具.
*/
proc_symlink("device-tree", NULL, "/sys/firmware/devicetree/base");
}

drivers/of/dynamic.c

of_node_put 节点的递减引用计数

1
2
3
4
5
6
7
8
9
10
/**
* of_node_put() - 节点的递减引用计数
* @node:Node 到 dec refcount,支持 NULL 以简化调用方的编写
*/
void of_node_put(struct device_node *node)
{
if (node)
kobject_put(&node->kobj);
}
EXPORT_SYMBOL(of_node_put);

of_node_get 节点的递增引用计数

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* of_node_get() - 增加节点的 refcount
* @node:Node 到 inc refcount,支持 NULL 以简化调用方的编写
*
* 返回:refcount 递增的节点。
*/
struct device_node *of_node_get(struct device_node *node)
{
if (node)
kobject_get(&node->kobj);
return node;
}
EXPORT_SYMBOL(of_node_get);

of_reconfig_chain: 设备树重配置事件的阻塞式通知链头

这是一个静态全局变量,它定义并初始化了一个阻塞式通知链的头部。所有对设备树动态重配置事件感兴趣的模块,都会将它们的通知器(notifier)注册到这个链上。

1
2
3
4
5
6
/*
* BLOCKING_NOTIFIER_HEAD 是一个宏, 用于静态地定义并初始化一个名为 of_reconfig_chain 的
* 'blocking_notifier_head' 结构体. 这个结构体是阻塞式通知链的头部,
* 它内部包含了一个原始通知链的头部(head)和一个用于同步访问的读写信号量(rwsem).
*/
static BLOCKING_NOTIFIER_HEAD(of_reconfig_chain);

of_reconfig_notifier_register: 注册一个设备树重配置通知器

这是一个上层封装函数,为其他模块提供了一个清晰的、特定于设备树重配置的注册接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*
* 函数 of_reconfig_notifier_register
* @nb: 指向 struct notifier_block 的指针. 这个结构体包含了回调函数指针和优先级, 代表一个订阅者.
* @return: 成功时返回0, 失败时返回错误码.
*/
int of_reconfig_notifier_register(struct notifier_block *nb)
{
/*
* 调用通用的阻塞式通知链注册函数, 将订阅者(nb)添加到特定的 of_reconfig_chain 链上.
* 这是一个典型的封装, 将通用功能包装成特定领域的API.
*/
return blocking_notifier_chain_register(&of_reconfig_chain, nb);
}
/*
* 将此函数符号导出, 以便其他GPL许可证的内核模块可以调用它来订阅设备树重配置事件.
*/
EXPORT_SYMBOL_GPL(of_reconfig_notifier_register);

这个代码片段展示了Linux内核中事件发布的核心机制,即如何触发一个阻塞式通知链(blocking notifier chain)来广播一个事件。of_reconfig_notify是特定于设备树重配置事件的发布函数,而blocking_notifier_call_chain是通用的、用于调用任何阻塞式通知链的API。

这套机制在STM32H750这样的单核无MMU系统上是事件驱动架构的基石。其核心是blocking_notifier_call_chain使用的读写信号量(rwsem)。当一个事件被发布时,该函数会获取一个读锁

  • 在单核抢占式内核中,这个读锁允许多个任务并发地读取(即发布事件),但会阻塞任何尝试获取写锁(即注册/注销订阅者)的任务,直到所有读者都完成。
  • 最重要的是,这个锁机制确立了一个契约:由于调用者持有的是一个允许休眠的锁,因此链上所有订阅者的回调函数都被允许阻塞或休眠(例如,等待I/O)。这使得订阅者可以执行复杂的操作,而不必担心破坏原子上下文。blocking_notifier的”blocking”正源于此。

of_reconfig_notify: 发布一个设备树重配置通知

此函数是设备树子系统中用于广播“设备树已发生变化”事件的入口点。当内核的其他部分(如设备树覆盖管理器)需要通知系统一个节点已被添加或移除时,就会调用此函数。

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
/*
* 函数 of_reconfig_notify
* @action: 一个无符号长整型数, 描述了发生的具体变化, 例如 OF_RECONFIG_CHANGE_ADD.
* @p: 指向 struct of_reconfig_data 的指针, 包含了变化的详细信息, 如被改变的节点.
* @return: 返回一个标准的Linux错误码, 成功时为0.
*/
int of_reconfig_notify(unsigned long action, struct of_reconfig_data *p)
{
/*
* 定义一个整型变量 rc, 用于存储通知链调用的返回值.
*/
int rc;
/*
* 定义一个指向 of_reconfig_data 的指针 pr 并用 p 初始化.
* 这样做有时是为了在函数内部修改参数的副本, 但在这里它没有被修改.
*/
struct of_reconfig_data *pr = p;

/*
* 调用一个调试函数, 当内核启用动态调试时, 会打印出变化的动作、节点和属性信息.
*/
of_changeset_action_debug("notify: ", action, pr->dn, pr->prop);

/*
* 调用通用的阻塞式通知链执行函数. 这是此函数的核心.
* 它将遍历 of_reconfig_chain 链表, 并调用所有已注册订阅者的回调函数.
* action 和 p 会被原封不动地传递给每一个回调函数.
*/
rc = blocking_notifier_call_chain(&of_reconfig_chain, action, p);
/*
* 调用 notifier_to_errno 函数.
* 它将通知链的返回值 (如 NOTIFY_OK, NOTIFY_BAD) 转换为标准的Linux内核错误码 (如 0, -EINVAL).
* 这是为了让调用 of_reconfig_notify 的函数能以统一的方式处理返回结果.
*/
return notifier_to_errno(rc);
}

drivers/of/fdt.c

initial_boot_params fdt的指针地址

1
2
3
4
5
6
7
void *initial_boot_params __ro_after_init;
bool __init early_init_dt_verify(void *dt_virt, phys_addr_t dt_phys)
{
/* Setup flat device-tree pointer */
initial_boot_params = dt_virt;
initial_boot_params_pa = dt_phys;
}

early_init_dt_scan_root 获取顶级地址和大小单元格

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
/*
* early_init_dt_scan_root - 获取顶级地址和大小单元格
*/
int __init early_init_dt_scan_root(void)
{
const __be32 *prop;
const void *fdt = initial_boot_params;
int node = fdt_path_offset(fdt, "/");

if (node < 0)
return -ENODEV;

dt_root_size_cells = OF_ROOT_NODE_SIZE_CELLS_DEFAULT;
dt_root_addr_cells = OF_ROOT_NODE_ADDR_CELLS_DEFAULT;

prop = of_get_flat_dt_prop(node, "#size-cells", NULL);
if (!WARN(!prop, "No '#size-cells' in root node\n"))
dt_root_size_cells = be32_to_cpup(prop);
pr_debug("dt_root_size_cells = %x\n", dt_root_size_cells);

prop = of_get_flat_dt_prop(node, "#address-cells", NULL);
if (!WARN(!prop, "No '#address-cells' in root node\n"))
dt_root_addr_cells = be32_to_cpup(prop);
pr_debug("dt_root_addr_cells = %x\n", dt_root_addr_cells);

return 0;
}

early_init_dt_verify 验证fdt格式与校验

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
bool __init early_init_dt_verify(void *dt_virt, phys_addr_t dt_phys)
{
if (!dt_virt)
return false;

/* 检查设备树有效性 */
if (fdt_check_header(dt_virt))
return false;

/* 设置平面设备树指针*/
initial_boot_params = dt_virt;
initial_boot_params_pa = dt_phys;
of_fdt_crc32 = crc32_be(~0, initial_boot_params,
fdt_totalsize(initial_boot_params));

/* 初始化 {size,address} -cells 信息 */
early_init_dt_scan_root();

return true;
}

of_flat_dt_match 如果 node 与兼容值列表匹配,则返回 true

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*
* of_flat_dt_match -如果 node 与兼容值列表匹配,则返回 true
*/
static int __init of_flat_dt_match(unsigned long node, const char *const *compat)
{
unsigned int tmp, score = 0;

if (!compat)
return 0;

while (*compat) {
tmp = of_fdt_is_compatible(initial_boot_params, node, *compat);
if (tmp && (score == 0 || (tmp < score)))
score = tmp;
compat++;
}

return score;
}

of_flat_dt_match_machine 设备树匹配机器描述符

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
const void * __init of_flat_dt_match_machine(const void *default_match,
const void * (*get_next_compat)(const char * const**))
{
const void *data = NULL;
const void *best_data = default_match;
const char *const *compat;
unsigned long dt_root;
unsigned int best_score = ~1, score = 0;

dt_root = of_get_flat_dt_root();
while ((data = get_next_compat(&compat))) { //获取下一个机器描述符
score = of_flat_dt_match(dt_root, compat);
if (score > 0 && score < best_score) {
best_data = data;
best_score = score;
}
}
if (!best_data) {
const char *prop;
int size;

pr_err("\n unrecognized device tree list:\n[ ");

prop = of_get_flat_dt_prop(dt_root, "compatible", &size);
if (prop) {
while (size > 0) {
printk("'%s' ", prop);
size -= strlen(prop) + 1;
prop += strlen(prop) + 1;
}
}
printk("]\n\n");
return NULL;
}

pr_info("Machine model: %s\n", of_flat_dt_get_machine_name());

return best_data;
}

early_init_dt_check_for_initrd 检查 initrd(初始 RAM 磁盘) 的位置

  • 在设备树中,linux,initrd-start 是一个属性名称,通常用于指定初始 RAM 磁盘(initrd)的起始地址。初始 RAM 磁盘是一个临时的根文件系统,Linux 内核在启动时会加载它,以便完成系统初始化(例如加载驱动程序或挂载真正的根文件系统)。
  • 在u-boot中可以识别到Ramdisk文件,并且进行加载到内存中,然后添加到设备树中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[2025/3/31 20:05:25 122] ## Loading ramdisk (any) from FIT Image at 90080000 ...
[2025/3/31 20:05:25 125] Using 'conf0' configuration
[2025/3/31 20:05:25 129] Trying 'ramdisk' ramdisk subimage
[2025/3/31 20:05:25 135] Description: Ramdisk for for RT-Thread STM32H750i-ART-PI board
[2025/3/31 20:05:25 139] Created: 2025-03-31 12:01:54 UTC
[2025/3/31 20:05:25 142] Type: RAMDisk Image
[2025/3/31 20:05:25 145] Compression: uncompressed
[2025/3/31 20:05:25 149] Data Start: 0x9021c9d4
[2025/3/31 20:05:25 153] Data Size: 2097152 Bytes = 2 MiB
[2025/3/31 20:05:25 153] Architecture: ARM
[2025/3/31 20:05:25 157] OS: Linux
[2025/3/31 20:05:25 162] Load Address: unavailable
[2025/3/31 20:05:25 162] Entry Point: unavailable
[2025/3/31 20:05:25 163] Hash algo: sha1
[2025/3/31 20:05:25 169] Hash value: cf0cfc63b905e83fa53905c0bbbbac5e01124b6f
[2025/3/31 20:05:25 440] Verifying Hash Integrity ... sha1+ OK
[2025/3/31 20:05:25 636] Loading Ramdisk to c1600000, end c1800000 ... OK
[2025/3/31 20:05:25 716] Starting kernel ...
[2025/3/31 20:05:25 716]
[2025/3/31 20:05:31 847] [ 0.000000] OF: fdt: initrd_start=0xc1600000 initrd_end=0xc1800000
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
/**
* early_init_dt_check_for_initrd - 从平面树解码 initrd 位置
* @node:对包含 initrd 位置的节点的引用 ('Chosen')
*/
static void __init early_init_dt_check_for_initrd(unsigned long node)
{
u64 start, end;
int len;
const __be32 *prop;

if (!IS_ENABLED(CONFIG_BLK_DEV_INITRD))
return;

pr_debug("Looking for initrd properties... ");

prop = of_get_flat_dt_prop(node, "linux,initrd-start", &len);
if (!prop)
return;
start = of_read_number(prop, len/4);

prop = of_get_flat_dt_prop(node, "linux,initrd-end", &len);
if (!prop)
return;
end = of_read_number(prop, len/4);
if (start > end)
return;

__early_init_dt_declare_initrd(start, end);
phys_initrd_start = start;
phys_initrd_size = end - start;

pr_debug("initrd_start=0x%llx initrd_end=0x%llx\n", start, end);
}

early_init_dt_scan_chosen 扫描/chosen节点

  1. bootargs参数:由u-boot传递给内核的命令行参数。
    u-boot会把其在env环境变量中的bootargs参数设置覆盖fdt参数
    1
    [2025/3/31 20:05:31 854] [    0.000000] OF: fdt: Command line is: console=ttySTM0,115200 root=/dev/ram loglevel=8
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
int __init early_init_dt_scan_chosen(char *cmdline)
{
int l, node;
const char *p;
const void *rng_seed;
const void *fdt = initial_boot_params;

node = fdt_path_offset(fdt, "/chosen");
if (node < 0)
node = fdt_path_offset(fdt, "/chosen@0");
if (node < 0)
/* 即使没有 /chosen 节点,也要处理 cmdline 配置选项 */
goto handle_cmdline;

chosen_node_offset = node;

early_init_dt_check_for_initrd(node);//检查 initrd(初始 RAM 磁盘) 的位置
//编译和启用崩溃转储功能
early_init_dt_check_for_elfcorehdr(node); //CONFIG_CRASH_DUMP
//fdt中获取随机种子
rng_seed = of_get_flat_dt_prop(node, "rng-seed", &l);
if (rng_seed && l > 0) {
add_bootloader_randomness(rng_seed, l);

/* 尝试清除种子,这样就不会找到它。 */
fdt_nop_property(initial_boot_params, node, "rng-seed");

/* 更新 CRC 校验值*/
of_fdt_crc32 = crc32_be(~0, initial_boot_params,
fdt_totalsize(initial_boot_params));
}

/* 检索命令行 */
p = of_get_flat_dt_prop(node, "bootargs", &l);
if (p != NULL && l > 0)
strscpy(cmdline, p, min(l, COMMAND_LINE_SIZE));

handle_cmdline:
/*
* CONFIG_CMDLINE 是默认值,以防没有其他方法可以设置命令行,
* 除非设置了 CONFIG_CMDLINE_FORCE在这种情况下,我们将覆盖之前找到的任何内容。
*/
#ifdef CONFIG_CMDLINE
#if defined(CONFIG_CMDLINE_EXTEND)
strlcat(cmdline, " ", COMMAND_LINE_SIZE);
strlcat(cmdline, CONFIG_CMDLINE, COMMAND_LINE_SIZE);
#elif defined(CONFIG_CMDLINE_FORCE)
strscpy(cmdline, CONFIG_CMDLINE, COMMAND_LINE_SIZE);
#else
/* 没有来自引导加载程序的参数,使用内核的 cmdl*/
if (!((char *)cmdline)[0])
strscpy(cmdline, CONFIG_CMDLINE, COMMAND_LINE_SIZE);
#endif
#endif /* CONFIG_CMDLINE */

pr_debug("Command line is: %s\n", (char *)cmdline);

return 0;
}

early_init_dt_add_memory_arch 将内存块添加到内存块列表中。它会检查内存块的大小和对齐方式,并确保它们在有效范围内。

  • early_init_dt_add_memory_arch 函数用于将内存块添加到内存块列表中。它会检查内存块的大小和对齐方式,并确保它们在有效范围内。
  • memblock_add 函数用于将内存块添加到内存块管理器中。它会更新内存块的状态,以便在后续的内存分配中使用。
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
void __init __weak early_init_dt_add_memory_arch(u64 base, u64 size)
{
const u64 phys_offset = MIN_MEMBLOCK_ADDR;

if (size < PAGE_SIZE - (base & ~PAGE_MASK)) {
pr_warn("Ignoring memory block 0x%llx - 0x%llx\n",
base, base + size);
return;
}

if (!PAGE_ALIGNED(base)) {
size -= PAGE_SIZE - (base & ~PAGE_MASK);
base = PAGE_ALIGN(base);
}
size &= PAGE_MASK;

if (base > MAX_MEMBLOCK_ADDR) {
pr_warn("Ignoring memory block 0x%llx - 0x%llx\n",
base, base + size);
return;
}

if (base + size - 1 > MAX_MEMBLOCK_ADDR) {
pr_warn("Ignoring memory range 0x%llx - 0x%llx\n",
((u64)MAX_MEMBLOCK_ADDR) + 1, base + size);
size = MAX_MEMBLOCK_ADDR - base + 1;
}

if (base + size < phys_offset) {
pr_warn("Ignoring memory block 0x%llx - 0x%llx\n",
base, base + size);
return;
}
if (base < phys_offset) {
pr_warn("Ignoring memory range 0x%llx - 0x%llx\n",
base, phys_offset);
size -= phys_offset - base;
base = phys_offset;
}
memblock_add(base, size);
}

early_init_dt_scan_memory 查找并解析内存节点

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
/*
* early_init_dt_scan_memory - 查找并解析内存节点
*/
int __init early_init_dt_scan_memory(void)
{
int node, found_memory = 0;
const void *fdt = initial_boot_params;

fdt_for_each_subnode(node, fdt, 0) {
const char *type = of_get_flat_dt_prop(node, "device_type", NULL);
const __be32 *reg, *endp;
int l;
bool hotpluggable;

/* We are scanning "memory" nodes only */
if (type == NULL || strcmp(type, "memory") != 0)
continue;

if (!of_fdt_device_is_available(fdt, node))
continue;

reg = of_get_flat_dt_prop(node, "linux,usable-memory", &l);
if (reg == NULL)
reg = of_get_flat_dt_prop(node, "reg", &l);
if (reg == NULL)
continue;

endp = reg + (l / sizeof(__be32));
hotpluggable = of_get_flat_dt_prop(node, "hotpluggable", NULL);

pr_debug("memory scan node %s, reg size %d,\n",
fdt_get_name(fdt, node, NULL), l);

while ((endp - reg) >= (dt_root_addr_cells + dt_root_size_cells)) {
u64 base, size;

base = dt_mem_next_cell(dt_root_addr_cells, &reg);
size = dt_mem_next_cell(dt_root_size_cells, &reg);

if (size == 0)
continue;
pr_debug(" - %llx, %llx\n", base, size);
//将内存块添加到内存块列表中。它会检查内存块的大小和对齐方式,并确保它们在有效范围内。
early_init_dt_add_memory_arch(base, size);

found_memory = 1;

if (!hotpluggable)
continue;

if (memblock_mark_hotplug(base, size))
pr_warn("failed to mark hotplug range 0x%llx - 0x%llx\n",
base, base + size);
}
}
return found_memory;
}

early_init_dt_check_for_usable_mem_range 从平面树解码可用内存范围位置

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
static unsigned long chosen_node_offset = -FDT_ERR_NOTFOUND;

/*
* linux,usable-memory-range 的主要用途是用于 crash dump 内核。
* 最初,可用内存区域的数量为 1。现在可能有两个区域,低区域和高区域。
* 为了与现有的 user-space 和较旧的 kdump 兼容,低区域始终是 linux,usable-memory-range 的最后一个范围(如果存在)。
*/
#define MAX_USABLE_RANGES 2

/**
* early_init_dt_check_for_usable_mem_range - 从平面树解码可用内存范围位置
*/
void __init early_init_dt_check_for_usable_mem_range(void)
{
struct memblock_region rgn[MAX_USABLE_RANGES] = {0};
const __be32 *prop, *endp;
int len, i;
//int __init early_init_dt_scan_chosen(char *cmdline) -> node = fdt_path_offset(fdt, "/chosen"); chosen_node_offset = node;
unsigned long node = chosen_node_offset;

if ((long)node < 0)
return;

pr_debug("Looking for usable-memory-range property... ");

prop = of_get_flat_dt_prop(node, "linux,usable-memory-range", &len);
if (!prop || (len % (dt_root_addr_cells + dt_root_size_cells)))
return;

endp = prop + (len / sizeof(__be32));
for (i = 0; i < MAX_USABLE_RANGES && prop < endp; i++) {
rgn[i].base = dt_mem_next_cell(dt_root_addr_cells, &prop);
rgn[i].size = dt_mem_next_cell(dt_root_size_cells, &prop);

pr_debug("cap_mem_regions[%d]: base=%pa, size=%pa\n",
i, &rgn[i].base, &rgn[i].size);
}

memblock_cap_memory_range(rgn[0].base, rgn[0].size);
for (i = 1; i < MAX_USABLE_RANGES && rgn[i].size; i++)
memblock_add(rgn[i].base, rgn[i].size);
}

early_init_dt_scan_nodes 扫描设备树节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void __init early_init_dt_scan_nodes(void)
{
int rc;

/* 从 /chosen 节点检索各种信息*/
rc = early_init_dt_scan_chosen(boot_command_line);
if (rc)
pr_warn("No chosen node found, continuing without\n");

/*设置内存, 调用 early_init_dt_add_memory_arch*/
early_init_dt_scan_memory(); //查找并解析内存节点

/* 处理 linux,usable-memory-range 属性 */
early_init_dt_check_for_usable_mem_range(); //从平面树解码可用内存范围位置
}

early_init_fdt_scan_reserved_mem 创建保留内存区域

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
/**
* early_init_fdt_scan_reserved_mem() - 创建保留内存区域
*
* 此函数从早期分配器中获取内存,用于设备树结构中定义的设备独占使用。一旦早期分配器(即 memblock)被完全激活,它应该由 arch 特定的代码调用。
*/
void __init early_init_fdt_scan_reserved_mem(void)
{
int n;
int res;
u64 base, size;

if (!initial_boot_params)
return;

fdt_scan_reserved_mem(); //扫描单个 FDT 节点以获取预留内存
fdt_reserve_elfcorehdr(); //!IS_ENABLED(CONFIG_CRASH_DUMP) return

/* Process header /memreserve/ fields */
for (n = 0; ; n++) {
//搜索FDT头中定义的内存预留块
res = fdt_get_mem_rsv(initial_boot_params, n, &base, &size);
if (res) {
pr_err("Invalid memory reservation block index %d\n", n);
break;
}
if (!size)
break;
memblock_reserve(base, size);
}
}

__dtb_empty_root_begin 和 __dtb_empty_root_end

1
2
3
4
5
/*
* __dtb_empty_root_begin[] 和 __dtb_empty_root_end[] 由 cmd_wrap_S_dtb 在 scripts/Makefile.dtbs 中神奇创建
*/
extern uint8_t __dtb_empty_root_begin[];
extern uint8_t __dtb_empty_root_end[];
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# scripts/Makefile.dtbs
# 生成汇编文件以包装器件树编译器的输出
quiet_cmd_wrap_S_dtb = WRAP $@
cmd_wrap_S_dtb = { \
symbase=__$(patsubst .%,%,$(suffix $<))_$(subst -,_,$(notdir $*)); \
echo '\#include <asm-generic/vmlinux.lds.h>'; \
echo '.section $(builtin-dtb-section),"a"'; \
echo '.balign STRUCT_ALIGNMENT'; \
echo ".global $${symbase}_begin"; \
echo "$${symbase}_begin:"; \
echo '.incbin "$<" '; \
echo ".global $${symbase}_end"; \
echo "$${symbase}_end:"; \
echo '.balign STRUCT_ALIGNMENT'; \
} > $@

$(obj)/%.dtb.S: $(obj)/%.dtb FORCE
$(call if_changed,wrap_S_dtb)

$(obj)/%.dtbo.S: $(obj)/%.dtbo FORCE
$(call if_changed,wrap_S_dtb)
1
2
3
4
5
6
7
8
9
10
//drivers/of/empty_root.dtb.S
#include <asm-generic/vmlinux.lds.h>
.section .rodata,"a"
.balign STRUCT_ALIGNMENT
.global __dtb_empty_root_begin
__dtb_empty_root_begin:
.incbin "drivers/of/empty_root.dtb"
.global __dtb_empty_root_end
__dtb_empty_root_end:
.balign STRUCT_ALIGNMENT
1
2
3
4
5
6
7
8
9
//drivers/of/empty_root.dts
/dts-v1/;

/ {
/*#address-cells/#size-cells 是根节点的必需属性。
* 地址单元和大小单元都使用 2 个单元,以便在使用此空根节点的系统上完全支持 64 位地址和大小。 */
#address-cells = <0x02>;
#size-cells = <0x02>;
};

populate_properties 填充属性

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
static void populate_properties(const void *blob,
int offset,
void **mem,
struct device_node *np,
const char *nodename,
bool dryrun)
{
struct property *pp, **pprev = NULL;
int cur;
bool has_name = false;

pprev = &np->properties;
for (cur = fdt_first_property_offset(blob, offset);
cur >= 0;
cur = fdt_next_property_offset(blob, cur)) {
const __be32 *val;
const char *pname;
u32 sz;

val = fdt_getprop_by_offset(blob, cur, &pname, &sz);
if (!val) {
pr_warn("Cannot locate property at 0x%x\n", cur);
continue;
}

if (!pname) {
pr_warn("Cannot find property name at 0x%x\n", cur);
continue;
}

if (!strcmp(pname, "name"))
has_name = true;

pp = unflatten_dt_alloc(mem, sizeof(struct property),
__alignof__(struct property));
if (dryrun)
continue;

/*我们在 ePAPR 样式的 “phandle” 属性或旧版 “linux,phandle” 属性中接受扁平化的树 phandle。
* 如果两者都出现并且具有不同的值,事情会变得很奇怪。别这样。
*/
if (!strcmp(pname, "phandle") ||
!strcmp(pname, "linux,phandle")) {
if (!np->phandle)
np->phandle = be32_to_cpup(val);
}

/*我们处理 pSeries 动态设备树内容中使用的 “ibm,phandle” 属性
*/
if (!strcmp(pname, "ibm,phandle"))
np->phandle = be32_to_cpup(val);

pp->name = (char *)pname;
pp->length = sz;
pp->value = (__be32 *)val;
*pprev = pp;
pprev = &pp->next;
}

/*对于版本 0x10我们可能没有 name 属性,如果不存在,请从单元名称在此处重新创建它
*/
if (!has_name) {
const char *p = nodename, *ps = p, *pa = NULL;
int len;

while (*p) {
if ((*p) == '@')
pa = p;
else if ((*p) == '/')
ps = p + 1;
p++;
}

if (pa < ps)
pa = p;
len = (pa - ps) + 1;
pp = unflatten_dt_alloc(mem, sizeof(struct property) + len,
__alignof__(struct property));
if (!dryrun) {
pp->name = "name";
pp->length = len;
pp->value = pp + 1;
*pprev = pp;
memcpy(pp->value, ps, len - 1);
((char *)pp->value)[len - 1] = 0;
pr_debug("fixed up name for %s -> %s\n",
nodename, (char *)pp->value);
}
}
}

populate_node 填充设备节点

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
static int populate_node(const void *blob,
int offset,
void **mem,
struct device_node *dad,
struct device_node **pnp,
bool dryrun)
{
struct device_node *np;
const char *pathp;
int len;

pathp = fdt_get_name(blob, offset, &len);
if (!pathp) {
*pnp = NULL;
return len;
}

len++;

np = unflatten_dt_alloc(mem, sizeof(struct device_node) + len,
__alignof__(struct device_node));
if (!dryrun) {
char *fn;
of_node_init(np);
np->full_name = fn = ((char *)np) + sizeof(*np);

memcpy(fn, pathp, len);

if (dad != NULL) {
np->parent = dad;
np->sibling = dad->child;
dad->child = np;
}
}

populate_properties(blob, offset, mem, np, pathp, dryrun);
if (!dryrun) {
np->name = of_get_property(np, "name", NULL);
if (!np->name)
np->name = "<NULL>";
}

*pnp = np;
return 0;
}

unflatten_dt_nodes 从平面树分配并填充device_node

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
/**
* unflatten_dt_nodes - 从平面树分配并填充device_node
* @blob:父设备树 blob
* @mem:用于分配设备节点和属性的内存块
* @dad:父结构体 device_node
* @nodepp:调用创建的 device_node 树
*
* 返回:未拼合的设备树或错误代码的大小
*/
static int unflatten_dt_nodes(const void *blob,
void *mem,
struct device_node *dad,
struct device_node **nodepp)
{
struct device_node *root;
int offset = 0, depth = 0, initial_depth = 0;
#define FDT_MAX_DEPTH 64
struct device_node *nps[FDT_MAX_DEPTH];
void *base = mem;
bool dryrun = !base;
int ret;

if (nodepp)
*nodepp = NULL;

/*
* 如果设备子树有效,我们将取消拼合@dad。第一级深度中可能有多个节点。
* 我们需要将 @depth 设置为 1 以使 fdt_next_node() 满意,
* 因为当发现负 @depth 时,它会立即停止。否则,除第一个设备节点外,其他设备节点将无法成功展平。
*/
if (dad)
depth = initial_depth = 1;

root = dad;
nps[depth] = dad;

for (offset = 0;
offset >= 0 && depth >= initial_depth;
offset = fdt_next_node(blob, offset, &depth)) {
if (WARN_ON_ONCE(depth >= FDT_MAX_DEPTH - 1))
continue;
/* OF_KOBJ 的主要作用是将设备树与内核的 sysfs 文件系统集成.
* 启用 OF_KOBJ 后,设备树中的节点和属性可以通过 sysfs 文件系统暴露给用户空间,
* 从而方便调试和动态管理设备树。 */
if (!IS_ENABLED(CONFIG_OF_KOBJ) &&
!of_fdt_device_is_available(blob, offset))
continue;

ret = populate_node(blob, offset, &mem, nps[depth],
&nps[depth+1], dryrun);
if (ret < 0)
return ret;

if (!dryrun && nodepp && !*nodepp)
*nodepp = nps[depth+1];
if (!dryrun && !root)
root = nps[depth+1];
}

if (offset < 0 && offset != -FDT_ERR_NOTFOUND) {
pr_err("Error %d processing FDT\n", offset);
return -EINVAL;
}

/*
* Reverse the child list. Some drivers assumes node order matches .dts
* node order
*/
if (!dryrun)
reverse_nodes(root);

return mem - base;
}

__unflatten_device_tree 展开固件传递的设备树,创建 struct device_node 树

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
/**
* __unflatten_device_tree - 从平面 blob 创建 device_nodes 树
* @blob:要扩展的 blob
* @dad:父设备节点
* @mynodes:调用创建的 device_node 树
* @dt_alloc:为结果树的内存提供虚拟地址的分配器
* @detached:如果为 true,则在 @mynodes 上设置 OF_DETACHED
*
* 展开 device-tree,创建 struct device_node 树。它还填充了节点的 “name” 和 “type” 指针,因此可以使用正常的 device-tree walking 函数。
*
* 返回:失败时为 NULL,成功时包含未展平设备树的内存块。
*/
void *__unflatten_device_tree(const void *blob,
struct device_node *dad,
struct device_node **mynodes,
void *(*dt_alloc)(u64 size, u64 align),
bool detached)
{
int size;
void *mem;
int ret;

if (mynodes)
*mynodes = NULL;

pr_debug(" -> unflatten_device_tree()\n");

if (!blob) {
pr_debug("No device tree pointer\n");
return NULL;
}

pr_debug("Unflattening device tree:\n");
pr_debug("magic: %08x\n", fdt_magic(blob));
pr_debug("size: %08x\n", fdt_totalsize(blob));
pr_debug("version: %08x\n", fdt_version(blob));

if (fdt_check_header(blob)) {
pr_err("Invalid device tree blob header\n");
return NULL;
}

/* 首次通过,扫描尺寸*/
size = unflatten_dt_nodes(blob, NULL, dad, NULL);
if (size <= 0)
return NULL;

size = ALIGN(size, 4);
pr_debug(" size is %d, allocating...\n", size);

/* 为扩展的设备树分配内存*/
mem = dt_alloc(size + 4, __alignof__(struct device_node));
if (!mem)
return NULL;

memset(mem, 0, size);

*(__be32 *)(mem + size) = cpu_to_be32(0xdeadbeef);
/*
输出OF: fdt: unflattening (ptrval)...
内核地址保护机制:
从 Linux 内核 4.15 开始,为了防止内核地址泄露,%p 的默认行为被修改,不再直接输出指针的实际地址。
取而代之的是输出 (ptrval),表示这是一个指针值,但具体地址被隐藏。
如何控制 %p 的行为:

如果需要输出实际地址,可以使用其他格式化符,例如 %px 或 %pK:
%px:输出实际地址(仅在调试模式下可用)。
%pK:根据内核配置和权限决定是否输出实际地址。
*/
pr_debug(" unflattening %p...\n", mem);

/* Second pass, do actual unflattening */
ret = unflatten_dt_nodes(blob, mem, dad, mynodes);

if (be32_to_cpup(mem + size) != 0xdeadbeef)
pr_warn("End of tree marker overwritten: %08x\n",
be32_to_cpup(mem + size));

if (ret <= 0)
return NULL;

if (detached && mynodes && *mynodes) {
of_node_set_flag(*mynodes, OF_DETACHED);
pr_debug("unflattened tree is detached\n");
}

pr_debug(" <- unflatten_device_tree()\n");
return mem;
}

unflatten_device_tree 从平面 blob 创建 device_nodes 树

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
/**
* unflatten_device_tree - 从平面 blob 创建 device_nodes 树
* 展开固件传递的设备树,创建 struct device_node 树。它还填充了节点的 “name” 和 “type” 指针,因此可以使用正常的 device-tree walking 函数。
*/
void __init unflatten_device_tree(void)
{
void *fdt = initial_boot_params; //fdt的指针地址

/* 将静态放置的区域保存在 reserved_mem 数组中*/
fdt_scan_reserved_mem_reg_nodes();

/* Populate an empty root node when bootloader doesn't provide one */
if (!fdt) {
fdt = (void *) __dtb_empty_root_begin;
/* fdt_totalsize() 将用于副本大小 */
if (fdt_totalsize(fdt) >
__dtb_empty_root_end - __dtb_empty_root_begin) {
pr_err("invalid size in dtb_empty_root\n");
return;
}
of_fdt_crc32 = crc32_be(~0, fdt, fdt_totalsize(fdt));
fdt = copy_device_tree(fdt);
}

__unflatten_device_tree(fdt, NULL, &of_root,
early_init_dt_alloc_memory_arch, false);

/* 获取指向 “/chosen” 和 “/aliases” 节点的指针,以便在任何地方使用 */
of_alias_scan(early_init_dt_alloc_memory_arch);

unittest_unflatten_overlay_base();
}

drivers/of/kobj.c

__of_attach_node_sysfs: 在sysfs中为设备树节点创建目录

此函数的核心作用是接收一个设备树节点(struct device_node),并在sysfs文件系统中为其创建一个对应的目录。它处理了节点的层级关系,确保设备树的树状结构在sysfs中被正确地复制出来。

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
/*
* 函数 __of_attach_node_sysfs
* 两个下划线前缀通常表示这是一个内核内部使用的辅助函数.
* @np: 指向 struct device_node 的指针, 代表需要附加到sysfs的设备树节点.
* @return: 成功时返回0, 失败时返回负的错误码.
*/
int __of_attach_node_sysfs(struct device_node *np)
{
/*
* 定义一个指向字符的指针 name, 用于存储将在sysfs中创建的目录名.
*/
const char *name;
/*
* 定义一个指向 kobject 的指针 parent, 用于存储父节点的kobject.
* kobject是内核中代表sysfs目录项的核心数据结构.
*/
struct kobject *parent;
/*
* 定义一个指向 property 的指针 pp, 用作遍历节点属性时的迭代器.
*/
struct property *pp;
/*
* 定义一个整型变量 rc, 用于存储函数调用的返回值.
*/
int rc;

/*
* 检查内核是否配置开启了CONFIG_SYSFS, 以及of_kset是否已成功创建.
* of_kset 是代表 /sys/firmware/devicetree/ 这个根目录的kset对象.
* 如果任一条件不满足, 说明sysfs功能未启用或初始化失败, 直接返回成功(0), 不执行任何操作.
*/
if (!IS_ENABLED(CONFIG_SYSFS) || !of_kset)
return 0;

/*
* 将当前节点np的kobject(np->kobj)归属到of_kset集合中.
* 每个kobject都必须属于一个kset.
*/
np->kobj.kset = of_kset;
/*
* 检查当前节点是否有父节点.
*/
if (!np->parent) {
/*
* 如果没有父节点, 说明这是设备树的根节点.
* 为其创建一个名为 "base" 的安全名称. safe_name会动态分配内存.
* "base" 目录将作为所有设备树节点sysfs表示的根, 路径为 /sys/firmware/devicetree/base.
*/
name = safe_name(&of_kset->kobj, "base");
/*
* 根节点的kobject没有父kobject(其父级是kset).
*/
parent = NULL;
} else {
/*
* 如果有父节点, 则从节点的完整路径名(np->full_name)中提取基本名称(如"serial@40011000"),
* 并使用safe_name确保名称在sysfs中是有效且不冲突的. 其父目录就是其父节点的kobject.
*/
name = safe_name(&np->parent->kobj, kbasename(np->full_name));
/*
* 将父kobject指向其父节点的kobject.
*/
parent = &np->parent->kobj;
}
/*
* 检查safe_name是否因内存不足而失败.
*/
if (!name)
return -ENOMEM;

/*
* 调用kobject_add, 在父目录下(parent), 以指定的名称(name)正式创建并注册kobject(np->kobj).
* 这行代码执行后, sysfs中就会出现对应的目录.
*/
rc = kobject_add(&np->kobj, parent, "%s", name);
/*
* safe_name动态分配了内存, kobject_add已经复制了该名称, 所以这里必须释放原始的内存, 防止泄漏.
*/
kfree(name);
/*
* 如果kobject_add失败, 返回其错误码.
*/
if (rc)
return rc;

/*
* 使用 for_each_property_of_node 宏遍历当前节点(np)的所有属性(property).
*/
for_each_property_of_node(np, pp)
/*
* 对每一个属性, 调用 __of_add_property_sysfs 函数, 在刚创建的目录下为其创建对应的文件.
*/
__of_add_property_sysfs(np, pp);

/*
* 增加设备树节点np的引用计数.
* 因为现在有一个kobject正引用着它, 必须增加计数来防止该节点在kobject还存在时被意外释放.
*/
of_node_get(np);
/*
* 返回0, 表示成功.
*/
return 0;
}

__of_add_property_sysfs: 在sysfs中为设备树属性创建文件

此函数被__of_attach_node_sysfs调用,负责将单个设备树属性(如compatible, reg等)暴露为sysfs中的一个二进制文件。

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
/*
* 函数 __of_add_property_sysfs
* @np: 指向 struct device_node 的指针, 即属性所属的设备树节点.
* @pp: 指向 struct property 的指针, 即需要被添加到sysfs的属性.
* @return: 成功时返回0, 失败时返回负的错误码.
*/
int __of_add_property_sysfs(struct device_node *np, struct property *pp)
{
/*
* 定义一个整型变量 rc 用于保存返回值.
*/
int rc;

/*
* 检查属性名称是否以 "security-" 开头, 以判断其是否为安全敏感属性.
* strncmp比较前9个字符. 如果是, secure变量为true.
*/
bool secure = strncmp(pp->name, "security-", 9) == 0;

/*
* 同样检查CONFIG_SYSFS是否启用.
*/
if (!IS_ENABLED(CONFIG_SYSFS))
return 0;

/*
* 检查of_kset是否存在, 并且节点的kobject是否已经成功附加到sysfs.
* 如果父目录都没有创建成功, 就不能在其中创建文件.
*/
if (!of_kset || !of_node_is_attached(np))
return 0;

/*
* 初始化属性结构中内嵌的二进制属性(sysfs_bin_attribute)结构体.
*/
sysfs_bin_attr_init(&pp->attr);
/*
* 为属性文件创建一个安全的文件名, 父对象是该属性所属节点的kobject.
*/
pp->attr.attr.name = safe_name(&np->kobj, pp->name);
/*
* 设置文件的访问权限. 如果是安全属性, 权限为0400 (仅属主可读).
* 否则为0444 (所有人可读).
*/
pp->attr.attr.mode = secure ? 0400 : 0444;
/*
* 设置文件的大小. 如果是安全属性, 大小报告为0, 用户即使有权限也读不出内容.
* 否则, 大小就是属性值的实际长度.
*/
pp->attr.size = secure ? 0 : pp->length;
/*
* 指定当用户空间程序读取此文件时, 内核应该调用的函数.
* 这里指向 of_node_property_read, 该函数会负责从设备树中读取并返回属性的二进制值.
*/
pp->attr.read_new = of_node_property_read;

/*
* 调用 sysfs_create_bin_file, 在节点的kobject(即sysfs目录)下, 根据上面设置的属性创建一个二进制文件.
*/
rc = sysfs_create_bin_file(&np->kobj, &pp->attr);
/*
* 如果创建失败(rc不为0), 使用WARN宏打印一条内核警告信息.
* %pOF是特殊的格式化字符, 用于打印设备树节点的全路径.
*/
WARN(rc, "error adding attribute %s to node %pOF\n", pp->name, np);
/*
* 返回创建操作的结果.
*/
return rc;
}

drivers/of/of_reserved_mem.c

静态全局变量

1
2
3
4
static struct reserved_mem reserved_mem_array[MAX_RESERVED_REGIONS] __initdata;
static struct reserved_mem *reserved_mem __refdata = reserved_mem_array;
static int total_reserved_mem_cnt = MAX_RESERVED_REGIONS;
static int reserved_mem_count;

__reserved_mem_check_root 检查 /reserved-memory 中提供的 #size、#address 单元格是否与当前实现支持的值匹配,并检查是否提供了 ranges 属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*
* __reserved_mem_check_root() - 检查 /reserved-memory 中提供的 #size、#address 单元格是否与当前实现支持的值匹配,并检查是否提供了 ranges 属性
*/
static int __init __reserved_mem_check_root(unsigned long node)
{
const __be32 *prop;

prop = of_get_flat_dt_prop(node, "#size-cells", NULL);
if (!prop || be32_to_cpup(prop) != dt_root_size_cells)
return -EINVAL;

prop = of_get_flat_dt_prop(node, "#address-cells", NULL);
if (!prop || be32_to_cpup(prop) != dt_root_addr_cells)
return -EINVAL;

prop = of_get_flat_dt_prop(node, "ranges", NULL);
if (!prop)
return -EINVAL;
return 0;
}

early_init_dt_alloc_reserved_memory_arch 早期初始化设备树分配保留内存

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
static int __init early_init_dt_alloc_reserved_memory_arch(phys_addr_t size,
phys_addr_t align, phys_addr_t start, phys_addr_t end, bool nomap,
phys_addr_t *res_base)
{
phys_addr_t base;
int err = 0;
//#define MEMBLOCK_ALLOC_ANYWHERE (~(phys_addr_t)0) //Memblock 在任何地方分配
end = !end ? MEMBLOCK_ALLOC_ANYWHERE : end;
//32
align = !align ? SMP_CACHE_BYTES : align;
base = memblock_phys_alloc_range(size, align, start, end);
if (!base)
return -ENOMEM;

*res_base = base;
if (nomap) {
err = memblock_mark_nomap(base, size);
if (err)
memblock_phys_free(base, size);
}

if (!err)
kmemleak_ignore_phys(base);

return err;
}

__reserved_mem_init_node 调用区域特定的预留内存初始化代码

  • arch/arm/kernel/vmlinux.lds
1
2
__reservedmem_of_table = .; 
KEEP(*(__reservedmem_of_table)) KEEP(*(__reservedmem_of_table_end)) . = ALIGN(8);
  • vmlinux.map,这里匹配了kernel/dma/coherent.c的代码
1
2
3
4
5
6
7
               0x00000000c02a1308                . = ALIGN (0x8)
0x00000000c02a1308 __reservedmem_of_table = .
*(__reservedmem_of_table)
__reservedmem_of_table
0x00000000c02a1308 0xc4 kernel/dma/coherent.o
*(__reservedmem_of_table_end)
__reservedmem_of_table_end
  • __reservedmem_of_table 是一个符号,表示预留内存区域的设备树兼容性表的起始地址。
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
/*
* __reserved_mem_init_node() - 调用特定于区域的保留内存初始化代码
*/
static int __init __reserved_mem_init_node(struct reserved_mem *rmem)
{
extern const struct of_device_id __reservedmem_of_table[];
const struct of_device_id *i;
int ret = -ENOENT;

for (i = __reservedmem_of_table; i < &__rmem_of_table_sentinel; i++) {
reservedmem_of_init_fn initfn = i->data;
const char *compat = i->compatible;

if (!of_flat_dt_is_compatible(rmem->fdt_node, compat))
continue;

ret = initfn(rmem);
if (ret == 0) {
pr_info("initialized node %s, compatible id %s\n",
rmem->name, compat);
break;
}
}
return ret;
}

fdt_init_reserved_mem_node 此函数用于调用预留内存区域的特定于区域的初始化函数。

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
/**
* fdt_init_reserved_mem_node() - 初始化预留内存区域
* @rmem:reserved_mem要初始化的内存区域的 struct。
*
* 此函数用于调用预留内存区域的特定于区域的初始化函数。
*/
static void __init fdt_init_reserved_mem_node(struct reserved_mem *rmem)
{
unsigned long node = rmem->fdt_node;
int err = 0;
bool nomap;

nomap = of_get_flat_dt_prop(node, "no-map", NULL) != NULL;
//调用区域特定的预留内存初始化代码 rDMA
err = __reserved_mem_init_node(rmem);
if (err != 0 && err != -ENOENT) {
pr_info("node %s compatible matching fail\n", rmem->name);
if (nomap)
memblock_clear_nomap(rmem->base, rmem->size);
else
memblock_phys_free(rmem->base, rmem->size);
} else {
phys_addr_t end = rmem->base + rmem->size - 1;
bool reusable =
(of_get_flat_dt_prop(node, "reusable", NULL)) != NULL;

pr_info("%pa..%pa (%lu KiB) %s %s %s\n",
&rmem->base, &end, (unsigned long)(rmem->size / SZ_1K),
nomap ? "nomap" : "map",
reusable ? "reusable" : "non-reusable",
rmem->name ? rmem->name : "unknown");
}
}

fdt_reserved_mem_save_node 保存 FDT 节点以进行第二次通过初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/*
* fdt_reserved_mem_save_node() - 保存 FDT 节点以进行第二次通过初始化
*/
static void __init fdt_reserved_mem_save_node(unsigned long node, const char *uname,
phys_addr_t base, phys_addr_t size)
{
struct reserved_mem *rmem = &reserved_mem[reserved_mem_count];
//static int total_reserved_mem_cnt = MAX_RESERVED_REGIONS;//64
if (reserved_mem_count == total_reserved_mem_cnt) {
pr_err("not enough space for all defined regions.\n");
return;
}

rmem->fdt_node = node;
rmem->name = uname;
rmem->base = base;
rmem->size = size;

/*调用特定于区域的初始化函数 */
fdt_init_reserved_mem_node(rmem);

reserved_mem_count++;
return;
}

__reserved_mem_alloc_size 预留内存分配

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
/*
* __reserved_mem_alloc_size() - allocate reserved memory described by
* 'size', 'alignment' and 'alloc-ranges' properties.
*/
static int __init __reserved_mem_alloc_size(unsigned long node, const char *uname)
{
int t_len = (dt_root_addr_cells + dt_root_size_cells) * sizeof(__be32);
phys_addr_t start = 0, end = 0;
phys_addr_t base = 0, align = 0, size;
int len;
const __be32 *prop;
bool nomap;
int ret;

prop = of_get_flat_dt_prop(node, "size", &len);
if (!prop)
return -EINVAL;
//检查 size 属性的长度是否与 #size-cells 属性匹配
if (len != dt_root_size_cells * sizeof(__be32)) {
pr_err("invalid size property in '%s' node.\n", uname);
return -EINVAL;
}
size = dt_mem_next_cell(dt_root_size_cells, &prop);

prop = of_get_flat_dt_prop(node, "alignment", &len);
if (prop) {
if (len != dt_root_addr_cells * sizeof(__be32)) {
pr_err("invalid alignment property in '%s' node.\n",
uname);
return -EINVAL;
}
align = dt_mem_next_cell(dt_root_addr_cells, &prop);
}

nomap = of_get_flat_dt_prop(node, "no-map", NULL) != NULL;

/* 需要调整对齐方式以满足 CMA 要求
* CMA(分配大块的连续物理内存),需要MMU
*/
if (IS_ENABLED(CONFIG_CMA)
&& of_flat_dt_is_compatible(node, "shared-dma-pool")
&& of_get_flat_dt_prop(node, "reusable", NULL)
&& !nomap)
align = max_t(phys_addr_t, align, CMA_MIN_ALIGNMENT_BYTES);

prop = of_get_flat_dt_prop(node, "alloc-ranges", &len);
if (prop) {

if (len % t_len != 0) {
pr_err("invalid alloc-ranges property in '%s', skipping node.\n",
uname);
return -EINVAL;
}

while (len > 0) {
start = dt_mem_next_cell(dt_root_addr_cells, &prop);
end = start + dt_mem_next_cell(dt_root_size_cells,
&prop);

base = 0;
ret = __reserved_mem_alloc_in_range(size, align,
start, end, nomap, &base);
if (ret == 0) {
pr_debug("allocated memory for '%s' node: base %pa, size %lu MiB\n",
uname, &base,
(unsigned long)(size / SZ_1M));
break;
}
len -= t_len;
}

} else {
//早期初始化设备树分配保留内存
ret = early_init_dt_alloc_reserved_memory_arch(size, align,
0, 0, nomap, &base);
if (ret == 0)
pr_debug("allocated memory for '%s' node: base %pa, size %lu MiB\n",
uname, &base, (unsigned long)(size / SZ_1M));
}

if (base == 0) {
pr_err("failed to allocate memory for node '%s': size %lu MiB\n",
uname, (unsigned long)(size / SZ_1M));
return -ENOMEM;
}

/* 在 reserved_mem 数组中保存区域*/
fdt_reserved_mem_save_node(node, uname, base, size);
return 0;
}

fdt_scan_reserved_mem 扫描单个 FDT 节点以获取预留内存

1
2
3
4
5
6
7
8
9
10
11
12
reserved-memory {
#address-cells = <0x01>;
#size-cells = <0x01>;
ranges;

linux,cma {
compatible = "shared-dma-pool";
no-map;
size = <0x100000>;
linux,dma-default;
};
};
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
/*
* fdt_scan_reserved_mem() - 扫描单个 FDT 节点以获取预留内存
*/
int __init fdt_scan_reserved_mem(void)
{
int node, child;
int dynamic_nodes_cnt = 0, count = 0;
int dynamic_nodes[MAX_RESERVED_REGIONS];
const void *fdt = initial_boot_params;

node = fdt_path_offset(fdt, "/reserved-memory");
if (node < 0)
return -ENODEV;
//检查 /reserved-memory 中提供的 #size、#address 单元格是否与当前实现支持的值匹配,并检查是否提供了 ranges 属性
if (__reserved_mem_check_root(node) != 0) {
pr_err("Reserved memory: unsupported node format, ignoring\n");
return -EINVAL;
}
//遍历子节点 循环获取size大小
fdt_for_each_subnode(child, fdt, node) {
const char *uname;
int err;

if (!of_fdt_device_is_available(fdt, child))
continue;

uname = fdt_get_name(fdt, child, NULL);
//静态预留内存 需要具有<reg>属性
err = __reserved_mem_reserve_reg(child, uname);
if (!err)
count++;
/*
* 将动态放置区域的节点保存到一个数组中,该数组将在所有静态放置的区域被保留或标记为 no-map 后立即用于分配。
* 这样做是为了避免从静态放置的区域之一动态分配。
*/
if (err == -ENOENT && of_get_flat_dt_prop(child, "size", NULL)) {
dynamic_nodes[dynamic_nodes_cnt] = child;
dynamic_nodes_cnt++;
}
}
//动态预留内存
for (int i = 0; i < dynamic_nodes_cnt; i++) {
const char *uname;
int err;

child = dynamic_nodes[i];
uname = fdt_get_name(fdt, child, NULL);
err = __reserved_mem_alloc_size(child, uname);
if (!err)
count++;
}
total_reserved_mem_cnt = count;
return 0;
}

alloc_reserved_mem_array 使用 memblock 为 reserved_mem 数组分配内存

  • 这里需要重新动态分配给reserved_mem,因为之前的静态数组会在初始化结束后清空
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
/*
* alloc_reserved_mem_array() - 使用 memblock 为 reserved_mem 数组分配内存
*
* 此函数用于根据 DT 中定义的保留内存区域总数为 reserved_mem 数组分配内存。
* 分配新数组后,存储在初始静态数组中的信息将复制到此新数组,并从此点开始使用新数组。
*/
static void __init alloc_reserved_mem_array(void)
{
struct reserved_mem *new_array;
size_t alloc_size, copy_size, memset_size;
//fdt_scan_reserved_mem()重新赋值了total_reserved_mem_cnt=扫描到的静态内存数+动态内存数
alloc_size = array_size(total_reserved_mem_cnt, sizeof(*new_array));
if (alloc_size == SIZE_MAX) {
pr_err("Failed to allocate memory for reserved_mem array with err: %d", -EOVERFLOW);
return;
}
//为reserved_mem数组分配内存
new_array = memblock_alloc(alloc_size, SMP_CACHE_BYTES);
if (!new_array) {
pr_err("Failed to allocate memory for reserved_mem array with err: %d", -ENOMEM);
return;
}
//fdt_reserved_mem_save_node()写入reserved_mem_count的值,为FDT定义的预留内存大小
copy_size = array_size(reserved_mem_count, sizeof(*new_array));
if (copy_size == SIZE_MAX) {
memblock_free(new_array, alloc_size);
total_reserved_mem_cnt = MAX_RESERVED_REGIONS;
pr_err("Failed to allocate memory for reserved_mem array with err: %d", -EOVERFLOW);
return;
}

memset_size = alloc_size - copy_size;
//将静态数组中的信息复制到新数组中
memcpy(new_array, reserved_mem, copy_size);
//将新数组中未使用的内存区域清零
memset(new_array + reserved_mem_count, 0, memset_size);
//将reserved_mem指针指向新数组
reserved_mem = new_array;
}

__rmem_check_for_overlap

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
static void __init __rmem_check_for_overlap(void)
{
int i;

if (reserved_mem_count < 2)
return;

sort(reserved_mem, reserved_mem_count, sizeof(reserved_mem[0]),
__rmem_cmp, NULL);
for (i = 0; i < reserved_mem_count - 1; i++) {
struct reserved_mem *this, *next;

this = &reserved_mem[i];
next = &reserved_mem[i + 1];

if (this->base + this->size > next->base) {
phys_addr_t this_end, next_end;

this_end = this->base + this->size;
next_end = next->base + next->size;
pr_err("OVERLAP DETECTED!\n%s (%pa--%pa) overlaps with %s (%pa--%pa)\n",
this->name, &this->base, &this_end,
next->name, &next->base, &next_end);
}
}
}

fdt_scan_reserved_mem_reg_nodes 扫描 DT 并存储使用 “reg” 属性定义的保留内存区域的信息

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
/**
* fdt_scan_reserved_mem_reg_nodes() - 存储 “reg” 定义的保留内存区域的信息。
*
* 此函数用于扫描 DT 并存储使用 “reg” 属性定义的保留内存区域的信息。
* 通过调用 fdt_reserved_mem_save_node() 函数,区域节点号、名称、基址和大小都存储在 reserved_mem 数组中。
*/
void __init fdt_scan_reserved_mem_reg_nodes(void)
{
int t_len = (dt_root_addr_cells + dt_root_size_cells) * sizeof(__be32);
const void *fdt = initial_boot_params;
phys_addr_t base, size;
const __be32 *prop;
int node, child;
int len;

if (!fdt)
return;

node = fdt_path_offset(fdt, "/reserved-memory");
if (node < 0) {
pr_info("Reserved memory: No reserved-memory node in the DT\n");
return;
}

/*尝试动态分配新的 reserved_mem 数组*/
alloc_reserved_mem_array(); //使用 memblock 为 reserved_mem 数组分配内存

if (__reserved_mem_check_root(node)) {
pr_err("Reserved memory: unsupported node format, ignoring\n");
return;
}

fdt_for_each_subnode(child, fdt, node) {
const char *uname;

prop = of_get_flat_dt_prop(child, "reg", &len);
if (!prop)
continue;
if (!of_fdt_device_is_available(fdt, child))
continue;

uname = fdt_get_name(fdt, child, NULL);
if (len && len % t_len != 0) {
pr_err("Reserved memory: invalid reg property in '%s', skipping node.\n",
uname);
continue;
}

if (len > t_len)
pr_warn("%s() ignores %d regions in node '%s'\n",
__func__, len / t_len - 1, uname);

base = dt_mem_next_cell(dt_root_addr_cells, &prop);
size = dt_mem_next_cell(dt_root_size_cells, &prop);

if (size)
fdt_reserved_mem_save_node(child, uname, base, size);
}

/* 检查重叠的预留区域 */
__rmem_check_for_overlap();
}

drivers/of/platform.c

of_platform_notify: 处理设备树重配置事件的通知回调函数

该函数是核心的事件处理程序。当设备树发生变化时,它被内核的通知器(notifier)框架调用,根据变化的类型(添加或移除节点)来创建或销毁平台设备。

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
/*
* 静态函数 of_platform_notify
* 它作为通知链(notifier chain)中的回调函数.
* @nb: 指向一个 notifier_block 结构体的指针, 即下面定义的 platform_of_notifier.
* @action: 一个无符号长整型数, 表示发生的事件类型(如节点添加/删除).
* @arg: 一个 void 指针, 指向包含事件具体数据的结构体 (在这里是 of_reconfig_data).
* @return: 返回一个整数, 通常是 NOTIFY_OK 表示成功处理, 或 NOTIFY_BAD 表示有问题.
*/
static int of_platform_notify(struct notifier_block *nb,
unsigned long action, void *arg)
{
/*
* 将通用的 void* 参数 arg 强制类型转换为具体的 struct of_reconfig_data 指针.
* of_reconfig_data 结构体包含了重配置事件的详细信息, 如被改变的设备树节点 (dn).
*/
struct of_reconfig_data *rd = arg;
/*
* 定义两个指向 platform_device 的指针.
* pdev_parent 用于存储父平台设备.
* pdev 用于存储与被改变节点对应的平台设备.
*/
struct platform_device *pdev_parent, *pdev;
/*
* 定义一个布尔变量, 用于在销毁设备时检查其是否还有子设备存在.
*/
bool children_left;
/*
* 定义一个指向 device_node 的指针, 用于存储父节点.
*/
struct device_node *parent;

/*
* 使用 switch 语句来判断具体的事件变化类型.
* of_reconfig_get_state_change 是一个辅助函数, 用于从 action 和 rd 中解析出明确的事件状态.
*/
switch (of_reconfig_get_state_change(action, rd)) {
/*
* 情况一: 设备树中添加了一个新的节点.
*/
case OF_RECONFIG_CHANGE_ADD:
/*
* 获取被添加节点 (rd->dn) 的父节点.
*/
parent = rd->dn->parent;
/*
* 验证父节点是否为一个总线. 一个节点要能生成设备, 其父节点必须是根节点,
* 或者是一个已经被标记为 OF_POPULATED_BUS (已填充的总线) 的节点.
* 这样做是为了防止在不应有设备的地方 (如 /clocks 节点下) 创建设备.
*/
if (!of_node_is_root(parent) &&
!of_node_check_flag(parent, OF_POPULATED_BUS))
/*
* 如果父节点不满足条件, 说明这个通知不是给平台设备模型的, 直接返回成功, 让其他通知器处理.
*/
return NOTIFY_OK;

/*
* 检查被添加的节点是否已经被标记为 OF_POPULATED (已填充).
* 这可能发生在驱动程序手动调用 of_populate() 函数来创建设备的情况下.
* 如果是, 我们就不需要重复创建了.
*/
if (of_node_check_flag(rd->dn, OF_POPULATED))
return NOTIFY_OK;

/*
* 清除 fwnode (固件节点) 的 FWNODE_FLAG_NOT_DEVICE 标志.
* fwnode 是 device_node 的一个通用抽象. 清除此标志是为了确保
* 内核的设备链接(fw_devlink)功能能够正确地将此节点识别为一个设备, 并处理它与其他设备的依赖关系.
*/
rd->dn->fwnode.flags &= ~FWNODE_FLAG_NOT_DEVICE;
/*
* 通过父设备树节点(parent)查找对应的平台设备(platform_device).
* 这个函数会增加找到的设备的引用计数. 如果父节点是根节点, 找不到对应的平台设备, pdev_parent 会是 NULL.
*/
pdev_parent = of_find_device_by_node(parent);
/*
* 调用 of_platform_device_create 函数, 从设备树节点(rd->dn)创建对应的平台设备.
* 第三个参数是新创建设备的父设备. 如果 pdev_parent 有效, 则使用它, 否则父设备为 NULL.
*/
pdev = of_platform_device_create(rd->dn, NULL,
pdev_parent ? &pdev_parent->dev : NULL);
/*
* of_find_device_by_node 增加了一次 pdev_parent 的引用计数, 这里我们用完后需要减少一次, 防止内存泄漏.
*/
platform_device_put(pdev_parent);

/*
* 检查平台设备是否创建成功.
*/
if (pdev == NULL) {
/*
* 如果创建失败, 打印错误信息. '%pOF' 是一个特殊的格式说明符, 用于打印设备树节点的全路径名.
*/
pr_err("%s: failed to create for '%pOF'\n",
__func__, rd->dn);
/*
* of_platform_device_create 内部已经处理了错误码, 我们这里返回一个通用的错误.
* notifier_from_errno 将Linux错误码(-EINVAL)转换为通知链能理解的返回值.
*/
return notifier_from_errno(-EINVAL);
}
/*
* 退出 switch.
*/
break;

/*
* 情况二: 设备树中移除了一个节点.
*/
case OF_RECONFIG_CHANGE_REMOVE:

/*
* 检查被移除的节点是否本来就没有被填充(创建设备).
* 如果是, 那就无事可做.
*/
if (!of_node_check_flag(rd->dn, OF_POPULATED))
return NOTIFY_OK;

/*
* 通过被移除的设备树节点 (rd->dn) 查找对应的平台设备 (pdev).
* 此函数同样会增加找到的设备的引用计数.
*/
pdev = of_find_device_by_node(rd->dn);
/*
* 如果没有找到对应的平台设备, 说明这个节点不由我们管理, 直接返回.
*/
if (pdev == NULL)
return NOTIFY_OK;

/*
* 调用 of_platform_device_destroy 来销毁平台设备.
* 这个函数会注销设备, 与驱动解绑, 并减少设备的引用计数.
*/
of_platform_device_destroy(&pdev->dev, &children_left);

/*
* of_find_device_by_node 增加了一次 pdev 的引用计数,
* 这里我们必须减少它来平衡引用计数.
*/
platform_device_put(pdev);
/*
* 退出 switch.
*/
break;
}

/*
* 默认情况下, 返回 NOTIFY_OK 表示我们已经成功处理了该通知.
*/
return NOTIFY_OK;
}

platform_of_notifier: 定义通知器块

这是一个静态的全局变量,它定义了一个“通知器块”(notifier_block),将上面实现的 of_platform_notify 函数注册到内核的通知系统中。

1
2
3
4
5
6
7
8
9
10
11
12
/*
* 定义一个静态的 notifier_block 结构体变量 platform_of_notifier.
* 'static' 关键字使其仅在当前文件内可见.
*/
static struct notifier_block platform_of_notifier = {
/*
* .notifier_call 是结构体的一个成员, 它是一个函数指针.
* 这里将其指向我们上面实现的 of_platform_notify 函数.
* 当相关的通知事件发生时, 内核就会调用这个指针指向的函数.
*/
.notifier_call = of_platform_notify,
};

of_platform_register_reconfig_notifier: 注册设备树重配置通知器

这是一个简单的包装函数,它的唯一作用就是在内核初始化期间,调用内核API将上面定义的 platform_of_notifier 注册到设备树重配置的通知链中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/*
* 函数 of_platform_register_reconfig_notifier
* 它没有参数和返回值.
* 它的作用是注册上面定义的通知器块.
*/
void of_platform_register_reconfig_notifier(void)
{
/*
* 调用 of_reconfig_notifier_register 函数, 将 platform_of_notifier 注册到设备树重配置的通知链上.
* 从此以后, 每当有设备树重配置事件发生, of_platform_notify 函数就会被自动调用.
* WARN_ON 是一个宏, 如果注册函数返回错误(非零值), 它会在内核日志中打印一个警告信息.
*/
WARN_ON(of_reconfig_notifier_register(&platform_of_notifier));
}

of_platform_populate: 从设备树数据填充平台设备

此函数是Linux内核中执行”设备树填充”(Device Tree Population)操作的核心引擎。它的根本作用是遍历一个指定的设备树节点的所有子节点, 并为每一个符合条件的子节点创建一个platform_device实例。这个函数是连接静态硬件描述(设备树)和动态内核对象(设备驱动模型)之间的关键桥梁

of_platform_populate的原理可以被理解为一个受控的、单层遍历的设备实例化器。与旧的、可能递归深入的函数不同, 此函数只处理其直接子节点, 这使得设备创建的逻辑更清晰、更可控。

工作流程:

  1. 确定根节点: 函数首先确定从哪里开始遍历。如果调用者提供了一个root节点, 它就从该节点开始; 如果rootNULL, 它会默认从设备树的绝对根路径/开始。它会增加根节点的引用计数, 以确保在遍历过程中该节点不会被意外释放。
  2. 暂停设备链接同步: 在开始批量创建设备之前, 它会调用device_links_supplier_sync_state_pause()。这是一个性能优化, 当一次性创建大量设备时, 暂时禁用设备之间链接状态的同步可以减少不必要的开销。
  3. 遍历子节点: 这是最核心的一步。它使用for_each_child_of_node_scoped宏来安全地遍历root节点的所有直接子节点。
  4. 调用创建函数: 对于每一个子节点child, 它都会调用of_platform_bus_create函数。of_platform_bus_create(此文件中未显示, 但其行为是关键)会执行以下判断:
    • 它会检查child节点是否拥有一个compatible属性。如果没有, 它会被忽略。
    • 它会检查childcompatible属性是否与传入的matches列表(即总线类型列表)相匹配。
    • 如果childcompatible不在matches列表中, 那么of_platform_bus_create就会认为这个child是一个独立的、需要被实例化的设备, 于是它会为这个child创建一个platform_device实例。
    • 如果childcompatiblematches列表中, 那么它会被认为是一个新的”总线”或”容器”, of_platform_bus_create不会为它创建设备, 而是会**递归地调用of_platform_populate**来处理这个新总线下的子节点。
  5. 恢复与标记: 遍历完成后, 它会调用device_links_supplier_sync_state_resume()恢复设备链接的同步。然后, 它会在root节点上设置一个OF_POPULATED_BUS标志, 表明这个总线节点下的设备已经被填充过了, 以避免重复操作。
  6. 释放引用: 最后, 它调用of_node_put来减少在第一步中增加的根节点引用计数。

在STM32H750上的应用:

当我们之前分析的of_platform_default_populate调用of_platform_populate(NULL, match_table, NULL, NULL)时:

  1. 此函数会从设备树的根(/)开始。
  2. 它找到/soc节点。of_platform_bus_create会被调用。
  3. of_platform_bus_create内部, 它发现/soc节点的compatible = "simple-bus"match_table匹配。因此, 它不会为/soc创建设备, 而是会递归调用of_platform_populate, 并将/soc作为新的root节点。
  4. 现在, of_platform_populate/soc为根节点开始新一轮的遍历。
  5. 它遍历/soc的子节点, 如spi1, i2c1等。
  6. 对于spi1, of_platform_bus_create会被调用。它检查到spi1compatible = "st,stm32h7-spi", 这match_table中。因此, of_platform_bus_create会为spi1创建一个platform_device
  7. 这个过程对所有/soc下的外设节点重复进行。
  8. 最终结果是, 设备树中所有代表具体硬件外设的节点都被实例化成了内核可以管理的platform_device对象, 为后续驱动的probe过程铺平了道路。
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
/**
* of_platform_populate() - 从设备树数据填充平台设备
* @root: 开始探测的第一级节点的父节点, 或者为NULL表示从树的根开始
* @matches: 匹配表, 为NULL则使用默认值
* @lookup: 用于将ID和platform_data与设备节点匹配的辅助数据表
* @parent: 新创建设备的父设备, 为NULL表示是顶层设备
*
* 与of_platform_bus_probe()类似, 此函数遍历设备树并从节点创建设备.
* 不同之处在于, 它遵循现代惯例, 要求所有设备节点都有一个'compatible'属性,
* 并且它适合于创建作为根节点子节点的设备 (of_platform_bus_probe只会
* 创建由@matches参数选择的根的子节点).
*
* 新的板级支持代码应该使用此函数而不是of_platform_bus_probe().
*
* 返回: 成功时返回0, 失败时返回 < 0 的值.
*/
int of_platform_populate(struct device_node *root,
const struct of_device_id *matches,
const struct of_dev_auxdata *lookup,
struct device *parent)
{
int rc = 0;

/*
* 确定遍历的起始点.
* 如果提供了root, 就使用它; 否则, 从根路径 "/" 开始查找.
* of_node_get()会增加节点的引用计数, 防止在遍历时被释放.
*/
root = root ? of_node_get(root) : of_find_node_by_path("/");
if (!root)
return -EINVAL; /* 如果找不到根节点, 返回错误. */

/* 打印调试信息. */
pr_debug("%s()\n", __func__);
pr_debug(" starting at: %pOF\n", root);

/*
* 在批量创建设备前, 暂停设备链接的同步, 这是一个性能优化.
*/
device_links_supplier_sync_state_pause();
/*
* 使用 for_each_child_of_node_scoped 宏安全地遍历 root 节点的所有直接子节点.
* _scoped 版本确保了对子节点(child)的引用在每次循环迭代结束时被自动释放.
*/
for_each_child_of_node_scoped(root, child) {
/*
* 为每一个子节点调用 of_platform_bus_create.
* 这个内部函数会根据 'matches' 列表决定是创建设备还是递归填充.
*/
rc = of_platform_bus_create(child, matches, lookup, parent, true);
if (rc)
break; /* 如果任何一次创建失败, 立即停止并退出循环. */
}
/*
* 恢复设备链接的同步.
*/
device_links_supplier_sync_state_resume();

/*
* 在 root 节点上设置 OF_POPULATED_BUS 标志,
* 表明这个总线/容器节点下的设备已经被处理过了, 避免重复填充.
*/
of_node_set_flag(root, OF_POPULATED_BUS);

/*
* 调用 of_node_put() 来释放对 root 节点的引用, 与前面的 of_node_get() 配对.
*/
of_node_put(root);
return rc; /* 返回最终结果. */
}
/* 将此重要的API函数导出, 供内核其他部分(如架构相关的启动代码)使用. */
EXPORT_SYMBOL_GPL(of_platform_populate);

平台设备默认填充 (Population)

此代码片段是Linux内核设备树(Device Tree)处理和平台设备模型之间至关重要的粘合剂。它的核心作用是在内核启动的早期阶段, 自动地、有选择地遍历设备树, 并为其中的硬件节点创建相应的struct platform_device实例。这些platform_device实例是内核设备模型中的实体, 后续具体的平台驱动程序(platform driver)将会被绑定到这些实体上。

这个过程的根本原理可以被描述为**”按规则的硬件抽象实例化”**。内核不会为设备树中的每一个节点都创建一个平台设备。相反, of_platform_default_populate_init函数作为一个总调度器, 按照一套预设的规则和优先级来执行”填充”(populate)操作:

  1. 处理特殊情况: 它首先会处理一些特定于体系结构(如PowerPC)或特殊用途的节点, 例如为早期启动的显示设备或特定类型的预留内存(reserved-memory)区域手动创建平台设备。
  2. 处理固件节点: 它会查找/firmware节点并填充其下的所有子节点, 为它们创建设备。
  3. 处理总线节点: 这是最关键的一步。它调用of_platform_default_populate, 这个函数定义了一系列”总线”或”容器”类型的compatible字符串(如simple-bus, arm,amba-bus等)。然后, 它调用更底层的of_platform_populate函数, 该函数会:
    a. 从设备树的根开始, 查找所有匹配这个总线compatible列表的节点。
    b. 对于每一个找到的总线节点, 它会遍历其下一级的子节点
    c. 如果一个子节点本身看起来像一个独立的设备(即它有自己的compatible属性, 但这个属性在总线列表中), 那么内核就会为这个子节点创建一个platform_device实例并注册到系统中。

这个过程完成后, 原本只是静态数据描述的设备树, 就被转换成了内核中一个个”活”的、可以与驱动程序交互的struct device对象。arch_initcall_sync确保了这个关键的实例化过程在所有平台驱动程序开始探测之前就已经完成。


在STM32H750上的应用:

STM32H750的设备树(.dts文件)描述了其所有的片上外设。典型的结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/ {
soc {
compatible = "simple-bus"; // 匹配 of_platform_default_populate 的规则
#address-cells = <1>;
#size-cells = <1>;

spi1: spi@40013000 {
compatible = "st,stm32h7-spi"; // 这是一个设备, 将被实例化
reg = <0x40013000 0x400>;
...
};

i2c1: i2c@40012000 {
compatible = "st,stm32-i2c"; // 这也是一个设备, 将被实例化
reg = <0x40012000 0x400>;
...
};
// ... 其他外设
};
};

of_platform_default_populate_init被调用时:

  1. 最后的of_platform_default_populate(NULL, NULL, NULL)会被执行。
  2. 它会找到soc节点, 因为其compatible是”simple-bus”。
  3. 底层的of_platform_populate会开始遍历soc的子节点。
  4. 它看到spi1节点, 发现其compatible是”st,stm32h7-spi”, 这在总线列表中。于是, 内核会为spi1创建一个platform_device
  5. 它看到i2c1节点, 同样为其创建一个platform_device
  6. 这个过程持续进行, 为所有在soc下定义的、具有compatible属性的外设创建平台设备。
  7. 稍后, 当stm32h7-spi驱动和stm32-i2c驱动被加载时, 内核平台总线核心就会发现这些已经创建好的设备, 并调用相应驱动的probe函数。

因此, 这段代码是所有STM32片上外设驱动能够被成功探测和加载的绝对前提


代码详解

of_platform_default_populate: 默认填充函数

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
/**
* of_platform_default_populate - 使用默认的匹配表来填充平台设备
* @root: 开始填充的根节点 (如果为NULL, 则从/开始)
* @lookup: 辅助数据查找表 (可选)
* @parent: 新创建设备的父设备 (可选)
*/
int of_platform_default_populate(struct device_node *root,
const struct of_dev_auxdata *lookup,
struct device *parent)
{
/*
* 定义一个静态的匹配表. 这里列出的是所有被认为是"总线"或"容器"的
* compatible字符串. 当填充过程遇到这些节点时, 它会继续深入其子节点,
* 而不是为这个节点本身创建设备.
*/
static const struct of_device_id match_table[] = {
{ .compatible = "simple-bus", },
{ .compatible = "simple-mfd", }, // Multi-Function Device
{ .compatible = "isa", },
#ifdef CONFIG_ARM_AMBA
{ .compatible = "arm,amba-bus", }, // AMBA是ARM的总线架构
#endif /* CONFIG_ARM_AMBA */
{} /* 空条目, 标志列表结束. */
};

/*
* 调用更底层的of_platform_populate函数, 并将上面定义的总线匹配表传递给它.
* 这个底层函数会执行实际的遍历和设备创建工作.
*/
return of_platform_populate(root, match_table, lookup, parent);
}
EXPORT_SYMBOL_GPL(of_platform_default_populate);

of_platform_default_populate_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
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
/*
* 定义一个用于匹配特定预留内存区域的compatible字符串列表.
*/
static const struct of_device_id reserved_mem_matches[] = {
{ .compatible = "phram" }, // 物理RAM设备
{ .compatible = "qcom,rmtfs-mem" }, // 高通相关
// ... 其他特定用途的预留内存
{}
};

/* 内核启动时调用的初始化函数. */
static int __init of_platform_default_populate_init(void)
{
struct device_node *node;

/* 暂时暂停设备链接的同步, 可能会提高批量创建设备时的性能. */
device_links_supplier_sync_state_pause();

if (IS_ENABLED(CONFIG_PPC)) {
/* --- PowerPC 架构的特殊处理逻辑 --- */
// ... (为STM32场景省略详细分析)
} else {
/* --- 通用架构 (包括ARM) 的处理逻辑 --- */

/*
* 步骤1: 为特定的预留内存区域创建设备.
* for_each_matching_node会遍历整个设备树, 查找所有匹配reserved_mem_matches
* 列表的节点, 并为它们一一创建平台设备.
*/
for_each_matching_node(node, reserved_mem_matches)
of_platform_device_create(node, NULL, NULL);

/* 步骤2: 填充/firmware下的设备. */
node = of_find_node_by_path("/firmware");
if (node) {
of_platform_populate(node, NULL, NULL, NULL);
of_node_put(node); // 释放对节点的引用.
}

/* 步骤3: 处理 /chosen 节点下的简单帧缓冲(simple-framebuffer). */
node = of_get_compatible_child(of_chosen, "simple-framebuffer");
if (node) {
/*
* 禁用通用的系统帧缓冲(sysfb), 因为我们即将手动创建一个更具体的设备,
* 以防止后续产生重复的、冲突的帧缓冲设备.
*/
sysfb_disable(NULL);
of_platform_device_create(node, NULL, NULL);
of_node_put(node);
}

/*
* 步骤4: 最终的、全局的默认填充.
* 调用我们上面分析的函数, 从根节点开始, 根据总线列表,
* 递归地为所有符合条件的硬件节点创建平台设备.
* 这是实例化STM32上绝大多数外设的关键调用.
*/
of_platform_default_populate(NULL, NULL, NULL);
}

return 0;
}
/*
* arch_initcall_sync: 这是一个内核初始化宏, 确保此函数在启动时被调用.
* 它属于一个较早的初始化级别, 并且_sync后缀确保在它执行完成之前,
* 下一个级别的initcall不会开始. 这对于保证设备先于驱动被创建至关重要.
*/
arch_initcall_sync(of_platform_default_populate_init);

of_platform_device_create: 从设备树节点创建平台设备

此代码片段展示了Linux内核中负责将一个设备树(Device Tree)节点实例化为具体的platform_device内核对象的”工厂”函数。这是内核设备模型与设备树硬件描述之间最直接、最关键的链接点。当内核的填充(population)代码(如of_platform_populate)决定一个设备树节点代表一个需要被管理的硬件设备时, 就会调用这个API。

它的核心原理可以被理解为一个严格的、带状态追踪的四步实例化流程:

  1. 资格审查: 在做任何事情之前, 函数首先检查该设备树节点是否”合格”。
    • of_device_is_available(np): 检查设备树中该节点的status属性是否为”okay”或”ok”。如果status是”disabled”, 则该设备被认为是禁用的, 不应被创建。
    • of_node_test_and_set_flag(np, OF_POPULATED): 这是一个原子操作, 用来检查并设置OF_POPULATED标志。它能确保对于同一个设备树节点, platform_device只会被创建一次。如果该标志已被设置, 意味着设备已被创建, 函数会立即返回, 从而防止了系统中出现代表同一硬件的重复设备对象。
  2. 分配与初始化: 如果资格审查通过, 函数调用of_device_alloc来在内存中分配一个struct platform_device对象, 并用设备树节点中的信息(如名称)对其进行基础初始化。然后, 它会为这个新设备设置一些关键的默认属性:
    • 设置默认的DMA掩码。
    • 将其.bus指针明确地指向platform_bus_type, 这正式将其”身份”定义为一个平台设备。
    • 处理platform_data(一个用于传递非设备树数据的遗留机制)。
  3. 注册到内核: 调用of_device_add将这个新创建和配置好的设备正式注册到内核的设备模型核心中。这是最关键的一步, 从此刻起:
    • 该设备会出现在sysfs文件系统的/sys/devices/platform/目录下。
    • 平台总线(platform_bus_type)会被通知有一个新设备加入, 并会立即触发总线的match流程, 尝试为这个新设备寻找一个匹配的驱动程序。
  4. 健壮的错误处理: 如果在注册步骤(of_device_add)失败, 函数会执行一个完整的”回滚”操作:
    • 调用platform_device_put来释放已分配的platform_device结构体的内存。
    • 调用of_node_clear_flag(np, OF_POPULATED)来清除在第一步中设置的OF_POPULATED标志, 将设备树节点恢复到”未被创建”的状态, 从而允许后续的代码再次尝试为它创建设备。

of_platform_device_create_pdata: 核心实现

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
/*
* of_platform_device_create_pdata: 分配、初始化并注册一个of_device(平台设备).
* @np: 指向要为其创建设备的设备树节点.
* @bus_id: 分配给设备的名称.
* @platform_data: 用于填充 platform_data 指针的数据 (遗留机制).
* @parent: Linux设备模型中的父设备.
*
* 返回: 指向创建的平台设备的指针, 如果设备未被注册则返回NULL.
* 不可用的设备将不会被注册.
*/
static struct platform_device *of_platform_device_create_pdata(
struct device_node *np,
const char *bus_id,
void *platform_data,
struct device *parent)
{
struct platform_device *dev;

pr_debug("create platform device: %pOF\n", np);

/*
* 步骤1: 资格审查.
* 1. 检查设备树中此节点的 status 属性是否为 "okay".
* 2. 原子地检查并设置 OF_POPULATED 标志, 如果已被设置, 说明设备已创建, 直接返回.
*/
if (!of_device_is_available(np) ||
of_node_test_and_set_flag(np, OF_POPULATED))
return NULL;

/*
* 步骤2a: 分配 platform_device 结构体内存并进行基础初始化.
*/
dev = of_device_alloc(np, bus_id, parent);
if (!dev)
goto err_clear_flag; /* 分配失败, 跳转到错误处理. */

/*
* 步骤2b: 配置设备的关键属性.
*/
dev->dev.coherent_dma_mask = DMA_BIT_MASK(32); /* 设置默认的32位DMA掩码. */
if (!dev->dev.dma_mask)
dev->dev.dma_mask = &dev->dev.coherent_dma_mask;
dev->dev.bus = &platform_bus_type; /* 关键: 将其明确地归属到平台总线. */
dev->dev.platform_data = platform_data; /* 附加遗留的platform_data. */
of_msi_configure(&dev->dev, dev->dev.of_node); /* 配置MSI中断(如果支持). */

/*
* 步骤3: 将设备正式注册到内核设备模型中.
*/
if (of_device_add(dev) != 0) {
platform_device_put(dev); /* 注册失败, 释放已分配的设备结构体. */
goto err_clear_flag; /* 跳转到错误处理. */
}

/* 创建并注册成功, 返回指向新设备的指针. */
return dev;

err_clear_flag:
/*
* 步骤4: 错误处理回滚.
* 清除 OF_POPULATED 标志, 允许后续代码再次尝试创建此设备.
*/
of_node_clear_flag(np, OF_POPULATED);
return NULL;
}

of_platform_device_create: 便捷的API封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* of_platform_device_create - 分配、初始化并注册一个of_device
* @np: 指向要为其创建设备的节点.
* @bus_id: 分配给设备的名称.
* @parent: Linux设备模型中的父设备.
*
* 返回: 指向创建的平台设备的指针, 如果设备未被注册则返回NULL.
* 不可用的设备将不会被注册.
*/
struct platform_device *of_platform_device_create(struct device_node *np,
const char *bus_id,
struct device *parent)
{
/*
* 这是一个简洁的封装函数, 它直接调用核心实现函数,
* 并为 platform_data 参数传递 NULL.
* 在现代的、完全基于设备树的系统中, 这是标准的用法.
*/
return of_platform_device_create_pdata(np, bus_id, NULL, parent);
}
/* 将此重要的API函数导出, 供内核其他部分使用. */
EXPORT_SYMBOL(of_platform_device_create);

drivers/of/property.c

of_property_match_string 在列表中查找字符串并返回索引

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
/**
* of_property_match_string() - 在列表中查找字符串并返回索引
* @np: 指向包含 String List 属性的节点的指针
* @propname: 字符串列表属性名称
* @string: 指向要在字符串列表中搜索的字符串的指针
*
* 在设备节点属性(列表字符串)中搜索 string 的完全匹配项。
*
* Return: 成功时字符串首次出现的索引,如果属性不存在,则为 -EINVAL,如果属性没有值,则为 -ENODATA,如果字符串在属性数据的长度内未以 null 结尾,则为 -EILSEQ。
*/
int of_property_match_string(const struct device_node *np, const char *propname,
const char *string)
{
const struct property *prop = of_find_property(np, propname, NULL);
size_t l;
int i;
const char *p, *end;

if (!prop)
return -EINVAL;
if (!prop->value)
return -ENODATA;

p = prop->value;
end = p + prop->length;

for (i = 0; p < end; i++, p += l) {
l = strnlen(p, end - p) + 1;
if (p + l > end)
return -EILSEQ;
pr_debug("comparing %s with %s\n", string, p);
if (strcmp(string, p) == 0)
return i; /* Found it; return index */
}
return -ENODATA;
}
EXPORT_SYMBOL_GPL(of_property_match_string);

of_property_read_string_index 读取字符串列表属性

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
/**
* of_property_read_string_helper() - 用于解析字符串属性的实用程序帮助程序
* @np:要从中读取属性值的设备节点。
* @propname:需要搜索的属性名称。
* @out_strs:输出字符串指针数组。
* @sz:要读取的数组元素数。
* @skip:列表开头要跳过的字符串数。
*
* 请勿直接调用此函数。它是 of_property_read_string*() 系列函数的实用程序帮助程序。
*/
int of_property_read_string_helper(const struct device_node *np,
const char *propname, const char **out_strs,
size_t sz, int skip)
{
const struct property *prop = of_find_property(np, propname, NULL);
int l = 0, i = 0;
const char *p, *end;

if (!prop)
return -EINVAL;
if (!prop->value)
return -ENODATA;
p = prop->value;
end = p + prop->length;

for (i = 0; p < end && (!out_strs || i < skip + sz); i++, p += l) {
l = strnlen(p, end - p) + 1;
if (p + l > end)
return -EILSEQ;
if (out_strs && i >= skip)
*out_strs++ = p;
}
i -= skip;
return i <= 0 ? -ENODATA : i;
}
EXPORT_SYMBOL_GPL(of_property_read_string_helper);

/**
* of_property_read_string_index() - 从多个字符串属性中查找并读取字符串。
* @np:要从中读取属性值的设备节点。
* @propname:需要搜索的属性名称。
* @index:字符串在字符串列表中的索引
* @output:指向以 null 结尾的返回字符串的指针,仅当返回值为 0 时才修改。
*
* 在设备树节点中搜索属性,并在该属性中包含的字符串列表中检索以 null 结尾的字符串值(指向数据的指针,而不是副本)。
*
* 返回:成功时为 0,如果属性不存在,则为 -EINVAL,如果属性没有值,则为 -ENODATA,如果字符串在属性数据的长度内未以 null 结尾,则返回 -EILSEQ。
*
* 仅当可以解码有效字符串时,才会修改 out_string 指针。
*/
static inline int of_property_read_string_index(const struct device_node *np,
const char *propname,
int index, const char **output)
{
int rc = of_property_read_string_helper(np, propname, output, 1, index);
return rc < 0 ? rc : 0;
}