Linux 驱动设备匹配过程

一、Linux 驱动-总线-设备模型

1、驱动分层        

        Linux内核需要兼容多个平台,不同平台的寄存器设计不同导致操作方法不同,故内核提出分层思想,抽象出与硬件无关的软件层作为核心层来管理下层驱动,各厂商根据自己的硬件编写驱动代码作为硬件驱动层

2、设备&总线&驱动

        Linux内核建立的 设备-总线-驱动 模型,定义如下:

1、device
include\linux\device.h
struct device {
    ...
    struct bus_type	*bus;		  /* type of bus device is on */
    struct device_driver *driver; /* which driver has allocated this device */
    struct device_node	*of_node; /* associated device tree node */

    ...
}

2、driver
include\linux\device\driver.h
struct device_driver {
    ...
    struct bus_type		*bus;
    ...
}

3、bus
include\linux\bus\bus.h
struct bus_type {
    ...
	int (*match)(struct device *dev, struct device_driver *drv);
	int (*probe)(struct device *dev);
    ...
}

        这里提到的是虚拟总线,总线能将对应的设备和驱动进行匹配,可以用下面的命令查看不同总线类型

/sys/bus # ls -l 
......
drwxr-xr-x 4 root root 0 2023-02-21 13:35 i2c
drwxr-xr-x 4 root root 0 2023-02-21 13:35 mmc
drwxr-xr-x 5 root root 0 2023-02-21 13:35 pci
drwxr-xr-x 4 root root 0 2023-02-20 07:09 platform
drwxr-xr-x 4 root root 0 2023-02-21 13:35 scsi
drwxr-xr-x 4 root root 0 2023-02-21 13:35 usb
......
总线类型描述
I2C总线挂在i2c总线(硬件)下的从设备,比如加密芯片、rtc芯片、触摸屏芯片等等都需要驱动,自然也要按照分离思想来设计。内核中的i2c 总线就是用来帮助i2c从设备的设备信息和驱动互相匹配的
Platform总线

像i2c、spi这样硬件有实体总线的,从设备驱动可以用总线来管理。那么没有总线的硬件外设怎么办?比如gpio、uart、i2c控制器、spi 控制器…等等,这些通通用 platform 总线来管理

二、驱动匹配设备过程简述

        在写驱动时会用到一些注册函数比如:platform_driver_register,spi_register_driver、i2c_add_driver,接下来分析内核驱动和设备匹配的流程,原理就是在注册到总线的时候,去获取对方的链表并根据规则检测,匹配后调用probe(),也就是驱动的入口函数

        以Platform Driver举例,整个匹配过程如下

2.1 整体调用逻辑

module_platform_driver
    |-- module_driver
        |-- __platform_driver_register
            |-- driver_register
                |-- bus_add_driver
                    |-- driver_attach
                        |-- bus_for_each_dev
                            |-- __driver_attach
                                |-- driver_match_device
                                    |-- platform_match
                                        |-- of_driver_match_device
                                            |-- of_match_device
                                                |-- __of_match_node
                                |-- driver_probe_device
                                    |-- really_probe
                                        |-- call_driver_probe
                                            |-- platform_probe
                                                |-- drv->probe()

2.2 module_platform_driver

        封装了一层,展开后实际上就是module_init和module_exit

/* module_platform_driver() - Helper macro for drivers that don't do
 * anything special in module init/exit.  This eliminates a lot of
 * boilerplate.  Each module may only use this macro once, and
 * calling it replaces module_init() and module_exit()
 */
#define module_platform_driver(__platform_driver) \
	module_driver(__platform_driver, platform_driver_register, \
			platform_driver_unregister)

        例如对于MTK某平台UFS驱动,传入__platform_driver 参数为

static struct platform_driver ufs_mtk_pltform = {
    .probe      = ufs_mtk_probe,
    .remove     = ufs_mtk_remove,
    .shutdown   = ufshcd_pltfrm_shutdown,
    .driver = {
        .name   = "ufshcd-mtk",
        .pm     = &ufs_mtk_pm_ops,
        .of_match_table = ufs_mtk_of_match,
     },
};

2.3 module_driver

/**
 * module_driver() - Helper macro for drivers that don't do anything
 * special in module init/exit. This eliminates a lot of boilerplate.
 * Each module may only use this macro once, and calling it replaces
 * module_init() and module_exit().
 *
 * @__driver: driver name
 * @__register: register function for this driver type
 * @__unregister: unregister function for this driver type
 * @...: Additional arguments to be passed to __register and __unregister.
 *
 * Use this macro to construct bus specific macros for registering
 * drivers, and do not use it on its own.
 */
