基于 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 的組成分為兩個部分 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_CONTROLfdtdec_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_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()
    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.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
  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; // 獲取對應的 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) 總結
  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
#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 ) 總結
  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_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 模型樹枝.

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/718948.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/718948.shtml
英文地址,請注明出處:http://en.pswp.cn/news/718948.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

使用Matlab計算IGRAv2探空站的Tm和PWV

1. 探空站IGRAv2數據 探空站的Tm常作為真值&#xff0c;去檢驗Tm線性公式或者ERA5 Tm等的精度 。 探空站PWV常作為真值&#xff0c;去檢驗GNSS PWV等的精度 2. Tm 的計算方法 Tm 的計算方法有兩種在前面的文章有講&#xff0c;這里用 使用水汽壓和溫度計算Tm。 ei和 Ti 表示…

本地如何配置支付寶模擬支付場景并結合內網穿透實現公網環境調試開發?

文章目錄 前言1. 下載當面付demo2. 修改配置文件3. 打包成web服務4. 局域網測試5. 內網穿透6. 測試公網訪問7. 配置二級子域名8. 測試使用固定二級子域名訪問 前言 在沙箱環境調試支付SDK的時候&#xff0c;往往沙箱環境部署在本地&#xff0c;局限性大&#xff0c;在沙箱環境…

項目管理,如何做到流程標準化?

在項目管理的流程中&#xff0c;每個階段都有自己的起止范圍&#xff0c;有本階段的輸入文件和本階段要產生的輸出文件。 同時&#xff0c;每個階段都有本階段的控制關口&#xff0c;即本階段完成時將產生的重要文件也是進入下一階段的重要輸入文件。每個階段完成時一定要通過…

還在猶豫學不學?鴻蒙技術是否有前途的最強信號來了

2024年3月3日 上午10 點&#xff0c;深圳官方賬號發布了一篇關于鴻蒙技術發展的重要文章&#xff0c;看到這篇文章后我非常激動&#xff0c;忍不住和大家分享一下&#xff01; 華為鴻蒙系統自提出以來&#xff0c;網友們的態度各不相同&#xff0c;有嘲笑“安卓套殼”的&#…

2024 CHINASHOP丨悠絡客AI應用亮點搶鮮看,還有價值百元門票免費送哦!

3月13日-15日&#xff0c;備受國內外關注的第二十四屆中國零售業博覽會&#xff08;2024 CHINASHOP&#xff09;將在上海國家會展中心正式開展&#xff01;悠絡客作為深耕智慧門店15年的公有云人工智能企業&#xff0c;也將帶著全新AI產品和智慧門店解決方案亮相展會&#xff0…

Tomcat核心組件概述

Tomcat是一個免費的開放源代碼的Web應用服務器&#xff0c;屬于輕量級應用服務器&#xff0c;在中小型系統和并發訪問用戶不是很多的場合下被普遍使用&#xff0c;是開發和調試JSP程序的首選。Tomcat技術先進、性能穩定&#xff0c;而且免費&#xff0c;因而深受Java愛好者的喜…

Windows系統中ollama下載模型前設置下載路徑

Windows系統中ollama下載模型前設置下載路徑 一開始設置了用戶環境變量 OLLAMA_MODELS&#xff0c;沒有效果 添加系統環境變量后&#xff0c;ollama pull和run的模型都到了環境變量中的路徑下&#xff1b; 設置完后可以在cmd中檢查一下&#xff1a;echo %ollama_models% ollam…

PySide6實現注冊,登錄流程

目錄 一:實現思路 二:代碼實現 三:完整代碼和界面 一:實現思路 設計三個窗口界面,運行程序,打開注冊界面,填寫用戶名和密碼,信息填寫完整,校驗通過,插入數據庫。跳轉到登錄界面,輸入用戶名和密碼,校驗通過跳轉到主界面。

Electron 多顯示器渲染

Electron打出的包&#xff0c;如果當前有倆個顯示器&#xff0c;則可以配置當前顯示倒哪個顯示器上&#xff0c;或者可以配置不同的顯示器&#xff0c;啟動不同的項目&#xff0c;只在Windows和Linux下測試過&#xff0c;Mac沒有真機&#xff0c;可以利用docker安裝MacOS環境&a…

使用mapbox navigation搭建一個安卓導航 示例

