sdio wifi驱动的加载流程

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)函数。

你可能感兴趣的:(sdio wifi驱动的加载流程)