U-Boot VII explains the Driver Model architecture, configuration, commands, and initialization process in detail

  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.
insert image description here
  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=ybe , and the corresponding actual peripheral drivers need to CONFIG_DM_xxxbe enabled through enable. Among them, xxx indicates a specific peripheral device. For example, enabling CONFIG_DM_SERIALwill automatically add the corresponding source code file in the Makefile:
insert image description here
  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_xxxthe 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).
insert image description here

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_"#_nameuse 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
insert image description here

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.hin ll_entry_declare, which is the key to the implementation.
insert image description here
  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:
insert image description here

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 plator platdata) and Flattened Device Tree (device tree, often referred to in code ). fdtAmong 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.hthe 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-platdatafeatures . However, it is of-platdataonly available in SPL/TPLthe 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.hdefined ofin 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 driverthe plat_autoand of_to_platmembers in ).
insert image description here
  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.dtsinamed .

  The newly *-u-boot.dtsiadded will not *.dtsbe directly referenced by any other files, because these *-u-boot.dtsifiles 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:

  1. <orig_filename>-u-boot.dtsi# <orig_filename> is the name corresponding to the .dts to be compiled
  2. <CONFIG_SYS_SOC>-u-boot.dtsi
  3. <CONFIG_SYS_CPU>-u-boot.dtsi
  4. <CONFIG_SYS_VENDOR>-u-boot.dtsi
  5. u-boot.dtsi

frame

  U-Boot's DM uses uclassand udevicethese two abstract classes to manage all device drivers, and these two abstract classes correspond to uclass_driverand driver. udeviceis created driverdynamically ; uclassis uclass_drivercreated . But only when it is created udevicewill it find the corresponding one uclass, so in the end it will only be created if driverit exists uclass.
insert image description here
  What is really useful is an independent uclass_driverand driver, uclassthey udeviceare managed through and respectively. In terms of code implementation, uclassand are udeviceactually 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_datain . A simple block diagram is as follows:
insert image description here
  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=yyou can add this DEMO to our build, and then conduct a learning test.

global_data

  ./include/asm-generic/global_data.hstruct global_dataThe structure in the file manages the global variables of the entire U-Boot. After we define CONFIG_DM=y, global_datathere 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

  uclassand uclass_driverare defined in ./include/dm/uclass.hthe file, among them, uclassis an abstract class that divides devices of the same type into a group for classification management; uclass_driverprovides a consistent interface for a group of related device drivers. There isuclass a one-to-one correspondence with . uclass_driver
insert image description here
  In the expression, we usually use uclass_idto 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.hthe file enum uclass_id, it should be noted that the ID is actually used in struct uclass_driverthe .
insert image description here

struct uclass

  uclassGroup devices of the same type into a group for classified management. Note that uclassit is automatically generated by U-Boot during the initialization process, and not all uclass_idcorresponding uclasswill be generated, only those corresponding uclass_driverand udevicematched by uclasswill 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 uclassthis structure is actually a linked list node. After DM is initialized, all existing devices uclasswill form a gd->uclass_rootdoubly linked list with as the head of the linked list. This linked list is strung together through sibling_nodeits members.
insert image description here
  When DM is initialized, it will traverse all automatically uclass_driver, and every time it finds a , uclass_driverit will check uclasswhether 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_driverin uclass, and then associate uclass_driverit with . That is, uclassit is created uclass_driverdynamically .

struct uclass_driver

  uclass_driverProvides a consistent interface for a set of related drivers, one uclassfor uclass_driver . In terms of code implementation, struct uclassthe uc_drvin 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_drivermacros must be used UCLASS_DRIVER(__name)for definition. In addition to using the struct uclass_driverdefinition __namewill also define a section with the same name, and __nameput it into the section with the same name. Take serialas an example , as shown below:
insert image description here
  uclass_driverit is managed by a linked list struct uclassas a node, and uclass_drivereach must be associated with a struct uclassspecified (on a linked list node). When there is no correspondinguclass_driver , one will be automatically created and then associated.struct uclassstruct uclass
insert image description here

udevices and drivers

  udeviceand are driverdefined in ./include/dm/device.hthe file, where udeviceis an abstract class used to represent a device (an instance of the driver); driverit is the actual driver corresponding to a device. udeviceThe relationship with drivercan be many-to-one (multiple devices may share the same one driver).
