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

news2024/9/24 3:26:46

文章目录

  • 一、设备树加载使用
    • 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 的组成分为两个部分 ubootkernel. 并不是单纯的只用 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_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 地址.
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

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

// 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

调用链如下所示.

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()函数

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
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
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
// 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. 也可以像树根那样手动创建一个结构.

#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, 没有则创建一个 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
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.

// 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()接口
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 决定因素如下.
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)
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构建树根.

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
#endif
    return 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-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 模型中的树枝.

//./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
// 封装 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 直接返回 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=1status = okay;
  1. 对于需要解析的节点, 获取节点的 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_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.

  • 第三阶段
//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 模型树枝.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1488150.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

营业执照年报申报

姿势&#xff1a;营业执照年报申报 借鉴文章&#xff1a;个体工商户年报申报流程&#xff08;不要再花冤枉钱&#xff09; 1、国家企业信用信息公示系统 地址&#xff1a;https://www.gsxt.gov.cn/index.html 2、登录&#xff08;重庆的方式二简单&#xff09;

SpringBoot+Vue实现el-table表头筛选排序(附源码)

&#x1f468;‍&#x1f4bb;作者简介&#xff1a;在笑大学牲 &#x1f39f;️个人主页&#xff1a;无所谓^_^ ps&#xff1a;点赞是免费的&#xff0c;却可以让写博客的作者开心好几天&#x1f60e; 前言 后台系统对table组件的需求是最常见的&#xff0c;不过element-ui的el…

SAP PP学习笔记05 - BOM配置(Customize)1 - 修正参数

上次学习了BOM相关的内容。 SAP PP学习笔记04 - BOM1 - BOM创建&#xff0c;用途&#xff0c;形式&#xff0c;默认值&#xff0c;群组BOM等_sap销售bom与生产bom-CSDN博客 SAP PP学习笔记04 - BOM2 -通过Serial来做简单的BOM变式配置&#xff0c;副明细&#xff0c;BOM状态&…

禁用pycharm中解释器的-u选项

用pycharm远程连接服务器跑代码的时候&#xff0c;想在配置中设置好入参&#xff0c;可以直接运行如下图。 但是运行之后发现总会在运行脚本前多出来一个参数选项‘-u’&#xff0c;不能被正确识别就走不下去。 ssh://rootxxxxx:22/usr/bin/python -m torch.distributed.laun…

用边缘计算网关解决离散行业数采问题-天拓四方

一、引言 随着工业4.0时代的来临&#xff0c;离散制造行业正面临数字化转型的关键节点。离散制造的特点是小批量、多品种、高复杂度&#xff0c;如何实现高效、精准的数据采集与分析&#xff0c;提升生产效率和产品质量&#xff0c;成为行业亟待解决的问题。边缘计算网关作为一…

MySQL 逗号分隔查询--find_in_set()函数

业务场景&#xff1a; 在使用MySQL的时候&#xff0c;可能的某个字段存储的是一个英文逗号分割的字符串&#xff08;这里我们不讨论表设计的合理性&#xff09;&#xff0c;如图所示&#xff1a; 我们在查询的时候需要匹配逗号分割中的某个字符串&#xff0c;该怎么查询呢&am…

Python爬虫:爬虫基本概念和流程

前言&#xff1a; 零基础学Python&#xff1a;Python从0到100最新最全教程。 想做这件事情很久了&#xff0c;这次我更新了自己所写过的所有博客&#xff0c;汇集成了Python从0到100&#xff0c;共一百节课&#xff0c;帮助大家一个月时间里从零基础到学习Python基础语法、Pyth…

Sora爆火,数字人IP如何借助AIGC视频生成软件制作短视频营销?

ChatGPT、Sora等大模型的出现&#xff0c;创新了短视频内容创作生产方式。但目前Sora模型无法准确模拟复杂场景的物理特性&#xff0c;并且可能无法理解因果关系导致视频失真。 广州虚拟动力基于用户使用需求&#xff0c;推出了AIGC数字人视频生成平台&#xff0c;企业、品牌可…

Linux CentOS使用Docker部署Apache Superset并实现远程分析数据

文章目录 前言1. 使用Docker部署Apache Superset1.1 第一步安装docker 、docker compose1.2 克隆superset代码到本地并使用docker compose启动 2. 安装cpolar内网穿透&#xff0c;实现公网访问3. 设置固定连接公网地址 前言 Superset是一款由中国知名科技公司开源的“现代化的…

GPT-4劲敌来袭!Mistral Large全球第二大模型重磅上线,你准备好体验了吗?

