本文为读书笔记,整理自网络文献和源码
7、wifi驱动解析
7.1、WIFI内核实现的大致框架
Linux下已经支持了市面上的大多数wifi卡的驱动
•每种wifi卡都是需要固件(firmware)才能驱动的,并且同一种卡工作在不同接口时对应的firmware是不一样的
•需要注意的是:很多firmware都是要花钱的
•固件通过其驱动下载到无线卡中,才能驱动起来
wifi驱动的通用的软件架构
1. 分为两部分,上面为主机端驱动,下面是我们之前所说的firmware
2. 其中固件部分的主要工作是:因为天线接受和发送回来的都是802.11帧的帧,而主机接受和传送出来的数据都必须是802.3的帧,所以必须由firmware来负责802.3的帧和802.11帧之间的转换,
3. 当天线收到数据,并被firmware处理好后会放在一个buffer里,并产生一个中断,主机在收到中断后就去读这个buffer。
7.2 平台总线下的wifi设备和驱动:
设备:
structplatform_device brcm_device_wlan = {
.name = "bcmdhd_wlan",
.id = 1,
.num_resources = ARRAY_SIZE(brcm_wlan_resources),
.resource = brcm_wlan_resources,
.dev = {
.platform_data =&brcm_wlan_control,
},
};
驱动:
staticstruct platform_driver wifi_device = {
.probe = wifi_probe,
.remove = wifi_remove,
.suspend = wifi_suspend,
.resume = wifi_resume,
.driver = {
.name = "bcmdhd_wlan",
}
};
platform_driver_register(&wifi_device);
platform_driver_register(&wifi_device)被调用流程:
late_initcall(dhd_module_init);
dhd_module_init
àwl_android_wifictrl_func_add
àwifi_add_dev
àplatform_driver_register(&wifi_device)
7.3 Sdio总线下的wifi设备和驱动:
drivers/net/wireless/bcmdhd/bcmsdh_linux.c
late_initcall(dhd_module_init);
dhd_module_init
àdhd_bus_register() // Dhd_sdio.c
àbcmsdh_register(&dhd_sdio); //Bcmsdh_linux.c
à sdio_function_init// bcmsdh_sdmmc_linux.c
àsdio_register_driver // Sdio_bus.c
à driver_register(&drv->drv);
à bus_add_driver(drv);
àdriver_add_groups(drv,drv->groups);
或者:
#ifdef BCMSDH_MODULE
module_init(bcmsdh_module_init);
bcmsdh_module_init(void)
à sdio_function_init
àsdio_register_driver
……(同上)
Sdio设备的驱动由sdio_driver结构体定义,sdio_register_driver函数将该设备驱动挂载到sdio_bus_type总线上。
/* * SDIOfunction device driver */
structsdio_driver {
char *name; //设备名
const struct sdio_device_id *id_table; //设备驱动ID
int (*probe)(struct sdio_func *, conststruct sdio_device_id *); //匹配函数
void (*remove)(struct sdio_func *);
struct device_driver drv;
};
/* bcmsdh_sdmmc_linux.c*/
staticstruct sdio_driver bcmsdh_sdmmc_driver = {
.probe =bcmsdh_sdmmc_probe,
.remove =bcmsdh_sdmmc_remove,
.name ="bcmsdh_sdmmc",
.id_table = bcmsdh_sdmmc_ids,
#if(LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 39)) &&defined(CONFIG_PM)
.drv = {
.pm = &bcmsdh_sdmmc_pm_ops,
},
#endif /*(LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 39)) &&defined(CONFIG_PM) */
};
error = sdio_register_driver(&bcmsdh_sdmmc_driver);
/**
* sdio_register_driver- register a function driver
* @drv:SDIO function driver
*/
intsdio_register_driver(struct sdio_driver *drv)
{
drv->drv.name = drv->name;
drv->drv.bus = &sdio_bus_type; //设置driver的bus为sdio_bus_type
returndriver_register(&drv->drv);
}
staticstruct bus_type sdio_bus_type = {
.name ="sdio",
.dev_attrs = sdio_dev_attrs,
.match =sdio_bus_match,
.uevent =sdio_bus_uevent,
.probe =sdio_bus_probe,
.remove =sdio_bus_remove,
.pm = SDIO_PM_OPS_PTR,
};
注意:设备或者驱动注册到系统中的过程中,都会调用相应bus上的匹配函数来进行匹配合适的驱动或者设备,对于sdio设备的匹配是由sdio_bus_match和sdio_bus_probe函数来完成。
sdio_bus_match(structdevice *dev, struct device_driver *drv)
àsdio_match_device(func, sdrv)
àsdio_match_one(func, ids)
通过匹配id_table 和 sdio_driver设备驱动中id,来匹配合适的驱动或设备, 如果匹配成功那么会最终会调用.probe函数,来完成相关操作。
sdio_bus_probe
àsdio_match_device(func, drv);
àret = drv->probe(func, id); //调用wifi驱动的probe函数,也就是bcmsdh_sdmmc_linux.c 中的bcmsdh_sdmmc_probe
bcmsdh_sdmmc_probe函数分析
bcmsdh_sdmmc_probe
àbcmsdh_probe
àbcmsdh_attach
à sdioh_attach
à sdioh_sdmmc_card_enablefuncs(sd)
à sdio_enable_func(gInstance->func[1])//使能sdio功能设备
到此wifi作为sdio设备的初始化完毕,sdio通道打通。后面就主要针对的是wifi作为网络设备初始化。
7.4 Wifi网络功能的初始化
Linux网络设备驱动中的重要数据结构:struct net_device和struct net_device_ops
在struct net_device_ops结构中声明了:
网络设备注册,启用,停止,发送数据帧,选择网卡队列(对于支持网卡多队列的),设置网络设备mac地址(如果此接口没有被定义将不会修改mac地址,那说明驱动要实现,网卡都需要支持才行,不过现在基本都支持),验证网络设备的mac地址,改变网络设备的MTU(如果该接口没有被定义则返回error,那说明驱动要实现,网卡要支持才行,不过现在基本都支持),获取网络设备的使用统计状态net_device_stats(比如统计发了多少个数据包,接受了多少个数据包,发了多少字节,接受了多少字节,多少坏包等等,详解下面net_device_stats数据结构,如果驱动没有定义此接口,dev->statswill
be used.)
在:
staticstruct net_device_ops dhd_ops_pri = {
.ndo_open = dhd_open,
.ndo_stop = dhd_stop,
.ndo_get_stats = dhd_get_stats,
.ndo_do_ioctl = dhd_ioctl_entry,
.ndo_start_xmit = dhd_start_xmit,
.ndo_set_mac_address =dhd_set_mac_address,
.ndo_set_multicast_list =dhd_set_multicast_list,
};
staticstruct net_device_ops dhd_ops_virt = {
.ndo_get_stats = dhd_get_stats,
.ndo_do_ioctl = dhd_ioctl_entry,
.ndo_start_xmit = dhd_start_xmit,
.ndo_set_mac_address =dhd_set_mac_address,
.ndo_set_multicast_list =dhd_set_multicast_list,
};
staticconst struct net_device_ops wl_cfgp2p_if_ops = {
.ndo_open = wl_cfgp2p_if_open,
.ndo_stop = wl_cfgp2p_if_stop,
.ndo_do_ioctl = wl_cfgp2p_do_ioctl,
.ndo_start_xmit = wl_cfgp2p_start_xmit,
};
staticconst struct net_device_ops dhd_mon_if_ops = {
.ndo_open = dhd_mon_if_open,
.ndo_stop = dhd_mon_if_stop,
.ndo_start_xmit = dhd_mon_if_subif_start_xmit,
.ndo_set_multicast_list =dhd_mon_if_set_multicast_list,
.ndo_set_mac_address = dhd_mon_if_change_mac,
};
其中dhd_ops_pri的初始化流程:
……
dhd_bus_register
àbcmsdh_register(&dhd_sdio);
static bcmsdh_driver_t dhd_sdio ={
dhdsdio_probe,
dhdsdio_disconnect
};
其中:dhdsdio_probe
àdhd_net_attach
à net->netdev_ops =&dhd_ops_virt;
或者
ànet->netdev_ops =&dhd_ops_pri;
将dhd_ops_pri赋给netdev_ops
intbcmsdh_probe(struct device *dev)
{
……
if(!(sdhc->ch = drvinfo.attach((vendevid>> 16),
(vendevid& 0xFFFF), 0, 0, 0, 0,
(void *)regs,NULL, sdh))) {
printk("%s: deviceattach failed\n", __FUNCTION__);
goto err;
}
……
}