insert image description here
  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.drivernameroot_driver

struct udevice

  udeviceContains information about the device, which is essentially a driver instance that must be bound to a specific port or peripheral driver ( udevicemust be associated const struct driver *driverwith driver). udeviceIt cannot be associated with UCLASS itself, and must struct driverbe idassociated 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 udevicethis structure is actually a linked list node. After the DM is initialized, all udevicewill form a doubly linked list with as the head of the linked list. This linked list is connected gd->dm_rootby the child_headand 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 .uclassudeviceuclass_nodestruct udevice
insert image description here
driverdriverudevicedriverudevicedriver

uclassDifferent from , udeviceit 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, platnon-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_platmethod 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.
insert image description here
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_ALIASa 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_ALIASthe 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_ALIASor 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

  driverContains 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 driverBoth 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. udeviceIt struct driveris idassociated with its UCLASS according to the attribute in .

  In code implementation, drivermacros must be used U_BOOT_DRIVER(__name)for definition. In addition to using the struct uclass_driverdefinition __namewill also define a section with the same name, and __nameput it into the section with the same name. Take serialas an example , as shown below:
insert image description here
  driverit is managed by a linked list struct udeviceas a node, and drivereach must be associated with a struct udevicespecified (on a linked list node). When there is no correspondingdriver , one will be automatically created and then associated.struct udevicestruct udevice
insert image description here

struct udevice_id

  struct udevice_idis for specific drivers to list the compatibility strings supported by the driver. The driver and the device in the specific device tree struct udevice_idare compatiblematched through in.

struct udevice_id {
    
    
	const char *compatible;		/* 一个字符串 */
	ulong data;					/* 兼容字符串对应的数据,具体使用方式由驱动决定 */
};

  struct udevice_idThe role of is to facilitate the definition of multiple compatible strings at a time, and finally assign them struct driverto of_matchthe members in . Among them, the member compatibleis used to compatiblematch with in the device tree; datait will be passed to struct udevicein driver_data(see initialization process later).
insert image description here

DM command

  U-Boot provides DM-related commands in ./cmd/dm.cthe file , and you can view DM-related information on the U-Boot command interface. After entering the command line mode of U-Boot, enter helpor ?, 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
insert image description here

dm compat

  dm compatUsed 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.
insert image description here
The meaning of each column is as follows:

column name meaning
Driver The name of the driver, the value driver->nameof
Compatible Driver compatibility string, the value driver->of_matchof . If the device Compatibletree matches here, it means that the device tree node device uses this driver

dm devres

  dm devresUsed 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 DEVRESto enable.

dm drivers

  dm driversIt 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.
insert image description here
The meaning of each column is as follows:

column name meaning
Driver The name of the driver, the value driver->nameof
uid UID is the corresponding value enum uclass_idin
uclass UCLASS name, the value uclass_driver->nameof
Devices Device name, the value udevice->nameof

dm static

  dm staticUsed 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->nameThe name of the driver defined in
Address drive memory address

dm tree

  dm treeUsed to display the complete tree of devices.
insert image description here
The meaning of each column is as follows:

