基于 rk3566 的 uboot 分析 - dts 加载和 dm 模型的本质

本文记录了在 RK3566 平台上,U-Boot 中设备树的加载与使用过程,并对设备模型(Device Model, DM)的核心机制进行了分析。文章主要包括以下内容:

  • U-Boot 中设备树的加载
  • U-Boot DM 模型的创建机制及其核心实现
  • RK3566 平台上 DM 模型的具体构造流程
  • dm 模型数据结构
  • 常见的接口说明

一、设备树加载使用

    dts 即 Device Tree Source 设备树源码, Device Tree 用来描述 soc 的硬件信息. 更多请参考 Linux设备树 - DTS语法、节点、设备树解析等 本文主要描述 rk3566 中的加载使用流程. 当前主流的 dm 模型树, 都是由设备树构建出来的.

1、概述

    rk3566 中对 dtb 的组成分为两个部分 ubootkernel. 并不是单纯的只用 uboot 和只用 kernel, 两者都用到了. 他们在内存中的分布如下. 下图描述的是 RK3566 的 dtb img 位置.

     rk3566 uboot 中设备树的加载有两个阶段, 首先使用 uboot arch/arm/dts中的设备树, 之后再加载使用 kernel dtb 中的设备树.

2、第一阶段

    ruboot 的 dtb 由两个宏决定.编译请参考: 【u-boot】u-boot对设备树的节点解析, 上图的 rk3566 uboot 镜像对应的配置如下.

1
2
3
4
5
6
7
8
9
10
11
12
13
// 是否使用 dtb
CONFIG_OF_CONTROL=y

// dtb 被打包成到 uboo.bin 文件中
// 通过 __dtb_dt_begin 符号来获取 dtb 地址
CONFIG_OF_EMBED is not set

// 没有定义 CONFIG_OF_EMBED 且定义了这个宏
// u-boot.dtb 和 u-boot.bin 分离. u-boot.dtb 放在 u-boot.bin 后面
CONFIG_OF_SEPARATE=y

//二阶段使用 kernel 的 dtb
CONFIG_USING_KERNEL_DTB=y

common/board_f.c中定义了 fdtdec_setup 函数用来解析 dts.

1
2
3
4
5
6
7
static const init_fnc_t init_sequence_f[] = {
setup_mon_len,
#ifdef CONFIG_OF_CONTROL
fdtdec_setup, // 解析 dts
#endif
...
};

1) fdtdec_setup

  1. 当使用 CONFIG_OF_EMBED 的方式时, dtb 被打包成到 uboo.bin 文件中, 通过 __dtb_dt_begin符号来获取 dtb 地址
  2. 没有定义 CONFIG_OF_EMBED 且定义了 CONFIG_OF_SEPARATE u-boot.dtb 和 u-boot.bin 分离. u-boot.dtb 放在 u-boot.bin 后面, 通过 _end符号来获取 dtb 地址.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int fdtdec_setup(void)
{
......

// 当使用 CONFIG_OF_EMBED 的方式时, dtb 被打包成到 uboo.bin 文件中
// 通过__dtb_dt_begin 符号来获取 dtb 地址
# ifdef CONFIG_OF_EMBED
# ifdef CONFIG_SPL_BUILD
gd->fdt_blob = __dtb_dt_spl_begin;
# else
gd->fdt_blob = __dtb_dt_begin; // 这里
# endif
# elif defined CONFIG_OF_SEPARATE // 如果定义了这个
# ifdef CONFIG_SPL_BUILD
if (IS_ENABLED(CONFIG_SPL_SEPARATE_BSS))
gd->fdt_blob = (ulong *)&_image_binary_end;
else
gd->fdt_blob = (ulong *)&__bss_end;
# else
/* FDT is at end of image */
gd->fdt_blob = (ulong *)&_end; // dtb 追加到 uboot 的 bin 文件后面时,通过 _end 符号来获取 dtb 地址
......
return fdtdec_prepare_fdt();
}

2) 总结

    rk 3566 中 u-boot.dtb 和 u-boot.bin 分离. u-boot.dtb 放在 u-boot.bin 后面, 通过 _end符号来获取 dtb 地址. 设备树被保存进 gd->fdt_blob中.

3、第二阶段

1) kernle dtb 编译打包

对应的文件 kernel/scripts/mkmultidtb.py

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
def main():
if (len(sys.argv) < 2) or (sys.argv[1] == '-h'):
print __doc__
sys.exit(2)

BOARD = sys.argv[1]
TARGET_DTBS = DTBS[BOARD]
target_dtb_list = ''
default_dtb = True

for dtb, value in TARGET_DTBS.items():
if default_dtb:
# 打包 arch/arm64/boot/dts/rockchip/ 目录下的 dtb 文件为一个新文件并命名为 rk-kernel.dtb
# 保存这个文件到 target_dtb_list
ori_file = 'arch/arm64/boot/dts/rockchip/' + dtb + '.dtb'
shutil.copyfile(ori_file, "rk-kernel.dtb")
target_dtb_list += 'rk-kernel.dtb '
default_dtb = False
new_file = dtb + value + '.dtb'
ori_file = 'arch/arm64/boot/dts/rockchip/' + dtb + '.dtb'
shutil.copyfile(ori_file, new_file)
target_dtb_list += ' ' + new_file

print target_dtb_list
# 将前面生成的文件 rk-kernel.dtb 和 logo 一起打包进 resource.img 中
os.system('scripts/resource_tool logo.bmp logo_kernel.bmp logo720.bmp logo_kernel720.bmp' + target_dtb_list)
# 删除掉生成的 rk-kernel.dtb
os.system('rm ' + target_dtb_list)

if __name__ == '__main__':
main()
    1. 打包 arch/arm64/boot/dts/rockchip/目录下的 dtb 文件为一个新文件并命名为 rk-kernel.dtb
    1. rk-kernel.dtb logo.bmp 等文件打包进 resource.img.
    1. 删掉生成的 rk-kernel.dtb .

2) 加载流程

  1. 如果定义了 CONFIG_USING_KERNEL_DTB 才会调用 init_kernel_dtb()函数

  2. 从环境变量获取 fdt_addr. 它由 ENV_MEM_LAYOUT_SETTINGS 指定. 这里指定为 0x0a100000

1
2
3
4
5
6
7
8
9
// include/configs/rk3568_common.h
#define ENV_MEM_LAYOUT_SETTINGS \
"scriptaddr=0x00c00000\0" \
"pxefile_addr_r=0x00e00000\0" \
"fdt_addr_r=0x0a100000\0" \ // 这里指定 ftd_addr 地址
"kernel_addr_no_low_bl32_r=0x00280000\0" \
"kernel_addr_r=0x00a80000\0" \
"kernel_addr_c=0x04080000\0" \
"ramdisk_addr_r=0x0a200000\0"
  1. 调用 rockchip_read_dtb_file((void *)fdt_addr);, 在 resource.img 中搜索 rk-kernel.dtb , 找到之后把它加载到 ftd_addr .

  2. 更新 gd->fdt_blob = (void *)fdt_addr;即指定 dtb 为加载的 rk-kernel.dtb

调用链如下所示.

1
2
3
4
5
6
7
8
9
10
board_init() -->
init_kernel_dtb() -->
rockchip_read_dtb_file((void *)fdt_addr); -->
rockchip_read_resource_dtb(fdt_addr, &hash, &hash_size); -->
file = get_file_info(DEFAULT_DTB_FILE); --> // 在 resource.img 中搜索 DEFAULT_DTB_FILE 这个宏被定义为 rk-kernel.dtb
rockchip_read_resource_file(fdt_addr, file->name, 0, 0); // 将 dtb 加载到 ftd_addr 这个地址.
dtb_okay:
gd->fdt_blob = (void *)fdt_addr; // 更新 dtb
gd->flags |= GD_FLG_KDTB_READY; // 设置标志位
dm_scan_fdt((void *)gd->fdt_blob, false); // 更新 dm 模型树