#define module_driver(__driver, __register, __unregister, ...) \
static int __init __driver##_init(void) \
{ \
	return __register(&(__driver) , ##__VA_ARGS__); \
} \
module_init(__driver##_init); \
static void __exit __driver##_exit(void) \
{ \
	__unregister(&(__driver) , ##__VA_ARGS__); \
} \
module_exit(__driver##_exit);

2.4 __platform_driver_register

        注意此处的__register是传进来的__platform_driver_register

/**
 * __platform_driver_register - register a driver for platform-level devices
 * @drv: platform driver structure
 * @owner: owning module/driver
 */
int __platform_driver_register(struct platform_driver *drv,
		struct module *owner)
{
    drv->driver.owner = owner;
    drv->driver.bus = &platform_bus_type;
  
    return driver_register(&drv->driver);
}
EXPORT_SYMBOL_GPL(__platform_driver_register);

        对bus参数进行赋值 

struct bus_type platform_bus_type = {
    .name		= "platform",
    .dev_groups	= platform_dev_groups,
    .match	= platform_match,
    .uevent	= platform_uevent,
    .probe	= platform_probe,
    .remove	= platform_remove,
    .shutdown	= platform_shutdown,
    .dma_configure= platform_dma_configure,
    .dma_cleanup= platform_dma_cleanup,
    .pm	= &platform_dev_pm_ops,
};
EXPORT_SYMBOL_GPL(platform_bus_type);

2.5 driver_register

/**
 * driver_register - register driver with bus
 * @drv: driver to register
 *
 * We pass off most of the work to the bus_add_driver() call,
 * since most of the things we have to do deal with the bus
 * structures.
 */
int driver_register(struct device_driver *drv)
{
    ......
	other = driver_find(drv->name, drv->bus);
	if (other) {
		pr_err("Error: Driver '%s' is already registered, "
			"aborting...\n", drv->name);
		return -EBUSY;
	}

	ret = bus_add_driver(drv);
    ......
}
EXPORT_SYMBOL_GPL(driver_register);

2.6 bus_add_driver

        drv->bus->p->drivers_autoprobe默认是1,结构体定义时就赋值了

struct subsys_private {
    ...
    unsigned int drivers_autoprobe:1;    
}
/**
 * bus_add_driver - Add a driver to the bus.
 * @drv: driver.
 */
int bus_add_driver(struct device_driver *drv)
{
    ......
    if (drv->bus->p->drivers_autoprobe) {
		error = driver_attach(drv);
		if (error)
			goto out_del_list;
	}
    ......
}

2.7 driver_attach

/**
 * driver_attach - try to bind driver to devices.
 * @drv: driver.
 *
 * Walk the list of devices that the bus has on it and try to
 * match the driver with each one.  If driver_probe_device()
 * returns 0 and the @dev->driver is set, we've found a
 * compatible pair.
 */
int driver_attach(struct device_driver *drv)
{
	return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
}
EXPORT_SYMBOL_GPL(driver_attach);

2.8 bus_for_each_dev

        此函数 fn 即为  __driver_attach 函数指针,data参数 是 drv

int bus_for_each_dev(struct bus_type *bus, struct device *start,
		     void *data, int (*fn)(struct device *, void *))
{
	struct klist_iter i;
	struct device *dev;
	int error = 0;

	if (!bus || !bus->p)
		return -EINVAL;

	klist_iter_init_node(&bus->p->klist_devices, &i,
			     (start ? &start->p->knode_bus : NULL));
	while (!error && (dev = next_device(&i)))
		error = fn(dev, data);
	klist_iter_exit(&i);
	return error;
}
EXPORT_SYMBOL_GPL(bus_for_each_dev);

2.9 __driver_attach 

static int __driver_attach(struct device *dev, void *data){
    ......
    ret = driver_match_device(drv, dev);
    ......
    ret = driver_probe_device(drv, dev);
    ......
}
2.9.1 driver_match_device
static inline int driver_match_device(struct device_driver *drv,
				      struct device *dev)
{
	return drv->bus->match ? drv->bus->match(dev, drv) : 1;
}

/* 返回 1 是可以继续往下走的 ret <= 0 不行*/

        可以看到在Register时有match回调 

struct bus_type platform_bus_type = {
    ......
    .match	= platform_match,
    .probe	= platform_probe,
    ......
};
2.9.1.1 platform_match
static int platform_match(struct device *dev, struct device_driver *drv)
{
	struct platform_device *pdev = to_platform_device(dev);
	struct platform_driver *pdrv = to_platform_driver(drv);

	/* When driver_override is set, only bind to the matching driver */
	if (pdev->driver_override)
		return !strcmp(pdev->driver_override, drv->name);

	/* Attempt an OF style match first */
	if (of_driver_match_device(dev, drv))
		return 1;

	/* Then try ACPI style match */
	if (acpi_driver_match_device(dev, drv))
		return 1;

	/* Then try to match against the id table */
	if (pdrv->id_table)
		return platform_match_id(pdrv->id_table, pdev) != NULL;

	/* fall-back to driver name match */
	return (strcmp(pdev->name, drv->name) == 0);
}
2.9.1.2 of_driver_match_device
/**
 * of_driver_match_device - Tell if a driver's of_match_table matches a device.
 * @drv: the device_driver structure to test
 * @dev: the device structure to match against
 */
static inline int of_driver_match_device(struct device *dev,
					 const struct device_driver *drv)
{
	return of_match_device(drv->of_match_table, dev) != NULL;
}

         of_match_table定义如下

static struct platform_driver ufs_mtk_pltform = {
    .probe      = ufs_mtk_probe,
    .remove     = ufs_mtk_remove,
    .shutdown   = ufshcd_pltfrm_shutdown,
    .driver = {
        .name   = "ufshcd-mtk",
        .pm     = &ufs_mtk_pm_ops,
        .of_match_table = ufs_mtk_of_match,
     },
};
static const struct of_device_id ufs_mtk_of_match[] = {
    { .compatible = "mediatek,mtxxxx-ufshci" },
};
2.9.1.3 of_match_device
const struct of_device_id *of_match_device(const struct of_device_id *matches,
                    const struct device *dev)
{
    if (!matches || !dev->of_node || dev->of_node_reused)
           return NULL;
    return of_match_node(matches, dev->of_node);
}
EXPORT_SYMBOL(of_match_device);
2.9.1.4 of_match_node
const struct of_device_id *of_match_node(const struct of_device_id *matches,
                    const struct device_node *node)
{
    match = __of_match_node(matches, node);
}
EXPORT_SYMBOL(of_match_node);
2.9.1.5 __of_match_node
static
const struct of_device_id *__of_match_node(const struct of_device_id *matches,
                        const struct device_node *node)
{

for (; matches->name[0] ||
       matches->type[0] || 
       matches->compatible[0]; matches++) { 
    /* 每次循环,选择Vendor驱动中的match table结构体数组的下一个比较 */
    score = __of_device_is_compatible(node, matches->compatible,
    matches->type, matches->name);
	if (score > best_score) {
	    best_match = matches;
	    best_score = score;
	}
    }
    return best_match;
}
2.9.1.6 __of_device_is_compatible
static int __of_device_is_compatible(const struct device_node *device,
            const char *compat, const char *type, const char *name)
{
    ......
    if (of_compat_cmp(cp, compat, strlen(compat)) == 0) {
           score = INT_MAX/2 - (index << 2);
           break;
    }
    ......
}

        cp即为从设备树节点中获取的compatible信息,示例如下

ufshci: ufshci@112b0000 {
        compatible = "mediatek,mtxxxx-ufshci";
        reg = <0 0x112b0000 0 0x2a00>;
}
2.9.2 driver_probe_device
static int driver_probe_device(struct device_driver *drv, 
            struct device *dev)
{
    ......
    ret = __driver_probe_device(drv, dev);
    ......
}
2.9.2.1 __driver_probe_device

        initcall_debug是一个内核参数,可以跟踪initcall,用来定位内核初始化的问题。在cmdline中增加initcall_debug后,内核启动过程中会在调用每一个init函数前有一句打印,结束后再有一句打印并且输出了该Init函数运行的时间,通过这个信息可以用来定位启动过程中哪个init函数运行失败以及哪些init函数运行时间较长

        really_probe_debug()内部还是调用了really _probe()

static int __driver_probe_device(struct device_driver *drv, struct device *dev)
{
    ......
	if (initcall_debug)
		ret = really_probe_debug(dev, drv);
	else
		ret = really_probe(dev, drv);
    ......
}
2.9.2.2 really_probe
static int really_probe(struct device *dev, struct device_driver *drv)
{
    ......
    ret = call_driver_probe(dev, drv);
    ......
}
2.9.2.3 call_driver_probe
static int call_driver_probe(struct device *dev,
            struct device_driver *drv)
{
    ......
	if (dev->bus->probe)
		ret = dev->bus->probe(dev);
	else if (drv->probe)
		ret = drv->probe(dev);
    ......
}
2.9.2.4 platform_probe

        不管走没有dev->bus->probe,最终都会走到drv->probe

static int platform_probe(struct device *_dev)
{
	struct platform_driver *drv = to_platform_driver(_dev->driver);
	struct platform_device *dev = to_platform_device(_dev);
	......
	if (drv->probe) {
		ret = drv->probe(dev);
		if (ret)
			dev_pm_domain_detach(_dev, true);
	}
    ......
}

        此时驱动匹配设备成功,会走到之前Register的probe 

static struct platform_driver ufs_mtk_pltform = {
    .probe      = ufs_mtk_probe,
    ......
};

三、设备匹配驱动过程简述

3.1 整体调用逻辑

解析设备树
    |-- of_platform_default_populate_init
        |-- of_platform_default_populate
            |-- of_platform_populate
                |-- of_platform_bus_create
                    |-- of_platform_device_create_pdata
                        |-- of_device_add
                            |-- device_add 
                                |-- bus_probe_device
                                    |-- device_initial_probe
                                        |-- __device_attach
                                            |-- bus_for_each_drv
                                                |-- __device_attach_driver
                                                    |-- driver_match_device
                                                    |-- driver_probe_device

/* 自己编写module,使用Platform_device_register()也会走到device_add() */

3.2 解析设备树

        在Linux kernel初始化时,会解析Bootloader传递的设备树信息,设备树中满足下列条件的节点能被转换为内核里的platform_device

(1)根节点下含有compatile属性的子节点,会转换为platform_device;
(2)含有特定compatile属性的节点的子节点,会转换为platform_device,
如果一个节点的compatile属性,它的值是这4者之一:“simple-bus”,“simple-mfd”,“isa”,“arm,amba-bus”, 那么它的子结点(需含compatile属性)也可以转换为platform_device。
(3)总线I2C、SPI节点下的子节点:不转换为platform_device,  某个总线下到子节点,应该交给对应的总线驱动程序来处理, 它们不应该被转换为platform_device。

3.2.1 start_kernel
asmlinkage __visible void __init start_kernel(void)
{
	char *command_line;
    ......
	setup_arch(&command_line);
    ......
}
3.2.2 setup_arch
void __init setup_arch(char **cmdline_p)
{
	......
	arch_mem_init(cmdline_p);
    ......
}
3.2.3 arch_mem_init
static void __init arch_mem_init(char **cmdline_p)
{
	plat_mem_setup();  //1.解析设备树三个重要节点
    ......
	device_tree_init();//2.解析所有子节点
}
3.2.4 plat_mem_setup
void __init plat_mem_setup(void)
{
	......
	if (loongson_fdt_blob)
		__dt_setup_arch(loongson_fdt_blob);
}
3.2.4.1 __dt_setup_arch
void __init __dt_setup_arch(void *bph)
{
	if (!early_init_dt_scan(bph))
		return;

	mips_set_machine_name(of_flat_dt_get_machine_name());
}
3.2.4.2 early_init_dt_scan
bool __init early_init_dt_scan(void *params)
{
	......
	status = early_init_dt_verify(params);
	......
	early_init_dt_scan_nodes();
}
3.2.4.3 early_init_dt_scan_nodes

        解析三个对于系统非常重要的节点 

void __init early_init_dt_scan_nodes(void)
{
    /* chosen节点操作,将bootargs拷贝到boot_command_line指向的内存,
       boot_command_line是一个全局变量 */
	of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);

	/* 根据根节点的#address-cells属性和#size-cells属性初始化全局变量
       dt_root_size_cells和dt_root_addr_cells */
	of_scan_flat_dt(early_init_dt_scan_root, NULL);

	/* 配置内存 起始地址,大小等 */
	of_scan_flat_dt(early_init_dt_scan_memory, NULL);
}
3.2.5 device_tree_init
void __init device_tree_init(void)
{
	......
	if (early_init_dt_verify(initial_boot_params))
		unflatten_and_copy_device_tree();
}
3.2.5.1 unflatten_and_copy_device_tree
void __init unflatten_and_copy_device_tree(void)
{
	......
	unflatten_device_tree();
}
3.2.5.2 unflatten_device_tree
void __init unflatten_device_tree(void)
{
	__unflatten_device_tree(initial_boot_params, NULL, &of_root,
				early_init_dt_alloc_memory_arch, false);
	......
}
3.2.5.3 __unflatten_device_tree
static void __unflatten_device_tree(const void *blob,
			     struct device_node **mynodes,
			     void * (*dt_alloc)(u64 size, u64 align))
{
	/* First pass, scan for size */
	......
	size = (unsigned long)unflatten_dt_node(blob, NULL, &start, NULL, NULL, 0, true);
	......
}
3.2.5.4 __unflatten_device_tree
static void * unflatten_dt_node(const void *blob,
                void *mem,
                int *poffset,
                struct device_node *dad,
                struct device_node **nodepp,
                unsigned long fpsize,
                bool dryrun)
{
    const __be32 *p;
    struct device_node *np;
    struct property *pp, **prev_pp = NULL;
    const char *pathp;
    unsigned int l, allocl;
    static int depth;
    int old_depth;
    int offset;
    int has_name = 0;
    int new_format = 0;

    /* 获取node节点的name指针到pathp中 */
    pathp = fdt_get_name(blob, *poffset, &l);
    if (!pathp)
        return mem;

    allocl = ++l;

    /* version 0x10 has a more compact unit name here instead of the full
     * path. we accumulate the full path size using "fpsize", we'll rebuild
     * it later. We detect this because the first character of the name is
     * not '/'.
     */
    if ((*pathp) != '/') {
        new_format = 1;
        if (fpsize == 0) {
            /* root node: special case. fpsize accounts for path
             * plus terminating zero. root node only has '/', so
             * fpsize should be 2, but we want to avoid the first
             * level nodes to have two '/' so we use fpsize 1 here
             */
            fpsize = 1;
            allocl = 2;
            l = 1;
            pathp = "";
        } else {
            /* account for '/' and path size minus terminal 0
             * already in 'l'
             */
            fpsize += l;
            allocl = fpsize;
        }
    }

    /* 分配struct device_node内存,包括路径全称大小 */
    np = unflatten_dt_alloc(&mem, sizeof(struct device_node) + allocl,
                __alignof__(struct device_node));
    if (!dryrun) {
        char *fn;
        of_node_init(np);

        /* 填充full_name,full_name指向该node节点的全路径名称字符串 */
        np->full_name = fn = ((char *)np) + sizeof(*np);
        if (new_format) {
            /* rebuild full path for new format */
            if (dad && dad->parent) {
                strcpy(fn, dad->full_name);
                fn += strlen(fn);
            }
            *(fn++) = '/';
        }
        memcpy(fn, pathp, l);

        /* 节点挂接到相应的父节点、子节点和姊妹节点 */
        prev_pp = &np->properties;
        if (dad != NULL) {
            np->parent = dad;
            np->sibling = dad->child;
            dad->child = np;
        }
    }
    /* 处理该node节点下面所有的property */
    for (offset = fdt_first_property_offset(blob, *poffset);
         (offset >= 0);
         (offset = fdt_next_property_offset(blob, offset))) {
        const char *pname;
        u32 sz;

        if (!(p = fdt_getprop_by_offset(blob, offset, &pname, &sz))) {
            offset = -FDT_ERR_INTERNAL;
            break;
        }

        if (pname == NULL) {
            pr_info("Can't find property name in list !\n");
            break;
        }
        if (strcmp(pname, "name") == 0)
            has_name = 1;
        pp = unflatten_dt_alloc(&mem, sizeof(struct property),
                    __alignof__(struct property));
        if (!dryrun) {
            /* We accept flattened tree phandles either in
             * ePAPR-style "phandle" properties, or the
             * legacy "linux,phandle" properties.  If both
             * appear and have different values, things
             * will get weird.  Don't do that. */

            /* 处理phandle,得到phandle值 */
            if ((strcmp(pname, "phandle") == 0) ||
                (strcmp(pname, "linux,phandle") == 0)) {
                if (np->phandle == 0)
                    np->phandle = be32_to_cpup(p);
            }
            /* And we process the "ibm,phandle" property
             * used in pSeries dynamic device tree
             * stuff */
            if (strcmp(pname, "ibm,phandle") == 0)
                np->phandle = be32_to_cpup(p);
            pp->name = (char *)pname;
            pp->length = sz;
            pp->value = (__be32 *)p;
            *prev_pp = pp;
            prev_pp = &pp->next;
        }
    }
    /* with version 0x10 we may not have the name property, recreate
     * it here from the unit name if absent
     */
    /* 为每个node节点添加一个name的属性 */
    if (!has_name) {
        const char *p1 = pathp, *ps = pathp, *pa = NULL;
        int sz;

        /* 属性name的value值为node节点的名称,取“/”和“@”之间的子串,设备和驱动的别名匹配用的就是这个地方的name */
        while (*p1) {
            if ((*p1) == '@')
                pa = p1;
            if ((*p1) == '/')
                ps = p1 + 1;
            p1++;
        }
        if (pa < ps)
            pa = p1;
        sz = (pa - ps) + 1;
        pp = unflatten_dt_alloc(&mem, sizeof(struct property) + sz,
                    __alignof__(struct property));
        if (!dryrun) {
            pp->name = "name";
            pp->length = sz;
            pp->value = pp + 1;
            *prev_pp = pp;
            prev_pp = &pp->next;
            memcpy(pp->value, ps, sz - 1);
            ((char *)pp->value)[sz - 1] = 0;
        }
    }
    /* 填充device_node结构体中的name和type成员 */
    if (!dryrun) {
        *prev_pp = NULL;
        np->name = of_get_property(np, "name", NULL);
        np->type = of_get_property(np, "device_type", NULL);

        if (!np->name)
            np->name = "<NULL>";
        if (!np->type)
            np->type = "<NULL>";
    }

    old_depth = depth;
    *poffset = fdt_next_node(blob, *poffset, &depth);
    if (depth < 0)
        depth = 0;
    /* 递归调用node节点下面的子节点 */
    while (*poffset > 0 && depth > old_depth)
        mem = unflatten_dt_node(blob, mem, poffset, np, NULL,
                    fpsize, dryrun);

    if (*poffset < 0 && *poffset != -FDT_ERR_NOTFOUND)
        pr_err("unflatten: error %d processing FDT\n", *poffset);

    /*
     * Reverse the child list. Some drivers assumes node order matches .dts
     * node order
     */
    if (!dryrun && np->child) {
        struct device_node *child = np->child;
        np->child = NULL;
        while (child) {
            struct device_node *next = child->sibling;
            child->sibling = np->child;
            np->child = child;
            child = next;
        }
    }

    if (nodepp)
        *nodepp = np;

    return mem;
}

3.3 设备发起匹配

        如果不是走的platform_device_register(),那么会走下面的流程,从device_add()开始后面就是一样的 

3.3.1 of_platform_default_populate_init

        此函数就是为了在设备树解析出来后进行驱动匹配的,会在内核初始化阶段调用

static int __init of_platform_default_populate_init(void)
{
	...
	...
	/* Populate everything else. */
	of_platform_default_populate(NULL, NULL, NULL);
	...
	...
}
arch_initcall_sync(of_platform_default_populate_init);
arch_initcall_sync(of_platform_default_populate_init);

start_kernel
    |-- rest_init();
        |-- pid = kernel_thread(kernel_init, NULL, CLONE_FS);
            |-- kernel_init
                |-- kernel_init_freeable();
                    |-- do_basic_setup();
                        |-- do_initcalls();

for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)
    do_initcall_level(level);  // do_initcall_level(3)
    for (fn = initcall_levels[3]; fn < initcall_levels[3+1]; fn++)
         do_one_initcall(initcall_from_entry(fn)); /*就是调用"arch_initcall_sync*/
3.3.2 of_platform_default_populate
int of_platform_default_populate(struct device_node *root,
				 const struct of_dev_auxdata *lookup,
				 struct device *parent)
{
	return of_platform_populate(root, of_default_bus_match_table, lookup,
				    parent);
}
EXPORT_SYMBOL_GPL(of_platform_default_populate);
 3.3.3 of_platform_populate
int of_platform_populate(struct device_node *root,
			const struct of_device_id *matches,
			const struct of_dev_auxdata *lookup,
			struct device *parent)
{
	......
	//遍历根节点下的每一个子设备节点并把device_node的信息填充到创建platform_device中
	for_each_child_of_node(root, child) {
		rc = of_platform_bus_create(child, matches, lookup, parent, true);
		if (rc) {
			of_node_put(child);
			break;
		}
	}
    ......
}
EXPORT_SYMBOL_GPL(of_platform_populate);
  3.3.4 of_platform_bus_create
static int of_platform_bus_create(struct device_node *bus,
				  const struct of_device_id *matches,
				  const struct of_dev_auxdata *lookup,
				  struct device *parent, bool strict)
{
    ......
	dev = of_platform_device_create_pdata(bus, bus_id, platform_data, parent);
}
  3.3.5 of_platform_device_create_pdata
static struct platform_device *of_platform_device_create_pdata(
					struct device_node *np,
					const char *bus_id,
					void *platform_data,
					struct device *parent)
{
    ......
	//of_device_add函数就是把platform_device用平台总线去匹配驱动了
	if (of_device_add(dev) != 0) {
		platform_device_put(dev);
		goto err_clear_flag;
	}
}
  3.3.6 of_device_add
int of_device_add(struct platform_device *ofdev)
{
    ......
	return device_add(&ofdev->dev);
}
 3.3.7 device_add
int device_add(struct device *dev)
{
    ......
	bus_probe_device(dev);
}
EXPORT_SYMBOL_GPL(device_add);

  3.3.8 bus_probe_device
void bus_probe_device(struct device *dev)
{
    ......
	if (bus->p->drivers_autoprobe)
		device_initial_probe(dev);
    ......
}
3.3.9 device_initial_probe
void device_initial_probe(struct device *dev)
{
	__device_attach(dev, true);
}
3.3.10 __device_attach
static int __device_attach(struct device *dev, bool allow_async)
{
    ......
    ret = bus_for_each_drv(dev->bus, NULL, &data,
					__device_attach_driver);
}
3.3.11 bus_for_each_drv

        遍历bus上的driver进行匹配

int bus_for_each_drv(struct bus_type *bus, struct device_driver *start,
        void *data, int (*fn)(struct device_driver *, void *))
{
    ......
    klist_iter_init_node(&bus->p->klist_drivers, &i,
    start ? &start->p->knode_bus : NULL);
    while ((drv = next_driver(&i)) && !error)
        error = fn(drv, data);
    klist_iter_exit(&i);
}
EXPORT_SYMBOL_GPL(bus_for_each_drv);
3.3.12 __device_attach_driver
static int __device_attach_driver(struct device_driver *drv, void *_data)
{
    ......
	ret = driver_match_device(drv, dev);

	/* 匹配成功调用platform_driver的probe函数进行硬件的初始化动作 */
	return driver_probe_device(drv, dev);
}

此后,函数的内容就和驱动匹配设备时流程一致了,先判断是否match,然后调用probe

【参考博客】

[1] Linux设备驱动和设备匹配过程_linux驱动和设备匹配过程-CSDN博客

[2] platform 总线_怎么查询platform 总线-CSDN博客

[3] Linux Driver 和Device匹配过程分析(1)_linux设备驱动和设备树的match过程-CSDN博客

[4] Linux驱动(四)platform总线匹配过程_platform平台设备匹配过程-CSDN博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/662581.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

手把手从0到1教你做STM32+FreeRTOS智能家居--第10篇之ASR-PRO语音识别模块

前言 先看实验效果&#xff0c;通过ASR-PRO语音智能识别控制模块&#xff0c;来控制STM32单片机实现对应的控制功能。因为后台好多小伙伴私信问用的是什么语音模块&#xff0c;并且很少在网上看到如何使用此模块相关的文章&#xff0c;所以我将会在本篇文章详细介绍一下此模块…

国际数字影像产业园|科技与文创产品创意集市,共筑创新文化新高地

5月29日&#xff0c;为进一步增强园区与企业之间粘性&#xff0c;不断激发企业的创新活力&#xff0c;园区举办了“数媒大厦科技与文创产品创意集市活动”。本次活动由成都树莓信息技术有限公司主办&#xff0c;成都目莓商业管理有限公司、树莓科技&#xff08;成都&#xff09…

成都欣丰洪泰文化传媒有限公司助力品牌快速崛起

在当今数字化浪潮汹涌的时代&#xff0c;电商行业作为新经济的代表&#xff0c;正以其独特的魅力和无限的潜力&#xff0c;引领着商业模式的创新与变革。在这个充满机遇与挑战的领域里&#xff0c;成都欣丰洪泰文化传媒有限公司凭借其专业的电商服务能力和前瞻性的战略眼光&…

【Paddle】Inplace相关问题:反向传播、影响内存使用和性能

【Paddle】Inplace相关问题&#xff1a;反向传播、影响内存使用和性能 写在最前面inplace 的好处有哪些&#xff1f;能降低计算复杂度吗在反向传播时&#xff0c;Inplace为什么会阻碍呢&#xff1f;“计算图的完整性受损”表达有误原地操作 sin_()为什么原地操作会阻碍反向传播…

IPFoxy Tips:海外代理IP适用的8个跨境出海业务

在当今数字化时代&#xff0c;互联网已经成为商业和个人生活不可或缺的一部分。IP代理作为出海业务的神器之一&#xff0c;备受跨境出海业务人员关注。IPFoxy动态、静态纯净代理IP也根据业务需求的不同&#xff0c;分为静态住宅、动态住宅、静态IPv4、静态IPv6四种类型代理。那…

祝贺!阿里云PolarDB斩获数据库国际顶会ICDE 2024工业赛道最佳论文

5月17日消息&#xff0c;在荷兰举行的国际顶级数据库学术会议ICDE 2024上&#xff0c;阿里云斩获工业和应用赛道的“最佳论文奖”&#xff0c;这也是中国企业首次获此殊荣。阿里云PolarDB创新性地解决了数据库Serverless中跨机事务迁移的核心难题&#xff0c;将跨机迁移时间压缩…

零经验转行的人,嵌入式软件开发项目经历咋办?

零基础转行&#xff0c;首先一定要系统的把基础知识学一遍&#xff0c;这样就有个系统的概念。先说一下前置知识&#xff0c;因为你完全没基础&#xff0c;这些知识还是有必要了解的&#xff0c;要了解一下刚好我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「嵌…

2024年03月 Python(六级)真题解析#中国电子学会#全国青少年软件编程等级考试

Python等级考试(1~6级)全部真题・点这里 一、单选题(共25题,共50分) 第1题 以下选项中,创建类正确的是?() A: class test1:def prt(self):……B: class Mg(

HNU-计算机体系结构-实验2-Tomasulo算法

计算机体系结构 实验2 计科210X 甘晴void 202108010XXX 1 实验目的 熟悉Tomasulo模拟器同时加深对Tomasulo算法的理解&#xff0c;从而理解指令级并行的一种方式-动态指令调度。 掌握Tomasulo算法在指令流出、执行、写结果各阶段对浮点操作指令以及load和store指令进行什么…

视频怎么提取成文字?7个软件教你快速进行视频文字提取

视频怎么提取成文字&#xff1f;7个软件教你快速进行视频文字提取 提取视频中的文字通常需要使用视频转文字的软件或在线工具。以下是七款常用的软件和工具&#xff0c;它们可以帮助您快速进行视频文字提取&#xff1a; 口袋视频转换器&#xff1a;这是一款专业的视频转文字…

springboot 作为客户端接收服务端的 tcp 长连接数据,并实现自定义结束符,解决 粘包 半包 问题

博主最近的项目对接了部分硬件设备&#xff0c;其中有的设备只支持tcp长连接方式传输数据&#xff0c;博主项目系统平台作为客户端发起tcp请求到设备&#xff0c;设备接收到请求后作为服务端保持连接并持续发送数据到系统平台。 1.依赖引入 连接使用了netty&#xff0c;如果项…

人工智能应用-实验5-BP 神经网络分类手写数据集

文章目录 &#x1f9e1;&#x1f9e1;实验内容&#x1f9e1;&#x1f9e1;&#x1f9e1;&#x1f9e1;代码&#x1f9e1;&#x1f9e1;&#x1f9e1;&#x1f9e1;分析结果&#x1f9e1;&#x1f9e1;&#x1f9e1;&#x1f9e1;实验总结&#x1f9e1;&#x1f9e1; &#x1f9…

图形学初识--空间变换

文章目录 前言正文矩阵和向量相乘二维变换1、缩放2、旋转3、平移4、齐次坐标下总结 三维变换1、缩放2、平移3、旋转绕X轴旋转&#xff1a;绕Z轴旋转&#xff1a;绕Y轴旋转&#xff1a; 结尾&#xff1a;喜欢的小伙伴可以点点关注赞哦 前言 前面章节补充了一下基本的线性代数中…

统计计算五|MCMC( Markov Chain Monte Carlo)

系列文章目录 统计计算一|非线性方程的求解 统计计算二|EM算法&#xff08;Expectation-Maximization Algorithm&#xff0c;期望最大化算法&#xff09; 统计计算三|Cases for EM 统计计算四|蒙特卡罗方法&#xff08;Monte Carlo Method&#xff09; 文章目录 系列文章目录一…

彻底理解浏览器的进程与线程

彻底理解浏览器的进程与线程 什么是进程和线程&#xff0c;两者的区别及联系浏览器的进程和线程总结浏览器核心进程有哪些浏览器进程与线程相关问题 什么是进程和线程&#xff0c;两者的区别及联系 进程和线程是操作系统中用于管理程序执行的两个基本概念进程的定义及理解 定义…

今日分享站

同志们&#xff0c;字符函数和字符串函数已经全部学习完啦&#xff0c;笔记也已经上传完毕&#xff0c;大家可以去看啦。字符函数和字符串函数and模拟函数 加油&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;

应用上架后的关键!苹果商店(AppStore)运营策略与技巧指南

1、运营期&#xff1a;怎么能活得好&#xff1f; ▍封号和下架问题 14天 在收到苹果封号通知&#xff08;我们将会在14天后封你的账号&#xff09;如果觉得冤枉可以在14天内进行申诉。14天并不是一个严格准确的时间&#xff0c;有可能会在第15天或者在第20天&#xff0c;甚至…

开源基于Node编写的批量HTML转PDF

LTPP批量HTML转PDF工具 Github 地址 LTPP-GIT 地址 官方文档 功能 LTPP 批量 HTML 转 PDF 工具支持将当前目录下所有 HTML 文件转成 PDF 文件&#xff0c;并且在新目录中保存文件结构与原目录结构一致 说明 一共两个独立版本&#xff0c;html-pdf 目录下是基于 html-pdf 模…

【考研数学】数学一和数学二哪个更难?如何复习才能上90分?

很明显考研数学一更难&#xff01; 不管是复习量还是题目难度 对比项考研数学一考研数学二适用专业理工科类及部分经济学类理工科类考试科目高等数学、线性代数、概率论与数理统计高等数学、线性代数试卷满分150分150分考试时间180分钟180分钟试卷内容结构高等数学约60%&…

在 iCloud.com 上导入、导出或打印联系人

想将iPhone上的电话本备份一份到本地电脑上&#xff0c;发现iTunes好像只是音乐播放了&#xff0c;不再支持像电话本等功能&#xff0c;也不想通过其他第三方软件&#xff0c;好在可以通过iCloud进行导入导出。下面只是对操作过程进行一个图片记录而已&#xff0c;文字说明可以…