column name meaning
Class The UCLASS name of the device, that is, the value uclass_driver->nameof
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->nameof
Name Display the device name (i.e. the value udevice->nameof

dm uclass

  dm uclassUsed to display each class and a list of devices in that class.
insert image description here

container_of

  In DM, devices are managed through a linked list, and the management of the linked list scripts/kconfig/list.huses container_ofthe macro defined in . The in U-Boot container_ofis 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 ( ;).
insert image description here

const typeof( ((type *)0)->member ) *__mptr = (ptr);

  typeofIs a keyword to get the member type. Therefore, the first half of the sentence is const typeof( ((type *)0)->member )actually to obtain memberthe type of , and the whole sentence is memberto define the pointer variable _mptrwith the type of and assign a value ptr, ptrwhich is actually a pointer member to .

(type *)( (char *)__mptr - ((size_t) &((type *)0)->member) );

  1. (char *)__mptrCoercively convert the member type to char *, which requires the address to be added or subtracted in bytes
  2. offsetofUsed 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.
  3. (char *)__mptr - ((size_t) &((type *)0)->member) It is to get the first address of typethe structure variable, but the type is char*, and finally used (type *)to convert to type the type pointer.

in conclusion

  container_ofThe final purpose is to return the base address of the structure memberwhere is located . Simply put, container_ofthe 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_ofthe universality of cannot be achieved (the alternative is to use type coercion, but it can only be used in a specific type).
insert image description here

initialization process

  As mentioned earlier uclass, and udeviceare 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.

  1. According to the found device information (Platform Data device nameor Device Tree compatible) to traverse driverto match its members char *name;or const struct udevice_id *of_match;, if there is no driver, just give up
  2. driverAfter 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 iduclassuclassuclassdriverenum uclass_id iduclass driverenum uclass_id id;
  3. uclass driverOnce a is found , a is created uclass, uclassand the member of struct uclass_driver *uc_drv;is pointed to the found one uclass driver, and the newly created one is returned uclass.
  4. Finally create one udevicewith members const struct driver *driver;pointing to the found driverand members struct uclass *uclass;pointing to the obtained above uclass.

  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 uclassto .post_bindMost uclassof .post_bindwill call directly or indirectly dm_scan_fdt_devto traverse the sub-devices of the current device. Some nodes are not enabled, and nodes that are not enabled will not have corresponding devices.
insert image description here
  The interface for DM initialization is dm_init_and_scanin./common/board_f.cin the file called before relocation static int initf_dm(void)and./common/board_r.cin the filestatic int initr_dm(void). Initialization after relocation is not much different from before relocation.
insert image description here
  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. bootstageIt is not enabled by defaultWhat's really related to DM initialization isdm_init_and_scanthat we focus on this function next.

dm_init_and_scan

  dm_init_and_scanDefined drivers/core/root.cin , pre_reloc_onlywhen 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-relocthe attribute or DM_FLAG_PRE_RELOCthe devices with the flag will be parsed); pre_reloc_onlyif it is false, all nodes will be parsed parse.
insert image description here
  It can be seen from the input parameters dm_init_and_scanof that the input parameters are true before relocation, so fewer nodes will be parsed. In addition, OF_LIVEit is a dynamic tree, which is not enabled by default, because it is of-platdataonly SPL/TPLavailable in the stage, so all of-platdatarelated . DM_EVENTI don't enable it by default here, just ignore it.

dm_init

  dm_initDefined drivers/core/root.cin and primarily used to initialize ./drivers/core/root.cthe 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).
insert image description here

All devices are children of the root device. The only role of the root device is to manage other devices

  dm_initThe input parameter of_liveindicates 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-platdataThe feature is not enabled, so the code in else is directly executed, gd->uclass_rootpointing to gd->uclass_root_s, and then initializing the members gd->uclass_rootin : gd->uclass_root.next = gd->uclass_rootand gd->uclass_root.prev = gd->uclass_root.
insert image description here

device_bind_by_name

  device_bind_by_nameDefined drivers/core/device.cin 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.cand U_BOOT_DRIVER(root_driver)bound to the device defined in .

lists_driver_lookup_name

  lists_driver_lookup_nameDefined drivers/core/device.cin , it will traverse all struct drivercorresponding sections and match the specified driver name (input parameter). For the initialization of the root device here, the input parameter nameis the value of the name ./drivers/core/root.cdefined U_BOOT_DRIVER(root_driver)in root_driverand will eventually return the base address 0x804b438.
insert image description here

device_bind_common

  device_bind_commonDefined drivers/core/device.c/in , the function is to bind the device driver, device, and UCLASS ( root_driverthe 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.

  1. Call to find the corresponding UCLASS uclass_getaccording to the device ID. This includes the operations related to processing UCLASS, which will be described in detail later.
  2. dev = calloc(1, sizeof(struct udevice));Apply for a udevicenode memory, that is, create a device (during DM initialization, it will be created here ROOT DEVICE), and then initialize the linked list nodes in it.
    insert image description here
    1. INIT_LIST_HEADUsed to point each linked list node to itself
    2. dev_set_platthat isdev->plat_ = plat;
    3. 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 .
      insert image description here
  3. 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; */
    		}
    	}
    
  4. The current device can choose to save some platform data in the corresponding UCLASS uclass_plat_to . If the corresponding uclass->per_device_plat_autois not 0, apply for memory, and call dev_set_uclass_plat(dev, ptr);assignment dev->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);
    	}
    
  5. If the current device has a parent node device, initialize the parent node device per_child_plat_auto, and then call list_add_tailto 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 through child_headand these two members, and there will be detailed diagrams later.sibling_node
  6. uclass_bind_deviceCall Add the device created above to UCLASS, and at the same time, if the device has a parent device, it needs to call child_post_bindthe method of the parent device (for the DM initialization here, the root device has no parent device). Directly on the picture:
    insert image description here
  7. Call the corresponding driver bindmethod to complete the binding between the device and the corresponding driver, then call the parent device corresponding to the current device child_post_bind(there is actually the same as uclass_bind_devicein ), 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
    insert image description here
    uclasspost_binddm_scan_fdt_dev ➜ dm_scan_fdt_nodelists_bind_fdt