2.1) board_init

如果定义了 CONFIG_USING_KERNEL_DTB 才会调用 init_kernel_dtb()函数

1
2
3
4
5
6
7
8
9
10
11
int board_init(void)
{
......
#ifdef CONFIG_USING_KERNEL_DTB // 定义了这个才会使用 kernel
#ifdef CONFIG_MTD_BLK
board_mtd_blk_map_partitions();
#endif
init_kernel_dtb();
#endif
......
}

2.2) init_kernel_dtb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
int init_kernel_dtb(void)
{
ulong fdt_addr = 0;
void *ufdt_blob;
int ret = -ENODEV;

if (gd->ram_size <= SZ_128M)
fdt_addr = env_get_ulong("fdt_addr1_r", 16, 0);

// 从环境变量获取 fdt_addr. 0x0a100000
if (!fdt_addr)
fdt_addr = env_get_ulong("fdt_addr_r", 16, 0);

......

ret = rockchip_read_dtb_file((void *)fdt_addr);
if (!ret) {
if (!dtb_check_ok((void *)fdt_addr, (void *)gd->fdt_blob)) {
ret = -EINVAL;
printf("Kernel dtb mismatch this platform!\n");
} else {
goto dtb_okay;
}
}

......

dtb_okay:
ufdt_blob = (void *)gd->fdt_blob; // 保存 uboot 的 dtb
gd->fdt_blob = (void *)fdt_addr; // 更新使用的 fdt_blob 为 kernel 的 dtb
......
return 0;
}

2.3) rockchip_read_dtb_file

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
int rockchip_read_dtb_file(void *fdt_addr)
{
int hash_size = 0;
int ret = -1;
u32 fdt_size;
char *hash;

// 检查 resource.img 是否存在
resource_traverse_init_list();

......

// 直接在 resource.img 中查找 rk-kernel.dtb
// 将 rk-kernel.dtb 读取到 fdt_addr
ret = rockchip_read_resource_dtb(fdt_addr, &hash, &hash_size);
if (ret) {
printf("Failed to load DTB, ret=%d\n", ret);
return ret;
}

// 验证 dtb 的合法性
if (fdt_check_header(fdt_addr)) {
printf("Invalid DTB magic !\n");
return -EBADF;
}

// 更新大小
fdt_size = fdt_totalsize(fdt_addr);

......

return 0;
}

2.4) rockchip_read_resource_dtb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// arch/arm/mach-rockchip/resource_img.c
#define DEFAULT_DTB_FILE "rk-kernel.dtb"

int rockchip_read_resource_dtb(void *fdt_addr, char **hash, int *hash_size)
{
struct resource_file *file = NULL;
int ret;

#ifdef CONFIG_ROCKCHIP_HWID_DTB
file = resource_read_hwid_dtb();
#endif

if (!file) // 直接在 resource.img 中查找 rk-kernel.dtb
file = get_file_info(DEFAULT_DTB_FILE);

if (!file)
return -ENODEV;

// 将 rk-kernel.dtb 读取到 fdt_addr
ret = rockchip_read_resource_file(fdt_addr, file->name, 0, 0);
if (ret < 0)
return ret;

if (fdt_check_header(fdt_addr))
return -EBADF;

*hash = file->hash;
*hash_size = file->hash_size;
printf("DTB: %s\n", file->name);

return 0;
}

参考: 瑞芯微RK3399设备树传递分析

3) 总结

    rk3566 从环境变量 fdt_addr_r获取 fdt_addr的地址 0x0a100000. 然后在 resource.img中搜索 rk-kernel.dtb, 找到之后把它加载到 ftd_addr. 最后更新 gd->fdt_blob = (void *)fdt_addr; 即指定 dtb 为加载的 rk-kernel.dtb.

二、dm 模型

    md(driver model) 驱动模型, 就是为驱动定义一个统一的访问接口, 提高代码的管理和使用效率.本质是以树状的形式组织各个设备驱动. 每一个模块就是一个树枝. 如下图所示. 该图展示了 dm 模型的树形结构. 它由树根 gd->md_root 向下延伸, 按照 dts 中的树状结构形式组织各个设备驱动模块(树枝). 它和 dts 中的树状结构是完全对应的.

    rk3566 的 U-Boot 的 dm 树构建分三次构筑, 前两次使用 uboot dts 构筑. 最后一次使用 kernel 的 dtb 进行构筑.

  • 第一次构筑在 initf_dm(void) 中主要通过调用 dm_scan_fdt()初始化配置了 u-boot,dm-pre-reloc;等属性节点的外设.
  • 第二次构筑在 initr_dm(void) 中主要通过调用 dm_scan_fdt()重新创建一颗 dm 树, 再解析一遍带有 u-boot,dm-pre-reloc;属性的设备节点的外设.
  • 第三次构筑在 board_init(void) 中调用 dm_scan_fdt()首先删掉 uboot 中带有 u-boot,dm-pre-reloc;的设备节点, 之后使用 kernel dtb 初始化所有 okay 节点.
  • 整个 uboot 中有两棵这样的树, 第一棵树在第一次创建它被保存在gd->dm_root_f, 第二棵树在第二次创建, 在第三次对这颗树进行补充, 它被保存在 gd->dm_root.

1、树的创建

    dm 模型有两种创建方式, 一种是通过 driver_info 来创建, 需要注意的是 driver_info 描述的是 udevice 而非 driver, 这种方式不需要设备树直接创建. 常用的方式就是宏 U_BOOT_DEVICE. 也可以像树根那样手动创建一个结构.

1
2
#define U_BOOT_DEVICE(__name)                       \
ll_entry_declare(struct driver_info, __name, driver_info)

    另一种则是通过设备树创建. 无论采用那种方式最终都是调用 device_bind_common来创建并连接 uclass, uclass_driver, udevice, driver. 如下图所示

    通过两次调用 device_bind_common这个函数就可以创建图中的结构关系, 第一次调用创建 UCLASS_SYSRESET(uclass), sysreset_syscon_reboot(udev), 并且建立和sysreset(uc_drv), sysreset_syscon_reboot(drv)之间的关系. 第二次调用则在UCLASS_SYSRESET(uclass), 上面追加了 mytest. 由此可见这个函数是贯穿整个 dm 模型的核心. 因此优先分析这个函数.

1) device_bind_common

    这个函数是 uboot 用来创建 dm 树枝的核心函数. 理解了这个函数就理解了 dm 模型的创建.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
