本文主要是介绍基于 rk3566 的 uboot 分析 - dts 加载和 dm 模型的本质,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
文章目录
- 一、设备树加载使用
- 1、概述
- 2、第一阶段
- 1) fdtdec_setup
- 2) 总结
- 3、第二阶段
- 1) kernle dtb 编译打包
- 2) 加载流程
- 2.1) board_init
- 2.2) init_kernel_dtb
- 2.3) rockchip_read_dtb_file
- 2.4) rockchip_read_resource_dtb
- 3) 总结
- 二、dm 模型
- 1、树的创建
- 1) device_bind_common
- 1.1) uclass_get
- 1.2) uclass_bind_device
- 1.3) 总结
- 2) 树根 gd->dm_root
- 3) 使用设备树创建 dm 模型
- 3.1) dm_scan_fdt
- 3.1.1) dm_init_and_scan
- 3.1.2) lists_bind_fdt
- 3.1.2.1) device_bind_with_driver_data
- 3.1.2.2) driver_check_compatible
- 3.2 ) 总结
- 2、 rk3566 dm 的构建
作者: baron
利用 rk3566 分析记录, dts 加载使用, uboot 中的 dm 模型.
一、设备树加载使用
dts 即 Device Tree Source 设备树源码, Device Tree 用来描述 soc 的硬件信息. 更多请参考 Linux设备树 - DTS语法、节点、设备树解析等 本文主要描述 rk3566 中的加载使用流程. 当前主流的 dm 模型树, 都是由设备树构建出来的.
1、概述
rk3566 中对 dtb 的组成分为两个部分 uboot 和 kernel. 并不是单纯的只用 uboot 和只用 kernel, 两者都用到了. 他们在内存中的分布如下. 下图描述的是 RK3566 的 dtb img 位置.
rk3566 uboot 中设备树的加载有两个阶段, 首先使用 uboot arch/arm/dts
中的设备树, 之后再加载使用 kernel dtb 中的设备树.
2、第一阶段
ruboot 的 dtb 由两个宏决定.编译请参考: 【u-boot】u-boot对设备树的节点解析, 上图的 rk3566 uboot 镜像对应的配置如下.
// 是否使用 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.
static const init_fnc_t init_sequence_f[] = {setup_mon_len,
#ifdef CONFIG_OF_CONTROLfdtdec_setup, // 解析 dts
#endif...
};
1) fdtdec_setup
- 当使用 CONFIG_OF_EMBED 的方式时, dtb 被打包成到 uboo.bin 文件中, 通过
__dtb_dt_begin
符号来获取 dtb 地址 - 没有定义 CONFIG_OF_EMBED 且定义了 CONFIG_OF_SEPARATE u-boot.dtb 和 u-boot.bin 分离. u-boot.dtb 放在 u-boot.bin 后面, 通过
_end
符号来获取 dtb 地址.
int fdtdec_setup(void)
{
......// 当使用 CONFIG_OF_EMBED 的方式时, dtb 被打包成到 uboo.bin 文件中
// 通过__dtb_dt_begin 符号来获取 dtb 地址
# ifdef CONFIG_OF_EMBED
# ifdef CONFIG_SPL_BUILDgd->fdt_blob = __dtb_dt_spl_begin;
# elsegd->fdt_blob = __dtb_dt_begin; // 这里
# endif
# elif defined CONFIG_OF_SEPARATE // 如果定义了这个
# ifdef CONFIG_SPL_BUILDif (IS_ENABLED(CONFIG_SPL_SEPARATE_BSS))gd->fdt_blob = (ulong *)&_image_binary_end;elsegd->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
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 = Truefor dtb, value in TARGET_DTBS.items():if default_dtb:# 打包 arch/arm64/boot/dts/rockchip/ 目录下的 dtb 文件为一个新文件并命名为 rk-kernel.dtb # 保存这个文件到 target_dtb_listori_file = 'arch/arm64/boot/dts/rockchip/' + dtb + '.dtb'shutil.copyfile(ori_file, "rk-kernel.dtb")target_dtb_list += 'rk-kernel.dtb 'default_dtb = Falsenew_file = dtb + value + '.dtb'ori_file = 'arch/arm64/boot/dts/rockchip/' + dtb + '.dtb'shutil.copyfile(ori_file, new_file)target_dtb_list += ' ' + new_fileprint 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.dtbos.system('rm ' + target_dtb_list)if __name__ == '__main__':main()
-
- 打包
arch/arm64/boot/dts/rockchip/
目录下的 dtb 文件为一个新文件并命名为 rk-kernel.dtb
- 打包
-
- 将 rk-kernel.dtb 和 logo.bmp 等文件打包进 resource.img.
-
- 删掉生成的 rk-kernel.dtb .
2) 加载流程
-
如果定义了 CONFIG_USING_KERNEL_DTB 才会调用
init_kernel_dtb()
函数 -
从环境变量获取 fdt_addr. 它由 ENV_MEM_LAYOUT_SETTINGS 指定. 这里指定为
0x0a100000
// 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"
-
调用
rockchip_read_dtb_file((void *)fdt_addr);
, 在 resource.img 中搜索 rk-kernel.dtb , 找到之后把它加载到 ftd_addr . -
更新
gd->fdt_blob = (void *)fdt_addr;
即指定 dtb 为加载的 rk-kernel.dtb
调用链如下所示.
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.dtbrockchip_read_resource_file(fdt_addr, file->name, 0, 0); // 将 dtb 加载到 ftd_addr 这个地址.dtb_okay:gd->fdt_blob = (void *)fdt_addr; // 更新 dtbgd->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()
函数
int board_init(void)
{
......
#ifdef CONFIG_USING_KERNEL_DTB // 定义了这个才会使用 kernel
#ifdef CONFIG_MTD_BLKboard_mtd_blk_map_partitions();
#endifinit_kernel_dtb();
#endif
......
}
2.2) init_kernel_dtb
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. 0x0a100000if (!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 的 dtbgd->fdt_blob = (void *)fdt_addr; // 更新使用的 fdt_blob 为 kernel 的 dtb
......return 0;
}
2.3) rockchip_read_dtb_file
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_addrret = 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
// 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_DTBfile = resource_read_hwid_dtb();
#endifif (!file) // 直接在 resource.img 中查找 rk-kernel.dtbfile = get_file_info(DEFAULT_DTB_FILE);if (!file)return -ENODEV;// 将 rk-kernel.dtb 读取到 fdt_addrret = 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
. 也可以像树根那样手动创建一个结构.
#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 模型的创建.
// 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, 没有则创建一个 uclassret = uclass_get(drv->id, &uc);if (ret) {debug("Missing uclass for driver %s\n", drv->name);return ret;}// 是否使用 KERNEL 的 dtb
#ifdef CONFIG_USING_KERNEL_DTBif (gd->flags & GD_FLG_RELOC) {// 如果定义了这些宏进入条件判断// UCLASS_MMC UCLASS_RKNAND UCLASS_SPI_FLASH UCLASS_MTD UCLASS_PCI UCLASS_AHCIif (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_DEVRESINIT_LIST_HEAD(&dev->devres_head);
#endifdev->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->platdataif (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_platdatasize = 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_platdataif (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
int uclass_get(enum uclass_id id, struct uclass **ucp)
{struct uclass *uc;*ucp = NULL;// 在 gd->uclass_root 中搜索 uclss, 没有调用 uclass_adduc = 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.
// 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;}// 分配一个 uclassuc = calloc(1, sizeof(*uc));if (!uc)return -ENOMEM;// 根据 priv_auto_alloc_size 分配空间并初始化 uc->privif (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_rootlist_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
- 将
dev->uclass_node
连接到uc->dev_head
- 回调父设备的的
dev->parent->uclass->uc_drv->child_post_bind()
接口
int uclass_bind_device(struct udevice *dev)
{struct uclass *uc;int ret;uc = dev->uclass; // 获取对应的 uclasslist_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) 总结
- 在 gd->uclass_root 中搜索 uclss, 查找到则返回对应的 uclss, 找到直接返回
- 前面没找到 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. - 创建一个 udevice 并对其成员变量进行初始化,
dev->driver = drv;
、dev->uclass = uc;
等. - 为 udev 的成员变量 platdata, uclass_platdata, parent_platdata 分配空间. 他们的 size 决定因素如下.
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;
- 将 dev->sibling_node 连接到 parent->child_head 父节点, 即上图用于连接 udevice 之间的线
- 将 dev->uclass_node 连接到 uc->dev_head, 即上图中用于连接 udevice 和 uclass 之间的线.
- 设置dev->flags |= DM_FLAG_BOUND
- 整个过程依次回调的接口如下, 常用的接口为
dev->drv->bind(dev)
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);
- 如果代码已经重定位, 在创建 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
构建树根.
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
来构建的. 相关定义如下.
// ./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) 对应的驱动. 他的定义如下
// ./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
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
#endifreturn dm_scan_fdt_node(gd->dm_root, blob, 0, pre_reloc_only);
}
在 .config 中搜索配置如下
➜ 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
#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-relocif (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))
#endifcontinue;// 节点是否设置 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 模型中的树枝.
//./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_idret = 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
// 封装 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
static int driver_check_compatible(const struct udevice_id *of_match,const struct udevice_id **of_idp,const char *compat)
{// 没设置 of_match 直接返回 errif (!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 ) 总结
- 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; |
- 对于需要解析的节点, 获取节点的 compatible 属性. 使用该属性和 driver->of_match 进行匹配. 如下图所示, 该图示例了 rockchip_display 这个设备的匹配. 左边是 driver, 右边是 dts.
匹配成功则调用 device_bind_common 创建 dm 模型, 如下所示的结构关系.
2、 rk3566 dm 的构建
有了前面的知识我们就可以从整体来分析 rk3566 的 dm 模型树的构建过程了. 前面说过构建过程分为三个阶段. 他们分别如下.
- 第一阶段
// 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
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
指定属性
CONFIG_OF_SPL_REMOVE_PROPS="clock-names interrupt-parent assigned-clocks assigned-clock-rates assigned-clock-parents"
- 第二阶段
//common/board_r.c
static init_fnc_t init_sequence_r[] = {......
#ifdef CONFIG_DMinitr_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_TIMERgd->timer = NULL;
#endifbootstage_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_EARLYret = dm_timer_init();if (ret)return ret;
#endifreturn 0;
}
#endif
第二阶段将第一阶段创建的 dm 模型树保存到 gd->dm_root_f
. 设置 gd->dm_root
为空, 之后从新再创建一遍, 注意这里传入的是 false. 因此会解析设备树二级节点的所有 okay
节点. 注意这时候还是使用的 uboot 的 dtb.
- 第三阶段
//common/board_r.c
static init_fnc_t init_sequence_r[] = {......
#ifdef CONFIG_DMinitr_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 的 dtbdtb_okay:gd->fdt_blob = (void *)fdt_addr; // 更新 dtbgd->flags |= GD_FLG_KDTB_READY; // 设置标志位dm_scan_fdt((void *)gd->fdt_blob, false); // 更新 dm 模型树
第三阶段首加载 kernel 的 dtb, 然后使用 kernel 的 dtb 调用 dm_scan_fdt 扫描设备树并且解析所有根目录下的二级子节点, 对于具有 okay
的子节点进行匹配并且创建 dm 模型树枝.
这篇关于基于 rk3566 的 uboot 分析 - dts 加载和 dm 模型的本质的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!