uclass_get

  The device needs to belong to UCLASS, uclass_getwhich is defined drivers/core/uclass.cin , to gd->uclass_roottraverse 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.

  1. Call to uclass_findtraverse gd->uclass_rootthe uclass linked list pointed to by to find the uclass with the specified id. For DM initialization, since gd->dm_rootis NULL, it is not actually executed lis_for_each_entry; otherwise, the expansion is as follows:
    insert image description here
  2. When the uclass with the specified id cannot be found, call to uclass_addcreate a new uclass
    1. Call lists_uclass_lookupfind uclass_driver, return the found uclass_driveraddress , otherwise return an error
      insert image description here
    2. Create a new uclass, then perform a series of initializations, and finally return the newly created uclass.
      insert image description here
      1. uc = calloc(1, sizeof(*uc));Apply for a UCLASS node memory
      2. Judgment uclass_driver->priv_autoApply for uclaas->priv_ memory space (interface uclass_set_privis a simple assignment statement uc->priv_ = priv;).
      3. INIT_LIST_HEAD()Used dev_headto sibling_nodepoint pointers in and to themselves
      4. list_addResponsible for serializing the requested UCLASS node memory to gd->uclass_rootthe linked list
      5. Determine and uclasscall the interfaceuclass_driver corresponding to the current:inituc_drv->init

dev_set_ofnode

  OF_CONTROLIndicates whether the device tree is enabled, which is enabled by default, so it will continue to call the one defined drivers/core/device.cin dev_set_ofnodeand node_point the root node
insert image description here

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_probecalled .

  1. Devices should be probed by uclass code or generic device code (for example device_find_global_by_ofnode()).
  2. 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_probeDefined drivers/core/device.cin , 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.

  1. device_of_to_platCall 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.
    1. If the current device has a parent device, recursively execute the parent device'sdevice_of_to_plat
      insert image description here
    2. Called to device_alloc_privallocate 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;
      }
      
      1. if (drv->priv_auto && !dev_get_priv(dev))It is based on drv->priv_autothe allocation dev->priv_of memory space. This is the private data of the device!
      2. if (size && !dev_get_uclass_priv(dev))It is based on dev->uclass->uc_drv->per_device_autothe allocated dev->uclass_priv_space, which is used to save the private data of its UCLASS owned by the device
      3. if (size && !dev_get_parent_priv(dev))It is used to save the data of its parent device owned by the current device according to the dev->parent->driver->per_child_autoallocated 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_
    3. 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 memory udevice ->plat_pointed to by ), and the subsequent driver will obtain relevant resources from the platform data when using the hardware.
      insert image description here
  2. If the device has a parent, then probe the parent device first to ensure that all parent devices are probed. direct recursive device_probeimplementation
    insert image description here
  3. 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.
  4. Call to device_get_dma_constraintsfill 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.
  5. Call the interface that needs to be executed in the uclass (corresponding uclass_driver) corresponding to the current device before uclass_pre_probe_deviceexecuting the detection device.
    1. pre_probe()The method in the uclass (corresponding uclass_driver) corresponding to the current device
      insert image description here
    2. child_pre_probe()The method in the uclass (corresponding uclass_driver) corresponding to the parent device of the current device
      insert image description here
  6. Called the current device's parent device child_pre_probe.
  7. The call dev_has_ofnodeonly processes the default clock for devices with a valid ofnode (the clock frequency defined in the device node)
  8. Execute the probe function of the driver of the device to actually activate the device.
    insert image description here
  9. Call the interface that needs to be executed in uclass (corresponding uclass_driver) after uclass_post_probe_deviceexecuting the detection device. This includes post_probe()methods and child_post_probe()methods of the parent uclass (the corresponding uclass_driver).
    1. child_post_probe()The method in the uclass (corresponding uclass_driver) corresponding to the parent device of the current device
      insert image description here
    2. post_probe()The method in the uclass (corresponding uclass_driver) corresponding to the current device
      insert image description here
  10. 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_scanthe analysis .

