Ultra detailed [Uboot - MMC driver development] Uboot driver model

Posted by sridhar golyandla on Sat, 22 Jan 2022 21:53:00 +0100

The full text takes a week and carefully summarizes more than 20000 words. I hope it will be helpful to you. I feel I can praise, pay attention and don't get lost. There are more dry goods in the follow-up!

Before reading the article, promise me to calm down and taste it slowly!

 

3.1. What is the Uboot driver model

Friends who have studied Linux basically know the device driver model of Linux. Uboot also introduces the driver model of uboot (driver model: DM) according to the driver model architecture of Linux.

This driver model provides a unified method for driver definition and access interface. The compatibility between drivers and the standard type of access are improved. The uboot driver model is similar to the device driver model in the kernel.

 

3.2 why is there a driving model

Whether Linux or Uboot, the generation of a new object must have its problems to be solved, and the driver model is no exception!

  • Improve the reusability of code: in order to make the code run on different hardware platforms and architectures, we must maximize the reusability of code.
  • High cohesion and low coupling: the idea of layering is also to achieve this goal. Low coupling now provides a unified abstract access interface to the outside world, and high cohesion will focus on the abstract implementation with close correlation.
  • Easy to manage: in the process of continuous development, there are more and more hardware devices and more drivers. In order to better manage drivers, we also need a set of excellent driver architecture!

 

3.3. How to use uboot's DM model

The use of DM model can be configured through menuconfig.

make menuconfig

① : menuconfig configure global DM model

Device Drivers ->  Generic Driver Options -> Enable Driver Model  

Open the Driver Model through the above path and finally configure it in Config file, CONFIG_DM=y

② : Specifies the DM model of a driver

After the global DM model is opened, we can enable or disable the DM function for the impassable driver module. Take MMC driver as an example:

Device Drivers -> MMC Host controller Support -> Enable MMC controllers using Driver Model

Finally reflected in Config in config file_ DM_ MMC=y

In the corresponding driver, you can see the judgment #if! CONFIG_ IS_ Enabled (dm_mmc) to judge whether to open the DM driving model.

In the Makefile file of the management driver, you can also see obj - $(config $(SPL $) DM_ MMC) += mmc-uclass. o. To determine whether to add the driver model to the compilation options.

In short, we need to open the DM model and finally reflect it on several configuration information:

  • CONFIG_DM=y, global DM model open
  • CONFIG_DM_XXX=y, opening of a driven DM model
  • You can view the compilation of corresponding macros through Kconifg and Makefile

3.4 DM model data structure

To understand the whole driving framework of DM model, we must first understand its brick by brick! That is, the data structures that make up the driving framework.

① global_data

typedef struct global_data {
...
#ifdef CONFIG_DM
	struct udevice	*dm_root;	/* Root instance for Driver Model */
	struct udevice	*dm_root_f;	/* Pre-relocation root instance */
	struct list_head uclass_root;	/* Head of core tree */
#endif
...
}

global_data, which manages the global variables of the whole Uboot, where dm_root,dm_root_f,uclass_root is used to manage the entire DM model. What do these variables mean?

  • dm_root: root device of DM model
  • dm_root_f: Reset forward root device
  • uclass_root: the head of the UCLASS linked list

The ultimate role of these variables is to manage the udevice device information and uclass driver classes in the whole model.

 

② uclass

Let's first look at the structure uclass

/**
 * struct uclass - a U-Boot drive class, collecting together similar drivers
 *
 * A uclass provides an interface to a particular function, which is
 * implemented by one or more drivers. Every driver belongs to a uclass even
 * if it is the only driver in that uclass. An example uclass is GPIO, which
 * provides the ability to change read inputs, set and clear outputs, etc.
 * There may be drivers for on-chip SoC GPIO banks, I2C GPIO expanders and
 * PMIC IO lines, all made available in a unified way through the uclass.
 *
 * @priv: Private data for this uclass
 * @uc_drv: The driver for the uclass itself, not to be confused with a
 * 'struct driver'
 * @dev_head: List of devices in this uclass (devices are attached to their
 * uclass when their bind method is called)
 * @sibling_node: Next uclass in the linked list of uclasses
 */