一.代碼示例地址&#xff1a; https://github.com/mapbox/mapbox-navigation-android-examples/tree/main 二. 具體步驟&#xff1a; git clone gitgithub.com:mapbox/mapbox-navigation-android-examples.git Go to app/src/main/res/values Look for mapbox_access_token.…

Sora:探索大型視覺模型的前世今生、技術內核及未來趨勢

Sora&#xff0c;一款由OpenAI在2024年2月推出的創新性文生視頻的生成式AI模型&#xff0c;能夠依據文字說明&#xff0c;創作出既真實又富有想象力的場景視頻&#xff0c;展現了其在模擬現實世界方面的巨大潛能。本文基于公開技術文檔和逆向工程分析&#xff0c;全面審視了Sor…

leetcode-回溯法-字符串分割問題

131. 分割回文串 #include<vector> #include<iostream> #include<queue> using namespace std;class Solution { public: vector<string> path_; vector<vector<string>> res_;vector<vector<string>> partition(string s) {…

pytorch(四)用pytorch實現線性回歸

文章目錄 代碼過程準備數據設計模型設計構造函數與優化器訓練過程訓練代碼和結果pytorch中的Linear層的底層原理&#xff08;個人喜歡&#xff0c;不用看&#xff09;普通矩陣乘法實現Linear層實現 回調機制 代碼過程 訓練過程&#xff1a; 準備數據集設計模型&#xff08;用來…

國圖公考:山東事業編考試即將開始

山東事業編考試時間為2024年3月10日-9.00-11.30分 考試科目為公基寫作 準考證打印時間為2024年3月5日9.00-3月10日9.30分 準考證打印入口&#xff1a;山東考試信息網 綜合類筆試在全省十六市均設置考點&#xff0c;參加考試的考生可憑借準考證和本人身份證參加筆試

Python爬蟲實戰(基礎篇)—13獲取《人民網》【最新】【國內】【國際】寫入Word(附完整代碼)

文章目錄 專欄導讀背景測試代碼分析請求網址請求參數代碼測試數據分析利用lxml+xpath進一步分析將獲取鏈接再獲取文章內容測試代碼寫入word完整代碼總結專欄導讀 ????本文已收錄于《Python基礎篇爬蟲》 ????本專欄專門針對于有爬蟲基礎準備的一套基礎教學,輕松掌握Py…

第 2 個 Java Web 應用工程(JSP JavaBean DB)(含源碼)(圖文版)

JavaBean 是一種符合特定約定的 Java 類&#xff0c;通常用于在 Java 應用程序中封裝數據以及提供對數據的訪問和修改方法。 本文示例&#xff1a;建立一個 Tomcat 工程&#xff0c;編寫一個 JSP 頁面&#xff0c;調用 JavaBean 訪問數據庫并顯示到頁面上&#xff0c;發布到 T…

【開源物聯網平臺】物聯網設備上云提供開箱即用接入SDK

一、項目介紹 IOTDeviceSDK是物聯網平臺提供的設備端軟件開發工具包&#xff0c;可簡化開發過程&#xff0c;實現設備快速接入各大物聯網平臺。 設備廠商獲取SDK后&#xff0c;根據需要選擇相應功能進行移植&#xff0c;即可快速集成IOTDeviceSDK&#xff0c;實現設備的接入。…

gradle中設置變量,在代碼中讀取

在app的gradlew文件中設置變量appModelCode&#xff0c;設置manifestPlaceholders android {def appModelCode 1 //1:模式1 2:模式2def appModelName "model1"if (appModelCode 1) {...}defaultConfig {applicationId appIdminSdk 26targetSdk 32versionCode app…

音視頻數字化(視頻線纜與接口)

目錄 1、DVI接口 2、DP接口 之前的文章【音視頻數字化(線纜與接口)】提到了部分視頻線纜,今天再補充幾個。 視頻模擬信號連接從蓮花頭的“復合”線開始,經歷了S端子、色差分量接口,通過亮度、色度盡量分離的辦法提高畫面質量,到VGA已經到了模擬的頂峰,實現了RGB的獨立…

android 推薦一個上拉加載更多,下拉刷新的框架(非常好用)

作者&#xff1a;scwang 大神 GitHub - scwang90/SmartRefreshLayout: &#x1f525;下拉刷新、上拉加載、二級刷新、淘寶二樓、RefreshLayout、OverScroll&#xff0c;Android智能下拉刷新框架&#xff0c;支持越界回彈、越界拖動&#xff0c;具有極強的擴展性&#xff0c;…