U-Boot 之七 详解 Driver Model 架构配置命令初始化流程
Posted ZC·Shou
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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 结构体来将平台特定的配置信息(寄存器的地址,总线速度等)传递给驱动程序,相关定义位于 ./include/dm/platdata.h
文件中。在 DM 初始化后(实际是在 probe 后),设备信息最终被存放到 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 内部将自动解析设备树获取相关设备信息,这些信息数据被称为 ofdata
(Open-Firmware data 的简称),设备树相关结构(代码部分)定义位于 ./include/dm/of.h
等以 of
开头的文件中。在 DM 初始化后(实际是在 probe 后),设备信息最终也是被存放到 udevice ->plat_
指向的内存中(具体方式就是通过 driver
中的 plat_auto
和 of_to_plat
成员)。
U-Boot 的设备树来自于 Linux Kernel。根据官方说明,为了在不对原设备树源文件进行额外更改的情况下保持兼容性,U-Boot 引入了名为 *-u-boot.dtsi
的 U-Boot 专用文件。
新增加的 *-u-boot.dtsi
并不会被其他任何 *.dts
文件件直接所引用,因为,这些 *-u-boot.dtsi
文件是直接在 Makefile 文件中引入。 这样才能彻底避免 U-Boot 修改来自 Linux Kernel 的设备树文件。包含的优先级由高到低如下:
<orig_filename>-u-boot.dtsi
# <orig_filename> 就是要编译的 .dts 对应的名字<CONFIG_SYS_SOC>-u-boot.dtsi
<CONFIG_SYS_CPU>-u-boot.dtsi
<CONFIG_SYS_VENDOR>-u-boot.dtsi
u-boot.dtsi
框架
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_DM
struct 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_id
对应的 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
这个这个成员串起来的。
在 DM 初始化时,会自动遍历所有 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 */
#endif
int 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
会同时位于多个链表中。
在 DM 初始化时,就会遍历所有 driver
,每发现一个 driver
就会查找对应的 udevice
是否存在,不存在就会以 driver
的名字新建一个设备。也就是说,udevice
是根据 driver
动态创建的。
与
uclass
不同,udevice
是包含父设备与子设备这种一对多的层级关系的,这个层级关系与设备树中的一个节点与多个子节点相对应
一个设备将通过一个 ‘bind’ 调用来产生,要么是由于 U_BOOT_DRVINFO()
宏(在这种情况下,plat
是非 null),要么是由于设备树中的一个节点(在这种情况下,of_offset
为 >= 0)。在后一种情况下,我们将在驱动程序的 of_to_plat
方法中将设备树信息转换为 plat
(如果设备有设备树节点,则在 probe 方法之前调用)。
使设备工作的顺序是 bind ➜ of_to_plat(如果使用设备树)➜ probe
。与 Linux Kernel 相比,U-Boot 的驱动程序模型有一个独立于bind/unbind 的 probe/remove 步骤,这是因为,很多设备在 U-Boot 中并不会使用,Probe 的代价得不偿失,因此在必要时才会 Probe 设备。
关于 Device Sequence Numbers
在多数情况下 U-Boot 从 0 开始为设备进行编号。这个编号唯一地标识了其 UCLASS 中的一个设备,因此在一个特定 UCLASS 中没有两个设备可以具有相同的序列号。序列号从 0 开始,但允许有间隙。 例如,一个开发板可能有 I2C1、I2C4、I2C5,但没有 I2C0、I2C2、I2C3。设备如何编号的选择取决于特定的开发板,在某些情况下可能由 SoC 设置。
设备序列号在绑定设备时进行解析,存储在 udevice->seq_
成员变量中,且在设备的整个生命周期内都不会改变。
设备获取序列号的位置由 DM_SEQ_ALIAS
配置项控制,该选项在 U-Boot 本身和 SPL 中可以有不同的值。如果未设置此选项,则忽略别名。即使启用了 CONFIG_DM_SEQ_ALIAS
,uclass 仍必须设置 DM_UC_FLAG_SEQ_ALIAS
标志,以便其设备按别名排序。
设置这些选项后,具有别名(例如 “serial2”)的设备将获得该序列号 2。其他设备在所有别名和所有现有号码之后获得下一个可用号码。这意味着如果只有一个别名 “serial2”,未别名的串行设备将被分配 3 及之后,0 和 1 未使用。
如果未设置 CONFIG_DM_SEQ_ALIAS
或 DM_UC_FLAG_SEQ_ALIAS
,则所有设备将按照从 0 开始的简单顺序获得序列号。要找到下一个要分配的编号,驱动程序模型会扫描以找到最大现有编号,然后使用下一个。它不会试图填补空白。
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
然后关联起来。
struct udevice_id
struct udevice_id
是给那些具体的驱动程序用来列出驱动程序支持的兼容字符串的。驱动与具体的设备树中的设备就是通过 struct udevice_id
中的 compatible
来匹配。
struct udevice_id
const char *compatible; /* 一个字符串 */
ulong data; /* 兼容字符串对应的数据,具体使用方式由驱动决定 */
;
struct udevice_id
的作用就是方便一次定义多个个兼容字符串,并最终赋值给 struct driver
中的 of_match
成员。其中,成员 compatible
用来与设备树中的 compatible
匹配;data
则会传递给 struct udevice
中的 driver_data
(见后文初始化流程)。
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
的通用性(代替方案是使用类型强转,但是也就限定了只能用在特定类型中)。
初始化流程
前面说了 uclass
和 udevice
都是动态创建的,创建的依据就是 U-Boot 驱动中使用 Device Tree 和 Platform Data 定义的设备。初始化流程会依次遍历 Platform Data 创建的设备 和 Device Tree 创建的设备,创建及关联 udevice、uclass 及相关驱动。
- 根据找到设备信息(Platform Data 设备的
name
或 Device Tree 的compatible
)去遍历driver
匹配其成员char *name;
或const struct udevice_id *of_match;
,如果没有驱动,则直接放弃 - 找到
driver
后,继续根据driver
成员enum uclass_id id
去查找对应的uclass
。如果uclass
存在返回找到的uclass
,否则继续以driver
成员enum uclass_id id
去遍历uclass driver
匹配其成员enum uclass_id id;
,如果没有则放弃 - 找到
uclass driver
之后,就会创建一个uclass
,并将uclass
中的成员struct uclass_driver *uc_drv;
指向找到的uclass driver
,返回新创建的uclass
。 - 最后创建一个
udevice
,并将其成员const struct driver *driver;
指向找到的driver
以及将成员struct uclass *uclass;
指向上面获取到的uclass
。
这里需要注意,设备是包含层级关系的树型结构,但是,DM 初始化只负责遍历所有一级设备,下一层级的设备是由本层设备对应的 uclass
的 .post_bind
方法负责遍历的。绝大多数 uclass
的 .post_bind
会直接或间接调用 dm_scan_fdt_dev
来遍历当前设备的子设备。 有些节点并没有被使能,不被使能的节点不会有对应的设备。
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 功能(./common/bootstage.c
)记录每个阶段的执行时间等信息,可以将此记录信息报告给用户,并将其传递给操作系统进行日志记录/进一步分析。默认 bootstage
并没有启用,所以这里直接忽略。真正与 DM 初始化相关的是 dm_init_and_scan
,接下来重点关注这个函数。
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 的时则会对所有节点都进行解析。
由上层调用函数 dm_init_and_scan
的入参可知,在重定位之前入参为 true,因此,解析的节点会比较少。此外,OF_LIVE
是动态树,这个默认没有启用,因为 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)
)。 根设备不是通过设备树定义的,而是直接在代码中定义的(即 Platform Data 方式)。
所有设备都是根设备的子节点。根设备的唯一作用就是用来管理其他设备
dm_init
的入参 of_live
表示是否启用了 Live Device Tree,由源码可知该入参并没有被使用。Live Device Tree 是一个与 Flattened Device Tree 相对应的概念,主要用于加快启动的扫描时间,但是只能在重定位之后才能使用。
of-platdata
特性也没有启用,因此,直接执行 else 中的代码,将 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)
绑定。
lists_driver_lookup_name
lists_driver_lookup
U-Boot Driver Model领域模型设计分析
需求分析
在2014年以前,uboot没有一种类似于linux kernel的设备驱动模型,随着uboot支持的设备越来越多,其一直受到如下问题困扰:
- 设备初始化流程都独立实现,而且为了集成到系统,需要修改核心公共代码(如init_sequence)
- 很多子系统只允许一个驱动,比如无法同时支持USB2.0和USB3.0
- 子系统间的交互实现各异,开发难度大
- 没有个统一的设备视图(如linux的/sys)
uboot driver model(U-Boot驱动模型,以下简写dm)的提出就是为了解决这些问题,它的设计目标包括:
- 提供统一设备驱动框架,降低设备驱动的开发复杂度
- 提供设备树视图
- 支持设备组
- 支持设备lazy init
- 支持设备驱动沙盒测试
- 较小的系统开销(内存和CPU)
对象设计
对象的设计之所以区分静态形式和运行态形式,考量的出发点是设计模块化。
静态表达形式的对象是离散的,和系统和其他对象隔离开,减小对象的复杂度,利于模块化设计,遵循人类表达习惯。
运行态形式的对象是把所有对象组合成层次视图,有着清晰的数据关联视图。方便系统运行时数据的流动。
静态表达形式
device: FDT(设备树文本描述) 或者 静态数据结构U_BOOT_DEVICE(以数据段形式组织)
driver: 静态数据结构U_BOOT_DRIVER(以数据段形式组织)
运行态形式
udevice: 设备对象(以链表形式组织)
driver: 驱动对象。作为udevice的一个属性
uclass:设备组公共属性对象(以链表形式组织),外部顶层对象,作为udevice的一个属性
uclass_driver: 设备组公共行为对象,作为uclass的一个属性
领域建模
uboot设备模型中udevice为核心对象,以树型模型组织(如下),其为dm的顶层结构。
单个udevice建模如下,详细对象定义参见《附:核心数据结构》小节。
所有对象可以按udevice或者uclass进行遍历。
DM初始化流程
DM初始化流程包括:
- 模型初始化
- 静态对象初始化
- 运行态对象初始化
- 设备组公共初始化
- 设备初始化
DM初始化的总入口接口:dm_init_and_scan(),其主要由以下三块组成:
dm_init():创建udevice和uclass空链表,创建根设备(root device)
dm_scan_platdata():扫描U_BOOT_DEVICE定义的设备,创建对应的udevice和uclass对象,查找并绑定相应driver,并调用probe流程。
dm_scan_fdt():扫描由FDT设备树文件定义的设备,创建对应的udevice和uclass对象,查找并绑定相应driver,并调用probe流程。
附:核心数据结构
U_BOOT_DRIVER(demo_shape_drv) = { .name = "demo_shape_drv", .of_match = demo_shape_id, .id = UCLASS_DEMO, .ofdata_to_platdata = shape_ofdata_to_platdata, .ops = &shape_ops, .probe = dm_shape_probe, .remove = dm_shape_remove, .priv_auto_alloc_size = sizeof(struct shape_data), .platdata_auto_alloc_size = sizeof(struct dm_demo_pdata), }; #define U_BOOT_DRIVER(__name) \\ ll_entry_declare(struct driver, __name, driver) #define ll_entry_declare(_type, _name, _list) \\ _type _u_boot_list_2_##_list##_2_##_name __aligned(4) \\ __attribute__((unused, \\ section(".u_boot_list_2_"#_list"_2_"#_name))) struct driver { char *name; enum uclass_id id; const struct udevice_id *of_match; int (*bind)(struct udevice *dev); int (*probe)(struct udevice *dev); int (*remove)(struct udevice *dev); int (*unbind)(struct udevice *dev); int (*ofdata_to_platdata)(struct udevice *dev); int (*child_post_bind)(struct udevice *dev); int (*child_pre_probe)(struct udevice *dev); int (*child_post_remove)(struct udevice *dev); int priv_auto_alloc_size; int platdata_auto_alloc_size; int per_child_auto_alloc_size; int per_child_platdata_auto_alloc_size; const void *ops; /* driver-specific operations */ uint32_t flags; }; U_BOOT_DEVICE(demo0) = { .name = "demo_shape_drv", .platdata = &red_square, };
#define U_BOOT_DEVICE(__name) \\ ll_entry_declare(struct driver_info, __name, driver_info)
struct driver_info { const char *name; const void *platdata; #if CONFIG_IS_ENABLED(OF_PLATDATA) uint platdata_size; #endif }; struct uclass { void *priv; struct uclass_driver *uc_drv; struct list_head dev_head; struct list_head sibling_node; }; UCLASS_DRIVER(demo) = { .name = "demo", .id = UCLASS_DEMO, };
#define UCLASS_DRIVER(__name) \\ ll_entry_declare(struct uclass_driver, __name, uclass)
struct uclass_driver { const char *name; enum uclass_id id; int (*post_bind)(struct udevice *dev); int (*pre_unbind)(struct udevice *dev); int (*pre_probe)(struct udevice *dev); int (*post_probe)(struct udevice *dev); int (*pre_remove)(struct udevice *dev); int (*child_post_bind)(struct udevice *dev); int (*child_pre_probe)(struct udevice *dev); int (*init)(struct uclass *class); int (*destroy)(struct uclass *class); int priv_auto_alloc_size; int per_device_auto_alloc_size; int per_device_platdata_auto_alloc_size; int per_child_auto_alloc_size; int per_child_platdata_auto_alloc_size; const void *ops; uint32_t flags; }; struct udevice { const struct driver *driver; const char *name; void *platdata; void *parent_platdata; void *uclass_platdata; int of_offset; ulong driver_data; struct udevice *parent; void *priv; struct uclass *uclass; void *uclass_priv; void *parent_priv; struct list_head uclass_node; struct list_head child_head; struct list_head sibling_node; uint32_t flags; int req_seq; int seq; #ifdef CONFIG_DEVRES struct list_head devres_head; #endif };
--EOF--
以上是关于U-Boot 之七 详解 Driver Model 架构配置命令初始化流程的主要内容,如果未能解决你的问题,请参考以下文章