struct uclass {
	void *priv;								//Private data of uclass
	struct uclass_driver *uc_drv;			//The collection of operands of the uclass class
	struct list_head dev_head;				//All equipment of the uclass
	struct list_head sibling_node;			//Next uclass node
};

According to the notes, we can see that uclass is equivalent to a teacher and manages all udevice s corresponding to a category.

For example: for an IIC driver, there is only one driver framework, but there are many devices driven by IIC, such as EEPROM, MCU 6050, etc;

It's all here, dev_ The head linked list is used to manage all devices under the driver class.

Summary: uclass is used to manage all devices under this type, and there is a corresponding uclass_driver driver.

  • definition

uclass is automatically generated by uboot, and not all uclass will be generated. There is a corresponding uclass_driver and uclass matched by udevice will be generated.

  • deposit

All generated uclass will be mounted GD - > uclass_ Root linked list.

  • Related API

Directly traverse the linked list GD - > uclass_ Root linked list and according to uclass_id to get the corresponding uclass.

int uclass_get(enum uclass_id key, struct uclass **ucp);
// From GD - > UCLASS_ Get the corresponding ucla ss from the root linked list

 

③ uclass_driver

As mentioned above, we can see that the uclass class contains uclass_driver structure, uclass_driver, as its name implies, is the driver of uclass. Its main function is to provide a unified management interface for uclass. The structure is as follows:

/**
 * struct uclass_driver - Driver for the uclass
 *
 * A uclass_driver provides a consistent interface to a set of related
 * drivers.
 */
struct uclass_driver {
    const char *name; // The uclass_driver command
    enum uclass_id id; // Corresponding uclass id
/* The following function pointers are mainly the difference of call timing */
    int (*post_bind)(struct udevice *dev); // After udevice is bound to the uclass, it is called.
    int (*pre_unbind)(struct udevice *dev); // Before udevice is bound to the uclass, it is called.
    int (*pre_probe)(struct udevice *dev); // Before calling a udevice of the uclass, probe is called.
    int (*post_probe)(struct udevice *dev); // After the udevice of the uclass is probe, it is called.
    int (*pre_remove)(struct udevice *dev);// Before calling a udevice of the uclass, remove is called.
    int (*child_post_bind)(struct udevice *dev); // After a sub device of one of the uclass's udevice is bound to the udevice, it is called.
    int (*child_pre_probe)(struct udevice *dev); // Before calling a sub device of a udevice of uclass, probe is called.
    int (*init)(struct uclass *class); // Called when the uclass is installed
    int (*destroy)(struct uclass *class); // Called when the uclass is destroyed
    int priv_auto_alloc_size; // How much private data needs to be allocated to the corresponding uclass
    int per_device_auto_alloc_size; //
    int per_device_platdata_auto_alloc_size; //
    int per_child_auto_alloc_size; //
    int per_child_platdata_auto_alloc_size;  //
    const void *ops; //Operation set
    uint32_t flags;   // Identified as
};
  • definition

uclass_ Driver is mainly through UCLASS_ Driver to define. Here is a brief description of the underlying code. Be patient!

Take pinctrl as an example

UCLASS_DRIVER(pinctrl) = {
	.id = UCLASS_PINCTRL,
	.post_bind = pinctrl_post_bind,
	.flags = DM_UC_FLAG_SEQ_ALIAS,
	.name = "pinctrl",
};
/* Declare a new uclass_driver */
#define UCLASS_DRIVER(__name)						\
	ll_entry_declare(struct uclass_driver, __name, uclass)

