[uboot] (番外) uboot driver model

It is recommended to first read "[uboot] (Fan outside) uboot fdt introduction", to understand the function of uboot fdt, will be used in the driver model.

==============================================================================================================

First, the description

1, uboot's drive model is briefly introduced

Uboot introduces a driver model that provides a unified approach to driver definition and access interfaces. Improved compatibility between drivers and standard types of access. The uboot driver model is similar to the device driver model in the kernel, but it differs. In the following we will simply refer to the driver model as DM. In fact, it is also referred to as uboot.

Specific details are recommended. /doc/driver-model/README.txt

2, how to enable uboot DM function

(1)The configuration CONFIG_DM is defined in configs/tiny210_defconfig as follows:

CONFIG_DM=y
  • 1

(2)Enable config of the corresponding uclass driver. DM and uclass are closely related. If we want to introduce DM in a module, we need to use the uclass driver of the corresponding module instead of the old universal driver. We will continue to explain on uclass. Taking serial as an example, in order to introduce DM in serial, I opened the CONFIG_DM_SERIAL macro in configs/tiny210_defconfig0 as follows

CONFIG_DM_SERIAL=y

Look at driver/serial/Makefile

Ifdef CONFIG_DM_SERIAL
Obj-y += serial-uclass.o ## Introducing dm's serial core driver
Else
Obj-y += serial.o ## Universal serial core driver
Endif
## It can be found that the driver code of the compiled serial core is different.

(3)Corresponding device drivers should also introduce dm functionsThe device drivers mainly implement interaction with the underlying layer and provide interfaces for the uclass layer. Follow-up details.

__ Follow-up is explained by serial-uclass

Second, uboot DM overall architecture

1, the four components of DM

Uboot DM has four main components

  • UdeviceSimple refers to a device object, which can be understood as a device in the kernel.
  • DriverThe driver of udevice can be understood as device_driver in kernel. Communicates with the underlying hardware devices and provides the device with an upper-facing interface.
  • UclassFirst look at the description of uclass in README.txt:
Uclass - a group of devices which operate in the same way. A uclass provides
        a way of accessing individual devices within the group, but always
        using the same interface. For example a GPIO uclass provides
        operations for get/set value. An I2C uclass may have 10 I2C ports,
        4 with one driver, and 6 with another.

Uclass, a group of devices that use the same set of operations. Equivalent to an abstraction. Uclass provides a uniform interface for devices that use the same interface. For example, the GPIO uclass provides a get/set interface. For another example, there may be 10 I2C ports under one I2C uclass, 4 using one driver, and 6 using another driver.

  • Uclass_driver Corresponds to the uclass driver. Mainly provide uclass operations, such as some operations when binding udevice.

2, call the relationship framework diagram

DM下的接口调用流程

3, the relationship between each other

Take a look at the above picture:

  • The upper interfaces are directly communicating with the uclass interface.
  • Uclass can be understood as some interfaces with the same attributes of udevice external operations. The uclass driver is uclass_driver, which mainly provides interfaces for the upper layer.
  • Udevice refers to the abstraction of the specific device, the corresponding driver is the driver, the driver is mainly responsible for communication with the hardware, and provides the actual operation set for the uclass.
  • The way udevice finds the corresponding uclass is mainly through: whether the id of the driver corresponding to udevice matches the id of uclass_driver corresponding to uclass.
  • Udevice will be bound to the uclass. The driver will be bound to udevice. Uclass_driver will be bound to uclass.

Here is a brief introduction: uclass and udevice are dynamically generated. When parsing devices in fdt, udevice is dynamically generated. Then find the driver corresponding to udevice, get the uclass_driver id through the uclass id in the driver. Finds whether the corresponding uclass has been generated from the uclass list, and generates a uclass dynamically if it is not generated.

4, GD and DM related parts

Typedef struct global_data {
#ifdef CONFIG_DM
    Struct udevice *dm_root; /* Root instance for Driver Model */
// The root device in the DM is also the first udevice created in uboot, which corresponds to the root node in dts.
    Struct udevice *dm_root_f; /* Pre-relocation root instance */
// root device in DM before relocation
    Struct list_head uclass_root; /* Head of core tree */
// uclass list, all uclasses that are matched by udevice will be mounted on this list
#endif
} gd_t;

Third, the DM four main components are detailed

Followed by the data structure, how to define, storage location, how to get four parts for explanation

0、uclass id

Each uclass has its own corresponding ID number. Defined in its uclass_driver. The uclass id in the driver of the attached udevice must be the same. All uclass ids are defined in include/dm/uclass-id.h List some of the ids below

enum uclass_id {
    /* These are used internally by driver model */
    UCLASS_ROOT = 0,
    UCLASS_DEMO,
    UCLASS_CLK,     /* Clock source, e.g. used by peripherals */
    UCLASS_PINCTRL,     /* Pinctrl (pin muxing/configuration) device */
    UCLASS_SERIAL,      /* Serial UART */
}

1、uclass

  • (1) Data structure