// drivers/core/device.c
static int device_bind_common(struct udevice *parent, const struct driver *drv,
const char *name, void *platdata,
ulong driver_data, ofnode node,
uint of_platdata_size, struct udevice **devp)
{
struct udevice *dev;
struct uclass *uc;
int size, ret = 0;

if (devp)
*devp = NULL;
if (!name)
return -EINVAL;

// 获取 drv->id 对应的的 uclass, 没有则创建一个 uclass
ret = uclass_get(drv->id, &uc);
if (ret) {
debug("Missing uclass for driver %s\n", drv->name);
return ret;
}

// 是否使用 KERNEL 的 dtb
#ifdef CONFIG_USING_KERNEL_DTB
if (gd->flags & GD_FLG_RELOC) {

// 如果定义了这些宏进入条件判断
// UCLASS_MMC UCLASS_RKNAND UCLASS_SPI_FLASH UCLASS_MTD UCLASS_PCI UCLASS_AHCI
if (drv->id == UCLASS_MMC || drv->id == UCLASS_RKNAND ||
drv->id == UCLASS_SPI_FLASH || drv->id == UCLASS_MTD ||
drv->id == UCLASS_PCI || drv->id == UCLASS_AHCI) {

// 如果 GD_FLG_KDTB_READY 被定义即 kernel dtb 已经被加载
// 且设备 id 是 UCLASS_MMC 直接返回
if ((gd->flags & GD_FLG_KDTB_READY) &&
(drv->id == UCLASS_MMC))
return 0;

// 遍历该 uclass 下的设备, 如果该设备已经创建则直接返回.
list_for_each_entry(dev, &uc->dev_head, uclass_node) {
if (!strcmp(name, dev->name)) {
debug("%s do not bind dev already in list %s\n",__func__, dev->name);
dev->node = node;
return 0;
}
}
}

struct udevice *n;

// 遍历 uclass 下的设备
list_for_each_entry_safe(dev, n, &uc->dev_head, uclass_node) {

// 如果 uclass 下面已经存在该设备且设置了 u-boot,dm-pre-reloc 或者 u-boot,dm-spl 则进入判断
if (!strcmp(name, dev->name) &&
(dev_read_bool(dev, "u-boot,dm-pre-reloc") ||
dev_read_bool(dev, "u-boot,dm-spl"))) {

// 如果设备 id 是 UCLASS_CRYPTO 和 UCLASS_WDT 则直接返回
if (drv->id == UCLASS_CRYPTO ||
drv->id == UCLASS_WDT) {
debug("%s do not delete uboot dev: %s\n",
__func__, dev->name);
return 0;
} else if (drv->id == UCLASS_REGULATOR) {

} else { // 否则删除该设备的 uclass_node, 即从 uclass 中删掉这个设备
list_del_init(&dev->uclass_node);
}
}
}
}
#endif

// 创建一个 udevice 并做一些初始化
dev = calloc(1, sizeof(struct udevice));
if (!dev)
return -ENOMEM;

// 初始化链表以及成员变量
INIT_LIST_HEAD(&dev->sibling_node);
INIT_LIST_HEAD(&dev->child_head);
INIT_LIST_HEAD(&dev->uclass_node);
#ifdef CONFIG_DEVRES
INIT_LIST_HEAD(&dev->devres_head);
#endif
dev->platdata = platdata;
dev->driver_data = driver_data;
dev->name = name;
dev->node = node;
dev->parent = parent;
dev->driver = drv;
dev->uclass = uc;

dev->seq = -1;
dev->req_seq = -1;
if (CONFIG_IS_ENABLED(OF_CONTROL) && CONFIG_IS_ENABLED(DM_SEQ_ALIAS)) {
if (uc->uc_drv->flags & DM_UC_FLAG_SEQ_ALIAS) {
if (uc->uc_drv->name && ofnode_valid(node)) {
dev_read_alias_seq(dev, &dev->req_seq);
}
}
}

// 如果设置了 platdata_auto_alloc_size 以及 OF_PLATDATA
// 则分配对应的空间且设置为 dev->platdata
if (drv->platdata_auto_alloc_size) {
bool alloc = !platdata;

if (CONFIG_IS_ENABLED(OF_PLATDATA)) {
if (of_platdata_size) {
dev->flags |= DM_FLAG_OF_PLATDATA;
if (of_platdata_size <
drv->platdata_auto_alloc_size)
alloc = true;
}
}
if (alloc) {
dev->flags |= DM_FLAG_ALLOC_PDATA;
dev->platdata = calloc(1,
drv->platdata_auto_alloc_size);
if (!dev->platdata) {
ret = -ENOMEM;
goto fail_alloc1;
}
if (CONFIG_IS_ENABLED(OF_PLATDATA) && platdata) {
memcpy(dev->platdata, platdata,
of_platdata_size);
}
}
}

// 分配 per_device_platdata_auto_alloc_size 大小的空间
// 设置为 dev->uclass_platdata
size = uc->uc_drv->per_device_platdata_auto_alloc_size;
if (size) {
dev->flags |= DM_FLAG_ALLOC_UCLASS_PDATA;
dev->uclass_platdata = calloc(1, size);
if (!dev->uclass_platdata) {
ret = -ENOMEM;
goto fail_alloc2;
}
}

// 分配 per_child_platdata_auto_alloc_size
// 设置为 dev->parent_platdata
if (parent) {
size = parent->driver->per_child_platdata_auto_alloc_size;
if (!size) {
size = parent->uclass->uc_drv->
per_child_platdata_auto_alloc_size;
}
if (size) {
dev->flags |= DM_FLAG_ALLOC_PARENT_PDATA;
dev->parent_platdata = calloc(1, size);
if (!dev->parent_platdata) {
ret = -ENOMEM;
goto fail_alloc3;
}
}
}

// 连接到父节点
if (parent)
list_add_tail(&dev->sibling_node, &parent->child_head);

// 将 dev 连接到 uclass
// 回调父设备的的 uc_drv->child_post_bind() 接口
ret = uclass_bind_device(dev);
if (ret)
goto fail_uclass_bind;

if (drv->bind) { // 回调 bind 接口
ret = drv->bind(dev);
if (ret)
goto fail_bind;
}

// 回调 parent->driver->child_post_bind(dev);
if (parent && parent->driver->child_post_bind) {
ret = parent->driver->child_post_bind(dev);
if (ret)
goto fail_child_post_bind;
}

// 回调 uc->uc_drv->post_bind(dev);
if (uc->uc_drv->post_bind) {
ret = uc->uc_drv->post_bind(dev);
if (ret)
goto fail_uclass_post_bind;
}

if (parent)
pr_debug("Bound device %s to %s\n", dev->name, parent->name);
if (devp)
*devp = dev;

dev->flags |= DM_FLAG_BOUND;

return 0;
......

1.1) uclass_get

1
2
3
4
5
6
7
8
9
10
11
12
13
int uclass_get(enum uclass_id id, struct uclass **ucp)
{
struct uclass *uc;

*ucp = NULL;
// 在 gd->uclass_root 中搜索 uclss, 没有调用 uclass_add
uc = uclass_find(id);
if (!uc)
return uclass_add(id, ucp);
*ucp = uc;

return 0;
}

    uclass_add创建一个对应 uclass_id 的 uclass 并且会在 UCLASS_DRIVER 定义的列表中查找对应 id 的 uclss_driver. 并对其进行绑定. 绑定之后回调 uc_drv->init(uc), 同时为 uclass->priv分配 uc_drv->priv_auto_alloc_size大小的空间, 如果找不到对应 id 的 uclsss_driver 则返回 err.

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
// drivers/core/uclass.c
// 1. 在 UCLASS_DRIVER 定义的列表中查找对应 id 的 uclss_driver. 没有 drv 则报错返回.
// 2. 分配一个 uclass, 根据 priv_auto_alloc_size 分配空间并初始化 uc->priv
// 3. 设置 uc->uc_drv 并初始化链表, 将 uc->sibling_node 挂接到 gd->uclass_root
// 4. 如果设置了则回调 uc_drv->init(uc)
static int uclass_add(enum uclass_id id, struct uclass **ucp)
{
struct uclass_driver *uc_drv;
struct uclass *uc;
int ret;

*ucp = NULL;
// 在 UCLASS_DRIVER 定义的列表中查找对应 id 的 uclss_driver.
uc_drv = lists_uclass_lookup(id);
if (!uc_drv) { // 没有 drv 则报错返回.
debug("Cannot find uclass for id %d: please add the UCLASS_DRIVER() declaration for this UCLASS_... id\n",id);
return -EPFNOSUPPORT;
}

// 分配一个 uclass
uc = calloc(1, sizeof(*uc));
if (!uc)
return -ENOMEM;

// 根据 priv_auto_alloc_size 分配空间并初始化 uc->priv
if (uc_drv->priv_auto_alloc_size) {
uc->priv = calloc(1, uc_drv->priv_auto_alloc_size);
if (!uc->priv) {
ret = -ENOMEM;
goto fail_mem;
}
}

// 设置 uc_drv 并初始化链表
uc->uc_drv = uc_drv;
INIT_LIST_HEAD(&uc->sibling_node);
INIT_LIST_HEAD(&uc->dev_head);
// 将 uc->sibling_node 挂接到 gd->uclass_root
list_add(&uc->sibling_node, &DM_UCLASS_ROOT_NON_CONST);

// 如果设置了则回调 uc_drv->init(uc)
if (uc_drv->init) {
ret = uc_drv->init(uc);
if (ret)
goto fail;
}
......

return ret;
}