#define ll_entry_declare(_type, _name, _list)				\
	_type _u_boot_list_2_##_list##_2_##_name __aligned(4)		\
			__attribute__((unused,				\
			section(".u_boot_list_2_"#_list"_2_"#_name)))

The above is basically our underlying code. It's a little around, but it's not difficult! We just need to replace the macro!

Through the above definition, after replacing the macro, the final definition is as follows:

struct uclass_driver _u_boot_list_2_uclass_2_pinctrl = {
	.id = UCLASS_PINCTRL,
	.post_bind = pinctrl_post_bind,
	.flags = DM_UC_FLAG_SEQ_ALIAS,
	.name = "pinctrl",
}
//At the same time, it is stored in the section_ u_ boot_ list_ 2_ uclass_ 2_ In pinctrl, that is, the content of the section section
  • deposit

From the above structure, its definition is stored in the segment_ u_ boot_ list_ 2_ uclass_ 2_ In pinctrl, where can I see it?

In u-boot Search in the map file_ u_boot_list_2_uclass_2_pinctrl, you can find all the drivers defined in the program.

I believe you will have questions here. Why is UCLASS_ What about 2? Let's take a look, and we'll also see uclass_1 and uclass_3. What do these two represent? Look down!

  • Related API

Want to get uclass_driver needs to get UCLASS first_ driver table.

struct uclass_driver *uclass =
        ll_entry_start(struct uclass_driver, uclass); 
// Will be based on u_boot_list_2_uclass_1 segment address to get UCLASS_ Address of driver table

    const int n_ents = ll_entry_count(struct uclass_driver, uclass);
// Get UCLASS_ Length of driver table

struct uclass_driver *lists_uclass_lookup(enum uclass_id id)
// From UCLASS_ Obtain UCLASS with uclass id in driver table_ driver. 

As the notes describe, the UCLASS mentioned above_ 1 and uclass_3 plays a positioning role and is used to calculate UCLASS_ Length of 2!

The above API is mainly used according to uclass_id to find the corresponding uclass_driver, and then operate the udevice under the corresponding uclass.

 

④ uclass_id

We're in uclass_ In the driver, you see a uclass_id type. What is the relationship between this type and uclass?

We know that uclass represents a category driven by, uclass_driver is the driver of uclass and provides a unified operation interface for uclass. For different types of drivers, uclass is required_ ID to distinguish!

In fact, each type of device ` ` uclass has a unique corresponding uclass_id, which runs through the device model, is also the key point of udevice's association with uclass'.

enum uclass_id {
	/* These are used internally by driver model */
	UCLASS_ROOT = 0,
	UCLASS_DEMO,
	UCLASS_TEST,
	UCLASS_TEST_FDT,
	UCLASS_TEST_BUS,
	UCLASS_TEST_PROBE,
......
	/* U-Boot uclasses start here - in alphabetical order */
	UCLASS_ACPI_PMC,	/* (x86) Power-management controller (PMC) */
	UCLASS_ADC,		/* Analog-to-digital converter */
	UCLASS_AHCI,		/* SATA disk controller */
	UCLASS_AUDIO_CODEC,	/* Audio codec with control and data path */
	UCLASS_AXI,		/* AXI bus */
	UCLASS_BLK,		/* Block device */
	UCLASS_BOARD,		/* Device information from hardware */
......
};

Here, we regard it as a sign of equipment identification!

Finally, the two structures of the pressure shaft come out, which is also the final operation object of the DM model.

 

⑤ udevice

/**
 * struct udevice - An instance of a driver
 *
 * This holds information about a device, which is a driver bound to a
 * particular port or peripheral (essentially a driver instance).
 *
 */
struct udevice {
	const struct driver *driver;		//driver corresponding to device
	const char *name;					//The name of the device
	void *platdata;
	void *parent_platdata;
	void *uclass_platdata;
	ofnode node;						//Device tree node
	ulong driver_data;
	struct udevice *parent;				//Parent device
	void *priv;							// Pointer to private data
	struct uclass *uclass;				//The uclass to which the driver belongs
	void *uclass_priv;
	void *parent_priv;
	struct list_head uclass_node;
	struct list_head child_head;
	struct list_head sibling_node;
	uint32_t flags;
	int req_seq;
	int seq;
#ifdef CONFIG_DEVRES
	struct list_head devres_head;
#endif
};
  • definition

    • * hard encoding: * code called U_BOOT_DEVICE macro to define device resources, which is actually a device instance.
    • **Device tree: * * write the device description information in the corresponding DTS file, then compile it into DTB, and finally generate it dynamically after uboot parses the device tree.
    • Parameter transfer mode: it is very flexible to transfer equipment resource information through the command line or interface.

 

  • deposit
    udevice is the most basic equipment unit. We regard it as an independent individual. All operations of the upper layer are ultimately related to the structure.

After we create a device, in order to obey the unified management, the structure will be connected to the DM model and incorporated into the mechanism. So where will udevice be connected?

  • Connect udevice to the corresponding uclass, which is mainly used to manage the same kind of drivers
  • In addition, udevice with parent-child relationship will also be connected to udevice - > child_ Head linked list, easy to call

It can be roughly understood as follows:

  • Related API
#define uclass_foreach_dev(pos, uc) \
    list_for_each_entry(pos, &uc->dev_head, uclass_node)

#define uclass_foreach_dev_safe(pos, next, uc)  \
    list_for_each_entry_safe(pos, next, &uc->dev_head, uclass_node)

int uclass_get_device(enum uclass_id id, int index, struct udevice **devp); // Get udevice from uclass by index
int uclass_get_device_by_name(enum uclass_id id, const char *name, // Get udevice from uclass by device name
                  struct udevice **devp);
int uclass_get_device_by_seq(enum uclass_id id, int seq, struct udevice **devp);
int uclass_get_device_by_of_offset(enum uclass_id id, int node,
                   struct udevice **devp);
int uclass_get_device_by_phandle(enum uclass_id id, struct udevice *parent,
                 const char *name, struct udevice **devp);
int uclass_first_device(enum uclass_id id, struct udevice **devp);
int uclass_first_device_err(enum uclass_id id, struct udevice **devp);
int uclass_next_device(struct udevice **devp);
int uclass_resolve_seq(struct udevice *dev);

The main function of these related API s is based on uclass_id, find the corresponding uclass, and then find the corresponding udevice according to the index value or name

 

⑥ driver

struct driver {
	char *name;							//Drive name
	enum uclass_id id;					//Drive the corresponding uclass_id	
	const struct udevice_id *of_match;	//Matching function
	int (*bind)(struct udevice *dev);	//Binding function
	int (*probe)(struct udevice *dev);	//Register function
	int (*remove)(struct udevice *dev);
	int (*unbind)(struct udevice *dev);
	int (*ofdata_to_platdata)(struct udevice *dev);
	int (*child_post_bind)(struct udevice *dev);
	int (*child_pre_probe)(struct udevice *dev);
	int (*child_post_remove)(struct udevice *dev);
	int priv_auto_alloc_size;
	int platdata_auto_alloc_size;
	int per_child_auto_alloc_size;
	int per_child_platdata_auto_alloc_size;
	const void *ops;	/* driver-specific operations */
	uint32_t flags;
#if CONFIG_IS_ENABLED(ACPIGEN)
	struct acpi_ops *acpi_ops;
#endif
};
  • definition

driver object, mainly through U_BOOT_DRIVER to define

Take pinctrl as an example

U_BOOT_DRIVER(xxx_pinctrl) = {
	.name		= "xxx_pinctrl",
	.id		= UCLASS_PINCTRL,
	.of_match	= arobot_pinctrl_match,
	.priv_auto_alloc_size = sizeof(struct xxx_pinctrl),
	.ops		= &arobot_pinctrl_ops,
	.probe		= arobot_v2s_pinctrl_probe,
	.remove 	= arobot_v2s_pinctrl_remove,
};
/* Declare a new U-Boot driver */
#define U_BOOT_DRIVER(__name)						\
	ll_entry_declare(struct driver, __name, driver)


#define ll_entry_declare(_type, _name, _list)				\
	_type _u_boot_list_2_##_list##_2_##_name __aligned(4)		\
			__attribute__((unused,				\
			section(".u_boot_list_2_"#_list"_2_"#_name)))

Through the above definition, the final structure we define is as follows:

struct driver _u_boot_list_2_driver_2_xxx_pinctrl = {
	.name		= "xxx_pinctrl",
	.id		= UCLASS_PINCTRL,
	.of_match	= arobot_pinctrl_match,
	.priv_auto_alloc_size = sizeof(struct xxx_pinctrl),
	.ops		= &arobot_pinctrl_ops,
	.probe		= arobot_v2s_pinctrl_probe,
	.remove 	= arobot_v2s_pinctrl_remove,
}
//At the same time, it is stored in the section_ u_ boot_ list_ 2_ driver_ 2_ xxx_ In pinctrl
  • deposit

From the above structure, its definition is stored in the segment_ u_boot_list_2_driver_2_xxx, where can I see it?

In u-boot Search in the map file_ u_boot_list_2_driver, you can find all drivers defined in the program.

Finally, all driver structures are placed in a list u_boot_list_2_driver_1 and u_ boot_ list_ 2_ driver_ In the interval of 3.

  • Related API
 
/*Get the driver table first*/
struct driver *drv =
        ll_entry_start(struct driver, driver);		// Will be based on u_boot_list_2_driver_1 segment address to get UCLASS_ Address of driver table
  const int n_ents = ll_entry_count(struct driver, driver);		// Pass u_ boot_ list_ 2_ driver_ Subtract from the segment address of 3 u_ boot_ list_ 2_ driver_ Obtain the length of the driver table from the segment address of 1

/*Traverse all driver s*/
struct driver *lists_driver_lookup_name(const char *name)	// Get the driver with name from the driver table.

As described in the note, the driver mentioned above_ 1 and driver_3 plays a positioning role and is used to calculate the driver_ Length of 2!

The above API is mainly used to find the corresponding driver driver by name.

To sum up, the data structure related to DM model is introduced, and the overall design architecture is as follows:

Just like the red line part, how to implement the binding of driver and udevice, uclass and uclass_ What about the binding of driver?

To really understand this, we have to go deep into the initialization process of DM.

 
 

3.5 God's perspective of DM driven model

For DM model, we observe how the whole model framework is from the perspective of God!

From the perspective of object design, the driving model of Uboot can be divided into static form and dynamic form.

  • Static mode: the object is discrete and separated from other objects, which reduces the complexity of the object and is conducive to modular design.

  • Dynamic mode: the object in the running state expression is to combine all static objects into a hierarchical view with a clear data association view

In the static mode, the driver model mainly divides the objects into udevice and driver, that is, device and driver. The two are like two tracks of a train and will never intersect. The driver and device can register as many as they want.

Let's take a look at the description of udevice:

/**
 * struct udevice - An instance of a driver
 *
 * This holds information about a device, which is a driver bound to a
 * particular port or peripheral (essentially a driver instance).
 *
 */

udevice is an example of a driver. Two disjoint tracks want love after all. So how to make it intersect? This is what dynamic mode needs to do!

**In dynamic mode, * * uclass and uclass are introduced_ Driver has two data structures, which realize the management of udevice and driver.

Look at uclass and uclass_ Description of two structures of driver:

/**
 * struct uclass - a U-Boot drive class, collecting together similar drivers
 *
 */


/**
 * struct uclass_driver - Driver for the uclass
 *
 * A uclass_driver provides a consistent interface to a set of related
 * drivers.
 *
 */
  • uclass: device group public attribute object. As an attribute of udevice, it is mainly used to manage all devices of a driver class.
  • uclass_driver: the public behavior object of the device group, the driver of UCLASS, which is mainly used to bind, register and remove the devices and drivers managed by UCLASS.

Through the introduction of these two structures, uncorrelated udevice driver s can be associated!

Binding of udevice and driver: through the of of driver_ Match and compatible attributes to pair and bind.

Binding between udevice and uclass: uclass under driver in udevice_ ID to the uclass corresponding to uclass_driver's uclass_ Match ID.

uclass and uclass_ Binding of driver: known uclass under driver in udevice_ ID, while creating uclass, use ` ` uclass_ Find the corresponding uclass ID_ Driver object, and then uclass_driver is bound to uclass'!

The overall structure is as follows:

 

3.6 DM model - Udevice and driver binding

I believe that after reading the overall architecture of DM from the perspective of God, everyone has a certain understanding of DM framework. Let's take a look at the specific implementation details!

The initialization of DM is divided into two parts. One is the initialization before relocate redirection: initf_dm, one is initialization after relocate redirection: initr_dm.

We compare the two functions:

static int initf_dm(void)
{
#if defined(CONFIG_DM) && CONFIG_VAL(SYS_MALLOC_F_LEN)
	int ret;

	bootstage_start(BOOTSTAGE_ID_ACCUM_DM_F, "dm_f");
	ret = dm_init_and_scan(true);					//This is true
	bootstage_accum(BOOTSTAGE_ID_ACCUM_DM_F);
	if (ret)
		return ret;
#endif
#ifdef CONFIG_TIMER_EARLY
	ret = dm_timer_init();
	if (ret)
		return ret;
#endif

	return 0;
}

static int initr_dm(void)
{
	int ret;

	/* Save the pre-reloc driver model and start a new one */
	gd->dm_root_f = gd->dm_root;
	gd->dm_root = NULL;
#ifdef CONFIG_TIMER
	gd->timer = NULL;
#endif
	bootstage_start(BOOTSTAGE_ID_ACCUM_DM_R, "dm_r");
	ret = dm_init_and_scan(false);						//false here
	bootstage_accum(BOOTSTAGE_ID_ACCUM_DM_R);
	if (ret)
		return ret;

	return 0;
}

Both call dm_init_and_scan is an interface. The key difference between the two is the difference of parameters.

  • First, explain the "u-boot, DM pre reloc" attribute in the dts node. When this attribute is set, it means that the device needs to be used before relocate.
  • When DM_ init_ and_ When the parameter of scan is true, only nodes with "u-boot, DM pre reloc" attribute will be parsed. When the parameter is false, all nodes will be resolved.

The general steps of DM initialization are as follows:

The above program execution flow chart, let's explain several functions in detail.

 

① dm_init

int dm_init(bool of_live)
{
	int ret;

	if (gd->dm_root) {
		dm_warn("Virtual root driver already exists!\n");
		return -EINVAL;
	}
	INIT_LIST_HEAD(&DM_UCLASS_ROOT_NON_CONST);

#if defined(CONFIG_NEEDS_MANUAL_RELOC)
	fix_drivers();
	fix_uclass();
	fix_devices();
#endif

	ret = device_bind_by_name(NULL, false, &root_info, &DM_ROOT_NON_CONST);		//Find root_driver driver and bind
	if (ret)
		return ret;
#if CONFIG_IS_ENABLED(OF_CONTROL)
# if CONFIG_IS_ENABLED(OF_LIVE)
	if (of_live)
		DM_ROOT_NON_CONST->node = np_to_ofnode(gd->of_root);
	else
#endif
		DM_ROOT_NON_CONST->node = offset_to_ofnode(0);
#endif
	ret = device_probe(DM_ROOT_NON_CONST);										//probe activate root_driver drive
	if (ret)
		return ret;

	return 0;
}

dm_ The name of init is misleading. This function mainly initializes the root device root_driver, according to this device, initializes global_ DM in data_ root,uclass_root.

 

② lists_bind_fdt

We usually use the device tree to define various devices, so this function is the protagonist.

This function is mainly used to find sub devices, and then find the corresponding driver to bind according to the found sub devices! That is, the binding of driver and device is implemented.

int lists_bind_fdt(struct udevice *parent, ofnode node, struct udevice **devp,
		   bool pre_reloc_only)
{
	struct driver *driver = ll_entry_start(struct driver, driver);				//Get the starting address of the driver list
	const int n_ents = ll_entry_count(struct driver, driver);					//Get the total number of driver lists
	const struct udevice_id *id;
	struct driver *entry;
	struct udevice *dev;
	bool found = false;
	const char *name, *compat_list, *compat;
	int compat_length, i;
	int result = 0;
	int ret = 0;

	if (devp)
		*devp = NULL;
	name = ofnode_get_name(node);
	log_debug("bind node %s\n", name);

	compat_list = ofnode_get_property(node, "compatible", &compat_length);		//Get the compatible attribute to match the driver driver
	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;
	}

	/*
	 * Walk through the compatible string list, attempting to match each
	 * compatible string in order such that we match in order of priority
	 * from the first string to the last.
	 */
	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++) {				//Cycle to determine whether all drives match	
			ret = driver_check_compatible(entry->of_match, &id,
						      compat);
			if (!ret)
				break;
		}
		if (entry == driver + n_ents)
			continue;

		if (pre_reloc_only) {
			if (!ofnode_pre_reloc(node) &&
			    !(entry->flags & DM_FLAG_PRE_RELOC)) {
				log_debug("Skipping device pre-relocation\n");
				return 0;
			}
		}

		log_debug("   - found match at '%s': '%s' matches '%s'\n",
			  entry->name, entry->of_match->compatible,
			  id->compatible);
		ret = device_bind_with_driver_data(parent, entry, name,
						   id->data, node, &dev);								//This function is used to create a udevice object and bind it to the found driver
		if (ret == -ENODEV) {
			log_debug("Driver '%s' refuses to bind\n", entry->name);
			continue;
		}
		if (ret) {
			dm_warn("Error binding driver '%s': %d\n", entry->name,
				ret);
			return ret;
		} else {
			found = true;
			if (devp)
				*devp = dev;
		}
		break;
	}

	if (!found && !result && ret != -ENODEV)
		log_debug("No match for node '%s'\n", name);

	return result;
}

lists_bind_fdt function is mainly used to scan each node in the device tree;

According to the scanned udevice device information, match the same compatible driver through compatible. After the matching is successful, the corresponding struct udevice structure will be created, which will point to the device resource and driver at the same time, so that the device resource and driver are bound together.

 
 

3.7 DM model - execution of probe detection function

As mentioned above, the initialization of DM model is completed, but we only established the binding relationship between driver and udevice. When will we call the probe probe function in our driver? When did uclass and driver match?

What about the above, dm_init is only responsible for initializing and binding udevice and driver. Of course, the probe probe function is executed when the driver is initialized!

Take mmc drive as an example below! The initialization process is as follows:

The detailed code will not be expanded here!

After MMC driver initialization, did you notice MMC_ The probe function indirectly calls the probe function written by our driver.

The execution process is clear above: according to uclass_id, call ` ` uclass_get_device_by_seq to get udevice, and then call device_probe to find the probe corresponding to the driver.

int device_probe(struct udevice *dev)
{
	const struct driver *drv;
	int ret;
	int seq;

	if (!dev)
		return -EINVAL;

	if (dev->flags & DM_FLAG_ACTIVATED)
		return 0;

	drv = dev->driver;													//Get driver
	assert(drv);

	ret = device_ofdata_to_platdata(dev);
	if (ret)
		goto fail;

	/* Ensure all parents are probed */
	if (dev->parent) {													//Parent device probe
		ret = device_probe(dev->parent);
		if (ret)
			goto fail;

		/*
		 * The device might have already been probed during
		 * the call to device_probe() on its parent device
		 * (e.g. PCI bridge devices). Test the flags again
		 * so that we don't mess up the device.
		 */
		if (dev->flags & DM_FLAG_ACTIVATED)
			return 0;
	}

	seq = uclass_resolve_seq(dev);
	if (seq < 0) {
		ret = seq;
		goto fail;
	}
	dev->seq = seq;

	dev->flags |= DM_FLAG_ACTIVATED;

	/*
	 * Process pinctrl for everything except the root device, and
	 * continue regardless of the result of pinctrl. Don't process pinctrl
	 * settings for pinctrl devices since the device may not yet be
	 * probed.
	 */
	if (dev->parent && device_get_uclass_id(dev) != UCLASS_PINCTRL)
		pinctrl_select_state(dev, "default");

	if (CONFIG_IS_ENABLED(POWER_DOMAIN) && dev->parent &&
	    (device_get_uclass_id(dev) != UCLASS_POWER_DOMAIN) &&
	    !(drv->flags & DM_FLAG_DEFAULT_PD_CTRL_OFF)) {
		ret = dev_power_domain_on(dev);
		if (ret)
			goto fail;
	}

	ret = uclass_pre_probe_device(dev);
	if (ret)
		goto fail;

	if (dev->parent && dev->parent->driver->child_pre_probe) {
		ret = dev->parent->driver->child_pre_probe(dev);
		if (ret)
			goto fail;
	}

	/* Only handle devices that have a valid ofnode */
	if (dev_of_valid(dev)) {
		/*
		 * Process 'assigned-{clocks/clock-parents/clock-rates}'
		 * properties
		 */
		ret = clk_set_defaults(dev, 0);
		if (ret)
			goto fail;
	}

	if (drv->probe) {												
		ret = drv->probe(dev);										//Call driven probe
		if (ret)
			goto fail;
	}

	ret = uclass_post_probe_device(dev);
	if (ret)
		goto fail_uclass;

	if (dev->parent && device_get_uclass_id(dev) == UCLASS_PINCTRL)
		pinctrl_select_state(dev, "default");

	return 0;
fail_uclass:
	if (device_remove(dev, DM_REMOVE_NORMAL)) {
		dm_warn("%s: Device '%s' failed to remove on error path\n",
			__func__, dev->name);
	}
fail:
	dev->flags &= ~DM_FLAG_ACTIVATED;

	dev->seq = -1;
	device_free(dev);

	return ret;
}

The main work is summarized as follows:

  • Get driver according to udevice
  • Then judge whether the parent device is probe
  • probe the parent device
  • Call the probe function of the driver

 
 

3.8 DM model - uclass and uclass_driver binding

The above has completed the probe function call of the driver, and the basic bottom layer is ready. When is uclass related to uclass_driver binding to provide a unified API for the upper layer?

uclass and uclass_driver binding is also to ensure that the driver exists and the device exists after driving the probe, and finally bind uclass and uclass for the driver_ Driver, which provides a unified interface for the upper layer.

Take MMC drive as an example

Go back to the drive flow chart above and see MMC_ do_ The Preinit function? RET = uclass is called inside_ get(uclass_MMC, &uc);, This function is the real combination of uclass and uclass_driver binding.

int uclass_get(enum uclass_id id, struct uclass **ucp)
{
	struct uclass *uc;

	*ucp = NULL;
	uc = uclass_find(id);
	if (!uc)
		return uclass_add(id, ucp);
	*ucp = uc;

	return 0;
}

uclass_get mainly implements: according to uclass_id to find out whether the corresponding UCLASS is added to global_ data->uclass_ If it is not added to the root linked list, call uclass_add function to realize UCLASS and uclass_driver and add it to global_ data->uclass_ In the root linked list.

static int uclass_add(enum uclass_id id, struct uclass **ucp)
{
	struct uclass_driver *uc_drv;
	struct uclass *uc;
	int ret;

	*ucp = NULL;
	uc_drv = lists_uclass_lookup(id);					//According to UCLASS_ Find the corresponding driver ID
	if (!uc_drv) {
		debug("Cannot find uclass for id %d: please add the UCLASS_DRIVER() declaration for this UCLASS_... id\n",
		      id);
		/*
		 * Use a strange error to make this case easier to find. When
		 * a uclass is not available it can prevent driver model from
		 * starting up and this failure is otherwise hard to debug.
		 */
		return -EPFNOSUPPORT;
	}
	uc = calloc(1, sizeof(*uc));
	if (!uc)
		return -ENOMEM;
	if (uc_drv->priv_auto_alloc_size) {
		uc->priv = calloc(1, uc_drv->priv_auto_alloc_size);
		if (!uc->priv) {
			ret = -ENOMEM;
			goto fail_mem;
		}
	}
	uc->uc_drv = uc_drv;												//uclass and uclass_driver binding
	INIT_LIST_HEAD(&uc->sibling_node);
	INIT_LIST_HEAD(&uc->dev_head);
	list_add(&uc->sibling_node, &DM_UCLASS_ROOT_NON_CONST);				//Add to global_ data->uclass_ Root linked list

	if (uc_drv->init) {
		ret = uc_drv->init(uc);
		if (ret)
			goto fail;
	}

	*ucp = uc;

	return 0;
fail:
	if (uc_drv->priv_auto_alloc_size) {
		free(uc->priv);
		uc->priv = NULL;
	}
	list_del(&uc->sibling_node);
fail_mem:
	free(uc);

	return ret;
}

Well, I've basically sorted out all the DM models of Uboot here. It takes a week. I always feel that it's not easy to explain it by myself!

If it helps you, remember to praise it!
 
 

3.9 reference documents

[1] : https://www.dazhuanlan.com/archevalier/topics/1323360

[2] : https://blog.csdn.net/ooonebook/article/details/53234020

Topics: Linux uboot U-boot