Struct uclass {
    Void *priv; // private data pointer for uclass
    Struct uclass_driver *uc_drv; // corresponding uclass driver
    Struct list_head dev_head; // chain header, all udevices to which the connection belongs
    Struct list_head sibling_node; // linked list node, used to connect uclass to uclass_root list
};
  • (2) How to define uclass is automatically generated by uboot. And not all uclass will be generated, there is a corresponding uclass driver and there is a uclass that is matched by udevice will be generated. Refer to the Uboot DM Initialization section below for details. Or refer to uclass_add implementation.

  • (3) Storage locationAll generated uclasses will be mounted on the gd->uclass_root list.

  • (4) How to get, API directly traverse the linked list gd->uclass_root list and get the corresponding uclass according to the uclass id. The specific uclass_get-"uclass_find implements this function. There are the following APIs:

Int uclass_get(enum uclass_id key, struct uclass **ucp);
// Get the corresponding uclass from the gd->uclass_root list

2、uclass_driver

  • (1) Data structure include/dm/uclass.h
Struct uclass_driver {
    Const char *name; // the command of the uclass_driver
    Enum uclass_id id; // corresponding uclass id
/* The following function pointers are mainly the difference between the call timings */
    Int (*post_bind)(struct udevice *dev); // Called after udevice is bound to the uclass
    Int (*pre_unbind)(struct udevice *dev); // Called before udevice is unbundled out of the uclass
    Int (*pre_probe)(struct udevice *dev); // Called before a udevice of the uclass is probed
    Int (*post_probe)(struct udevice *dev); // Called after a udevice of the uclass is executed by the probe
    Int (*pre_remove)(struct udevice *dev);// Called before a udevice of the uclass is removed
    Int (*child_post_bind)(struct udevice *dev); // Called after a child of a udevice of the uclass is bound to the udevice
    Int (*child_pre_probe)(struct udevice *dev); // Called before a probe of a udevice of the uclass is executed by the probe
    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 for 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; //action collection
    Uint32_t flags; // is identified as
};
  • (2) How to define UTYPE_DRIVER is used to define uclass_driver. with serial-uclass as an example
UCLASS_DRIVER(serial) = {
    .id        = UCLASS_SERIAL,
    .name        = "serial",
    .flags        = DM_UC_FLAG_SEQ_ALIAS,   
    .post_probe    = serial_post_probe,
    .pre_remove    = serial_pre_remove,
    .per_device_auto_alloc_size = sizeof(struct serial_dev_priv),
};

UCLASS_DRIVER is implemented as follows:

#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)))
About ll_entry_declare We have already introduced in [uboot] (Chapter 6) uboot Process - Command Line Mode and Command Processing Introduction

Finally get a structure below

Struct uclass_driver _u_boot_list_2_uclass_2_serial = {
    .id = UCLASS_SERIAL, // set the corresponding uclass id
    .name = "serial",
    .flags = DM_UC_FLAG_SEQ_ALIAS,
    .post_probe = serial_post_probe,
    .pre_remove = serial_pre_remove,
    .per_device_auto_alloc_size = sizeof(struct serial_dev_priv),
}

And stored in the .u_boot_list_2_uclass_2_serial section.

  • (3) Storage location Through the above, we know that the serial uclass_driver structure _u_boot_list_2_uclass_2_serial is stored in the .u_boot_list_2_uclass_2_serial section. Obtain the following by looking at u-boot.map
.u_boot_list_2_uclass_1
                0x23e368e0 0x0 drivers/built-in.o
.u_boot_list_2_uclass_2_gpio
                0x23e368e0 0x48 drivers/gpio/built-in.o
                0x23e368e0 _u_boot_list_2_uclass_2_gpio // Symbol of gpio uclass driver
.u_boot_list_2_uclass_2_root
                0x23e36928 0x48 drivers/built-in.o
                0x23e36928 _u_boot_list_2_uclass_2_root // root uclass symbol of drvier
.u_boot_list_2_uclass_2_serial
                0x23e36970 0x48 drivers/serial/built-in.o
                0x23e36970 _u_boot_list_2_uclass_2_serial // Serial uclass driver symbol
.u_boot_list_2_uclass_2_simple_bus
                0x23e369b8 0x48 drivers/built-in.o
                0x23e369b8 _u_boot_list_2_uclass_2_simple_bus
.u_boot_list_2_uclass_3
                0x23e36a00 0x0 drivers/built-in.o
                0x23e36a00 . = ALIGN (0x4)

Finally, all uclass driver structures are placed in the range of .u_boot_list_2_uclass_1 and .u_boot_list_2_uclass_3 in the form of a list. This list is referred to as uclass_driver table.

  • (4) How to get, APITo get uclass_driver, you need to get uclass_driver table first. Uclass_driver table can be obtained by the following macro
Struct uclass_driver *uclass =
        Ll_entry_start(struct uclass_driver, uclass);
// will get the address of uclass_driver table according to the segment address of .u_boot_list_2_uclass_1

    Const int n_ents = ll_entry_count(struct uclass_driver, uclass);
/ / Get the length of the uclass_driver table

Then by traversing this uclass_driver table, the corresponding uclass_driver is obtained. Have the following API

Struct uclass_driver *lists_uclass_lookup(enum uclass_id id)
// Get uclass_driver with uclass id id from uclass_driver table.

3、udevice

  • (1) Data structureinclude/dm/device.h