1.2) uclass_bind_device

  1. dev->uclass_node连接到 uc->dev_head
  2. 回调父设备的的 dev->parent->uclass->uc_drv->child_post_bind()接口
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
int uclass_bind_device(struct udevice *dev)
{
struct uclass *uc;
int ret;

uc = dev->uclass; // 获取对应的 uclass
list_add_tail(&dev->uclass_node, &uc->dev_head);

// 回调父设备的的 uc_drv->child_post_bind() 接口
if (dev->parent) {
struct uclass_driver *uc_drv = dev->parent->uclass->uc_drv;

if (uc_drv->child_post_bind) {
ret = uc_drv->child_post_bind(dev);
if (ret)
goto err;
}
}

return 0;
err:
list_del(&dev->uclass_node);

return ret;
}

1.3) 总结

  1. 在 gd->uclass_root 中搜索 uclss, 查找到则返回对应的 uclss, 找到直接返回
  2. 前面没找到 uclss, 创建一个对应 uclass_id 的 uclass 并且会在 UCLASS_DRIVER 定义的列表中查找对应 id 的 uclss_driver. 并对其进行绑定. 绑定之后回调 uc_drv->init(uc), 同时为 uclass->priv分配 uc_drv->priv_auto_alloc_size大小的空间, 如果找不到对应 id 的 uclsss_driver 则返回 err.
  3. 创建一个 udevice 并对其成员变量进行初始化, dev->driver = drv;dev->uclass = uc;等.
  4. 为 udev 的成员变量 platdata, uclass_platdata, parent_platdata 分配空间. 他们的 size 决定因素如下.
1
2
3
platdata ==>         drv->platdata_auto_alloc_size
uclass_platdata ==> uc->uc_drv->per_device_platdata_auto_alloc_size;
parent_platdata ==> parent->driver->per_child_platdata_auto_alloc_size;
  1. 将 dev->sibling_node 连接到 parent->child_head 父节点, 即上图用于连接 udevice 之间的线
  2. 将 dev->uclass_node 连接到 uc->dev_head, 即上图中用于连接 udevice 和 uclass 之间的线.
  3. 设置dev->flags |= DM_FLAG_BOUND
  4. 整个过程依次回调的接口如下, 常用的接口为 dev->drv->bind(dev)
1
2
3
4
5
dev->uc->uc_drv->init(uc);
dev->parent->uclass->uc_drv->child_post_bind(dev);
dev->drv->bind(dev); // 这个回调常用于构建当前设备驱动描述的树枝的下一级树枝.
dev->parent->driver->child_post_bind(dev);
dev->uc->uc_drv->post_bind(dev);
  1. 如果代码已经重定位, 在创建 udevice 时需要对设备进行判定. 如果 drv->id 是 UCLASS_MMC UCLASS_RKNAND UCLASS_SPI_FLASH UCLASS_MTD UCLASS_PCI UCLASS_AHCI这些中的一个, 且该设备已经创建则直接返回. 如果该设备节点设置了 u-boot,dm-pre-reloc或者 u-boot,dm-spl, 除了 UCLASS_CRYPTO UCLASS_CRYPTO这两个 id 的设备都将从 uclass 链表中删除.

2) 树根 gd->dm_root

    dm_init 用于构建树根, 首先判断 gd->dm_root如果已经注册了就返回错误. 然后初始化 gd->uclass_root链表. 最后调用 device_bind_by_name构建树根.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int dm_init(bool of_live)
{
int ret;

if (gd->dm_root) { // 如果已经注册了就返回错误
dm_warn("Virtual root driver already exists!\n");
return -EINVAL;
}
INIT_LIST_HEAD(&DM_UCLASS_ROOT_NON_CONST); // 初始化 gd->uclass_root 链表
......

ret = device_bind_by_name(NULL, false, &root_info, &DM_ROOT_NON_CONST);
......

ret = device_probe(DM_ROOT_NON_CONST);
if (ret)
return ret;

return 0;
}

    树根的构建并没有使用设备树而是使用root_info来构建的. 相关定义如下.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// ./include/dm/device-internal.h
#define DM_ROOT_NON_CONST (((gd_t *)gd)->dm_root)
#define DM_UCLASS_ROOT_NON_CONST (((gd_t *)gd)->uclass_root)

//./drivers/core/root.c
static const struct driver_info root_info = {
.name = "root_driver",
};

// drivers/core/root.c
U_BOOT_DRIVER(root_driver) = {
.name = "root_driver",
.id = UCLASS_ROOT,
.priv_auto_alloc_size = sizeof(struct root_priv),
};

    device_bind_by_name首先遍历 U_BOOT_DRIVER定义的驱动列表, 查找 name(root_driver) 对应的驱动. 他的定义如下

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
// ./drivers/core/device.c
/* 传入的参数如下
* parent = NULL
* pre_reloc_only = false
* info = root_info
* devp = DM_ROOT_NON_CONST
*/
int device_bind_by_name(struct udevice *parent, bool pre_reloc_only,
const struct driver_info *info, struct udevice **devp)
{
struct driver *drv;
uint platdata_size = 0;

// info->name = "root_driver",
// 遍历 U_BOOT_DRIVER 定义的驱动列表, 查找 name 对应的驱动
drv = lists_driver_lookup_name(info->name);
if (!drv)
return -ENOENT;
if (pre_reloc_only && !(drv->flags & DM_FLAG_PRE_RELOC))
return -EPERM;

#if CONFIG_IS_ENABLED(OF_PLATDATA)
platdata_size = info->platdata_size;
#endif

// 核心接口, 这个接口创建了 udevice
// 并且建立了 udevice uclass uclsaa_driver driver 之间的关系.
// 也就是上图的树枝和树根.
return device_bind_common(parent, drv, info->name,
(void *)info->platdata, 0, ofnode_null(), platdata_size,
devp);
}

    device_bind_by_name调用 device_bind_common 创建树根. 树根的结构如下所示.

3) 使用设备树创建 dm 模型

    dm 模型中通过 dm_scan_fdt 扫描设备树并创建树枝.

3.1) dm_scan_fdt

1
2
3
4
5
6
7
8
9
10
11
12
13
int dm_scan_fdt(const void *blob, bool pre_reloc_only)
{

// 如果定义了宏 CONFIG_OF_LIVE 则调用 dm_scan_fdt_live
// 否则调用 dm_scan_fdt_node
#if CONFIG_IS_ENABLED(OF_LIVE)
if (of_live_active())
return dm_scan_fdt_live(gd->dm_root, gd->of_root,
pre_reloc_only);
else
#endif
return dm_scan_fdt_node(gd->dm_root, blob, 0, pre_reloc_only);
}

    在 .config 中搜索配置如下

1
2
➜ grep "CONFIG_OF_LIVE" -rn .config
733:CONFIG_OF_LIVE=y

    dm_scan_fdt_live, dm_scan_fdt_node, 这两个函数的实现其实是一样的. 只有一些宏的配置略有差异如下图所示. 标出了两者代码关键的地方.

    文章就以 dm_scan_fdt_live 进行分析, 因为 rk3566 是配置了 CONFIG_OF_LIVE 这个宏的.

