网站开发报价模板,专门做衬衣网站,wordpress怎么做主题,东莞网站系统哪里好一、Linux 驱动-总线-设备模型
1、驱动分层 Linux内核需要兼容多个平台#xff0c;不同平台的寄存器设计不同导致操作方法不同#xff0c;故内核提出分层思想#xff0c;抽象出与硬件无关的软件层作为核心层来管理下层驱动#xff0c;各厂商根据自己的硬件编写驱动…一、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_registerspi_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 dont 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 dont 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, weve 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 drivers 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: ufshci112b0000 {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);elseret 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
arch/arm64/kernel/head.S....../* 准备好C语言环境 */bl start_kernel
init/main.casmlinkage __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, well 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_namefull_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(Cant 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. Dont 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[31]; 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博客