Struct udevice {
    Const struct driver *driver; // The driver corresponding to the udevice
    Const char *name; // device name
    Void *platdata; // platform data for the udevice
    Void *parent_platdata; // platform data provided to the parent device
    Void *uclass_platdata; // Provide platform data for the used uclass
    Int of_offset; // The dtb node offset of the udevice represents the node inside the dtb node
    Ulong driver_data; // drive data
    Struct udevice *parent; // parent device
    Void *priv; // pointer to private data
    Struct uclass *uclass; // own uclass
    Void *uclass_priv; // the private data pointer provided to the own uclass
    Void *parent_priv; // private data pointer supplied to its parent device
    Struct list_head uclass_node; // used to connect to the linked list of its own uclass
    Struct list_head child_head; // linked list header, connected to its child device
    Struct list_head sibling_node; // used to connect to the linked list of its parent device
    Uint32_t flags; // identifier
    Int req_seq;
    Int seq;
#ifdef CONFIG_DEVRES
    Struct list_head devres_head;
#endif
};
  • (2) How to define In the case where dtb exists, the dtb is dynamically generated by uboot, and is subsequently described in the section "Initialization of uboot DM".

  • (3) Storage location

    • Connect to the corresponding uclass That is, it will connect to uclass->dev_head
    • Connect to the parent device's child device list That is, it will connect to udevice->child_head, and the final root device is the root device gd->dm_root.
  • (4) How to get, API

    • Get udevice from uclass Traverse uclass->dev_head to get the corresponding udevice. Have the following 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 through 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);

4、driver

It is similar to the uclass_driver method.

  • (1) Data structureinclude/dm/device.h
Struct driver {
    Char *name; // driver name
    Enum uclass_id id; // corresponding uclass id
    Const struct udevice_id *of_match; // matching table of compatible string, used to match the device node in the device tree
    Int (*bind)(struct udevice *dev); // used to bind the target device to the driver
    Int (*probe)(struct udevice *dev); // for probe target device, active
    Int (*remove)(struct udevice *dev); // Used to remove the target device. Disable
    Int (*unbind)(struct udevice *dev); // used to unbind the target device into the driver
    Int (*ofdata_to_platdata)(struct udevice *dev); // Parse the dts node corresponding to udevice and convert it to platform data of udevice before probe
    Int (*child_post_bind)(struct udevice *dev); // If the child device of the target device is bound, call
    Int (*child_pre_probe)(struct udevice *dev); // Called before a child of the target device is probed
    Int (*child_post_remove)(struct udevice *dev); // Called after a child device of the target device is removed
    Int priv_auto_alloc_size; //How much space needs to be allocated as private data for its udevice
    Int platdata_auto_alloc_size; / / How much space needs to be allocated as platform data for its udevice
    Int per_child_auto_alloc_size; // How much space is needed for each child device of the target device as private data for the parent device
    Int per_child_platdata_auto_alloc_size; // How much space needs to be allocated for each child device of the target device as the platform data of the parent device
    Const void *ops; /* driver-specific operations */ // The pointer to the operation set, provided to the uclass, without specifying the format of the operation set, determined by the specific uclass
    Uint32_t flags; // some flag bits
};
  • (2) How to define Define a driver by U_BOOT_DRIVER with s5pv210 as an example: Driver/serial/serial_s5p.c
U_BOOT_DRIVER(serial_s5p) = {
    .name    = "serial_s5p",
    .id    = UCLASS_SERIAL,
    .of_match = s5p_serial_ids,
    .ofdata_to_platdata = s5p_serial_ofdata_to_platdata,
    .platdata_auto_alloc_size = sizeof(struct s5p_serial_platdata),
    .probe = s5p_serial_probe,
    .ops    = &s5p_serial_ops,
    .flags = DM_FLAG_PRE_RELOC,
};

U_BOOT_DRIVER is implemented as follows:

#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)))
About ll_entry_declare We have already introduced in [uboot] (Chapter 6) uboot Process - Command Line Mode and Command Processing Introduction

Finally get the following structure

struct driver _u_boot_list_2_driver_2_serial_s5p= {
    .name    = "serial_s5p",
    .id    = UCLASS_SERIAL,
    .of_match = s5p_serial_ids,
    .ofdata_to_platdata = s5p_serial_ofdata_to_platdata,
    .platdata_auto_alloc_size = sizeof(struct s5p_serial_platdata),
    .probe = s5p_serial_probe,
    .ops    = &s5p_serial_ops,
    .flags = DM_FLAG_PRE_RELOC,
};

And stored in the .u_boot_list_2_driver_2_serial_s5p section

  • (3) Storage location Through the above, we know that the driver structure _u_boot_list_2_driver_2_serial_s5p of serial_s5p is stored in the .u_boot_list_2_driver_2_serial_s5p segment. Obtain the following by looking at u-boot.map
 .u_boot_list_2_driver_1
                0x23e36754        0x0 drivers/built-in.o
.u_boot_list_2_driver_2_gpio_exynos
                0x23e36754       0x44 drivers/gpio/built-in.o
                0x23e36754                _u_boot_list_2_driver_2_gpio_exynos
.u_boot_list_2_driver_2_root_driver
                0x23e36798       0x44 drivers/built-in.o
                0x23e36798                _u_boot_list_2_driver_2_root_driver
