1、为何/sys中driver的uevent文件只写?
该文件用于调试udev或者人为引起uevent的发送,执行类似:
$ echo add > /sys/…/driver/…/uevent
命令时,会模拟一个uevent(ADD)事件,在kernel中,该文件的属性设置为只写。
小结:
bus、driver的uevent文件只写
device(包括bus设备)的uevent文件可读可写
2、用户空间中驱动模块的处理逻辑:
核心处理函数是handle_module_loading() –>
get_module_dep(modalias, NULL, 1, MODULES_BLKLST, &dep);
parse_alias_to_list("/lib/modules/modules.alias", &alias_list); // 解析/lib/modules/modules.alias文件中模块别名到一个特定的链表alias_list
parse_blacklist_to_list(blacklist, &extra_blacklist); // 解析/lib/modules/modules.blacklist所指定的模块黑名单到一个特定的 链表extra_blacklist
load_dep_file(dep_name); // 加载/lib/modules/modules.dep中的模块依赖清单到内存
get_module_name_from_alias(module_name, &module_aliases, &alias_list) // 检查从uevent接收到的模块名称是否是一个别名
validate_module(module_name, dep_file, &extra_blacklist, dep); // 检查该模块是否在黑名单中
get_mod_args() // 遍历lmod_args链表,从中找到与当前模块匹配的模块参数(用名称进行匹配)
insmod_s() // 加载模块
3、系统启动时,设备和驱动的一般加载过程:
1>系统启动后,注册设备,创建/sys相关文件
PS:注册设备时,会发送uevent(ADD) ,但是此时/sbin/ueventd进程尚未运行
2>执行/sbin/ueventd进程,在用户空间利用/sys/devices/xx/uevent文件,模拟设备的热插拔事件uevent(ADD)
3>用户空间收到该uevent(ADD)事件,加载对应驱动
4、wlan设备在kernel中的存在实体既是sdio_func,又是platform_device,
dhd_module_init()是wlan驱动注册的入口:
1>platform_device的驱动注册
dhd_module_init()
wl_android_wifictrl_func_add()
wifi_add_dev()
platform_driver_register(&wifi_device);
platform_driver_register(&wifi_device_legacy);
2>驱动抽象以及匹配ID(ACPI和设备树)
static struct platform_driver wifi_device = {
.probe = wifi_probe,
.remove = wifi_remove,
.suspend = wifi_suspend,
.resume = wifi_resume,
.driver = {
#ifdef CONFIG_ACPI
.acpi_match_table = ACPI_PTR(bcm_acpi_id),
#endif
.name = "wlan",
.of_match_table = wifi_device_dt_match,
}
};
static struct acpi_device_id bcm_acpi_id[] = {
/* ACPI IDs here */
{ "BCM43241" },
{ }
};
MODULE_DEVICE_TABLE(acpi, bcm_acpi_id);
static const struct of_device_id wifi_device_dt_match[] = {
{ .compatible = "android,bcmdhd_wlan", },
{},
};
MODULE_DEVICE_TABLE(of, wifi_device_dt_match);
3>platform总线上设备和驱动的匹配过程
struct bus_type platform_bus_type = {
.name = "platform",
.dev_attrs = platform_dev_attrs,
.match = platform_match,
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
}
platform_match()
of_driver_match_device() // 设备树
acpi_driver_match_device() // ACPI
acpi_match_device(drv->acpi_match_table, dev);
__acpi_match_device()
platform_match_id() // 平台总线本身ID
4> ACPI对ID匹配的处理
static struct acpi_scan_handler platform_handler = {
.ids = acpi_platform_device_ids,
.attach = acpi_create_platform_device,
};
static const struct acpi_device_id acpi_platform_device_ids[] = {
{ "PNP0D40" },
{ "BCM43241" },
…
{ }
};
acpi_init()
acpi_scan_init()
acpi_platform_init()
acpi_scan_add_handler(&platform_handler); // 将包含wlan设备ID的acpi_device_id节点添加到acpi_scan_handlers_list链表
list_add_tail(&handler->list_node, &acpi_scan_handlers_list);
acpi_bus_scan(ACPI_ROOT_OBJECT);
acpi_bus_device_attach()
acpi_bus_device_attach()
acpi_scan_attach_handler()
acpi_scan_match_handler()
acpi_scan_handler_matching()
匹配成功:
handler->attach(device, devid);
acpi_create_platform_device() // Create platform device for ACPI device node - create and register a platform device
注意:匹配成功的前提是,acpi相关代码中定义的wlan设备的acpi id与wlan驱动模块bcm43241.ko中定义的acpi id相同,但此时还未加载wlan驱动(bcm43241.ko),
因此,用户空间收到的uevent(ADD)事件不可能是wlan acpi发送上来的。
--------------------------------------------------------------------------------------------------------------------------------------------------
1>sdio_func的驱动注册
dhd_module_init()
dhd_bus_register()
bcmsdh_register()
sdio_function_init()
sdio_register_driver(&bcmsdh_sdmmc_driver);
2>驱动抽象以及匹配ID
static struct sdio_driver bcmsdh_sdmmc_driver = {
.probe = bcmsdh_sdmmc_probe,
.remove = bcmsdh_sdmmc_remove,
.name = "bcmsdh_sdmmc",
.id_table = bcmsdh_sdmmc_ids,
.drv = {
#if (LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 39)) && defined(CONFIG_PM)
.pm = &bcmsdh_sdmmc_pm_ops,
#endif /* (LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 39)) && defined(CONFIG_PM) */
.shutdown = bcmsdh_sdmmc_shutdown,
},
};
static const struct sdio_device_id bcmsdh_sdmmc_ids[] = {
{ SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, SDIO_DEVICE_ID_BROADCOM_4334) },
{ SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, SDIO_DEVICE_ID_BROADCOM_43340) },
{ SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, SDIO_DEVICE_ID_BROADCOM_4324) },
{ SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, SDIO_DEVICE_ID_BROADCOM_4335) },
{ SDIO_DEVICE_CLASS(SDIO_CLASS_NONE) },
{ /* end: all zeroes */ },
};
MODULE_DEVICE_TABLE(sdio, bcmsdh_sdmmc_ids);
3>mmc/sd/sdio子系统对设备、驱动的匹配处理
从mmc_rescan()入手,
INIT_DELAYED_WORK(&host->detect, mmc_rescan);
mmc_rescan()
mmc_rescan_try_freq()
mmc_attach_sdio()
sdio_alloc_func() // Allocate and initialise a new SDIO function structure.
mmc_add_card() // Register a new MMC card with the driver model.
sdio_add_func() // Register a new SDIO function with the driver model.
device_add()
kobject_uevent(&dev->kobj, KOBJ_ADD); // notify userspace by sending an uevent
--------------------------------------------------------------------------------------------------------------------------------------------------
wlan设备和驱动的完整处理逻辑:
1、mmc_rescan()延迟工作线程扫描到有wlan卡插入到sdio接口,则创建一个sdio_func结构,并将该sdio_func注册到sdio总线 ,
注册sdio_func的同时,会向用户空间发送uevent(ADD)事件。
与此同时,ACPI会根据acpi_platform_device_ids结构体数组,创建并注册wlan对应的platform_device,并向用户空间发送uevent(ADD)事件,
但该platform_device的创建并不能表明已有真实的wlan设备插入到sdio接口,因此,此时用户空间并不会加载bcm43241.ko模块。
2、用户空间的init进程(被/sbin/modprobe链接)接收到sdio_func的uevent(ADD)事件后,解析出该uevent(ADD)的模块别名是sdio_func的名称,
在modules.alias中定义了wlan的别名为该sdio_func的名称,因此匹配成功,加载wlan的驱动模块bcm43241.ko
3、在bcm43241模块中,会注册wlan对应的platform_driver和sdio_driver。
在这两种driver的注册过程中,还会利用平台总线和sdio总线的匹配原则(ID匹配)进行再次匹配。
4、实验验证:
1、用户空间对模块的加载处理逻辑?
是否直接利用创建设备时发送的uevent(ADD)加载?还是init(/sbin/modprobe)利用sysfs的uevent文件,模拟该ADD事件,以引起模块的加载。
结论:系统启动后,init利用sysfs的uevent文件,模拟该ADD事件,以引起驱动模块的加载。
因为sysfs中的uevent文件的创建在系统启动的时候,此时init进程还未执行。
2、如果是利用加载设备时发送的uevent(ADD),该uevent来自于哪个设备的注册?ACPI?SDIO?platform?
解决办法: 开启udev的调试模式,抓获事件的细节。
结论:看问题1
5、如果是利用sysfs的uevent文件,模拟该ADD事件,以引起驱动模块的加载。那么,使用的是哪一个uevent文件?
结论:看log,得出的结论是sdio_func的uevent文件。
-------------------------------------------------------------------------------------------------------------------------------------------------------------
最终结论:
1、在开机时,sdio子系统的mmc_rescan()函数检测到sdio wlan,该函数会为该sdio wlan分配一个sdio_func,
然后将该sdio_func注册到系统,注册的同时会在sysfs中创建uevent文件,然后向用户空间发送uevent(ADD)事件,
但“用户空间”无法针对该ADD事件作任何处理,因为此时用户空间init进程(被/sbin/modprobe链接)还未执行。
2、在编译系统源码的过程中,会针对.ko文件执行depmod命令,该工具会将模块中MODULE_DEVICE_TABLE(模块设备表,
表示该驱动支持哪些设备)导出到/lib/module/module.alias文件,wlan驱动对应有3个MODULE_DEVICE_TABLE设备表:
static struct acpi_device_id bcm_acpi_id[] = {
/* ACPI IDs here */
{ "BCM43241" },
{ }
};
MODULE_DEVICE_TABLE(acpi, bcm_acpi_id);
static const struct of_device_id wifi_device_dt_match[] = {
{ .compatible = "android,bcmdhd_wlan", },
{},
};
MODULE_DEVICE_TABLE(of, wifi_device_dt_match);
static const struct sdio_device_id bcmsdh_sdmmc_ids[] = {
{ SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, SDIO_DEVICE_ID_BROADCOM_4334) },
{ SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, SDIO_DEVICE_ID_BROADCOM_43340) },
{ SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, SDIO_DEVICE_ID_BROADCOM_4324) },
{ SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, SDIO_DEVICE_ID_BROADCOM_4335) },
{ SDIO_DEVICE_CLASS(SDIO_CLASS_NONE) },
{ /* end: all zeroes */ },
};
MODULE_DEVICE_TABLE(sdio, bcmsdh_sdmmc_ids);
导出到/lib/module/module.alias文件中的内容为:
# Aliases extracted from modules themselves.
alias acpi*:BCM43241:* bcm43241
alias of:N*T*Candroid,bcmdhd_wlan* bcm43241
alias sdio:c00v*d* bcm43241
alias sdio:c*v02D0d4335* bcm43241
alias sdio:c*v02D0d4324* bcm43241
alias sdio:c*v02D0dA94D* bcm43241
alias sdio:c*v02D0d4334* bcm43241
…
3、启动init进程后,该进程会扫描/sys/devices/xx/uevent文件,然后向其中写入“ADD”/“add”,
接下来会引起一个uevent(ADD)事件的广播,对于wlan设备,该uevent事件来自于sdio_func的
uevent处理函数,init进程会接收到该ADD事件,并解析该uevent事件。然后将该uevent包含的
alias和/lib/module/module.alias文件中的alias条目进行匹配,若匹配成功,则加载对应驱动模块。
对应wlan设备,会匹配alias sdio:c00v*d* bcm43241,然后加载bcm43241.ko驱动模块。
4、关于sdio_func本身的名称来由?
在注册sdio_func时,会设置该func的名称,命名规则是“mmc_card_id(func->card): func->num”
具体可参看sdio_add_func(struct sdio_func *func)函数。