3.1.1) dm_init_and_scan
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
#if CONFIG_IS_ENABLED(OF_LIVE)
static int dm_scan_fdt_live(struct udevice *parent,
const struct device_node *node_parent,
bool pre_reloc_only)
{
struct device_node *np;
int ret = 0, err;

// 循环扫描 parent 节点下的一级子节点
for (np = node_parent->child; np; np = np->sibling) {

// 首先判断是否定义了 pre_reloc_only
// 如果定义了宏 CONFIG_USING_KERNEL_DTB 则需要满足 u-boot,dm-pre-reloc 和 u-boot,dm-spl
// 没定义 CONFIG_USING_KERNEL_DTB 则需满足 u-boot,dm-pre-reloc
if (pre_reloc_only &&
#ifdef CONFIG_USING_KERNEL_DTB
(!of_find_property(np, "u-boot,dm-pre-reloc", NULL) &&
!of_find_property(np, "u-boot,dm-spl", NULL)))
#else
!of_find_property(np, "u-boot,dm-pre-reloc", NULL))
#endif
continue;

// 节点是否设置 okay 属性
if (!of_device_is_available(np)) {
pr_debug(" - ignoring disabled device\n");
continue;
}

// 满足前面的条件调用 lists_bind_fdt 进行匹配
err = lists_bind_fdt(parent, np_to_ofnode(np), NULL);
if (err && !ret) {
ret = err;
debug("%s: ret=%d\n", np->name, ret);
}

if (!pre_reloc_only && !strcmp(np->name, "firmware"))
ret = device_bind_driver_to_node(gd->dm_root,
"firmware", np->name, np_to_ofnode(np), NULL);
}

if (ret)
dm_warn("Some drivers failed to bind\n");

return ret;
}
#endif /* CONFIG_IS_ENABLED(OF_LIVE) */

    该函数的主要功能是扫描 parent节点下的子节点, 并且根据宏的配置决定如何解析 dts. 整理如下

宏相关配置 解析的节点(配置了这些 dts 属性才会解析, 少一个都不行)
配置CONFIG_OF_LIVE
配置pre_reloc_only=1
status = okay;+ u-boot,dm-pre-reloc
配置CONFIG_OF_LIVE
配置pre_reloc_only=1
配置 CONFIG_USING_KERNEL_DTB
status = okay;+ u-boot,dm-pre-reloc;+ u-boot,dm-spl;
配置CONFIG_OF_LIVE
没有配置 pre_reloc_only=1
status = okay;
3.1.2) lists_bind_fdt

    这个函数遍历 node 节点中的 compatible 属性, 并和 U_BOOT_DRIVER 定义的 driver->of_match 进行匹配. 匹配成功调用 device_bind_common 创建 dm 模型中的树枝.

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
//./drivers/core/lists.c
int lists_bind_fdt(struct udevice *parent, ofnode node, struct udevice **devp)
{
struct driver *driver = ll_entry_start(struct driver, driver);
const int n_ents = ll_entry_count(struct driver, driver);
const struct udevice_id *id;
struct driver *entry;
struct udevice *dev;
bool found = false;
const char *name, *compat_list, *compat;
int compat_length, i;
int result = 0;
int ret = 0;

if (devp)
*devp = NULL;
name = ofnode_get_name(node);
pr_debug("bind node %s\n", name);

// 获取 compatible 节点的字符串保存到 compat_list
// compat_length 保存字符串长度
compat_list = ofnode_get_property(node, "compatible", &compat_length);
if (!compat_list) {
......
}


// 遍历 compatible 中的字符串
for (i = 0; i < compat_length; i += strlen(compat) + 1) {
compat = compat_list + i;
pr_debug(" - attempt to match compatible string '%s'\n",
compat);

// 遍历 U_BOOT_DRIVER 定义的驱动
for (entry = driver; entry != driver + n_ents; entry++) {
// driver->of_match 和设备树 compatible 中的字符串进行匹配
// 匹配成功保存下该 udevice_id
ret = driver_check_compatible(entry->of_match, &id,
compat);
if (!ret)
break;
}
if (entry == driver + n_ents)
continue;

pr_debug(" - found match at '%s'\n", entry->name);
// 调用 device_bind_with_driver_data 它就 device_bind_common 的封装而已.
// 创建 uclass, uclass_driver, udevice, driver 并建立连接
ret = device_bind_with_driver_data(parent, entry, name,
id->data, node, &dev);
if (ret == -ENODEV) {
pr_debug("Driver '%s' refuses to bind\n", entry->name);
continue;
}
if (ret) {
dm_warn("Error binding driver '%s': %d\n", entry->name,
ret);
return ret;
} else {
found = true;
if (devp)
*devp = dev;
}
break;
}

if (!found && !result && ret != -ENODEV)
pr_debug("No match for node '%s'\n", name);

return result;
}
3.1.2.1) device_bind_with_driver_data
1
2
3
4
5
6
7
8
9
// 封装 device_bind_common
int device_bind_with_driver_data(struct udevice *parent,
const struct driver *drv, const char *name,
ulong driver_data, ofnode node,
struct udevice **devp)
{
return device_bind_common(parent, drv, name, NULL, driver_data, node,
0, devp);
}
3.1.2.2) driver_check_compatible
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static int driver_check_compatible(const struct udevice_id *of_match,
const struct udevice_id **of_idp,
const char *compat)
{
// 没设置 of_match 直接返回 err
if (!of_match)
return -ENOENT;

// 如果 of_match->compatible 和 compat 字符串相同就匹配成功.
while (of_match->compatible) {
if (!strcmp(of_match->compatible, compat)) {
*of_idp = of_match;
return 0;
}
of_match++;
}

return -ENOENT;
}

3.2 ) 总结

  1. dm_init_and_scan 首先遍历根节点下的子节点, 根据宏的配置决定解析哪些 dts.
宏相关配置 解析的节点(配置了这些 dts 属性才会解析, 少一个都不行)
配置CONFIG_OF_LIVE
配置pre_reloc_only=1
status = okay;+ u-boot,dm-pre-reloc
配置CONFIG_OF_LIVE
配置pre_reloc_only=1
配置 CONFIG_USING_KERNEL_DTB
status = okay;+ u-boot,dm-pre-reloc;+ u-boot,dm-spl;
没有配置CONFIG_OF_LIVE
配置pre_reloc_only=1
status = okay;+ u-boot,dm-pre-reloc+ u-boot,dm-tpl+ u-boot,dm-spl
没有配置CONFIG_OF_LIVE
配置pre_reloc_only=1
配置CONFIG_TPL_BUILD
status = okay;+ u-boot,dm-pre-reloc+ u-boot,dm-tpl
没有配置CONFIG_OF_LIVE
配置pre_reloc_only=1
配置CONFIG_SPL_BUILD
status = okay;+ u-boot,dm-pre-reloc+ u-boot,dm-spl
没有配置 pre_reloc_only=1 status = okay;
  1. 对于需要解析的节点, 获取节点的 compatible 属性. 使用该属性和 driver->of_match 进行匹配. 如下图所示, 该图示例了 rockchip_display 这个设备的匹配. 左边是 driver, 右边是 dts.

匹配成功则调用 device_bind_common 创建 dm 模型, 如下所示的结构关系.

2、 rk3566 dm 的构建

    有了前面的知识我们就可以从整体来分析 rk3566 的 dm 模型树的构建过程了. 前面说过构建过程分为三个阶段. 他们分别如下.

  • 第一阶段
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// common/board_f.c
static const init_fnc_t init_sequence_f[] = {
......
initf_dm,
......
}