.u_boot_list_2_driver_2_serial_s5p
                0x23e367dc       0x44 drivers/serial/built-in.o
                0x23e367dc                _u_boot_list_2_driver_2_serial_s5p
.u_boot_list_2_driver_2_simple_bus_drv
                0x23e36820       0x44 drivers/built-in.o
                0x23e36820                _u_boot_list_2_driver_2_simple_bus_drv
.u_boot_list_2_driver_3
                0x23e36864        0x0 drivers/built-in.o

Finally, all driver structures are placed in the range of .u_boot_list_2_driver_1 and .u_boot_list_2_driver_3 in the form of a list. This list is referred to as the driver table.

  • (4) How to get, APITo get the driver, you need to get the driver table first. The following macro can be used to get the driver table
Struct driver *drv =
        Ll_entry_start(struct driver, driver);
// will get the address of uclass_driver table according to the segment address of .u_boot_list_2_driver_1

    Const int n_ents = ll_entry_count(struct driver, driver);
/ / Get the length of the driver table

Then by traversing this driver table, you get the corresponding driver.

Struct driver *lists_driver_lookup_name(const char *name)
// Get the driver named name from the driver table.

Fourth, some API finishing of DM

Let's take a look at the previous section to understand.

1, uclass related API

Int uclass_get(enum uclass_id key, struct uclass **ucp);
// Get the corresponding uclass from the gd->uclass_root list

2, uclass_driver related API

Struct uclass_driver *lists_uclass_lookup(enum uclass_id id)
// Get uclass_driver with uclass id id from uclass_driver table.

3, udevice 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 device_bind(struct udevice *parent, const struct driver *drv,
        Const char *name, void *platdata, int of_offset,
        Struct udevice **devp)
// Initialize a udevice and bind it to its uclass and driver.

Int device_bind_by_name(struct udevice *parent, bool pre_reloc_only,
            Const struct driver_info *info, struct udevice **devp)
// Get the driver by name and call device_bind to initialize udevice and bind it to its uclass and driver.

Int uclass_bind_device(struct udevice *dev)
/ / Bind udevice to its corresponding uclass device list
{
    Uc = dev->uclass;
    List_add_tail(&dev->uclass_node, &uc->dev_head);
}

Int uclass_get_device(enum uclass_id id, int index, struct udevice **devp); // Obtain udevice from uclass through index. Note that the device will be probed during the acquisition process.
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);

4, driver related API

Struct driver *lists_driver_lookup_name(const char *name)
// Get the driver named name from the driver table.

Five, the expression of uboot equipment

1, description

There are two ways to add devices in uboot.

  • By directly defining the platform device (this way is basically not used)
  • By adding device information in the device tree

Note: This is just a definition of the device, and will eventually be parsed into a udevice structure by uboot.

2, directly define the platform device (this way is basically not used except the root device)

(1) Define by U_BOOT_DEVICE macro or directly define struct driver_info structure (2) U_BOOT_DEVICE macro takes ns16550_serial as an example

U_BOOT_DEVICE(overo_uart) = {
    "ns16550_serial",
    &overo_serial
};

U_BOOT_DEVICE is implemented as follows: Similar to U_BOOT_DRIVER above, it is not explained in detail here.

#define U_BOOT_DEVICE(__name)                       \
    ll_entry_declare(struct driver_info, __name, driver_info)

/* Declare a list of devices. The argument is a driver_info[] array */
#define U_BOOT_DEVICES(__name)                      \
    ll_entry_declare_list(struct driver_info, __name, driver_info)

(3) directly define the struct driver_info structure, taking the root device as an example Uboot will create a root device root as the ancestor of all devices The definition of root is as follows:

static const struct driver_info root_info = {
    .name       = "root_driver",
};

3. Add device information in the device tree.

Add the corresponding device node and information to the corresponding dts file, taking the serial of tiny210 as an example: Arch/arm/dts/s5pv210-tiny210.dts

/dts-v1/;
#include "skeleton.dtsi"
/{
        aliases {
                console = "/[email protected]";
        };  

        [email protected] {
                compatible = "samsung,exynos4210-uart";
                reg = <0xe2900000 0x100>;
                interrupts = <0 51 0>; 
                id = <0>;
        };  
};

The contents of dts are not mentioned here.

Sixth, uboot DM initialization

For some of the FDT APIs that may be used below, please refer to "[uboot] (Fan outside) uboot fdt introduction". For the APIs that may use some DMs below, please refer to the fourth section above.

1, the main work

  • DM initialization

    • Create the udevice of the root device root and store it in gd->dm_root. The root device is actually a virtual device, mainly providing a mount point for other devices in the uboot.
    • Initialize the uclass list gd->uclass_root
  • Analysis of udevice and uclass in DM

    • Udevice creation and uclass creation
    • Udevice and uclass binding
    • Uclass_driver and uclass binding
    • Driver and udevice binding
    • Partial driver function call

2, the entrance instructions

The interface initialized by dm is in dm_init_and_scan. It can be found that initf_dm and subsequent initr_dm before uboot relocate call this function.