近日&#xff0c;Mistral刚刚推出了一个新的大模型&#xff0c;叫做Mistral Large。 这个模型在全球的排名是第二&#xff0c;仅次于我们熟知的GPT-4&#xff0c;现在你可以通过API轻松访问到它。 Mistral Large是通过la Plateforme平台提供的&#xff0c;而且还在Azure上进行…

sparse transformer 常见稀疏注意力

参考&#xff1a; https://zhuanlan.zhihu.com/p/259591644 主要就是降低transformer自注意力模块的复杂度 复杂度主要就是 Q K^T影响的&#xff0c;稀疏注意力就是在Q点乘K的转置这模块做文章 下列式一些sparse transformer稀疏注意力方法 a、transformer原始的 &#xff0…

OpenGL 实现色温、色调、亮度、对比度、饱和度、高光

1.简介 色温&#xff1a;简单理解是色彩的温度&#xff0c;越低越冷如蓝色&#xff0c;约高越暖如红色。 亮度&#xff1a;增加就是给图片所有色彩加白色&#xff0c;减少加黑色。注意是只加黑白两种颜色&#xff0c;不然容易跟纯度弄混。 对比度&#xff1a;增加就是让白的…

微信如何设置自动回复消息,提升沟通效率的?

在日常微信聊天过程中&#xff0c;我们可能会频繁遇到相同问题的客户提问&#xff0c;特别是对于从事销售工作的朋友们而言&#xff0c;客户添加好友后的第一句话常常为“在吗”或“你好”。当我们拥有大量好友&#xff0c;手动逐一回复可能会耗费大量时间。因此&#xff0c;自…

Conda笔记--移动Conda环境后pip使用异常的解决

1--概述 由于各种原因&#xff0c;需要将Anaconda转变为Minicoda&#xff0c;为了保留之前安装的所有环境&#xff0c;直接将anaconda3/envs的所有环境拷贝到Miniconda/envs中&#xff0c;但在使用移动后环境时会出现pip的错误&#xff1a;bad interpreter: No such file or di…

Acwing---1497. 树的遍历

树的遍历 1.题目2.基本思想3.代码实现 1.题目 一个二叉树&#xff0c;树中每个节点的权值互不相同。 现在给出它的后序遍历和中序遍历&#xff0c;请你输出它的层序遍历。 输入格式 第一行包含整数 N&#xff0c;表示二叉树的节点数。 第二行包含 N个整数&#xff0c;表示二…

数字经济的下一步:Web3的潜力与前景

引言&#xff1a; 随着区块链技术的迅速发展&#xff0c;数字经济正迎来新的变革时代。在这个数字化时代&#xff0c;Web3作为区块链技术的延伸和演进&#xff0c;正在成为全球数字经济发展的重要方向。本文将深入探讨Web3的潜力与前景&#xff0c;以及它对数字经济发展的深远…

物联网边缘计算云边协同

文章目录 一、物联网云边协同1.IoT云边协同设计2.物联网平台设计3.物联网平台实现 二、部署环境1.节点配置2.版本信息 三、IoT云边协同部署1.部署Kubernetes集群2.部署KubeEdge3.部署ThingsBoard集群4.部署Node-RED边缘网关4.1.边缘网关功能4.2.部署EMQX4.2.部署Node-RED 5.配置…

文案如何让产品卖点看得见、摸得着?

好的电影能够让人记忆犹新&#xff0c;而好的文案也能让卖点可视化&#xff0c;卖点可视化就是让传播目的、产品优势、品牌形象等信息变得可感知&#xff0c;可视化的文案能够让产品功能、优势的展现可以更加直观、生动&#xff0c;从而缩短用户的购买决策时间。今天媒介盒子就…

成功的交易没有对错,只有逻辑

大部分人将交易失败归咎于心态&#xff0c;但其实我们是否认真思考过自己的基本功是否扎实呢&#xff1f;这篇文章将引导你换个角度看待交易&#xff0c;让你明白自己应该努力的方向。 曾经&#xff0c;你或许认为资金体量小、信息不对称、技术不过关、心态不过硬是阻碍交易发展…

【数据结构】一步一步实现AVL树

树和节点的定义 template<class K,class V> class AVLTreeNode {AVLTreeNode<K, V>* _left;AVLTreeNode<K, V>* _right;AVLTreeNode<K, V>* _parent;pair<K, V> _kv;int _bf;AVLTreeNode(const pair<K,V>& kv):_left(nullptr),_right…