U-Boot 之七 详解 Driver Model 架构、配置、命令、初始化流程
U-Boot 在 2014 年 4 月参考 Linux Kernel 的驱动模型设计并引入了自己的 Driver Model(官方简称 DM) 驱动架构。这个驱动模型(DM)为驱动的定义和访问接口提供了统一的方法,提高了驱动之间的兼容性以及访问的标准性。
文中涉及的代码均放到了我个人的 Github 上:https://github.com/ZCShou/BOARD-STM32F769I-EVAL,大家可以直接拿来边学习边验证,避免眼高手低。 本文中涉及的源码主要是使用 U-Boot-v2022.10
,不同版本源码差异可能较大!!!
配置
DM 架构需要通过配置项 CONFIG_DM=y
来启用,对应的实际外设的驱动则需要通过使能 CONFIG_DM_xxx
来使能。其中,xxx 表示某个具体的外设,例如,启用 CONFIG_DM_SERIAL
则会自动启用 Makefile 中添加对应的源码文件:
目前,绝大多数的设备的驱动均已经完全迁移到了 DM 架构,所以,在实际源码中,我们经常可以看到 CONFIG_DM_xxx
对应的驱动接口被实现了,旧版的则没有实现或者直接没有旧的驱动接口了(部分驱动仍然是旧驱动模式)。
链接选项
DM 驱动在编译后会被统一存放在最终的镜像文件中,每个设备的 DM 架构的驱动都会在编译时单独放到一个节区当中。 当我们编译 U-Boot 时所有这些驱动程序都会使用 __u_boot_list_2_"#_list"_2_"#_name
的节区作为名字,此外,这些节区还会被 __u_boot_list_2_"#_list"_1
和 __u_boot_list_2_"#_list"_3
包裹起来,这样就可以计算出所有 __u_boot_list_2_*
的大小。这些信息可以直接在 u-boot.map
看到:
实际不只有驱动,其他部分,例如,cmd,也是这样处理的
代码中的实现方式的关键就在于 UCLASS_DRIVER(__name)
、U_BOOT_DRIVER(__name)
等这几个宏值,这些宏值最终都会引用 ./include/linker_lists.h
中的相关宏 ll_entry_declare
,这个就是实现的关键。
在初始化过程中,U-Boot 就会遍历上面这些节区,然后进行内容匹配,依次创建各种设备和对应的 UCLASS。如下是根据驱动的名字查找指定驱动的方法:
Device Tree / Platform Data
驱动必须知道硬件的基本信息,U-Boot 支持 Platform Data(平台数据,代码中常简称 plat
或 platdata
)和 Flattened Device Tree(设备树,代码常简称 fdt
)这两种硬件基本配置信息提供方式。其中,平台数据是旧方式,设备树则是标准方式。
Platform Data
Platform Data 是通过一个 C 结构体来将平台特定的配置信息(寄存器的地址,总线速度等)传递给驱动程序,设备信息最终被存放到 udevice ->plat_
指向的内存中, 驱动可以随时通过 dev->plat_
访问他们的数据。官方指出,除非有必要的理由,否则不要使用平台数据这种方式,而应该使用设备树方式。
static const struct dm_demo_pdata red_square = {.colour = "red",.sides = 4.
};/* 直接定义(不推荐) */
static const struct driver_info info[] = {{.name = "demo_shape_drv",.plat = &red_square,},
};
demo1 = driver_bind(root, &info[0]);/* 使用 U_BOOT_DRVINFO 宏(推荐) */
U_BOOT_DRVINFO(demo0) = {.name = "demo_shape_drv",.plat = &red_square,
};
Platform Data 只有在有当内存限制不允许使用设备树时才会使用。 此外,U-Boot 提供了一种方法,自动将设备树转换为 Platform Data,即 of-platdata
特性。但是,of-platdata
仅在 SPL/TPL
阶段可用。
Device Tree
设备树提供了一种更灵活的提供设备数据的方法,官方推荐要使用设备树方式。U-Boot 内部将自动解析设备树获取相关设备信息,设备信息最终也是被存放到 udevice ->plat_
指向的内存中(具体方式就是通过 driver
中的 plat_auto
和 of_to_plat
)。
框架
U-Boot 的 DM 使用 uclass
和 udevice
这两个抽象的类来管理所有的设备驱动,这两个抽象类分别各自对应 uclass_driver
和 driver
。udevice
是根据 driver
动态创建的;uclass
是根据 uclass_driver
创建的。但是只有在创建 udevice
才会查找对应的 uclass
,因此, 最终是只有 driver
存在时,才会创建 uclass
。
真正有用的是一个个独立的 uclass_driver
和 driver
,他们分别通过 uclass
和 udevice
管理起来。在代码实现上,uclass
和 udevice
其实都是双向链表的节点,通过双向链表将所有驱动串起来进行管理。并最终由 global_data
中的相关变量指示链表位置。一个简易的框图如下所示:
官方提供了一个驱动 DEMO (drivers/demo
),通过开启 CONFIG_DM=y
、CONFIG_CMD_DEMO=y
、CONFIG_DM_DEMO=y
、CONFIG_DM_DEMO_IMX6ULL=y
就可以把这个 DEMO 添加我们的构建中,然后进行学习测试。
global_data
./include/asm-generic/global_data.h
文件中的 struct global_data
结构管理着整个 U-Boot 的全局变量,当我们定义 CONFIG_DM=y
后,global_data
中就会多出一些 DM 相关的字段,保存 DM 相关信息。具体见下面的代码注释:
struct global_data {
/* ... 略 ... */
#ifdef CONFIG_DMstruct udevice *dm_root; /* 指向 DM 的根设备 */struct udevice *dm_root_f; /* 指向重定向前的 DM 的根设备 */struct list_head uclass_root_s; /* 非只读性内存中的 UCLASS 链表表头 */struct list_head *uclass_root; /* UCLASS 链表表头指针,非只读内存中他就指向上面的 uclass_root_s *//* ... 略 ... */
#endif
/* ... 略 ... */
uclass 和 uclass_driver
uclass
和 uclass_driver
定义在 ./include/dm/uclass.h
文件中,其中,uclass
将同类型的设备划为一组进行归类管理;uclass_driver
为一组相关的驱动程序提供了一致的接口。uclass
与 uclass_driver
是一一对应的。
在表述中,我们通常使用 uclass_id 来表示一个 uclass,例如,UCLASS_ROOT 表示 ROOT UCLASS 本身,其对应的驱动则称为 uclass_driver_root
。 uclass 的 ID 可用值定义在 ./include/dm/uclass-id.h
文件的 enum uclass_id
中,当时需要注意,该 ID 实际是在 struct uclass_driver
中被使用。
struct uclass
uclass
将同类型的设备划为一组进行归类管理。注意,uclass
是 U-Boot 在初始化过程中自动生成的,并且不是所有 uclass
都会生成,有对应 uclass_driver
并且有被 udevice
匹配到的 uclass
才会生成(下面的初始化章节详细说明)。
struct uclass {void *priv_; /* uclass 本身使用的私有数据指针。不对外使用。*/struct uclass_driver *uc_drv; /* 一个 UCLASS 对一个 uclass_driver,这个指针指向对应的 uclass_driver */struct list_head dev_head; /* 本 UCLASS 下对应的 udevice 链表 */struct list_head sibling_node; /* 本 UCLASS 节点本身的前后节点(用于串联 uclass 链表) */
};
在代码实现上,struct uclass
这个结构体其实就是一个链表节点,当 DM 初始化之后,所有的 uclass
会形成一个由 gd->uclass_root
为链表头的双向链表。这个链表是通过其中的 sibling_node
这个这个成员串起来的。
在初始化时,会遍历所有 uclass_driver
,每发现一个 uclass_driver
就会查找其中的 ID 对应的 uclass
是否存在(判断条件是已存在的 uclass->uc_drv->id 是否等于当前 uclass_driver->id
),不存在就会以 uclass_driver
中的 ID新建一个 uclass
,然后关联到 uclass_driver
上。也就是说,uclass
是根据 uclass_driver
动态创建的。
struct uclass_driver
uclass_driver
为一组相关的驱动程序提供了一致的接口,每一个 uclass
都会对应一个 uclass_driver
。在代码实现上,struct uclass
中的 uc_drv
就指向了当前 uclass 对应的 uclass_driver
。
struct uclass_driver {const char *name; /* uclass driver 的名称,在定义 uclass_driver 时,填写的一个字符串 */enum uclass_id id; /* uclass 的 ID 号,取值见 ./include/dm/uclass-id.h 文件中的 enum uclass_id 定义 */int (*post_bind)(struct udevice *dev); /* 在一个新设备绑定到这个 uclass 后被调用 */int (*pre_unbind)(struct udevice *dev); /* 在一个设备从该 uclass 解绑定之前调用 */int (*pre_probe)(struct udevice *dev); /* 在 probe 一个新设备之前调用 */int (*post_probe)(struct udevice *dev); /* 在 probe 一个新设备之后调用 */int (*pre_remove)(struct udevice *dev); /* 在移除设备之前调用 */int (*child_post_bind)(struct udevice *dev); /* 在这个 uclass 的 child 绑定到一个设备之后被调用 */int (*child_pre_probe)(struct udevice *dev); /* 在这个 uclass 的 child 被 probed 之前被调用 */int (*child_post_probe)(struct udevice *dev); /* 在这个 uclass 的 child 被 probed 之后被调用 */int (*init)(struct uclass *class); /* 在创建一个新的 uclass 时被调用 */int (*destroy)(struct uclass *class); /* 在 uclass 被销毁时被调用 */int priv_auto; /* 如果非零,它就是 uclass->priv_ 指针中分配的私有数据的大小。如果为 0,则 uclass driver 负责分配所需私有数据的空间 */int per_device_auto; /* 每个 device 都可以将 uclass 拥有的私有数据保存在自己的 dev->uclass_priv_ 中。如果该值是非零,将在 device 初始化时自动分配该值大小的空间 */int per_device_plat_auto; /* 每个 device 都可以将 uclass 拥有的平台数据保存在自己的 dev->uclass_plat_ 中。如果该值是非零,将在 device 初始化时自动分配该值大小的空间 */int per_child_auto; /* 每个子设备可以保存它的 parent 私有数据到 dev->parent_priv_ 中。如果该值是非零,将在 device 初始化时自动分配该值大小的空间。在 udevice 对应的 driver 中,也存在该变量,只有 udevice 对应的 driver 中该值为 0 时,才会使用该值 */int per_child_plat_auto; /* 每个子设备可以保存它的 parent 平台数据到 dev->parent_plat_ 中。如果该值是非零,将在 device 初始化时自动分配该值大小的空间。在 udevice 对应的 driver 中,也存在该变量,只有 udevice 对应的 driver 中该值为 0 时,才会使用该值 */uint32_t flags; /* 这个 uclass 的标志(DM_UC_…) */
};
在代码实现中,uclass_driver
必须使用宏 UCLASS_DRIVER(__name)
来进行定义。该宏值除了使用 struct uclass_driver
定义 __name
变量外,还会定义一个同名节区,并将 __name
放到这个同名的节区当中。以 serial
为例,如下所示:
uclass_driver
是通过 struct uclass
链表管理起来的,每个 uclass_driver
都必须关联到一个指定的 struct uclass
上(链表节点上),当不存在 uclass_driver
对应的 struct uclass
时,就会自动创建一个 struct uclass
然后关联起来。
udevice 和 driver
udevice
和 driver
定义在 ./include/dm/device.h
文件中,其中,udevice
是一个抽象类表示一个设备(驱动程序的实例);driver
则是一个设备实际对应的驱动程序。udevice
与 driver
可以是多对一的关系(即多个设备可能共用同一个 driver
)。
不同于 UCLASS,udevice
和 driver
中均有 name
成员来标识自己,而且这两个名字可以是不相同的,也可以相同。例如,ROOT 设备的名字和 ROOT 驱动的名字均为 root_driver
。设备树中定义的设备名字通常是节点名,而对应的驱动名字则是代码中一个有确切含义的字符串。
struct udevice
udevice
包含有关设备的信息,其本质上是一个驱动程序实例,必须绑定到特定 port 或 peripheral 的驱动上的(udevice
必须通过其成员 const struct driver *driver
与一个指定的 driver
关联)。udevice
本身并无法关联 UCLASS,必须根据其关联 struct driver
中的 id
属性来关联其所在的 UCLASS 的。
struct udevice {const struct driver *driver;/* 此设备使用的驱动程序 */const char *name; /* 设备名称,通常为 FDT 节点名称 */void *plat_; /* 此设备的配置数据(DM 之外不能访问),这通常由驱动程序制定大小,并且由驱动程序负责填充内容 */void *parent_plat_; /* 该设备的父总线配置数据(DM 之外不能访问) */void *uclass_plat_; /* 此设备对应的 uclass 的配置数据(DM 之外不能访问) */ulong driver_data; /* 驱动程序数据字,用于将此设备与其驱动程序相匹配的条目 */struct udevice *parent; /* 该设备的父设备,顶级设备(例如,ROOT DEVICE)的 parent 为 NULL */void *priv_; /* 此设备的私有数据(DM 之外不能访问) */struct uclass *uclass; /* 指向该设备对应的 uclass 的指针 */void *uclass_priv_; /* 此设备对应的 uclass 的私有数据(DM 之外不能访问) */void *parent_priv_; /* 此设备的父设备的私有数据 */struct list_head uclass_node; /* 由此设备对应的 uclass 用于连接它的设备 */struct list_head child_head; /* 此设备的子设备列表 */struct list_head sibling_node; /* 所有设备列表中的下一个设备 */
#if !CONFIG_IS_ENABLED(OF_PLATDATA_RT)u32 flags_; /* 此设备的标志 DM_FLAG_xx */
#endifint seq_; /* 为该设备分配的序列号(-1 表示没有序列号)。这是在设备绑定时设置的,在设备对应的 uclass 中是唯一的。如果设备在设备树中有别名,则别名用于设置序列号。否则,使用下一个可用号码。序列号用于某些需要对设备进行编号的命令(例如 mmc dev)(DM 之外不能访问)*/
#if CONFIG_IS_ENABLED(OF_REAL)ofnode node_; /* 此设备的设备树节点的引用 */
#endif
#if CONFIG_IS_ENABLED(DEVRES)struct list_head devres_head; /* 与此设备关联的内存分配列表。当 CONFIG_DEVRES 被启用时,devm_kmalloc()和 friends 会添加到这个列表中。这样分配的内存将在移除或解绑设备时自动释放 */
#endif
#if CONFIG_IS_ENABLED(DM_DMA)ulong dma_offset; /* (CPU 的)物理地址空间和设备总线地址空间之间的偏移量 */
#endif
};
在代码实现上,struct udevice
这个结构体其实就是一个链表节点,当 DM 初始化之后,所有的 udevice
会形成一个由 gd->dm_root
为链表头的双向链表,这个链表是通过其中的 child_head
和 sibling_node
这个这个成员串起来的。与 uclass
不同, udevice
还支持通过 uclass_node
串联到 UCLASS 中。即一个 struct udevice
会同时位于多个链表中。
在初始化时,会遍历所有 driver
,每发现一个 driver
就会查找对应的 udevice
是否存在,不存在就会以 driver
的名字新建一个设备。也就是说,udevice
是根据 driver
动态创建的。
一个设备将通过一个 ‘bind’ 调用来产生,要么是由于 U_BOOT_DRVINFO()
宏(在这种情况下,plat
是非 null),要么是由于设备树中的一个节点(在这种情况下,of_offset
为 >= 0)。在后一种情况下,我们将在驱动程序的 of_to_plat
方法中将设备树信息转换为 plat
(如果设备有设备树节点,则在 probe 方法之前调用)。
关于 Device Sequence Numbers
在多数情况下 U-Boot 从 0 开始为设备进行编号。这个编号唯一地标识了其 UCLASS 中的一个设备,因此在一个特定 UCLASS 中没有两个设备可以具有相同的序列号。
序列号从 0 开始,但允许有间隙。 例如,一个开发板可能有 I2C1、I2C4、I2C5,但没有 I2C0、I2C2、I2C3。设备如何编号的选择取决于特定的开发板,在某些情况下可能由 SoC 设置。
设备序列号在绑定设备时进行解析,存储在 udevice->seq_
成员变量中,且在设备的整个生命周期内都不会改变。
struct driver
driver
含有创建新设备和删除设备的方法,设备由 platdata 或者 device tree 节点(通过查找与 of_match 匹配的 compatible 字符串进行配对)提供的信息来设置自己。
struct driver {char *name; /* 设备名字。在定义 driver 时指定的一个字符串 */enum uclass_id id; /* 标记此驱动属于哪个 uclass 的 id,取值是 ./include/dm/uclass-id.h 中定义的 enum uclass_id */const struct udevice_id *of_match; /* 要匹配的 compatible 字符串列表 */int (*bind)(struct udevice *dev); /* 绑定 device 到它的 driver 时被调用 */int (*probe)(struct udevice *dev); /* 被调用来探测一个设备,即激活设备 */int (*remove)(struct udevice *dev); /* 被调用来移除一个设备 */int (*unbind)(struct udevice *dev); /* 调用来解除设备与其驱动程序的绑定 */int (*of_to_plat)(struct udevice *dev); /* 在 probe 之前,解析对应 udevice 的 dts 节点,转化成 udevice 的平台数据(存放于 udevice->plat_ 中) */int (*child_post_bind)(struct udevice *dev); /* 在一个新的 child 设备被绑定之后调用 */int (*child_pre_probe)(struct udevice *dev); /* 在探测子设备之前调用。设备已分配内存,但尚未被探测。. */int (*child_post_remove)(struct udevice *dev); /* 在移除子设备后调用。设备已经分配了内存,但是它的 device_remove() 方法已经被调用 */int priv_auto; /* 如果非零,这是在 udevice->priv_ 指针中分配的私有数据的大小。如果为零,则驱动程序负责分配所需的任何数据。 */int plat_auto; /* 如果非零,这是要分配到 udevice->plat_ 指针中的平台数据的大小。这通常只对支持设备树的驱动程序(使用 of_match 的驱动程序)有用,因为使用 platform data 的驱动程序将拥有 U_BOOT_DRVINFO() 实例化中提供的数据 */int per_child_auto; /* 每个设备都可以保存其父设备拥有的私有数据。如果需要,如果该值非零,将自动分配到 udevice->parent_priv_ 指针中。 */int per_child_plat_auto; /* 总线喜欢存储关于其子节点的信息。如果非零,这是该数据的大小,将分配到子对象的 udevice->parent_plat_ 指针中 */const void *ops; /* driver的具体操作,这通常是一个由driver定义的函数指针列表,用于实现 uclass 所需的驱动程序函数。 */uint32_t flags; /* 驱动程序标志-参见' DM_FLAGS_…' */
#if CONFIG_IS_ENABLED(ACPIGEN)struct acpi_ops *acpi_ops; /* 高级配置和电源接口(ACPI)操作,允许设备向传递给Linux的ACPI表中添加东西 */
#endif
};
struct driver
都属于 UCLASS,代表同一类型的一类设备。驱动程序的共同元素可以在 UCLASS 中实现,或者 UCLASS 可以为其中的驱动程序提供一致的接口。udevice
是根据 struct driver
中的 id
属性来关联其所在的 UCLASS。
在代码实现中,driver
必须使用宏 U_BOOT_DRIVER(__name)
来进行定义。该宏值除了使用 struct uclass_driver
定义 __name
变量外,还会定义一个同名节区,并将 __name
放到这个同名的节区当中。以 serial
为例,如下所示:
driver
是通过 struct udevice
链表管理起来的,每个 driver
都必须关联到一个指定的 struct udevice
上(链表节点上),当不存在 driver
对应的 struct udevice
时,就会自动创建一个 struct udevice
然后关联起来。
DM 命令
U-Boot 在 ./cmd/dm.c
文件中提供了 DM 相关的命令,可以在 U-Boot 命令界面查看 DM 相关信息。进入 U-Boot 的命令行模式以后输入 help
或者 ?
,然后按下回车即可查看当前 U-Boot 默认支持的所有命令。还可以输入help 命令名
或者 ? 命令名
来查看命令的详细用法,例如,help dm
就会打印出 dm
这个命令的详细介绍。
dm compat
dm compat
用于显示与每个驱动程序相关联的兼容字符串(可以在每个开发板的设备树文件中查找这些字符串),如果有多个字符串,则每行显示一个。
各列含义如下:
列名 | 含义 |
---|---|
Driver | 驱动的名字,即 driver->name 的值 |
Compatible | 驱动兼容字符串,即 driver->of_match 的值。如果设备树中 Compatible 与这里的匹配,则表示设备树节点设备使用该驱动 |
dm devres
dm devres
用于显示一个设备的 devres(设备资源)记录列表。一些驱动程序使用 devres API 来分配内存,这样当设备被移除时,就可以自动释放内存(在驱动程序的 remove()
方法中不需要任何代码)。
该特性需要定义
CONFIG DEVRES
来启用。
dm drivers
dm drivers
用于显示所有可用的驱动程序,驱动程序对应的 UCLASS 和使用该驱动程序的设备列表(多个设备时每行一个设备),每行一个驱动。如果驱动程序没有对应的设备,则设备显示为 none
。
各列含义如下:
列名 | 含义 |
---|---|
Driver | 驱动的名字,即 driver->name 的值 |
uid | UID 即 enum uclass_id 中对应的值 |
uclass | UCLASS 名字,即 uclass_driver->name 的值 |
Devices | 设备名字,即 udevice->name 的值 |
dm static
dm static
用于显示由平台数据绑定的设备,即不是来自设备树的设备。这些通常都没有,但一些开发板可能会出于空间原因使用静态设备。
列名 | 含义 |
---|---|
Driver | driver->name 中定义的驱动的名字 |
Address | 驱动的内存地址 |
dm tree
dm tree
用于显示设备的完整树。
各列含义如下:
列名 | 含义 |
---|---|
Class | 设备的 UCLASS 名,即 uclass_driver->name 的值 |
Index | 在 UCLASS 中设备的索引号。注意不是 Sequence Number。 |
Probed | 如果设备处于活动状态,则显示 + |
Driver | 此设备使用的驱动程序的名称,即 driver->name 的值 |
Name | 以树型结构(含子设备容易查看)显示设备名称(即 udevice->name 的值) |
dm uclass
dm uclass
用于显示每个类以及该类中的设备列表。
container_of
DM 中是通过链表来管理设备的,链表的管理用到了 scripts/kconfig/list.h
中定义的 container_of
这个宏。U-Boot 中的 container_of
就是从 Linux 拿过来,这个宏的设计还是比较有意思,必须要重点解析一下。乍一看这个宏并不复杂,就一个代码块({}
)两个独立的语句(;
)。
const typeof( ((type *)0)->member ) *__mptr = (ptr);
typeof
是关键字,获取成员类型。所以,前半句 const typeof( ((type *)0)->member )
实际就是获取 member
的类型,整句就是以 member
的类型定义指针变量 _mptr
并赋值为 ptr
,ptr
实际是指向 member
的指针。
(type *)( (char *)__mptr - ((size_t) &((type *)0)->member) );
(char *)__mptr
将成员类型强制转化为char *
,这要地址进行加减时以字节为单位offsetof
用于获取结构体成员偏移量。这是个巧妙用法,我们知道,结构体成员得地址减去结构体基地址就是偏移量。而如果这个基地址为 0 ,则直接取成员地址就是偏移量。(char *)__mptr - ((size_t) &((type *)0)->member)
就是得到了type
结构体变量的首地址,只不过类型是char*
,最后使用(type *)
在转换为type
类型指针。
结论
container_of
最终的目的返回的就是 member
所在的结构体的基地址。简单来说,container_of
的作用就是根据结构体的成员获取结构体基地址。而 const typeof( ((type *)0)->member ) *__mptr = (ptr);
仅仅是个中间状态,如果没有这一句,就无法实现 container_of
的通用性(代替方案是使用类型强转,但是也就限定了只能用在特定类型中)。
初始化流程
DM 初始化的接口在 dm_init_and_scan
中,初始化流程主要有两次,入口函数分别是在重定位之前调用的 ./common/board_f.c
文件中的 static int initf_dm(void)
和在重定位之后调用的 ./common/board_r.c
文件中的 static int initr_dm(void)
。重定位后的初始化与重定位前并没有太多区别。
U-Boot 提供了 bootstage 记录每个阶段的执行时间等信息,可以将此记录信息报告给用户,并将其传递给操作系统进行日志记录/进一步分析。默认 bootstage
并没有启用,所以这里直接忽略。真正与 DM 初始化相关的是 dm_init_and_scan
,接下来重点关注这个函数。
至于这里为啥还需要根据
CONFIG_TIMER_EARLY
来初始化一个定时器暂时还不知道原因。
dm_init_and_scan
dm_init_and_scan
定义于 drivers/core/root.c
中,入参 pre_reloc_only
为 true 时表示只解析重定位之前的节点(只会对设备树中带有 u-boot,dm-pre-reloc
属性的节点或者带有 DM_FLAG_PRE_RELOC
标志的设备进行解析);pre_reloc_only
为 false 的时则会对所有节点都进行解析。
- 因为
of-platdata
仅在SPL/TPL
阶段可用,所以后续忽略所有of-platdata
相关代码。DM_EVENT
我这里默认也没有启用,直接忽略。
dm_init
dm_init
定义于 drivers/core/root.c
中,主要用于初始化 ./drivers/core/root.c
中定义的根设备(U_BOOT_DRIVER(root_driver)
)。 根设备不是通过设备树定义的,而是直接在代码中定义的,因此,它的初始化比较特殊。
所有设备都是根设备的子节点
dm_init
的入参 of_live
表示是否启用了 Live Device Tree,由源码可知该入参并没有被使用。Live Device Tree 是一个与 Flattened Device Tree 相对应的概念。主要用于加快启动的扫描时间,但是只能在重定位之后才能使用。
首先,将 gd->uclass_root
指向 gd->uclass_root_s
,然后初始化 gd->uclass_root
中的成员:gd->uclass_root.next = gd->uclass_root
和 gd->uclass_root.prev = gd->uclass_root
。
device_bind_by_name
device_bind_by_name
定义于 drivers/core/device.c
中,主要用于绑定那些不使用设备树定义的设备。这个接口用于创建一个设备并将其绑定到驱动程序。对于 DM 初始化来说,这里就会创建 ROOT 设备,并将设备与 ./drivers/core/root.c
中定义的 U_BOOT_DRIVER(root_driver)
绑定。
device_bind_by_name
是个通用接口,其他设备初始化也会调用,见后文
lists_driver_lookup_name
lists_driver_lookup_name
定义于 drivers/core/device.c
中,其会遍历所有 struct driver
对应的节区,从中匹配指定的驱动名字。这里的根设备的初始化,入参 name
就是与 ./drivers/core/root.c
中定义的 U_BOOT_DRIVER(root_driver)
中的 name 取值 root_driver
,最终会返回基地址 0x804b438
。
uclass_get
设备是需要归属 UCLASS 的,uclass_get
定义于 drivers/core/uclass.c
中,实现根据 udevice 中的 uclass id 遍历 gd->uclass_root
指向的 uclass 链表,返回找到的 uclass 地址,如果没有找到则会新建一个 uclass,并返回新建的 uclass 地址。
- 调用
uclass_find
遍历gd->uclass_root
指向的 uclass 链表,查找指定 id 的 uclass。对于 DM 初始化来说,由于gd->dm_root
是 NULL,因此不会实际执行lis_for_each_entry
;其他情况下,展开如下所示:
uclass_get
是个通用接口,其他设备初始化也会调用,见后文
- 当找不到指定 id 的 uclass 时,调用
uclass_add
新建一个 uclass- 调用
lists_uclass_lookup
查找uclass_driver
,返回找到的uclass_driver
地址,否则返回错误
- 新建一个 uclass,然后进行一系列初始化,最终返回新建的 uclass。
uc = calloc(1, sizeof(*uc));
申请一个 UCLASS 节点内存- 判断
uclass_driver->priv_auto
申请uclaas->priv_
内存空间(接口uclass_set_priv
就是一个简单的赋值语句uc->priv_ = priv;
)。 INIT_LIST_HEAD()
用于将dev_head
和sibling_node
中的指针指向自身list_add
负责将申请的 UCLASS 节点内存串联到gd->uclass_root
链表之上- 判断并调用当前
uclass
对应的uclass_driver
的init
接口:uc_drv->init
- 调用
device_bind_common
device_bind_common
定义于 drivers/core/device.c/
中,作用是将设备驱动、设备、UCLASS 三者(DM 初始化的 root_driver
设备 与 U_BOOT_DRIVER(root_driver)
、UCLASS_ROOT
)进行绑定,根据上面的初始化流程,只有存在一个设备驱动时,才会创建对应的设备。
- 调用
uclass_get
根据设备 ID 查找对应的 UCLASS,详细过程参见上面的介绍。 dev = calloc(1, sizeof(struct udevice));
申请一个udevice
节点内存,即建立一个设备(DM 初始化中,这里就会建立ROOT DEVICE
),然后初始化其中的链表节点。
INIT_LIST_HEAD
用于将各链表节点指向自身dev_set_plat
就是dev->plat_ = plat;
- 有些设备(如 SPI 总线、I2C 总线和串口)使用别名进行编号。因此需要从设备树节点中提取出来放到
dev->seq_
中。
- 初始化当前设备的使用的一些内存,例如 设备的的
plat_
。/* Check if we need to allocate plat */if (drv->plat_auto) { /* 当驱动中 将 plat_auto 设置为实际的 platform data 的大小 */bool alloc = !plat; /* plat 是入参,如果入参指定了,则就不会在分配驱动中 drv->plat_auto 的内存空间 *//** For of-platdata, we try use the existing data, but if* plat_auto is larger, we must allocate a new space*/if (CONFIG_IS_ENABLED(OF_PLATDATA)) {if (of_plat_size)dev_or_flags(dev, DM_FLAG_OF_PLATDATA);if (of_plat_size < drv->plat_auto)alloc = true;}if (alloc) {dev_or_flags(dev, DM_FLAG_ALLOC_PDATA);ptr = calloc(1, drv->plat_auto); /* 分配内存 */if (!ptr) {ret = -ENOMEM;goto fail_alloc1;}/** For of-platdata, copy the old plat into the new* space*/if (CONFIG_IS_ENABLED(OF_PLATDATA) && plat)memcpy(ptr, plat, of_plat_size);dev_set_plat(dev, ptr); /* dev->plat_ = plat; */}}
- 当前设备可以选择将对应的 UCLASS 中的一些平台数据保存到自己的
uclass_plat_
中。如果对应的uclass->per_device_plat_auto
不是 0,则申请内存,并调用dev_set_uclass_plat(dev, ptr);
赋值dev->uclass_plat_ = uclass_plat;
指向申请的内存。size = uc->uc_drv->per_device_plat_auto;if (size) {dev_or_flags(dev, DM_FLAG_ALLOC_UCLASS_PDATA);ptr = calloc(1, size);if (!ptr) {ret = -ENOMEM;goto fail_alloc2;}dev_set_uclass_plat(dev, ptr);}
- 如果当前设备存在父节点设备的话,则初始化父节点设备的
per_child_plat_auto
,然后调用list_add_tail
将新的设备添加到其父节点设备。对于我们的 ROOT 设备,其 parent 是 NULL,因此不需要添加。parent 不是 NULL 时,就会通过child_head
和sibling_node
这两个成员把设备串到 Parent 上,后文有详细图示。 - 调用
uclass_bind_device
将上面创建的设备添加到 UCLASS,同时,如果设备存在父设备的话,需要调用父设备的child_post_bind
方法(对于这里的 DM 初始化,根设备没有父设备)。直接上图:
- 调用当前设备对应驱动的
bind
方法完成设备与对应驱动的绑定,然后调用当前设备对应的父设备的child_post_bind
(这里与uclass_bind_device
中其实存在重复),最后调用当前设备对应的 UCLASS(对应的 uclass_driver) 的post_bind
方法
device_bind_common
是个通用接口,其他设备初始化也会调用,见后文
dev_set_ofnode
OF_CONTROL
表示是否启用了设备树,这个默认是启用的,因此会继续调用定义于 drivers/core/device.c
中的 dev_set_ofnode
,将根设备中 node_
指向根节点
device_probe
device_probe
定义于 drivers/core/device.c
中,用于激活一个设备以便它可以随时使用,为了节省资源,U-Boot 中的设备会被延迟探测。如果设备已经激活了,则直接返回。对于 DM 初始化来说,这里就是探测并激活根设备。
- 调用
device_of_to_plat
将 dts 中的信息转化为设备的平台数据,以便提供探测设备等操作所需的信息。这可能会导致一些其他设备被探测,如果这个设备依赖于它们,例如一条 GPIO 线将导致一个 GPIO 设备被探测。- 如果当前设备有父设备,则递归执行父设备的
device_of_to_plat
- 调用
device_alloc_priv
分配私有数据使用的内存
- 调用设备对应的驱动自己的
of_to_plat
,将设备树中描述的设备信息,转化为一个平台数据(存储于udevice ->plat_
指向的内存中),后续驱动在使用使用硬件时就从平台数据中获取相关资源。
- 如果当前设备有父设备,则递归执行父设备的
- 如果该设备存在 parent,那么先 probe parent 设备,确保所有的父设备都被 probed。直接递归
device_probe
实现
- 调用
device_get_dma_constraints
填充设备的 DMA 约束。从固件中获取设备的 DMA 约束。驱动程序后来使用此信息将物理地址转换为设备的总线地址空间。目前,仅支持设备树。 - 调用
uclass_pre_probe_device
执行探测设备前当前设备对应的 uclass(对应的 uclass_driver) 中需要执行的接口。- 当前设备对应的 uclass(对应的 uclass_driver) 中的
pre_probe()
方法
- 当前设备的父设备对应的 uclass(对应的 uclass_driver) 中的
child_pre_probe()
方法
- 当前设备对应的 uclass(对应的 uclass_driver) 中的
- 调用当前设备的父设备的
child_pre_probe
。 - 调用
dev_has_ofnode
只处理具有有效 ofnode 的设备 - 执行该设备的 driver 的 probe 函数,真正激活该设备。
- 调用
uclass_post_probe_device
执行探测设备后 uclass(对应的 uclass_driver)中需要执行的接口。这包括 uclass_driver 的post_probe()
方法和父 uclass(对应的 uclass_driver) 的child_post_probe()
方法。- 当前设备的父设备对应的 uclass(对应的 uclass_driver) 中的
child_post_probe()
方法
- 当前设备对应的 uclass(对应的 uclass_driver) 中的
post_probe()
方法
- 当前设备的父设备对应的 uclass(对应的 uclass_driver) 中的
device_probe
是个通用接口,其他设备初始化也会调用,见后文
dm_scan
dm_scan
定义于 drivers/core/root.c
中,负责初始化根设备之外的所有设备。 前面说过,U-Boot 支持 Platform Data(代码中常简称 plat)和 Flattened Device Tree(代码常简称 fdt)这两种驱动配置的基本方式。因此,这里必须处理这两种驱动定义的设备。
dm_scan_plat
dm_scan_plat
定义于 drivers/core/root.c
中,查找并绑定使用 U_BOOT_DRVINFO(__name)
直接定义的设备。U_BOOT_DRVINFO(__name)
定义于 ./include/dm/platdata.h
中,与 U_BOOT_DRIVER(__name)
类似,U_BOOT_DRVINFO(__name)
除了使用 struct driver_info
定义变量 __name
,也会同时定义一个同名节区,并将 __name
放到此节区中。
dm_scan_plat
实际就是遍历 driver_info
表(__u_boot_list_2_driver_info_1
到 __u_boot_list_2_driver_info_3
之间),然后以 driver_info
中的 name
成员去查找使用 U_BOOT_DRIVER(__name)
定义的驱动程序。
udevice
的建立、driver 的绑定、UCLASS 的建立、uclass_driver 的绑定与上面的 root 设备一样,其内部最终也是调用 device_bind_by_name
完成这一些列的动作(调用 device_bind_by_name
时传参不同而已)。
注意,所有节点都是以 gd->dm_root
为父节点!!!
dm_extended_scan
dm_extended_scan
定义于 drivers/core/root.c
中,处理设备树中定义的设备。根据源码,dm_extended_scan
主要包含两部分,首先,通过 dm_scan_fdt
处理设备树中的设备节点;其次,由于有些节点(/chosen
、/clocks
、/firmware
)本身不是设备,但可能包含一些设备。此时通过 dm_scan_fdt_ofnode_path
来挨个处理这些节点中的设备。
ofnode_root
就是根节点(of_offset=0
),而 ofnode_path
这是根据完整的路径来查找指定的设备树节点的,这两部分最终都是提供过 dm_scan_fdt_node
来处理节点设备的,因此我们只需要重点关注 dm_scan_fdt_node
即可。
dm_scan_fdt_node
dm_scan_fdt_node
定义于 drivers/core/root.c
中,实现扫描设备树,为节点绑定驱动。它会给绑定的设备树节点创建一个新 udevice,并使用入参 parent 作为其父设备(入参固定为 gd->dm_root
,也就是根设备)。
dm_scan_fdt_node
会从根节点开始,依次遍历所有子节点。由于根设备已经在前面单独初始化了,所以这里找的设备就是根节点下的第一个设备,然后使用 lists_bind_fdt
挨个对节点进行绑定(这里的绑定即将节点与对应的 udevice、driver、uclass、uclass_driver 关联起来)。
for (node = ofnode_first_subnode(parent_node); /* 以根节点开始,获取第一个子节点 */ofnode_valid(node);node = ofnode_next_subnode(node)) { /* 当前节点的子节点 */const char *node_name = ofnode_get_name(node); /* 节点名字 */if (!ofnode_is_enabled(node)) {pr_debug(" - ignoring disabled device\n");continue;}err = lists_bind_fdt(parent, node, NULL, NULL, pre_reloc_only); /* 绑定 udevice、driver、uclass */if (err && !ret) {ret = err;debug("%s: ret=%d\n", node_name, ret);}}
lists_bind_fdt
lists_bind_fdt
内部会创建一个 udevice,然后将 udevice 与当前节点(入参 node
)进行绑定。当然,创建 udevice 的同时,其对应的 driver,uclass、uclass_driver 都会进行绑定。
- 获取当前节点的
compatible
内容(基地址 + 长度) - 遍历
compatible
,然后挨个去driver
表(__u_boot_list_2_driver_1
到__u_boot_list_2_driver_3
之间)中去对比(节点的compatible
与 驱动中的of_match
匹配)。代码很简单,就是个两层循环
只要有匹配,则调用for (i = 0; i < compat_length; i += strlen(compat) + 1) {compat = compat_list + i;log_debug(" - attempt to match compatible string '%s'\n",compat);for (entry = driver; entry != driver + n_ents; entry++) {if (drv) {if (drv != entry)continue;if (!entry->of_match)break;}ret = driver_check_compatible(entry->of_match, &id,compat);if (!ret)break;}
device_bind_with_driver_data
➜device_bind_common
完成udevice
的建立、driver 的绑定、UCLASS 的建立、uclass_driver 的绑定(调用device_bind_common
时传参不同而已)。
注意,所有节点都是以gd->dm_root
为父节点!!!这样所有节点都会通过 parent 串起来。此外,device_bind_common
的入参 plat 是 NULL,也就是来自设备树的设备其 plat 数据都是后续重设备树提取的
dm_scan_other
dm_scan_other
定义于 drivers/core/root.c
中,用于搜索绑定其他特殊的设备。该函数是个 __WEAK
函数,没有实质内容,如果需要,必须自行实现。 例如,在 ./lib/efi/efi_app.c
和 ./boot/bootstd-uclass.c
中就有该接口的实现,用于添加额外的设备。
dm_probe_devices
dm_probe_devices
定义于 drivers/core/root.c
中,实现遍历 gd->dm_root
下的所有设备然后激活设备。前面说了,udevice
是使用其中的 child_head
和 sibling_node
这两个成员串联起来的,所以,这个接口很简单,通过递归 child_head
就可以变量所有设备了。
- 调用
device_probe
探测激活当前设备,这个流程和上面说的 ROOT 设备是一样的。 - 调用
list_for_each_entry(child, &dev->child_head, sibling_node)
递归后续子设备,如下是展开后的基本形式
参考
- https://blog.csdn.net/ZHONGCAI0901/article/details/117781158
- https://zhuanlan.zhihu.com/p/460754843
- https://www.cnblogs.com/YYFaGe/p/16672483.html
- https://blog.csdn.net/weixin_41028621/article/details/90643550
- https://blog.csdn.net/ooonebook/article/details/53234020
- https://u-boot.readthedocs.io/en/latest/develop/driver-model/design.html
相关文章:
U-Boot 之七 详解 Driver Model 架构、配置、命令、初始化流程
U-Boot 在 2014 年 4 月参考 Linux Kernel 的驱动模型设计并引入了自己的 Driver Model(官方简称 DM) 驱动架构。这个驱动模型(DM)为驱动的定义和访问接口提供了统一的方法,提高了驱动之间的兼容性以及访问的标准性。 …...
大数据算法重点
1 大数据亚线性空间算法 场景:用二进制存储一个数字N,需要log(N)的空间 问题:如果N特别大而且这样的N又特别的多,该怎么办呢? 思路:减少一些准确性,从而节省更多的空间。 解决办法:使…...
【Eclipse】The import xxxx cannot be resolved 问题解决
在Eclipse使用过程中,某一个类明明存在,但是使用import导入时,却总是提示The import xxxx cannot be resolved的错误,解决办法如下: 点击Project->Clean......
LinkWeChat系统Docker版部署注意事项
具体部署手册:https://www.yuque.com/linkwechat/help/ffi7bu注意事项:启动类配置文件路径需要修改,各个模块启动类原配置如下:.properties("spring.config.name:bootstrap", "config/run/bootstrap.yml")各个…...
【高数】不定积分之有理函数的积分
文章目录前言有理函数积分的通用解法有理函数的特殊解法前言 这个专栏开始更新高等数学的解题方法,本专栏没有特别强调概念,主要是让大家熟悉考研中的一些题型以及如何求解 关键步骤用蓝色高亮提示 总结方法用红色高亮提示 注意事项用绿色高亮提示 希望…...
Java——数组
目录 前言 一、数组的定义 二、数组声明和创建 三、三种初始化及内存分析 Java内存分析 三种初始化 静态初始化 动态初始化 数组的默认初始化 数组的四个基本特点 四、下标越界及小结 五、数组的使用 For-Each循环 数组作方法入参 数组作返回值 六、二维数组 七…...
产品分析|虎扑APP
不同于传统的体育新闻门户网站,虎扑以篮球社区起家,在经历了从体育论坛到体育新闻网站的发展后,又逐渐回归社区发展。 目前,虎扑汇聚了大量的男性用户,俨然成为了“互联网直男的自留地”。特立独行的发展方向使得虎扑不断发展壮大,同时也使得虎扑逐渐触碰到了行业天花板。…...
有限差分法-二维泊松方程及其Matlab程序实现
2.2 偏微分方程的差分解法 2.2.1 二维泊松方程 考虑区域 Ω \Omega Ω 上的二维泊松问题: { − ( ∂ 2...
【设计模式】6.代理模式
概述 代理模式:为一个对象提供一个替身,以控制对这个对象的访问。即通过代理访问目标对象 这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。 被代理的对象可以是:远程对象、创建开销大的对象或需要安全…...
SRC挖掘之Access验证校验的漏洞挖掘
漏洞已修复,感谢某大佬的知识分享。 任意用户密码重置->可获取全校师生个人mingan信息 开局就是信息收集。 对于挖掘edu的信息收集 1.可尝试谷歌搜索语法,获取学号信息 2. 旁站的渗透获取 3. 学校的贴吧获取(大部分都是本校学生) 当然我就是闲&a…...
GG-21 100V 5A逆功率继电器
1 用途 GG-21逆功率继电器在出现逆功率时,从电网中断开交流发电机。 2 概述 逆功率继电器是基于感应式原理(具有旋转磁场)而工作。 继电器导磁体由两个磁路系统组成:上磁路系统和下磁路系统。电流线圈安装在上磁路系统中,它由接在发电机某相的…...
MyBatis中#{}和${}的区别
目录 前言 1、处理参数的方式不同 2、${}的优点 3、SQL注入问题 4、like查询问题 前言 #{}和${}都可以在MyBatis中用来动态地接收参数,但二者在本质上还是有很大的区别。 1、处理参数的方式不同 ${} :预编译处理 MyBatis在处理#{}时,…...
ElementUi的使用
ElementUi使用说明 element ui安装与配置 npm i element-ui –S项目入口文件main.js 导入 Element-UI 相关资源// 导入组件库 import ElementUI from element-ui; // 导入组件相关样式 import element-ui/lib/theme-chalk/index.css; // 配置 Vue 插件 Vue.use(ElementUI);文档…...
termux手机端安装mysql(MariaDB)
目录1 下载MariaDB2 配置MariaDB3 启动MariaDB服务器查看进程pid杀死进程4 登录 Mysqltermux用户登录MySQLroot用户登录MySQL5 配置 MariaDB 远程登录创建一个可远程登录的用户:用户授权:刷新授权:6 停止 MariaDB 服务器7 可选,但…...
Python枚举类定义和使用(详解版)
一些具有特殊含义的类,其实例化对象的个数往往是固定的,比如用一个类表示月份,则该类的实例对象最多有 12 个;再比如用一个类表示季节,则该类的实例化对象最多有 4 个。 针对这种特殊的类,Python 3.4 中新…...
京东HBase异地多活调研
京东HBase平台架构 HBase Replication原理 HBase的Replication是基于WAL日志文件的,在主集群中的每个RegionServer上,由ReplicationSource线程来负责推送数据,在备集群的RegionServer上由ReplicationSink线程负责接收数据。ReplicationSourc…...
【LeetCode】剑指 Offer 18. 删除链表的节点(题目一) p119 -- Java Version
题目链接:https://leetcode.cn/problems/shan-chu-lian-biao-de-jie-dian-lcof/ 1. 题目介绍(18. 删除链表的节点) 给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。 返回删除后的链表的头节点。 注意&…...
SpringMVC异步请求
背景 Tomcat等应用服务器的连接线程池实际上是有限制的;每一个连接请求都会耗掉线程池的一个连接数;如果某些耗时很长的操作,如对大量数据的查询操作、调用外部系统提供的服务以及一些 IO 密集型操作等,会占用连接很长时间&#…...
这七个100%提高Python代码性能的技巧,一定要知道
B站|公众号:啥都会一点的研究生 相关阅读 整理了几个100%会踩的Python细节坑,提前防止脑血栓 整理了十个100%提高效率的Python编程技巧,更上一层楼 Python-列表,从基础到进阶用法大总结,进来查漏补缺 Python-元组&…...
计算机网络笔记、面试八股(五)—— 浏览器输入URL
本章目录5. 从输入URL到浏览器显示页面过程中都发生了什么5.1 URL输入5.2 DNS解析5.2.1 域名的等级5.2.2 DNS解析的流程5.2.3 DNS查询方式5.3 建立TCP连接5.4 发送HTTP/HTTPS请求5.5 服务器处理请求并返回HTTP响应5.6 浏览器解析渲染页面5.7 HTTP请求结束,断开TCP连…...
【速记】快速调通算法项目的环境
1.创建新的conda环境,避免把原有的环境给搞坏。 在CMD中执行,而不是在anaconda的命令行中执行: conda create -n 环境名 --offline python3.8 2.在pycharm中配置conda环境: setting->Project Interpreter->齿轮->add-&g…...
开放开源开先河(上)
目录 1.唯一性定义品牌 2.打造爆款塑造品牌 3.构筑生态体系传播品牌 2022年7月28日,以“软件定义世界 开源共筑未来”为主题的全球数字经济大会开放原子开源峰会在北京开幕,承办主峰会和为捐赠人进行授牌仪式的开放原子开源基金会再次进入公众视野。基金…...
TencentOS 3.1安装MySQL 8.0.32
到官网下载安装包:https://dev.mysql.com/downloads/mysql/ 使用如下命令解包。 tar xf mysql-8.0.32-1.el8.x86_64.rpm-bundle.tar 使用rpm -qa |grep mysql 和rpm -qa |grep mariadb检查是否安装过mysql 如果有,使用下命令移除: rpm -e …...
Javascript的API基本内容(五)
一、js组成 JavaScript的组成 ECMAScript: 规定了js基础语法核心知识。 比如:变量、分支语句、循环语句、对象等等 Web APIs : DOM 文档对象模型, 定义了一套操作HTML文档的API BOM 浏览器对象模型,定义了一套操作浏览器窗口的API 二、loc…...
分层测试(2)单元测试【必备】
1. 什么是单元测试? 对代码中的逻辑隔离的最小代码片段进行测试,验证其逻辑是否符合预期,单元可以是函数,方法,类,功能模块。 2. 单元测试的优点 掌握代码:单元测试允许开发人员了解单元提供…...
代码随想录算法训练营day45 |动态规划之背包问题 70. 爬楼梯 (进阶) 322. 零钱兑换 279.完全平方数
day4570. 爬楼梯 (进阶)1. 确定dp数组以及下标的含义2.确定递推公式3.dp数组如何初始化4.确定遍历顺序5.举例来推导dp数组322. 零钱兑换1. 确定dp数组以及下标的含义2.确定递推公式3.dp数组如何初始化4.确定遍历顺序5.举例推导dp数组279.完全平方数1. 确…...
秒懂算法 | 基于图神经网络的推荐算法
图神经网络(Graph Neural Networks,GNN)是近几年兴起的学科,用来作推荐算法自然效果也相当好,但是要学会基于图神经网络的推荐算法之前,需要对图神经网络自身有个了解。 图卷积网络(Graph Convolutional Networks,GCN)提出于2017年。GCN 的出现标志着图神经网络的出现。深度学习…...
CANoe TC8测试脚本的结构介绍
CANoe TC8脚本是通过vTESTstudio平台编写。每个协议(ARP\ICMPv4\IPv4\UDP\TCP\SOMEIP\DHCP)都有自己的vtt文件。每个vtt文件的测试树结构为: Test Fixture Fixture Preparation Test Case Test Case … Test Case Test Case Fixture Completion 当Test Fixture里的Test Case…...
DP(4)--区间DP
将n(1≤n≤200)堆石子绕圆形操场摆放,现要将石子有次序地合并成一堆。 规定每次只能选相邻的两堆石子合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。 (1)选择一种合并石子的方案,使得做n-1次合并,得分的总…...
【C语言】“qsort函数详解”与“使用冒泡思想模拟使用qsort”
✨✨✨✨如果文章对你有帮助记得点赞收藏关注哦!!✨✨✨✨ 文章目录✨✨✨✨如果文章对你有帮助记得点赞收藏关注哦!!✨✨✨✨qsort的介绍:一、qsort函数的使用✨比较int类型数据比较字符型数据比较结构体数据冒泡思想…...
网站登录账号密码保存在哪里/免费推广网站大全下载安装
中国汽车业已成为全球第一市场,标志着中国汽车产业进入了白热化竞争时代,因此,人们对汽车的操控性,安全性,易用性,舒适性,以及智能化要求也越来越高,更大的空间需求和更多的零部件因…...
苏州定制型网站建设/不要手贱搜这15个关键词
当百度在人为控制搜索结果的时候。应该是其他搜索引擎崛起的机会。可是国内的一些搜索引擎似乎根本就不在乎,或者说是对此无所谓。这只是我个人所见和认识。众所周知,2011将会是一个无从预知的一年,混乱或是战乱,让人扑朔迷离。而…...
企?I网站建站 ?V州 ??l/百度合作平台
JSR303数据校验 如何使用 Springboot中可以用validated来校验数据,如果数据异常则会统一抛出异常,方便异常中心统一处理。我们这里来写个注解让我们的name只能支持Email格式; application.yml sowhat:name: zhangsanperson:name: sowhat$…...
成都制作网站公司/seo网站优化工具大全
身份证中第十八位数字的计算方法为:1. 将前面的身份证号码17位数分别乘以不同的系数。从第一位到第十七位的系数分别为:7. 9 .10 .5. 8. 4. 2. 1. 6. 3. 7. 9. 10. 5. 8. 4. 2.2. 将这17位数字和系数相乘的结果相加。3. 用加出来和除以11,看余…...
wordpress 照片展示/西安seo顾问培训
1 详解1.1 Buffer 结构Buffer 是抽象类,子类共有7个实现,他们都是 abstract 类型,如下图所示:1.2 Buffer 创建allocateallocate 通过指定 buffer 的容量,然后新建对象,ByteBuffer 还可以通过 allocateDirec…...
渠道网络建设怎么写/搜索排名优化策划
最近在学习javascript函数式编程,对其中大名鼎鼎的curry十分感兴趣,curry函数可以接受一个函数,我们暂且称之为原始函数,返回的也是一个函数,柯里化函数,这个返回的柯里化函数功能十分强大,他在…...