// common/board_f.c
static int initf_dm(void)
{
#if defined(CONFIG_DM) && CONFIG_VAL(SYS_MALLOC_F_LEN)
int ret;

bootstage_start(BOOTSTATE_ID_ACCUM_DM_F, "dm_f");
ret = dm_init_and_scan(true);
bootstage_accum(BOOTSTATE_ID_ACCUM_DM_F);
if (ret)
return ret;
#endif
......

return 0;
}

     这里需要注意的是 dm_init_and_scan传入了 true. 因此 pre_reloc_only = 1

1
2
3
4
5
6
7
8
9
10
dm_init_and_scan(true); --> 
dm_init(IS_ENABLED(CONFIG_OF_LIVE)); --> // 创建树根
dm_scan_platdata(pre_reloc_only); --> // 解析非设备树创建的设备, 即通过宏 U_BOOT_DEVICE 创建的设备
lists_bind_drivers(DM_ROOT_NON_CONST, pre_reloc_only); -->
for (entry = info; entry != info + n_ents; entry++){ -->
device_bind_by_name(parent, pre_reloc_only, entry, &dev); -->
device_bind_common(...); --> // 创建 dm 模型树枝
}
dm_extended_scan_fdt(gd->fdt_blob, pre_reloc_only); -->
ret = dm_scan_fdt(gd->fdt_blob, pre_reloc_only); --> // 扫描设备树创建 dm 模型树枝.

    第⼀阶段候首先使用 /u-boot/arch/arm/dts/目录下的 dts 编译出来的 dtb 初始化硬件. 这个阶段只需要加载 emmc、nand、cru、grf、uart 等模块.他们由 status = okay;, u-boot,dm-pre-reloc;, u-boot,dm-spl;等属性指定, 具体请参考文章 3.2 的总结. 第⼀阶段为了速度和效率,会删除⼀些属性,也可以通过 defconfig ⾥的 CONFIG_OF_SPL_REMOVE_PROPS指定属性

1
CONFIG_OF_SPL_REMOVE_PROPS="clock-names interrupt-parent assigned-clocks assigned-clock-rates assigned-clock-parents"
  • 第二阶段
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
//common/board_r.c
static init_fnc_t init_sequence_r[] = {
......
#ifdef CONFIG_DM
initr_dm, // 第二阶段
#endif
......
#if defined(CONFIG_ARM) || defined(CONFIG_NDS32) || defined(CONFIG_RISCV)
board_init, // 第三阶段
#endif
......
};

//common/board_r.c
#ifdef CONFIG_DM
static int initr_dm(void)
{
int ret;

/* Save the pre-reloc driver model and start a new one */
gd->dm_root_f = gd->dm_root; // 保存第一阶段创建的模型树
gd->dm_root = NULL; // 设置为 null
#ifdef CONFIG_TIMER
gd->timer = NULL;
#endif
bootstage_start(BOOTSTATE_ID_ACCUM_DM_R, "dm_r");
ret = dm_init_and_scan(false); // 解析设备树二级节点的所有 ```okay```节点
bootstage_accum(BOOTSTATE_ID_ACCUM_DM_R);
if (ret)
return ret;
#ifdef CONFIG_TIMER_EARLY
ret = dm_timer_init();
if (ret)
return ret;
#endif

return 0;
}
#endif

    第二阶段将第一阶段创建的 dm 模型树保存到 gd->dm_root_f. 设置 gd->dm_root为空, 之后从新再创建一遍, 注意这里传入的是 false. 因此会解析设备树二级节点的所有 okay节点. 注意这时候还是使用的 uboot 的 dtb.

  • 第三阶段
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//common/board_r.c
static init_fnc_t init_sequence_r[] = {
......
#ifdef CONFIG_DM
initr_dm, // 第二阶段
#endif
......
#if defined(CONFIG_ARM) || defined(CONFIG_NDS32) || defined(CONFIG_RISCV)
board_init, // 第三阶段
#endif
......
};


board_init() -->
init_kernel_dtb() -->
rockchip_read_dtb_file((void *)fdt_addr); // 加载设备树为 kernel 的 dtb
dtb_okay:
gd->fdt_blob = (void *)fdt_addr; // 更新 dtb
gd->flags |= GD_FLG_KDTB_READY; // 设置标志位
dm_scan_fdt((void *)gd->fdt_blob, false); // 更新 dm 模型树

     第三阶段首加载 kernel 的 dtb, 然后使用 kernel 的 dtb 调用 dm_scan_fdt 扫描设备树并且解析所有根目录下的二级子节点, 对于具有 okay的子节点进行匹配并且创建 dm 模型树枝.

三、数据结构

1、udevice

  用于描述具体的硬件设备, 在当前的 dm 模型中, 在 uboot 启动的时候扫描 dts 自动创建. 详情参考 dts 加载和 dm 模型的本质. 其中需要关注的数据结构有 priv 常用于设置设备硬件私有数据结构. 通过接口void *dev_get_priv(struct udevice *dev) 返回.

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
struct udevice {  
const struct driver *driver; // 在 device_bind_common 中连接对应的 drv
const char *name; // 自动创建时, 由匹配到的 drv->name 设置. 也可以通过参数传入设置.
void *platdata; // 在 device_bind_common 由 drv->platdata_auto_alloc_size 指定
void *parent_platdata; // 在 device_bind_common 由 parent->driver->per_child_platdata_auto_alloc_size;
void *uclass_platdata; // 在 device_bind_common 由 uc->uc_drv->per_device_platdata_auto_alloc_size; 指定
ofnode node;
ulong driver_data; // 由 device_bind_common 传入的参数设置
struct udevice *parent; // 在 device_bind_common 设置, 连接父设备
void *priv; // 在 device_probe 中设置,由 dev->drv->priv_auto_alloc_size 决定大小
struct uclass *uclass; // 一般在 device_bind_common 中由
void *uclass_priv; // 在 device_probe 中设置,由 dev->uclass->uc_drv->per_device_auto_alloc_size
// 在 device_probe 中设置, 由 dev->parent->driver->per_child_auto_alloc_size 指定大小.
// 如果不存在则由 dev->parent->uclass->uc_drv->per_child_auto_alloc_size 指定大小
void *parent_priv;
struct list_head uclass_node; // 在 device_bind_common 中的 uclass_bind_device 设置, 连接到 uc->dev_head
struct list_head child_head; // 在子设备创建时设置, 连接子设备的 sibling_node
struct list_head sibling_node; // 在 device_bind_common 中设置, 连接到 parent->child_head
uint32_t flags;
int req_seq;
int seq; // // 在 device_probe 中设置, 表示 device 是第几个被注册, 由于他的唯一性, 因此也可以通过 seq 查找设备.
#ifdef CONFIG_DEVRES
struct list_head devres_head;
#endif
};

2、driver

  对应的 udevice 的驱动, 其中 probe 用于驱动的初始化. 已经 probe 就表示该设备已就绪可以使用. ofdata_to_platdata 接口在 probe 之前调用, 用于解析设备树. ops 则用于创建 drv 真的的操作接口. 通过 i2c_get_ops(dev) 返回该接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct driver {
char *name;
enum uclass_id id;
const struct udevice_id *of_match;
int (*bind)(struct udevice *dev); // 第三个在 device_bind_common 中被调用, 这个接口比较常用
int (*probe)(struct udevice *dev); // 在 device_probe 中第 5 个被调用
int (*remove)(struct udevice *dev);
int (*unbind)(struct udevice *dev);
int (*ofdata_to_platdata)(struct udevice *dev); // 在 device_probe 中第 4 个被调用
int (*child_post_bind)(struct udevice *dev); // 在子设备的 device_bind_common 中第四个被调用
int (*child_pre_probe)(struct udevice *dev); // 在 device_probe 中第 3 个被调用
int (*child_post_remove)(struct udevice *dev);
int priv_auto_alloc_size; // 在 device_probe 中指定 dev->priv 的大小
int platdata_auto_alloc_size; // 在 device_bind_common 中指定 dev->platdata 的大小
int per_child_auto_alloc_size; // 在 device_probe 中指定 child_dev->parent_priv 的大小
int per_child_platdata_auto_alloc_size;
const void *ops; /* driver-specific operations */
uint32_t flags;
};