dm_scan

  dm_scanDefined drivers/core/root.cin 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_platDefined drivers/core/root.cin , finds and binds U_BOOT_DRVINFO(__name)devices defined directly using . U_BOOT_DRVINFO(__name)Defined ./include/dm/platdata.hin , U_BOOT_DRIVER(__name)similar to , U_BOOT_DRVINFO(__name)except using struct driver_infoto define variables __name, and also define a section with the same name, and __nameput in this section.
insert image description here
  dm_scan_platIn fact, it is to traverse driver_infothe 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_3driver_infonameU_BOOT_DRIVER(__name)
insert image description here
  udevicedevice_bind_by_namedevice_bind_by_name
insert image description here
gd->dm_root

dm_extended_scan

  dm_extended_scanDefined drivers/core/root.cin , handles devices defined in the device tree. According to the source code, dm_extended_scanit mainly includes two parts. First, dm_scan_fdtprocess 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_pathuse to process the devices in these nodes one by one.
insert image description here
  ofnode_rootIt is the root node ( of_offset=0), and ofnode_paththis is to find the specified device tree node according to the complete path. These two parts are finally provided dm_scan_fdt_nodeto process the node device, so we only need to focus dm_scan_fdt_nodeon .

dm_scan_fdt_node

  dm_scan_fdt_nodeDefined drivers/core/root.cin , 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_rootbe the root device).

   dm_scan_fdt_nodeIt 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_fdtone 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_fdtnodeA 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).

  1. Get compatiblethe content (base address + length), if there is no device node compatible, 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;
    }
    
  2. Traverse compatible, and then go to driverthe 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_3compatibleof_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;
    		}
    		/* 这里就会返回 匹配的 struct udevice_id */
    		ret = driver_check_compatible(entry->of_match, &id,
    					      compat);
    		if (!ret)
    			break;
    	}
    
      As long as there is a match, call 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 treeudevicedevice_bind_commonid->datastruct udevice_iddevice_bind_commonstruct udevicedriver_data
    insert image description here
    gd->dm_rootdevice_bind_common
    insert image description here

dm_scan_other

  dm_scan_otherDefined drivers/core/root.cin , used to search for and bind special devices that are not visible to the DM. This function is a __WEAKfunction with no substance and must be implemented if necessary.
insert image description here
  There is a default implementation of this interface in ./lib/efi/efi_app.cand ./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.cby dm_scan_other.

  We could manually define all required devices in the device tree, but this is not required. ./boot/bootstd-uclass.cThe in dm_scan_otherwill bootdevautomatically 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_devicesDefined drivers/core/root.cin , realize traversing gd->dm_rootall 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_BINDthe 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;
}
  1. If the device has DM_FLAG_PROBE_AFTER_BINDthe flag , device_probecalling probe activates the current device. I mentioned earlier that the ROOT device is initialized separately and the Probe.

  2. 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 connected udevicein series child_headwith two members, so this interface is traversed .sibling_nodelist_for_each_entrychild_headsibling_node
    insert image description here

    1. list_entry()The role of is to obtain the pointer of the entire structure through the known pointer to the member sub-item.
    2. prefetch()Tell the cpu the elements to be used next, and prefetch them in advance to increase the speed
    3. &pos->member !=(head)is the end condition of forthe loop

    The following is the basic form after fully expanded
    insert image description here

  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_devicesis the traversal of each device
insert image description here

reference

  1. https://blog.csdn.net/ZHONGCAI0901/article/details/117781158
  2. https://zhuanlan.zhihu.com/p/460754843
  3. https://www.cnblogs.com/YYFaGe/p/16672483.html
  4. https://blog.csdn.net/weixin_41028621/article/details/90643550
  5. https://blog.csdn.net/ooonebook/article/details/53234020
  6. https://u-boot.readthedocs.io/en/latest/develop/driver-model/design.html

Guess you like

Origin blog.csdn.net/ZCShouCSDN/article/details/128600865