建设银行在上海的招聘网站,短网址在线生成哪个好,软件技术就业岗位,网络公司+网站建设+小程序U-Boot 在 2014 年 4 月参考 Linux Kernel 的驱动模型设计并引入了自己的 Driver Model#xff08;官方简称 DM#xff09; 驱动架构。这个驱动模型#xff08;DM#xff09;为驱动的定义和访问接口提供了统一的方法#xff0c;提高了驱动之间的兼容性以及访问的标准性。 … U-Boot 在 2014 年 4 月参考 Linux Kernel 的驱动模型设计并引入了自己的 Driver Model官方简称 DM 驱动架构。这个驱动模型DM为驱动的定义和访问接口提供了统一的方法提高了驱动之间的兼容性以及访问的标准性。 文中涉及的代码均放到了我个人的 Github 上https://github.com/ZCShou/BOARD-STM32F769I-EVAL大家可以直接拿来边学习边验证避免眼高手低。 本文中涉及的源码主要是使用 U-Boot-v2022.10不同版本源码差异可能较大
配置 DM 架构需要通过配置项 CONFIG_DMy 来启用对应的实际外设的驱动则需要通过使能 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_DMy、CONFIG_CMD_DEMOy、CONFIG_DM_DEMOy、CONFIG_DM_DEMO_IMX6ULLy 就可以把这个 DEMO 添加我们的构建中然后进行学习测试。
global_data ./include/asm-generic/global_data.h 文件中的 struct global_data 结构管理着整个 U-Boot 的全局变量当我们定义 CONFIG_DMy 后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。 不同于 UCLASSudevice 和 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 devDM 之外不能访问*/
#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 的值uidUID 即 enum uclass_id 中对应的值uclassUCLASS 名字即 uclass_driver-name 的值Devices设备名字即 udevice-name 的值
dm static dm static 用于显示由平台数据绑定的设备即不是来自设备树的设备。这些通常都没有但一些开发板可能会出于空间原因使用静态设备。
列名含义Driverdriver-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 并赋值为 ptrptr 实际是指向 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() 方法 调用当前设备的父设备的 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() 方法 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_offset0而 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 的同时其对应的 driveruclass、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/117781158https://zhuanlan.zhihu.com/p/460754843https://www.cnblogs.com/YYFaGe/p/16672483.htmlhttps://blog.csdn.net/weixin_41028621/article/details/90643550https://blog.csdn.net/ooonebook/article/details/53234020https://u-boot.readthedocs.io/en/latest/develop/driver-model/design.html