drv 需要手动创建, 通过 U_BOOT_DRIVER 创建.

1
2
3
4
5
6
7
#define U_BOOT_DRIVER(__name)                       \
ll_entry_declare(struct driver, __name, driver)

#define ll_entry_declare(_type, _name, _list) \
_type _u_boot_list_2_##_list##_2_##_name __aligned(4) \
__attribute__((unused, \
section(".u_boot_list_2_"#_list"_2_"#_name)))

  展开后得到.

1
struct driver _u_boot_list_2_driver_2___name __aligned(4) __attribute__((unused, section(".u_boot_list_2_driver_2___name")));

  因此该宏将对应的 drv 编译到指定的段 u_boot_list_2_driver_2_中. 可以通过以下接口获取对应的 drv.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct driver *lists_driver_lookup_name(const char *name)
{
struct driver *drv = ll_entry_start(struct driver, driver);
const int n_ents = ll_entry_count(struct driver, driver);
struct driver *entry;

// 遍历所有的 drv 返回对应 name 的 drv
for (entry = drv; entry != drv + n_ents; entry++) {
if (!strcmp(name, entry->name))
return entry;
}

/* Not found */
return NULL;
}

3、uclass

  在创建 udevice 时自动创建, 管理一类设备, 即同类的 udevice 由 uclass 进行统一管理. 每一个 uclalss 都有一个唯一的 uc_drv->uclass_id 进行描述.

1
2
3
4
5
6
struct uclass {
void *priv; // 在 device_bind_common 由 uc_drv->priv_auto_alloc_size 指定
struct uclass_driver *uc_drv; // 对应的 uclass drv
struct list_head dev_head; // 用于连接所属的 udevice
struct list_head sibling_node; // 连接到 gd->uclass_root
};

4、uclass_driver

  给出该类设备的统一接口. post_probe 接口常用来设置该类设备共有属性, 例如 i2c 的速率. post_bind 接口则常设置为 dm_scan_fdt_dev 用于扫描并创建其下的子设备 udevice. 和 driver 类似通过 UCLASS_DRIVER 创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
struct uclass_driver {
const char *name;
enum uclass_id id; // 所属的 uclass
int (*post_bind)(struct udevice *dev); // 第五个在 device_bind_common 中被调用
int (*pre_unbind)(struct udevice *dev);
int (*pre_probe)(struct udevice *dev); // 在 device_probe 中第 1 个被调用
int (*post_probe)(struct udevice *dev); // 在 device_probe 中第 6 个被调用
int (*pre_remove)(struct udevice *dev);
int (*child_post_bind)(struct udevice *dev); // 第二个在 device_bind_common 中被调用
int (*child_pre_probe)(struct udevice *dev); // 在 device_probe 中第 2 个被调用
int (*init)(struct uclass *class); // 第一个在 device_bind_common 中被调用
int (*destroy)(struct uclass *class);
int priv_auto_alloc_size;
int per_device_auto_alloc_size; // 在 device_probe 中为 dev->uclass_priv 分配 dev->uclass->uc_drv->per_device_auto_alloc_size
int per_device_platdata_auto_alloc_size; // 在 device_bind_common 中指 child_dev->uclass_platdata 的大小
int per_child_auto_alloc_size; // 在 device_probe 中如果 dev->parent->driver->per_child_auto_alloc_size 不存在则由它指定 child_dev->uclass_priv 大小.
int per_child_platdata_auto_alloc_size; // 在 device_bind_common 中指定 child_dev->parent_platdata 的大小
const void *ops; //
uint32_t flags;
};

5、 总结

  udevice, driver, uclass, uclass_driver 他们四为位一体, 在 uboot 中扫描 dts 自动创建. 以 i2c 为例进行说明. 下图展示了 rk3566 i2c 的组织架构.

  第一个阶段通过扫描 dts 创建了 i2c0 控制器 i2c2: i2c@fe5b0000 的 udevice 然后以及挂在该子设备 pmic 和 rk817_fg 对应的 device. 需要注意第一个阶段只会创建 device 并不会对硬件进行初始化(probe).

   第二阶段, 即调用 probe 初始化硬件, 注意和 linux 内核自动 probe 不同. uboot 的设计理念是, 即用即初始化, 不用不初始化. 因此 porbe 是手动调用的. 需要初始化硬件的时候手动调用 probe 函数进行初始化. 在初始化硬件(probe)的时候会检测其父设备的硬件是否已经初始化(probe), 如果父设备没有准备好则先初始化父设备. 如上图所示, 在 probe pmic 的时候会检测 i2c0 是否已经 probe, i2c0 没有 probe 则先调用 i2c0 的 probe 初始化 i2c0, 再调用 i2c class 提供的dm_i2c_read dm_i2c_write等统一接口在 pmic 的 probe 中初始化 pmic. 核心接口为 device_probe.

6、device_probe

  1. 检查标志位 DM_FLAG_ACTIVATED 判断是否已经完成 probe, 如果已经 probe 则直接返回.
  2. 为 dev 分配一些列空间如下.
1
2
3
4
5
dev->priv ==> drv->priv_auto_alloc_size
dev->uclass_priv ==> dev->uclass->uc_drv->per_device_auto_alloc_size
dev->parent_priv ==> dev->parent->driver->per_child_auto_alloc_size
==> dev->parent->uclass->uc_drv->per_child_auto_alloc_size // 如果前面的不存在则使用这个

  1. 调用父设备的 probe 函数, 遍历 uclass 中的子设备在没有用到的 seq 中返回一个空闲的 seq 设置 dev->seq 这个 seq 可以表示 dev 的初始化顺序, 第一个被初始化的 dev 的 seq 为 0, 第二个为 1 以此类推. 因此可以通过 seq 判断 dev 的初始化顺序.

  2. 进行一系列回调

1
2
3
4
5
6
回调 dev->uclass->uc_drv->pre_probe(dev); -->
回调 dev->parent->uclass->uc_drv->child_pre_probe(dev); -->
回调 dev->parent->driver->child_pre_probe(dev); -->
回调 dev->drv->ofdata_to_platdata(dev); --> // 常用接口用于解析 dts.
回调 dev->drv->probe(dev); --> // 常用接口用于初始化硬件的 probe 接口.
回调 dev->uclass->uc_drv->post_probe(dev); --> // 在这里设置共有的硬件特性
  1. 调用 pinctrl 设置 default 的 pin 脚状态.
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
int device_probe(struct udevice *dev)
{
const struct driver *drv;
int size = 0;
int ret;
int seq;

if (!dev)
return -EINVAL;

// 检查是否已经完成 probe
if (dev->flags & DM_FLAG_ACTIVATED)
return 0;

// 获取 drv
drv = dev->driver;
assert(drv);

// 为 dev->priv 分配空间 drv->priv_auto_alloc_size
if (drv->priv_auto_alloc_size && !dev->priv) {
dev->priv = alloc_priv(drv->priv_auto_alloc_size, drv->flags);
if (!dev->priv) {
ret = -ENOMEM;
goto fail;
}
}

// 为 dev->uclass_priv 分配空间 dev->uclass->uc_drv->per_device_auto_alloc_size
size = dev->uclass->uc_drv->per_device_auto_alloc_size;
if (size && !dev->uclass_priv) {
dev->uclass_priv = calloc(1, size);
if (!dev->uclass_priv) {
ret = -ENOMEM;
goto fail;
}
}

// 为 dev->parent_priv 分配空间 dev->parent->driver->per_child_auto_alloc_size
// 如果没有 per_child_auto_alloc_size 则根据 dev->parent->uclass->uc_drv->per_child_auto_alloc_size 分配空间
if (dev->parent) {
size = dev->parent->driver->per_child_auto_alloc_size;
if (!size) {
size = dev->parent->uclass->uc_drv->per_child_auto_alloc_size;
}
if (size && !dev->parent_priv) {
dev->parent_priv = alloc_priv(size, drv->flags);
if (!dev->parent_priv) {
ret = -ENOMEM;
goto fail;
}
}

// 调用父设备的 probe 函数
ret = device_probe(dev->parent);
if (ret)
goto fail;

if (dev->flags & DM_FLAG_ACTIVATED)
return 0;
}

// 遍历 uclass 中的子设备在没有用到的 seq 中返回一个空闲的 seq
// 这个 seq 可以表示 dev 的初始化顺序, 第一个被初始化的 dev 的 seq 为 0, 第二个为 1 以此类推
// 可以通过 seq 判断 dev 的初始化顺序.
seq = uclass_resolve_seq(dev);
if (seq < 0) {
ret = seq;
goto fail;
}
dev->seq = seq;

// 设置标志位
dev->flags |= DM_FLAG_ACTIVATED;

// 回调 pinctrl 设置 pin 脚状态
if (dev->parent && device_get_uclass_id(dev) != UCLASS_PINCTRL)
pinctrl_select_state(dev, "default");

// 回调 dev->uclass->uc_drv->pre_probe(dev);
// 回调 dev->parent->uclass->uc_drv->child_pre_probe(dev);
ret = uclass_pre_probe_device(dev);
if (ret)
goto fail;

// 回调 dev->parent->driver->child_pre_probe(dev);
if (dev->parent && dev->parent->driver->child_pre_probe) {
ret = dev->parent->driver->child_pre_probe(dev);
if (ret)
goto fail;
}

// 回调 dev->drv->ofdata_to_platdata(dev);
if (drv->ofdata_to_platdata && dev_has_of_node(dev)) {
ret = drv->ofdata_to_platdata(dev);
if (ret)
goto fail;
}

// 回调 dev->drv->probe(dev);
if (drv->probe) {
ret = drv->probe(dev);
if (ret) {
dev->flags &= ~DM_FLAG_ACTIVATED;
goto fail;
}
}

// 回调 dev->uclass->uc_drv->post_probe(dev);
ret = uclass_post_probe_device(dev);
if (ret)
goto fail_uclass;

// 回调 pinctrl 再次设置 pin 脚状态
if (dev->parent && device_get_uclass_id(dev) == UCLASS_PINCTRL)
pinctrl_select_state(dev, "default");

return 0;
fail_uclass:
if (device_remove(dev, DM_REMOVE_NORMAL)) {
dm_warn("%s: Device '%s' failed to remove on error path\n",
__func__, dev->name);
}
fail:
dev->flags &= ~DM_FLAG_ACTIVATED;

dev->seq = -1;
device_free(dev);

return ret;
}

四、常用接口

1、udevice 创建接口

1) device_bind_with_driver_data

设用设备节点 node 创建并返回一个 udevice

1
2
3
4
5
6
7
// parent: 父设备
// driver: 创建设备的 drv
// name: 设备名称, 一般设置的和 drv name 相同
// driver_data: drv 的私有数据
// node: 该设备对应的 dts 节点, 可以为 null
// devp: 返回创建的 udevice
int device_bind_with_driver_data(struct udevice *parent,const struct driver *drv, const char *name, ulong driver_data, ofnode node, struct udevice **devp)

2) device_bind

使用设备树偏移地址 of_offset 创建 udevice

1
2
3
4
5
6
7
// parent: 父设备
// driver: 创建设备的 drv
// name: 设备名称, 一般设置的和 drv name 相同
// platdata: 设置 dev->platedata
// of_offset: 设备节点的偏移, 可以为 -1
// devp: 返回创建的 udevice
int device_bind(struct udevice *parent, const struct driver *drv,const char *name, void *platdata, int of_offset, struct udevice **devp);

3) device_bind_by_name