Static int initf_dm(void)
{
#if defined(CONFIG_DM) && defined(CONFIG_SYS_MALLOC_F_LEN)
    Int ret;
    Ret = dm_init_and_scan(true); // Call dm_init_and_scan to initialize the DM and parse the device
    If (ret)
        Return ret;
#endif
    Return 0;
}

#ifdef CONFIG_DM
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; // Store the root device before relocate
    Gd->dm_root = NULL;
    Ret = dm_init_and_scan(false); // Call dm_init_and_scan to initialize the DM and parse the device
    If (ret)
        Return ret;
    Return 0;
}
#endif

The main difference is the parameters. First, let's 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 the dm_init_and_scan parameter is true, only nodes with the "u-boot, dm-pre-reloc" attribute are parsed. When the parameter is false, all nodes will be parsed. Since the case of "u-boot, dm-pre-reloc" is relatively small, only the case where the parameter is false is learned here. That is dm_init_and_scan(false); in initr_dm.

2, dm_init_and_scan description

driver/core/root.c

Int dm_init_and_scan(bool pre_reloc_only)
{
    Int ret;

    Ret = dm_init(); // Initialization of DM
    If (ret) {
        Debug("dm_init() failed: %d\n", ret);
        Return ret;
    }
    Ret = dm_scan_platdata(pre_reloc_only); // Parse udevice and uclass from platform device
    If (ret) {
        Debug("dm_scan_platdata() failed: %d\n", ret);
        Return ret;
    }

    If (CONFIG_IS_ENABLED(OF_CONTROL)) {
        Ret = dm_scan_fdt(gd->fdt_blob, pre_reloc_only); // parse udevice and uclass from dtb
        If (ret) {
            Debug("dm_scan_fdt() failed: %d\n", ret);
            Return ret;
        }
    }

    Ret = dm_scan_other(pre_reloc_only);
    If (ret)
        Return ret;

    Return 0;
}

3, DM initialization - dm_init

The corresponding code is as follows: Driver/core/root.c

#define DM_ROOT_NON_CONST (((gd_t *)gd)->dm_root) // The macro defines the root device pointer gd->dm_root
#define DM_UCLASS_ROOT_NON_CONST (((gd_t *)gd)->uclass_root) // The macro defines gd->uclass_root, the list of uclass

Int dm_init(void)
{
    Int ret;

    If (gd->dm_root) {
    // The root device already exists, indicating that the DM has been initialized.
        Dm_warn("Virtual root driver already exists!\n");
        Return -EINVAL;
    }

    INIT_LIST_HEAD(&DM_UCLASS_ROOT_NON_CONST);
        / / Initialize the uclass list

    Ret = device_bind_by_name(NULL, false, &root_info, &DM_ROOT_NON_CONST);
        // DM_ROOT_NON_CONST refers to the root device udevice, and root_info is the device information representing the root device.
        // device_bind_by_name will look for the driver that matches the device information, then create the corresponding udevice and uclass and bind it, and finally put it in DM_ROOT_NON_CONST.
        // device_bind_by_name will be explained later. Here we only need to know that the udevice of the root root device and the corresponding uclass have been created.

    If (ret)
        Return ret;
#if CONFIG_IS_ENABLED(OF_CONTROL)
    DM_ROOT_NON_CONST->of_offset = 0;
#endif
    Ret = device_probe(DM_ROOT_NON_CONST);
        // Perform a probe operation on the root device,
        / / device_probe follow-up instructions

    If (ret)
        Return ret;

    Return 0;
}

Here is the initialization of the completed DM (1) Create the udevice of the root device root and store it in gd->dm_root. (2) Initialize the uclass list gd->uclass_root

4, parse udevice and uclass from the platform device - dm_scan_platdata

jump over.

5, parse udevice and uclass from dtb - dm_scan_fdt

For fdt and some corresponding APIs, please refer to "[uboot] (Fan outside) uboot fdt introduction". The corresponding code is as follows (subsequently we ignore the case of pre_reloc_only=true): Driver/core/root.c

Int dm_scan_fdt(const void *blob, bool pre_reloc_only)
// The argument passed in at this time blob=gd->fdt_blob, pre_reloc_only=0
{
    Return dm_scan_fdt_node(gd->dm_root, blob, 0, pre_reloc_only);
/ / Directly call dm_scan_fdt_node
}

Int dm_scan_fdt_node(struct udevice *parent, const void *blob, int offset,
             Bool pre_reloc_only)
// parameters passed in at this time
// parent=gd->dm_root, indicating that the root device is the parent device to start parsing
// blob=gd->fdt_blob, specifies the corresponding dtb
// offset=0, scanning from the node with offset 0
// pre_reloc_only=0, not just the device before parsing relotion
{
    Int ret = 0, err;

        /* The following steps are equivalent to traversing each dts node and parsing it by calling lists_bind_fdt */

    For (offset = fdt_first_subnode(blob, offset);
        // Get the first child of the node under the offset offset of the blob device tree
         Offset > 0;
         Offset = fdt_next_subnode(blob, offset)) {
               / / Loop to find the next child node
        If (!fdtdec_get_is_enabled(blob, offset)) {
                        // Determine if the node status is disable, if it is, ignore it directly
            Dm_dbg(" - ignoring disabled device\n");
            Continue;
        }
        Err = lists_bind_fdt(parent, blob, offset, NULL);
                / / Analyze the binding of this node, the core of dm_scan_fdt, the following specific analysis
        If (err && !ret) {
            Ret = err;
            Debug("%s: ret=%d\n", fdt_get_name(blob, offset, NULL),
                  Ret);
        }
    }
    Return ret;
}

