In April 2014, U-Boot referred to the Linux Kernel driver model design and introduced its own Driver Model (officially referred to as DM) driver architecture. This driver model (DM) provides a unified method for driver definition and access interface, which improves the compatibility between drivers and the standardization of access.
The codes involved in this article are all placed on my personal Github: https://github.com/ZCShou/BOARD-STM32F769I-EVAL, you can use it directly to learn and verify, to avoid high-sightedness and low-handedness. The source code involved in this article is mainly used U-Boot-v2022.10
, and the source code of different versions may be quite different! ! !
configuration
The DM architecture needs to CONFIG_DM=y
be , and the corresponding actual peripheral drivers need to CONFIG_DM_xxx
be enabled through enable. Among them, xxx indicates a specific peripheral device. For example, enabling CONFIG_DM_SERIAL
will automatically add the corresponding source code file in the Makefile:
At present, the drivers of most devices have been completely migrated to the DM architecture. Therefore, in the actual source code, We can often see that CONFIG_DM_xxx
the corresponding driver interface is implemented, but the old version is not implemented or does not have the old driver interface (some drivers are still in the old driver mode).
link options
The DM driver will be uniformly stored in the final image file after compilation, and the DM architecture driver of each device will be placed in a separate section during compilation. When we compile U-Boot, all these drivers will __u_boot_list_2_"#_list"_2_"#_name
use the section area as the name. In addition, these section areas will be wrapped__u_boot_list_2_"#_list"_1
with and , so that the size of all can be calculated. This information can be seen :__u_boot_list_2_"#_list"_3
__u_boot_list_2_*
u-boot.map
In fact, not only the driver, but also other parts (for example, cmd) are handled in this way
The key to the implementation in the code lies in the macro values of UCLASS_DRIVER(__name)
, U_BOOT_DRIVER(__name)
etc. These macro values will eventually refer to the relevant macros ./include/linker_lists.h
in ll_entry_declare
, which is the key to the implementation.
During the initialization process, U-Boot will traverse the above sections, then perform content matching, and create various devices and corresponding UCLASS in turn. The following is the method to find the specified driver according to the name of the driver:
Device Tree / Platform Data
The driver must know the basic information of the hardware. U-Boot supports two methods of providing basic hardware configuration information, Platform Data (platform data, often referred to as plat
or platdata
) and Flattened Device Tree (device tree, often referred to in code ). fdt
Among them, the platform data is the old way, and the device tree is the standard way.
Platform Data
Platform Data is to pass platform-specific configuration information (register address, bus speed, etc.) to the driver through a C structure, and the relevant definitions are located in ./include/dm/platdata.h
the file After the DM is initialized (actually after the probe), the device information is finally stored in the memory udevice ->plat_
pointed to by and the driver can dev->plat_
access . The official pointed out that unless there is a necessary reason, do not use the platform data method, but the device tree method.
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,
};
The official recommendation is to use Platform Data only when memory constraints do not allow the use of a device tree. In addition, U-Boot provides a method to automatically convert the device tree into Platform Data, namely of-platdata
features . However, it is of-platdata
only available in SPL/TPL
the phase .
Device Tree
The device tree provides a more flexible method of providing device data, and the official recommendation is to use the device tree method. U-Boot will automatically parse the device tree to obtain relevant device information. These information data are called ofdata
(short for Open-Firmware data), and the device tree related structure (code part) is ./include/dm/of.h
defined of
in the file beginning with etc. After the DM is initialized (actually after the probe), the device information is finally stored in the memory udevice ->plat_
pointed to by (specifically through driver
the plat_auto
and of_to_plat
members in ).
The device tree of U-Boot comes from Linux Kernel. According to the official instructions, in order to maintain compatibility without making additional changes to the original device tree source files, U-Boot introduces a U-Boot special file *-u-boot.dtsi
named .
The newly *-u-boot.dtsi
added will not *.dts
be directly referenced by any other files, because these *-u-boot.dtsi
files are directly introduced in the Makefile. Only in this way can U-Boot completely avoid modifying the device tree files from the Linux Kernel. Included priority from high to low is as follows:
<orig_filename>-u-boot.dtsi
# <orig_filename> is the name corresponding to the .dts to be compiled<CONFIG_SYS_SOC>-u-boot.dtsi
<CONFIG_SYS_CPU>-u-boot.dtsi
<CONFIG_SYS_VENDOR>-u-boot.dtsi
u-boot.dtsi
frame
U-Boot's DM uses uclass
and udevice
these two abstract classes to manage all device drivers, and these two abstract classes correspond to uclass_driver
and driver
. udevice
is created driver
dynamically ; uclass
is uclass_driver
created . But only when it is created udevice
will it find the corresponding one uclass
, so in the end it will only be created if driver
it exists uclass
.
What is really useful is an independent uclass_driver
and driver
, uclass
they udevice
are managed through and respectively. In terms of code implementation, uclass
and are udevice
actually nodes of a doubly linked list, and all drivers are managed through a doubly linked list. And finally the linked list position is indicated by the relevant variable global_data
in . A simple block diagram is as follows:
The official provides a driver DEMO ( drivers/demo
), by opening CONFIG_DM=y
, CONFIG_CMD_DEMO=y
, CONFIG_DM_DEMO=y
, CONFIG_DM_DEMO_IMX6ULL=y
you can add this DEMO to our build, and then conduct a learning test.
global_data
./include/asm-generic/global_data.h
struct global_data
The structure in the file manages the global variables of the entire U-Boot. After we define CONFIG_DM=y
, global_data
there will be some DM-related fields in the file to save DM-related information. See the code comments below for details:
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
and uclass_driver
are defined in ./include/dm/uclass.h
the file, among them, uclass
is an abstract class that divides devices of the same type into a group for classification management; uclass_driver
provides a consistent interface for a group of related device drivers. There isuclass
a one-to-one correspondence with . uclass_driver
In the expression, we usually use uclass_id
to represent a uclass, for example, UCLASS_ROOT represents ROOT UCLASS itself, and its corresponding driver is called uclass_driver_root
. The available values of the uclass ID are defined in ./include/dm/uclass-id.h
the file enum uclass_id
, it should be noted that the ID is actually used in struct uclass_driver
the .
struct uclass
uclass
Group devices of the same type into a group for classified management. Note that uclass
it is automatically generated by U-Boot during the initialization process, and not all uclass_id
corresponding uclass
will be generated, only those corresponding uclass_driver
and udevice
matched by uclass
will be generated (details in the initialization section below).
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 链表) */
};
In terms of code implementation, struct uclass
this structure is actually a linked list node. After DM is initialized, all existing devices uclass
will form a gd->uclass_root
doubly linked list with as the head of the linked list. This linked list is strung together through sibling_node
its members.
When DM is initialized, it will traverse all automatically uclass_driver
, and every time it finds a , uclass_driver
it will check uclass
whether the ID corresponding to it exists (the judgment condition is that it already exists uclass->uc_drv->id 是否等于当前 uclass_driver->id
), and if it does not exist, it will create a new one with the ID uclass_driver
in uclass
, and then associate uclass_driver
it with . That is, uclass
it is created uclass_driver
dynamically .
struct uclass_driver
uclass_driver
Provides a consistent interface for a set of related drivers, one uclass
for uclass_driver
. In terms of code implementation, struct uclass
the uc_drv
in points to the current 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_…) */
};
In code implementation, uclass_driver
macros must be used UCLASS_DRIVER(__name)
for definition. In addition to using the struct uclass_driver
definition __name
will also define a section with the same name, and __name
put it into the section with the same name. Take serial
as an example , as shown below:
uclass_driver
it is managed by a linked list struct uclass
as a node, and uclass_driver
each must be associated with a struct uclass
specified (on a linked list node). When there is no correspondinguclass_driver
, one will be automatically created and then associated.struct uclass
struct uclass
udevices and drivers
udevice
and are driver
defined in ./include/dm/device.h
the file, where udevice
is an abstract class used to represent a device (an instance of the driver); driver
it is the actual driver corresponding to a device. udevice
The relationship with driver
can be many-to-one (multiple devices may share the same one driver
).
Unlike UCLASS, bothudevice
have members to identify themselves, and the two names can be different or the same. For example, the name of the ROOT device and the name of the ROOT driver are both . The device name defined in the device tree is usually a node name, and the corresponding driver name is a string with exact meaning in the code.driver
name
root_driver
struct udevice
udevice
Contains information about the device, which is essentially a driver instance that must be bound to a specific port or peripheral driver ( udevice
must be associated const struct driver *driver
with driver
). udevice
It cannot be associated with UCLASS itself, and must struct driver
be id
associated with the UCLASS it is in according to the attribute in its association.
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
};
In terms of code implementation, struct udevice
this structure is actually a linked list node. After the DM is initialized, all udevice
will form a doubly linked list with as the head of the linked list. This linked list is connected gd->dm_root
by the child_head
and this member. Unlikesibling_node
, it also supports concatenation . That is, a will be in multiple linked lists at the same time. When DM is initialized, it will traverse all , and every time a is found, it will check whether the corresponding exists, and if it does not exist, it will create a new device with the name of . That is, it is created dynamically .uclass
udevice
uclass_node
struct udevice
driver
driver
udevice
driver
udevice
driver
uclass
Different from ,udevice
it contains the one-to-many hierarchical relationship between the parent device and the child device. This hierarchical relationship corresponds to a node in the device tree and multiple child nodes.
A device will be created by a 'bind' call, either due to U_BOOT_DRVINFO()
the macro (in this case, plat
non-null), or due to a node in the device tree (in this case, of_offset
>= 0). In the latter case, we will translate the device tree information in the driver's of_to_plat
method plat
(called before the probe method if the device has a device tree node).
The sequence to make the device work is bind ➜ of_to_plat(如果使用设备树)➜ probe
. Compared with Linux Kernel, U-Boot's driver model has a probe/remove step independent of bind/unbind. This is because many devices will not be used in U-Boot, and the cost of Probe is not worth the loss, so when necessary Only then will the Probe device.
About Device Sequence Numbers
In most cases U-Boot starts numbering devices from 0. This number uniquely identifies a device within its UCLASS, so no two devices within a particular UCLASS can have the same serial number. Serial numbers start at 0, but gaps are allowed. For example, a development board may have I2C1, I2C4, I2C5, but not I2C0, I2C2, I2C3. The choice of how the devices are numbered depends on the particular development board and in some cases may be set by the SoC.
The device serial number is resolved when the device is bound, stored in udevice->seq_
the member variable, and will not change throughout the lifetime of the device.
The location where the device obtains the serial number is controlled by DM_SEQ_ALIAS
a configuration item, and this option can have different values in U-Boot itself and in SPL. If this option is not set, aliases are ignored. Even if enabled CONFIG_DM_SEQ_ALIAS
, a uclass must still have DM_UC_FLAG_SEQ_ALIAS
the flag so that its devices are sorted by alias.
When these options are set, devices with an alias (eg "serial2") will get this serial number 2. Other devices get the next available number after all aliases and all existing numbers. This means that if there is only one alias "serial2", unaliased serial devices will be assigned 3 and beyond, 0 and 1 are unused.
If CONFIG_DM_SEQ_ALIAS
or DM_UC_FLAG_SEQ_ALIAS
, all devices will get serial numbers in simple order starting from 0. To find the next number to assign, the driver model scans for the largest existing number, then uses the next one. It doesn't try to fill in the gaps.
struct driver
driver
Contains methods for creating new devices and removing devices, devices set themselves up with information provided by platdata or device tree nodes (paired by finding compatible strings that match of_match).
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
Both belong to UCLASS, representing a class of devices of the same type. Common elements of drivers can be implemented in UCLASS, or UCLASS can provide a consistent interface for drivers within it. udevice
It struct driver
is id
associated with its UCLASS according to the attribute in .
In code implementation, driver
macros must be used U_BOOT_DRIVER(__name)
for definition. In addition to using the struct uclass_driver
definition __name
will also define a section with the same name, and __name
put it into the section with the same name. Take serial
as an example , as shown below:
driver
it is managed by a linked list struct udevice
as a node, and driver
each must be associated with a struct udevice
specified (on a linked list node). When there is no correspondingdriver
, one will be automatically created and then associated.struct udevice
struct udevice
struct udevice_id
struct udevice_id
is for specific drivers to list the compatibility strings supported by the driver. The driver and the device in the specific device tree struct udevice_id
are compatible
matched through in.
struct udevice_id {
const char *compatible; /* 一个字符串 */
ulong data; /* 兼容字符串对应的数据,具体使用方式由驱动决定 */
};
struct udevice_id
The role of is to facilitate the definition of multiple compatible strings at a time, and finally assign them struct driver
to of_match
the members in . Among them, the member compatible
is used to compatible
match with in the device tree; data
it will be passed to struct udevice
in driver_data
(see initialization process later).
DM command
U-Boot provides DM-related commands in ./cmd/dm.c
the file , and you can view DM-related information on the U-Boot command interface. After entering the command line mode of U-Boot, enter help
or ?
, and then press Enter to view all the commands currently supported by U-Boot by default. You can also enter help 命令名
or ? 命令名
to view the detailed usage of the command, for example, the detailed introduction of thishelp dm
command will be printed out .dm
dm compat
dm compat
Used to display the compatibility strings associated with each driver (you can find these strings in each board's device tree file), one per line if there are more than one.
The meaning of each column is as follows:
column name | meaning |
---|---|
Driver | The name of the driver, the value driver->name of |
Compatible | Driver compatibility string, the value driver->of_match of . If the device Compatible tree matches here, it means that the device tree node device uses this driver |
dm devres
dm devres
Used to display a list of devres (device resource) records for a device. Some drivers use the devres API to allocate memory so that when the device is removed, the memory is automatically freed (no code required in the driver's remove()
methods ).
This feature needs to be defined
CONFIG DEVRES
to enable.
dm drivers
dm drivers
It is used to display all available drivers, the UCLASS corresponding to the driver and the list of devices using the driver (one device per line when there are multiple devices), and one driver per line. If the driver does not have a corresponding device, the device is shown as none
.
The meaning of each column is as follows:
column name | meaning |
---|---|
Driver | The name of the driver, the value driver->name of |
uid | UID is the corresponding value enum uclass_id in |
uclass | UCLASS name, the value uclass_driver->name of |
Devices | Device name, the value udevice->name of |
dm static
dm static
Used to display devices bound by platform data, i.e. not from the device tree. These are usually not available, but some boards may use static devices for space reasons.
column name | meaning |
---|---|
Driver | driver->name The name of the driver defined in |
Address | drive memory address |
dm tree
dm tree
Used to display the complete tree of devices.
The meaning of each column is as follows:
column name | meaning |
---|---|
Class | The UCLASS name of the device, that is, the value uclass_driver->name of |
Index | The index number of the device in UCLASS. Note that it is not Sequence Number. |
Probed | If the device is active, it displays+ |
Driver | The name of the driver used by this device, the value driver->name of |
Name | Display the device name (i.e. the value udevice->name of |
dm uclass
dm uclass
Used to display each class and a list of devices in that class.
container_of
In DM, devices are managed through a linked list, and the management of the linked list scripts/kconfig/list.h
uses container_of
the macro defined in . The in U-Boot container_of
is taken from Linux. The design of this macro is quite interesting, so we must focus on analyzing it. At first glance, this macro is not complicated, just a code block ( {}
) and two independent statements ( ;
).
const typeof( ((type *)0)->member ) *__mptr = (ptr);
typeof
Is a keyword to get the member type. Therefore, the first half of the sentence is const typeof( ((type *)0)->member )
actually to obtain member
the type of , and the whole sentence is member
to define the pointer variable _mptr
with the type of and assign a value ptr
, ptr
which is actually a pointer member
to .
(type *)( (char *)__mptr - ((size_t) &((type *)0)->member) );
(char *)__mptr
Coercively convert the member type tochar *
, which requires the address to be added or subtracted in bytesoffsetof
Used to get the structure member offset. This is a clever usage. We know that the address of the structure member minus the base address of the structure is the offset. And if the base address is 0, then directly fetching the member address is the offset.(char *)__mptr - ((size_t) &((type *)0)->member)
It is to get the first address oftype
the structure variable, but the type ischar*
, and finally used(type *)
to convert totype
the type pointer.
in conclusion
container_of
The final purpose is to return the base address of the structure member
where is located . Simply put, container_of
the function is to obtain the base address of the structure according to the members of the structure. And is const typeof( ((type *)0)->member ) *__mptr = (ptr);
just an intermediate state, without this sentence, container_of
the universality of cannot be achieved (the alternative is to use type coercion, but it can only be used in a specific type).
initialization process
As mentioned earlier uclass
, and udevice
are created dynamically, based on the devices defined by Device Tree and Platform Data in the U-Boot driver. The initialization process will traverse the devices created by Platform Data and Device Tree in turn, and create and associate udevice, uclass and related drivers.
- According to the found device information (Platform Data device
name
or Device Treecompatible
) to traversedriver
to match its memberschar *name;
orconst struct udevice_id *of_match;
, if there is no driver, just give up driver
After finding , continue to search fordriver
the corresponding member according to the member . If it exists , return the found one , otherwise continue to traverse the members to match its members , and give up if there is noenum uclass_id id
uclass
uclass
uclass
driver
enum uclass_id id
uclass driver
enum uclass_id id;
uclass driver
Once a is found , a is createduclass
,uclass
and the member ofstruct uclass_driver *uc_drv;
is pointed to the found oneuclass driver
, and the newly created one is returneduclass
.- Finally create one
udevice
with membersconst struct driver *driver;
pointing to the founddriver
and membersstruct uclass *uclass;
pointing to the obtained aboveuclass
.
It should be noted here that the device is a tree structure containing hierarchical relationships. However, DM initialization is only responsible for traversing all first-level devices, and the next-level devices are traversed by the method corresponding uclass
to .post_bind
Most uclass
of .post_bind
will call directly or indirectly dm_scan_fdt_dev
to traverse the sub-devices of the current device. Some nodes are not enabled, and nodes that are not enabled will not have corresponding devices.
The interface for DM initialization is dm_init_and_scan
in./common/board_f.c
in the file called before relocation static int initf_dm(void)
and./common/board_r.c
in the filestatic int initr_dm(void)
. Initialization after relocation is not much different from before relocation.
U-Boot provides the bootstage function (./common/bootstage.c
) to record information such as the execution time of each stage, and this recorded information can be reported to the user and passed to the operating system for logging/further analysis. bootstage
It is not enabled by defaultWhat's really related to DM initialization isdm_init_and_scan
that we focus on this function next.
dm_init_and_scan
dm_init_and_scan
Defined drivers/core/root.c
in , pre_reloc_only
when the input parameter is true, it means that only the nodes before the relocation will be parsed (only the nodes with u-boot,dm-pre-reloc
the attribute or DM_FLAG_PRE_RELOC
the devices with the flag will be parsed); pre_reloc_only
if it is false, all nodes will be parsed parse.
It can be seen from the input parameters dm_init_and_scan
of that the input parameters are true before relocation, so fewer nodes will be parsed. In addition, OF_LIVE
it is a dynamic tree, which is not enabled by default, because it is of-platdata
only SPL/TPL
available in the stage, so all of-platdata
related . DM_EVENT
I don't enable it by default here, just ignore it.
dm_init
dm_init
Defined drivers/core/root.c
in and primarily used to initialize ./drivers/core/root.c
the root device ( ) defined in U_BOOT_DRIVER(root_driver)
. The root device is not defined through the device tree, but is defined directly in the code (that is, the Platform Data method).
All devices are children of the root device. The only role of the root device is to manage other devices
dm_init
The input parameter of_live
indicates whether Live Device Tree is enabled, and the source code shows that this input parameter has not been used. Live Device Tree is a concept corresponding to Flattened Device Tree. It is mainly used to speed up the scan time of startup, but it can only be used after relocation.
of-platdata
The feature is not enabled, so the code in else is directly executed, gd->uclass_root
pointing to gd->uclass_root_s
, and then initializing the members gd->uclass_root
in : gd->uclass_root.next = gd->uclass_root
and gd->uclass_root.prev = gd->uclass_root
.
device_bind_by_name
device_bind_by_name
Defined drivers/core/device.c
in and mainly used to bind devices that are not defined using the device tree. This interface is used to create a device and bind it to a driver. For DM initialization, the ROOT device will be created here ./drivers/core/root.c
and U_BOOT_DRIVER(root_driver)
bound to the device defined in .
lists_driver_lookup_name
lists_driver_lookup_name
Defined drivers/core/device.c
in , it will traverse all struct driver
corresponding sections and match the specified driver name (input parameter). For the initialization of the root device here, the input parameter name
is the value of the name ./drivers/core/root.c
defined U_BOOT_DRIVER(root_driver)
in root_driver
and will eventually return the base address 0x804b438
.
device_bind_common
device_bind_common
Defined drivers/core/device.c/
in , the function is to bind the device driver, device, and UCLASS ( root_driver
the device initialized by DM and U_BOOT_DRIVER(root_driver)
) UCLASS_ROOT
, according to the above initialization process, only when there is a device driver, the corresponding device will be created.
- Call to find the corresponding UCLASS
uclass_get
according to the device ID. This includes the operations related to processing UCLASS, which will be described in detail later. dev = calloc(1, sizeof(struct udevice));
Apply for audevice
node memory, that is, create a device (during DM initialization, it will be created hereROOT DEVICE
), and then initialize the linked list nodes in it.
INIT_LIST_HEAD
Used to point each linked list node to itselfdev_set_plat
that isdev->plat_ = plat;
- Some devices, such as the SPI bus, I2C bus, and serial ports, are numbered using aliases. Therefore, it needs to be extracted from the device tree node and placed
dev->seq_
in .
- Initialize some memory used by the current device, such as the device's
plat_
. Note that the memory space is only allocated here, and the content is filled by the specific driver./* 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; */ } }
- The current device can choose to save some platform data in the corresponding UCLASS
uclass_plat_
to . If the correspondinguclass->per_device_plat_auto
is not 0, apply for memory, and calldev_set_uclass_plat(dev, ptr);
assignmentdev->uclass_plat_ = uclass_plat;
to point to the requested memory. Note that the memory space is only allocated here, and the content is filled by the specific driver.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); }
- If the current device has a parent node device, initialize the parent node device
per_child_plat_auto
, and then calllist_add_tail
to add a new device to its parent node device. For our ROOT device, its parent is NULL, so there is no need to add it. When parent is not NULL, the device will be connected to Parent throughchild_head
and these two members, and there will be detailed diagrams later.sibling_node
uclass_bind_device
Call Add the device created above to UCLASS, and at the same time, if the device has a parent device, it needs to callchild_post_bind
the method of the parent device (for the DM initialization here, the root device has no parent device). Directly on the picture:
- Call the corresponding driver
bind
method to complete the binding between the device and the corresponding driver, then call the parent device corresponding to the current devicechild_post_bind
(there is actually the same asuclass_bind_device
in ), and finally call the methoduclass
corresponding to the current device (corresponding uclass_driver) . Usually, in The method will traverse the sub-devices of the current device in turn (that is, the sub-nodes of the current node in the corresponding device tree), and bind all sub-devices to the current parent device. The method is called directly or indirectly , and then binds .post_bind
uclass
post_bind
dm_scan_fdt_dev ➜ dm_scan_fdt_node
lists_bind_fdt
uclass_get
The device needs to belong to UCLASS, uclass_get
which is defined drivers/core/uclass.c
in , to gd->uclass_root
traverse the uclass linked list pointed to by uclass id in udevice, and return the found uclass address, if not found, create a new uclass, and return the newly created uclass address.
- Call to
uclass_find
traversegd->uclass_root
the uclass linked list pointed to by to find the uclass with the specified id. For DM initialization, sincegd->dm_root
is NULL, it is not actually executedlis_for_each_entry
; otherwise, the expansion is as follows:
- When the uclass with the specified id cannot be found, call to
uclass_add
create a new uclass- Call
lists_uclass_lookup
finduclass_driver
, return the founduclass_driver
address , otherwise return an error
- Create a new uclass, then perform a series of initializations, and finally return the newly created uclass.
uc = calloc(1, sizeof(*uc));
Apply for a UCLASS node memory- Judgment
uclass_driver->priv_auto
Apply foruclaas->priv_
memory space (interfaceuclass_set_priv
is a simple assignment statementuc->priv_ = priv;
). INIT_LIST_HEAD()
Useddev_head
tosibling_node
point pointers in and to themselveslist_add
Responsible for serializing the requested UCLASS node memory togd->uclass_root
the linked list- Determine and
uclass
call the interfaceuclass_driver
corresponding to the current:init
uc_drv->init
- Call
dev_set_ofnode
OF_CONTROL
Indicates whether the device tree is enabled, which is enabled by default, so it will continue to call the one defined drivers/core/device.c
in dev_set_ofnode
and node_
point the root node
device_probe
To save resources, devices in U-Boot are probed late. Many devices are never used during U-Boot running, probing them takes time, requires memory, may increase the delay of the main loop, etc., so U-Boot will not proactively Probe devices unless necessary. According to the code, the root device is initialized and device_probe
called .
- Devices should be probed by uclass code or generic device code (for example
device_find_global_by_ofnode()
). - To activate a device, U-Boot first reads the device information data (
of_to_plat
), if there is a parent device, the parent device must be activated first
device_probe
Defined drivers/core/device.c
in , it is used to activate a device so that it can be used at any time. In order to save resources, the device in U-Boot will be detected with a delay. If the device is already activated, return directly. For DM initialization, this is where the root device is probed and activated.
device_of_to_plat
Call Convert the information in dts to the platform data of the device, so as to provide the information needed for operations such as detecting the device. This may cause some other devices that the current device depends on to be probed, for example a GPIO line will cause a GPIO device to be probed.- If the current device has a parent device, recursively execute the parent device's
device_of_to_plat
- Called to
device_alloc_priv
allocate memory used by various data held by this device. This is just to allocate memory space, and the actual content is filled by specific drivers.static int device_alloc_priv(struct udevice *dev) { const struct driver *drv; void *ptr; int size; drv = dev->driver; assert(drv); /* Allocate private data if requested and not reentered */ if (drv->priv_auto && !dev_get_priv(dev)) { ptr = alloc_priv(drv->priv_auto, drv->flags); if (!ptr) return -ENOMEM; dev_set_priv(dev, ptr); } /* Allocate private data if requested and not reentered */ size = dev->uclass->uc_drv->per_device_auto; if (size && !dev_get_uclass_priv(dev)) { ptr = alloc_priv(size, dev->uclass->uc_drv->flags); if (!ptr) return -ENOMEM; dev_set_uclass_priv(dev, ptr); } /* Allocate parent data for this child */ if (dev->parent) { size = dev->parent->driver->per_child_auto; if (!size) size = dev->parent->uclass->uc_drv->per_child_auto; if (size && !dev_get_parent_priv(dev)) { ptr = alloc_priv(size, drv->flags); if (!ptr) return -ENOMEM; dev_set_parent_priv(dev, ptr); } } return 0; }
if (drv->priv_auto && !dev_get_priv(dev))
It is based ondrv->priv_auto
the allocationdev->priv_
of memory space. This is the private data of the device!if (size && !dev_get_uclass_priv(dev))
It is based ondev->uclass->uc_drv->per_device_auto
the allocateddev->uclass_priv_
space, which is used to save the private data of its UCLASS owned by the deviceif (size && !dev_get_parent_priv(dev))
It is used to save the data of its parent device owned by the current device according to thedev->parent->driver->per_child_auto
allocated space. Note that only one of the private data of the parent device and the private data of the uclass of the parent device can be selected, and only the private data of the parent device is saved by default.dev->parent_priv_
- Call the driver corresponding to the device itself
of_to_plat
, and convert the device information described in the device tree into a platform data (stored in the memoryudevice ->plat_
pointed to by ), and the subsequent driver will obtain relevant resources from the platform data when using the hardware.
- If the current device has a parent device, recursively execute the parent device's
- If the device has a parent, then probe the parent device first to ensure that all parent devices are probed. direct recursive
device_probe
implementation
- Here
if (dev->parent && device_get_uclass_id(dev) != UCLASS_PINCTRL)
is to configure the corresponding PINCTRL of this device, but it should be noted that because the PINCTRL device may not have been Probe, so here is only the default state. - Call to
device_get_dma_constraints
fill the DMA constraints of the device. Get the device's DMA constraints from firmware. The driver later uses this information to translate the physical address into the device's bus address space. Currently, only device tree is supported. - Call the interface that needs to be executed in the uclass (corresponding uclass_driver) corresponding to the current device before
uclass_pre_probe_device
executing the detection device.pre_probe()
The method in the uclass (corresponding uclass_driver) corresponding to the current device
child_pre_probe()
The method in the uclass (corresponding uclass_driver) corresponding to the parent device of the current device
- Called the current device's parent device
child_pre_probe
. - The call
dev_has_ofnode
only processes the default clock for devices with a valid ofnode (the clock frequency defined in the device node) - Execute the probe function of the driver of the device to actually activate the device.
- Call the interface that needs to be executed in uclass (corresponding uclass_driver) after
uclass_post_probe_device
executing the detection device. This includespost_probe()
methods andchild_post_probe()
methods of the parent uclass (the corresponding uclass_driver).child_post_probe()
The method in the uclass (corresponding uclass_driver) corresponding to the parent device of the current device
post_probe()
The method in the uclass (corresponding uclass_driver) corresponding to the current device
- If it is currently a PINCTRL device, then for nodes with
pinctrl-names = "default";
attributes , PINCTRL will be automatically set here
Note that here I am only explaining the call in the root node device_probe
. Other devices will not actively call it when it is initialized, but will call it when it is actually needed. See dm_scan
the analysis .
dm_scan
dm_scan
Defined drivers/core/root.c
in is responsible for initializing all devices except the root device. As mentioned earlier, U-Boot supports two basic ways of driver configuration, Platform Data (often referred to as plat in the code) and Flattened Device Tree (often referred to as fdt in the code). Therefore, the devices defined by these two drivers must be dealt with here.
dm_scan_plat
dm_scan_plat
Defined drivers/core/root.c
in , finds and binds U_BOOT_DRVINFO(__name)
devices defined directly using . U_BOOT_DRVINFO(__name)
Defined ./include/dm/platdata.h
in , U_BOOT_DRIVER(__name)
similar to , U_BOOT_DRVINFO(__name)
except using struct driver_info
to define variables __name
, and also define a section with the same name, and __name
put in this section.
dm_scan_plat
In fact, it is to traverse driver_info
the table ( to__u_boot_list_2_driver_info_1
), and then use the members in to find the drivers defined by . The establishment of the driver, the establishment of UCLASS, and the binding of uclass_driver are the same as the root device above, and the internal actions are finally called to complete these series of actions (the parameters are different when calling). Note that all nodes are parented ! ! !__u_boot_list_2_driver_info_3
driver_info
name
U_BOOT_DRIVER(__name)
udevice
device_bind_by_name
device_bind_by_name
gd->dm_root
dm_extended_scan
dm_extended_scan
Defined drivers/core/root.c
in , handles devices defined in the device tree. According to the source code, dm_extended_scan
it mainly includes two parts. First, dm_scan_fdt
process the device nodes in the device tree through ; second, because some nodes ( /chosen
, /clocks
, /firmware
) are not devices themselves, they may contain some devices. At this time, dm_scan_fdt_ofnode_path
use to process the devices in these nodes one by one.
ofnode_root
It is the root node ( of_offset=0
), and ofnode_path
this is to find the specified device tree node according to the complete path. These two parts are finally provided dm_scan_fdt_node
to process the node device, so we only need to focus dm_scan_fdt_node
on .
dm_scan_fdt_node
dm_scan_fdt_node
Defined drivers/core/root.c
in , implements scanning the device tree and binding drivers for nodes. It will create a new udevice for the bound device tree node, and use the input parameter parent as its parent device (the input parameter is fixed to gd->dm_root
be the root device).
dm_scan_fdt_node
It will start from the root node and traverse all child nodes in turn. Since the root device has been initialized separately before, the device found here is the first device under the root node, and then use to bind the nodes lists_bind_fdt
one by one (the binding here is to connect the node with the corresponding udevice, driver, uclass, uclass_driver linked).
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
node
A udevice will be created internally, and then the udevice will be bound to the current node (input parameter ). Of course, when creating udevice, its corresponding driver, uclass, uclass_driver will be bound (if there is no uclass, a new one will be created).
- Get
compatible
the content (base address + length), if there is no device nodecompatible
, this node will be ignored.compat_list = ofnode_get_property(node, "compatible", &compat_length); if (!compat_list) { if (compat_length == -FDT_ERR_NOTFOUND) { log_debug("Device '%s' has no compatible string\n", name); return 0; } dm_warn("Device tree error at node '%s'\n", name); return compat_length; }
- Traverse
compatible
, and then go todriver
the table ( to__u_boot_list_2_driver_1
) to compare one by one (the node's matches the driver's ). The code is very simple, it is a two-layer loop__u_boot_list_2_driver_3
compatible
of_match
As long as there is a match, callfor (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; } /* 这里就会返回 匹配的 struct udevice_id */ ret = driver_check_compatible(entry->of_match, &id, compat); if (!ret) break; }
device_bind_with_driver_data
➜complete thedevice_bind_common
establishment of the driver, the establishment of UCLASS, the binding of uclass_driver ( the parameters are different when calling). Among them is the match, which is assigned to inside . Note that all nodes are parented ! ! ! In this way, all nodes will be connected by parent. In addition, the input parameter plat is NULL, that is, the plat data of the device from the device tree is extracted from the subsequent re-device treeudevice
device_bind_common
id->data
struct udevice_id
device_bind_common
struct udevice
driver_data
gd->dm_root
device_bind_common
dm_scan_other
dm_scan_other
Defined drivers/core/root.c
in , used to search for and bind special devices that are not visible to the DM. This function is a __WEAK
function with no substance and must be implemented if necessary.
There is a default implementation of this interface in ./lib/efi/efi_app.c
and ./boot/bootstd-uclass.c
, the former is to scan for UEFI devices available in U-Boot (create a block device in U-Boot), and the latter is used to bind boormethod devices. I don't use efi to start here, so it will be executed ./boot/bootstd-uclass.c
by dm_scan_other
.
We could manually define all required devices in the device tree, but this is not required. ./boot/bootstd-uclass.c
The in dm_scan_other
will bootdev
automatically create the bootstd device if the is not found in the device tree. If no bootmeth devices are found at all, it creates one for each available bootmeth driver.
For details about this part, see U-Boot Standard Boot
dm_probe_devices
dm_probe_devices
Defined drivers/core/root.c
in , realize traversing gd->dm_root
all devices under and then activate the device. As mentioned earlier, all U-Boot devices are initialized with a delay, and all devices with DM_FLAG_PROBE_AFTER_BIND
the flag , otherwise the device will be probed only when it is used later.
static int dm_probe_devices(struct udevice *dev, bool pre_reloc_only)
{
u32 mask = DM_FLAG_PROBE_AFTER_BIND;
u32 flags = dev_get_flags(dev);
struct udevice *child;
int ret;
if (pre_reloc_only)
mask |= DM_FLAG_PRE_RELOC;
if ((flags & mask) == mask) {
ret = device_probe(dev);
if (ret)
return ret;
}
list_for_each_entry(child, &dev->child_head, sibling_node)
dm_probe_devices(child, pre_reloc_only);
return 0;
}
-
If the device has
DM_FLAG_PROBE_AFTER_BIND
the flag ,device_probe
calling probe activates the current device. I mentioned earlier that the ROOT device is initialized separately and the Probe. -
Call
list_for_each_entry(child, &dev->child_head, sibling_node)
to variable all child nodes of the current device. As mentioned earlier, it is connectedudevice
in serieschild_head
with two members, so this interface is traversed .sibling_node
list_for_each_entry
child_head
sibling_node
list_entry()
The role of is to obtain the pointer of the entire structure through the known pointer to the member sub-item.prefetch()
Tell the cpu the elements to be used next, and prefetch them in advance to increase the speed&pos->member !=(head)
is the end condition offor
the loop
The following is the basic form after fully expanded
What we need to pay attention to is the recursive call here dm_probe_devices
. For each sub-device, the recursive call here will traverse to the sub-device of the sub-device, and so on. The end condition is to return to the previous layer until there is no sub-equipment. The following dm_probe_devices
is the traversal of each device
reference
- 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