使用 driver_info 创建 udevice, 不使用设备树.

1
2
3
4
5
// parent: 父设备
// pre_reloc_only: 是否已经重定位
// driver_info: udevice 设备描述结构
// devp: 返回创建的 udevice
int device_bind_by_name(struct udevice *parent, bool pre_reloc_only, const struct driver_info *info, struct udevice **devp);

2、uclass 操作函数

1) uclass_get

通过 uclass_id 返回对应的 uclass, 没有则创建一个 uclass 返回.

1
int uclass_get(enum uclass_id id, struct uclass **ucp);

2) uclass_get_name

通过 uclass_id 返回 uclass 的 name

1
const char *uclass_get_name(enum uclass_id id);

3) uclass_find_device

返回对应 uclass_id 的 uclass 对应的设备链表上的第 index 个 udevice

1
int uclass_find_device(enum uclass_id id, int index, struct udevice **devp);

4) uclass_find_first_device

返回对应 uclass_id 的 uclass 对应的设备链表上的第一个 udevice

1
int uclass_find_first_device(enum uclass_id id, struct udevice **devp);

5) uclass_find_first_device

返回所属 uclass 链表的下一个 udevice

1
int uclass_find_next_device(struct udevice **devp);

6) uclass_find_device_by_name

返回对应 uclass_id 的 uclass 的设备链表上对应 name 的 udevice

1
int uclass_find_device_by_name(enum uclass_id id, const char *name, struct udevice **devp);

7) uclass_find_device_by_seq

uclass_id 的 uclass 的设备链表上通过 seq 查找 udevice

1
int uclass_find_device_by_seq(enum uclass_id id, int seq_or_req_seq, bool find_req_seq, struct udevice **devp)

8) uclass_find_device_by_ofnode

uclass_id 的 uclass 的设备链表查找对应 node 的 device

1
int uclass_find_device_by_ofnode(enum uclass_id id, ofnode node, struct udevice **devp

3、device_probe 封装接口

1) uclass_get_device

返回 uclass_id 的 uclass 的设备链表上第 index 个 udevice 并进行 device_probe

1
int uclass_get_device(enum uclass_id id, int index, struct udevice **devp);

2) uclass_get_device_by_name

返回 uclass_id 的 uclass 的设备链表上对应 name 的 udevice 并进行 device_probe

1
int uclass_get_device_by_name(enum uclass_id id, const char *name, struct udevice **devp);

3) uclass_get_device_by_seq

返回 uclass_id 的 uclass 的设备链表上对应 seq 的 udevice 并进行 device_probe

1
int uclass_get_device_by_seq(enum uclass_id id, int seq, struct udevice **devp);

4) uclass_get_device_by_ofnode

返回 uclass_id 的 uclass 的设备链表上对应设备节点 node 的 udevice 并进行 device_probe

1
int uclass_get_device_by_ofnode(enum uclass_id id, ofnode node, struct udevice **devp);

6) uclass_first_device

返回 uclass_id 的 uclass 的设备链表上第一个 udevice 并进行 device_probe

1
int uclass_first_device(enum uclass_id id, struct udevice **devp);

7) uclass_next_device

返回所属 uclass 链表上的下一个 udevice 并进行 device_probe

1
int uclass_next_device(struct udevice **devp);

简单总结: 如果只需要返回某个 udevice 则使用带 find 的接口, 如果需要返回并且 probe 则使用带 get 的接口.

请我一杯咖啡吧!
braon 微信 微信
braon 支付宝 支付宝