Lists_bind_fdt is the core for parsing udevice and uclass from dtb. Its specific implementation is as follows: Driver/core/lists.c

Int lists_bind_fdt(struct udevice *parent, const void *blob, int offset,
           Struct udevice **devp)
// parent specifies the parent device, and the dts node of the corresponding device can be obtained through blob and offset, and the corresponding udevice structure is returned by devp.
{
    Struct driver *driver = ll_entry_start(struct driver, driver);
/ / Get the driver table address
    Const int n_ents = ll_entry_count(struct driver, driver);
/ / Get the driver table length
    Const struct udevice_id *id;
    Struct driver *entry;
    Struct udevice *dev;
    Bool found = false;
    Const char *name;
    Int result = 0;
    Int ret = 0;

    Dm_dbg("bind node %s\n", fdt_get_name(blob, offset, NULL));
// print the name of the currently parsed node
    If (devp)
        *devp = NULL;
    For (entry = driver; entry != driver + n_ents; entry++) {
/ / Traverse all the drivers in the driver table, specific reference to the third, fourth section
        Ret = driver_check_compatible(blob, offset, entry->of_match,
                          &id);
/ / Determine whether the compatibile field and the dts node in the driver match
        Name = fdt_get_name(blob, offset, NULL);
/ / Get the node name
        If (ret == -ENOENT) {
            Continue;
        } else if (ret == -ENODEV) {
            Dm_dbg("Device '%s' has no compatible string\n", name);
            Break;
        } else if (ret) {
            Dm_warn("Device tree error at offset %d\n", offset);
            Result = ret;
            Break;
        }

        Dm_dbg(" - found match at '%s'\n", entry->name);
        Ret = device_bind(parent, entry, name, NULL, offset, &dev);
/ / Find the corresponding driver, call device_bind for binding, will create corresponding udevice and uclass in this function and cut and bind, continue to explain later
        If (ret) {
            Dm_warn("Error binding driver '%s': %d\n", entry->name,
                Ret);
            Return ret;
        } else {
            Dev->driver_data = id->data;
            Found = true;
            If (devp)
                *devp = dev;
/ / Set udevice to the location pointed to by devp, return
        }
        Break;
    }

    If (!found && !result && ret != -ENODEV) {
        Dm_dbg("No match for node '%s'\n",
               Fdt_get_name(blob, offset, NULL));
    }

    Return result;
}

The device_bind implements the creation and binding of udevice and uclass and some initialization operations. Here, we will learn about device_bind. The implementation of device_bind is as follows (remove part of the code) Driver/core/device.c

Int device_bind(struct udevice *parent, const struct driver *drv,
        Const char *name, void *platdata, int of_offset,
        Struct udevice **devp)
// parent: parent device
// drv: the driver corresponding to the device
// name: device name
// platdata: platform data pointer for the device
// of_offset: the offset in dtb, which represents its dts node
// devp: pointer to the created udevice for returning
{
    Struct udevice *dev;
    Struct uclass *uc;
    Int size, ret = 0;

    Ret = uclass_get(drv->id, &uc);
        // Get the uclass corresponding to the driver id. If the uclass does not exist originally, then the uclass will be created here and its uclass_driver will be bound.

    Dev = calloc(1, sizeof(struct udevice));
        // assign a udevice

    Dev->platdata = platdata; // Set the platform data pointer of udevice
    Dev->name = name; // set the name of udevice
    Dev->of_offset = of_offset; // Set the dts node offset of udevice
    Dev->parent = parent; // Set the parent device of udevice
    Dev->driver = drv; // Set the corresponding driver of udevice, which is equivalent to the binding of driver and udevice
    Dev->uclass = uc; // Set the uclass to which udevice belongs

    Dev->seq = -1;
    Dev->req_seq = -1;
    If (CONFIG_IS_ENABLED(OF_CONTROL) && CONFIG_IS_ENABLED(DM_SEQ_ALIAS)) {
        /*
         * Some devices, such as a SPI bus, I2C bus and serial ports
         * are numbered using aliases.
         *
         * This is just a 'requested' sequence, and will be
         * resolved (and ->seq updated) when the device is probed.
         */
        If (uc->uc_drv->flags & DM_UC_FLAG_SEQ_ALIAS) {
            If (uc->uc_drv->name && of_offset != -1) {
                Fdtdec_get_alias_seq(gd->fdt_blob,
                        Uc->uc_drv->name, of_offset,
                        &dev->req_seq);
            }
                    / / Set the alias request number of udevice
        }
    }

    If (!dev->platdata && drv->platdata_auto_alloc_size) {
        Dev->flags |= DM_FLAG_ALLOC_PDATA;
        Dev->platdata = calloc(1, drv->platdata_auto_alloc_size);
                / / allocate space for platform data for udevice, determined by platdata_auto_alloc_size in the driver
    }

    Size = uc->uc_drv->per_device_platdata_auto_alloc_size;
    If (size) {
        Dev->flags |= DM_FLAG_ALLOC_UCLASS_PDATA;
        Dev->uclass_platdata = calloc(1, size);
                // The space allocated to the platform data used by udevice for its uclass is determined by per_device_platdata_auto_alloc_size in the driver of the owning uclass.
    }

    /* put dev into parent's successor list */
    If (parent)
        List_add_tail(&dev->sibling_node, &parent->child_head);
        // added to the parent device's child device list

    Ret = uclass_bind_device(dev);
        // uclass and udevice are bound, mainly to link udevice to the uclass device list.

    /* if we fail to bind we remove device from successors and free it */
    If (drv->bind) {
        Ret = drv->bind(dev);
        / / Execute udevice corresponds to the driver's bind function
    }

    If (parent && parent->driver->child_post_bind) {
        Ret = parent->driver->child_post_bind(dev);
        // execute the child_post_bind function of the driver of the parent device
    }
    If (uc->uc_drv->post_bind) {
        Ret = uc->uc_drv->post_bind(dev);
        If (ret)
            Goto fail_uclass_post_bind;
        // Execute the post_bind function of the own uclass
    }

    If (devp)
        *devp = dev;
        // return udevice

    Dev->flags |= DM_FLAG_BOUND;
        / / Set the flag that has been bound
        // Subsequent dev->flags & DM_FLAG_ACTIVATED or device_active macros can be used to determine if the device has been activated.

    Return 0;

The above completes the parsing of dtb, the creation of udevice and uclass, and the binding relationship of each component.Note that this is just a binding, that is, the driver's bind function is called, but the device has not been activated yet, that is, the probe function of the device has not yet been executed.

Seven, DM workflow

After the previous DM initialization and device parsing, we just established the binding relationship between udevice and uclass. But at this time udevice has not been probed, and its corresponding device has not been activated yet. Activating a device mainly through the device_probe function, so before introducing the DM workflow, the device_probe function is explained first.

1、device_probe

driver/core/device.c

Int device_probe(struct udevice *dev)
{
    Const struct driver *drv;
    Int size = 0;
    Int ret;
    Int seq;

    If (dev->flags & DM_FLAG_ACTIVATED)
        Return 0;
// indicates that the device has been activated

    Drv = dev->driver;
    Assert(drv);
/ / Get the driver corresponding to this device

    /* Allocate private data if requested and not reentered */
    If (drv->priv_auto_alloc_size && !dev->priv) {
        Dev->priv = alloc_priv(drv->priv_auto_alloc_size, drv->flags);
// Assign private data to the device
    }

    /* Allocate private data if requested and not reentered */
    Size = dev->uclass->uc_drv->per_device_auto_alloc_size;
    If (size && !dev->uclass_priv) {
        Dev->uclass_priv = calloc(1, size);
// Assign private data to the uclass to which the device belongs
    }

// here filter the probe of the parent device

    Seq = uclass_resolve_seq(dev);
    If (seq < 0) {
        Ret = seq;
        Goto fail;
    }
    Dev->seq = seq;

    Dev->flags |= DM_FLAG_ACTIVATED;
/ / Set the activation flag of udevice

    Ret = uclass_pre_probe_device(dev);
// uclass calls for some functions before the probe device

    If (drv->ofdata_to_platdata && dev->of_offset >= 0) {
        Ret = drv->ofdata_to_platdata(dev);
/ / Call the ofdata_to_platdata in the driver to convert the dts information into the platform data of the device
    }

    If (drv->probe) {
        Ret = drv->probe(dev);
// Call the driver's probe function, where the device is actually activated.
    }

    Ret = uclass_post_probe_device(dev);

    Return ret;
}

The main work is summarized as follows:

  • Assign device private data
  • Probe the parent device
  • Some functions that the uclass needs to call before executing the probe device
  • Call the driver's ofdata_to_platdata to convert the dts information to the platform data of the device.
  • Call the driver's probe function
  • Some functions that uclass needs to call after executing probe device

2, get a udevice through uclass and proceed to probe

Get a udevice through uclass and the probe has the following interface Driver/core/uclass.c

Int uclass_get_device(enum uclass_id id, int index, struct udevice **devp) //Get the udevice from the uclass's device list through the index, and proceed to the probe
Int uclass_get_device_by_name(enum uclass_id id, const char *name,
                  Struct udevice **devp) //Get the udevice from the device list of the uclass by the device name, and proceed to the probe
Int uclass_get_device_by_seq(enum uclass_id id, int seq, struct udevice **devp) //Get udevice from the uclass's device list by serial number, and proceed to probe
Int uclass_get_device_by_of_offset(enum uclass_id id, int node,
                   Struct udevice **devp) //Get the udevice from the device list of the uclass through the offset of the dts node, and proceed to the probe
Int uclass_get_device_by_phandle(enum uclass_id id, struct udevice *parent,
                 Const char *name, struct udevice **devp) //Get the udevice from the device list of the uclass through the "phandle" attribute of the device, and proceed to the probe
Int uclass_first_device(enum uclass_id id, struct udevice **devp) //Get the first udevice from the device list of the uclass and perform the probe
Int uclass_next_device(struct udevice **devp) //Get the next udevice from the device list of the uclass and perform the probe

These interfaces are mainly different in terms of methods for obtaining devices, but the methods of the probe devices are the same, and the probe devices are called by calling uclass_get_device_tail->device_probe. Take uclass_get_device as an example

Int uclass_get_device(enum uclass_id id, int index, struct udevice **devp)
{
    Struct udevice *dev;
    Int ret;

    *devp = NULL;
    Ret = uclass_find_device(id, index, &dev); //Get the corresponding udevice from the uclass's device list by index
    Return uclass_get_device_tail(dev, ret, devp); // Call uclass_get_device_tail to get the device's get, and finally call device_probe to probe the device.
}

Int uclass_get_device_tail(struct udevice *dev, int ret,
                  Struct udevice **devp)
{
    Ret = device_probe(dev);
// Call device_probe to probe the device. This function was explained earlier.
    If (ret)
        Return ret;

    *devp = dev;

    Return 0;
}

3, the workflow is simple

Serial-uclass is simpler, let's take serial-uclass as an example.

  • (0) Code support < 1 > A uclass_driver is defined in serial-uclass.c
UCLASS_DRIVER(serial) = {
    .id = UCLASS_SERIAL, //note the uclass id here
    .name = "serial",
    .flags = DM_UC_FLAG_SEQ_ALIAS,
    .post_probe = serial_post_probe,
    .pre_remove = serial_pre_remove,
    .per_device_auto_alloc_size = sizeof(struct serial_dev_priv),
};

< 2 > Define the serial dts node of s5pv210

[email protected] {
                Compatible = "samsung,exynos4210-uart"; //Note the compatible here
                Reg = <0xe2900000 0x100>;
                Interrupts = <0 51 0>;
                Id = <0>;
        };

< 3 > Defining device drivers

U_BOOT_DRIVER(serial_s5p) = {
    .name = "serial_s5p",
    .id = UCLASS_SERIAL, //note the uclass id here
    .of_match = s5p_serial_ids,
    .ofdata_to_platdata = s5p_serial_ofdata_to_platdata,
    .platdata_auto_alloc_size = sizeof(struct s5p_serial_platdata),
    .probe = s5p_serial_probe,
    .ops = &s5p_serial_ops,
    .flags = DM_FLAG_PRE_RELOC,
};

Static const struct udevice_id s5p_serial_ids[] = {
    { .compatible = "samsung,exynos4210-uart" }, //note the compatible here
    { }
};
  • (1) udevice and corresponding uclass creationIn the process of DM initialization, uboot creates its own udevice and uclass. Specific reference "six, initialization of uboot DM"

  • (2) binding between udevice and corresponding uclassIn the process of DM initialization, uboot implements the binding of udevice to the corresponding uclass. Specific reference "six, initialization of uboot DM"

  • (3) The probe corresponding to udevice is implemented by the module itself. For example, serial needs to select the required udevice for the probe during the initialization process of the serial. The serial-uclass is just a serial operation as a console, and it is not universal. Let's take a brief look at it. The code is as follows, filtering out irrelevant code Driver/serial/serial-uclass.c

Int serial_init(void)
{
    Serial_find_console_or_panic(); // Call serial_find_console_or_panic to initialize the serial as console
    Gd->flags |= GD_FLG_SERIAL_READY;

    Return 0;
}

Static void serial_find_console_or_panic(void)
{
    Const void *blob = gd->fdt_blob;
    Struct udevice *dev;
    Int node;

    If (CONFIG_IS_ENABLED(OF_CONTROL) && blob) {
        /* Check for a chosen console */

/ / This filter out the code to get the specified serial dts node

        If (!uclass_get_device_by_of_offset(UCLASS_SERIAL, node,
                            &dev)) {
// Here uclass_get_device_by_of_offset is called, udevice is obtained from the device list of the uclass by the offset of the dts node, and the probe is executed.
// Note that the device's probe is completed here! ! !
            Gd->cur_serial_dev = dev;
/ / Store udevice in gd->cur_serial_dev, the subsequent uclass can directly get the corresponding device through gd->cur_serial_dev and operate
// But note that this is not a common practice! ! !
            Return;
        }
    }
}
  • (4) uclass interface call 
    • You can do this by first extracting the corresponding uclass from the root_uclass list and then using uclass->uclass_driver->ops. This method is more versatile.
    • It is not recommended to call the interface of the direct expert by calling uclass, but the serial-uclass uses this method. This part should belong to the serial core, but it is also implemented in serial-uclass.c. Taking the serial_putc call as an example, the serial-uclass uses the following:
Void serial_putc(char ch)
{
    If (gd->cur_serial_dev)
        _serial_putc(gd->cur_serial_dev, ch);// Pass the serial udevice corresponding to the console as a parameter
}

Static void _serial_putc(struct udevice *dev, char ch)
{
    Struct dm_serial_ops *ops = serial_get_ops(dev);// Get the ops operation set of the driver function corresponding to the device
    Int err;

    Do {
        Err = ops->putc(dev, ch); // call the corresponding operation function in ops with udevice as the argument
    } while (err == -EAGAIN);
}

The entire process is briefly introduced here.

Almost all of them are on paper, and the follow-up will come to the actual use of a gpio-uclass.