硬件:IMX6Q
系統:Linux 4.1.15
一.驅動初始化
Dhd_linux.c (drivers\net\wireless\bcmdhd)
dhd_module_init調用dhd_wifi_platform_register_drv再到wifi_ctrlfunc_register_drv
wifi_ctrlfunc_register_drv:
static int wifi_ctrlfunc_register_drv(void) { wifi_adapter_info_t *adapter; /* multi-chip support not enabled, build one adapter information for * DHD (either SDIO, USB or PCIe) */ adapter = kzalloc(sizeof(wifi_adapter_info_t), GFP_KERNEL); if (adapter == NULL) { DHD_ERROR(("%s:adapter alloc failed", __FUNCTION__)); return -ENOMEM; } //分配初始化一個adapter adapter->name = "DHD generic adapter"; adapter->bus_type = -1; adapter->bus_num = -1; adapter->slot_num = -1; adapter->irq_num = -1; is_power_on = FALSE; wifi_plat_dev_probe_ret = 0; dhd_wifi_platdata = kzalloc(sizeof(bcmdhd_wifi_platdata_t), GFP_KERNEL); dhd_wifi_platdata->num_adapters = 1; dhd_wifi_platdata->adapters = adapter; init_waitqueue_head(&adapter->status_event); #if !defined(CONFIG_DTS) if (dts_enabled) { struct resource *resource; adapter->wifi_plat_data = (void *)&dhd_wlan_control; //操作結構體 resource = &dhd_wlan_resources; #ifdef CUSTOMER_HW /*調用dhd_wlan_init_gpio獲取DTS里面的gpio_wl_reg_on和gpio_wl_host_wake引腳 bcmdhd { compatible = "android,bcmdhd_wlan"; gpio_wl_reg_on = <&gpio2 24 GPIO_ACTIVE_HIGH>; gpio_wl_host_wake = <&gpio2 23 GPIO_ACTIVE_HIGH>; }; */ wifi_plat_dev_probe_ret = dhd_wlan_init_plat_data(); if (wifi_plat_dev_probe_ret) return wifi_plat_dev_probe_ret; #endif adapter->irq_num = resource->start; adapter->intr_flags = resource->flags & IRQF_TRIGGER_MASK; //對於不同接口,包括usb,sdio,pcie的wifi進行加載,單獨分析1 wifi_plat_dev_probe_ret = dhd_wifi_platform_load(); } #endif /* !defined(CONFIG_DTS) */ /* return probe function's return value if registeration succeeded */ return wifi_plat_dev_probe_ret; }
單獨分析1
/* 對於不同接口,包括usb,sdio,pcie的wifi進行加載 */
static int dhd_wifi_platform_load() { //Netlink初始化 wl_android_init(); if ((err = dhd_wifi_platform_load_usb())) goto end; else if ((err = dhd_wifi_platform_load_sdio())) goto end; else err = dhd_wifi_platform_load_pcie(); return err; }
我們這里只分析sdio的dhd_wifi_platform_load_sdio
主要是給所有adapters上電,然后匹配func
static int dhd_wifi_platform_load_sdio(void) { /* power up all adapters 給所有adapter上電*/ for (i = 0; i < dhd_wifi_platdata->num_adapters; i++) { bool chip_up = FALSE; int retry = POWERUP_MAX_RETRY; struct semaphore dhd_chipup_sem; adapter = &dhd_wifi_platdata->adapters[i]; do { sema_init(&dhd_chipup_sem, 0); /* 注冊一個虛擬的SDIO客戶端驅動程序,以便收到新的SDIO設備的通知 * 里面會注sdio_register_driver(&dummy_sdmmc_driver),注冊一個dummy_sdmmc_driver * 會匹配func,如果匹配不成功不會執行dummy_probe,會初始化失敗,所以要 * 保證sdio host讀取到了設備,添加了func。 */ err = dhd_bus_reg_sdio_notify(&dhd_chipup_sem); //給wifi上電 err = wifi_platform_set_power(adapter, TRUE, WIFI_TURNON_DELAY); if (err) { } else { wifi_platform_bus_enumerate(adapter, TRUE); } /* 等待dhd_bus_reg_sdio_notify注冊成功,里面會up(notify_semaphore) * 然后執行chip_up = TRUE,跳出循環 */ if (down_timeout(&dhd_chipup_sem, msecs_to_jiffies(POWERUP_WAIT_MS)) == 0) { printk("cxw dhd_wifi_platform_load_sdio down_timeout TRUE ......\n"); dhd_bus_unreg_sdio_notify(); chip_up = TRUE; break; } } } //注冊sdio_register_driver(&bcmsdh_sdmmc_driver),單獨分析2 err = dhd_bus_register(); return err; }
單獨分析2
dhd_bus_register主要是注冊sdio驅動sdio_register_driver(&dummy_sdmmc_driver);
/* devices we support, null terminated */ static const struct sdio_device_id bcmsdh_sdmmc_ids[] = { { SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, SDIO_DEVICE_ID_BROADCOM_DEFAULT) }, { SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, BCM4362_CHIP_ID) }, { SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, BCM43751_CHIP_ID) }, { SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, BCM43752_CHIP_ID) }, { SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, BCM43012_CHIP_ID) }, { SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, BCM43014_CHIP_ID) }, { SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, BCM43014_D11N_ID) }, { SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, BCM43014_D11N2G_ID) }, { SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, BCM43014_D11N5G_ID) }, /* { SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, SDIO_ANY_ID) }, */ { SDIO_DEVICE_CLASS(SDIO_CLASS_NONE) }, /* end: all zeroes */ { 0, 0, 0, 0}, }; static struct sdio_driver dummy_sdmmc_driver = { .probe = dummy_probe, .remove = dummy_remove, .name = "dummy_sdmmc", .id_table = bcmsdh_sdmmc_ids, };
通過總線驅動sdio_bus_match匹配,主要匹配三個參數ids->class || ids->vendor || ids->device。
匹配成功后調用bcmsdh_sdmmc_probe 》 sdioh_probe 》 bcmsdh_probe
void* bcmsdh_probe(osl_t *osh, void *dev, void *sdioh, void *adapter_info, uint bus_type, uint bus_num, uint slot_num) { /* 將BCMSDH層附加到SDIO主機控制器驅動程序 */ bcmsdh = bcmsdh_attach(osh, sdioh, ®s); if (bcmsdh == NULL) { SDLX_ERR(("%s: bcmsdh_attach failed\n", __FUNCTION__)); goto err; } bcmsdh_osinfo = MALLOC(osh, sizeof(bcmsdh_os_info_t)); if (bcmsdh_osinfo == NULL) { SDLX_ERR(("%s: failed to allocate bcmsdh_os_info_t\n", __FUNCTION__)); goto err; } bzero((char *)bcmsdh_osinfo, sizeof(bcmsdh_os_info_t)); bcmsdh->os_cxt = bcmsdh_osinfo; bcmsdh_osinfo->sdioh = sdioh; bcmsdh_osinfo->dev = dev; osl_set_bus_handle(osh, bcmsdh); #if defined(OOB_INTR_ONLY) spin_lock_init(&bcmsdh_osinfo->oob_irq_spinlock); /* Get customer specific OOB IRQ parametres: IRQ number as IRQ type */ bcmsdh_osinfo->oob_irq_num = wifi_platform_get_irq_number(adapter_info, &bcmsdh_osinfo->oob_irq_flags); if (bcmsdh_osinfo->oob_irq_num < 0) { SDLX_ERR(("%s: Host OOB irq is not defined\n", __FUNCTION__)); goto err; } #endif /* defined(BCMLXSDMMC) */ /* Read the vendor/device ID from the CIS */ vendevid = bcmsdh_query_device(bcmsdh); /* try to attach to the target device */ bcmsdh_osinfo->context = drvinfo.probe((vendevid >> 16), (vendevid & 0xFFFF), bus_num, slot_num, 0, bus_type, (void *)regs, osh, bcmsdh); if (bcmsdh_osinfo->context == NULL) { SDLX_ERR(("%s: device attach failed\n", __FUNCTION__)); goto err; } return bcmsdh; }
這里會調用drvinfo.probe,也就是dhdsdio_probe,前面有賦值
這里是重要部分了,涉及網絡相關的內容了
dhdsdio_probe(uint16 venid, uint16 devid, uint16 bus_no, uint16 slot, uint16 func, uint bustype, void *regsva, osl_t * osh, void *sdh) { /* attempt to attach to the dongle 嘗試連接到 dongle, * 設置是poll還是中斷模式,默認使用中斷 */ if (!(dhdsdio_probe_attach(bus, osh, sdh, regsva, devid))) { DHD_ERROR(("%s: dhdsdio_probe_attach failed\n", __FUNCTION__)); goto fail; } /* Attach to the dhd/OS/network interface 綁定到dhd/OS/網口 */ if (!(bus->dhd = dhd_attach(osh, bus, SDPCM_RESERVE))) { DHD_ERROR(("%s: dhd_attach failed\n", __FUNCTION__)); goto fail; } /* Allocate buffers 分配SDIO用的BUF */ if (!(dhdsdio_probe_malloc(bus, osh, sdh))) { DHD_ERROR(("%s: dhdsdio_probe_malloc failed\n", __FUNCTION__)); goto fail; } /* SDIO的一些特行初始化 */ if (!(dhdsdio_probe_init(bus, osh, sdh))) { DHD_ERROR(("%s: dhdsdio_probe_init failed\n", __FUNCTION__)); goto fail; } if (bus->intr) { /* Register interrupt callback, but mask it (not operational yet). */ DHD_INTR(("%s: disable SDIO interrupts (not interested yet)\n", __FUNCTION__)); bcmsdh_intr_disable(sdh); /* 設置中斷,當有中斷來的時候調用dhdsdio_isr */ if ((ret = bcmsdh_intr_reg(sdh, dhdsdio_isr, bus)) != 0) { DHD_ERROR(("%s: FAILED: bcmsdh_intr_reg returned %d\n", __FUNCTION__, ret)); goto fail; } DHD_INTR(("%s: registered SDIO interrupt function ok\n", __FUNCTION__)); } else { DHD_INFO(("%s: SDIO interrupt function is NOT registered due to polling mode\n", __FUNCTION__)); } DHD_INFO(("%s: completed!!\n", __FUNCTION__)); /* Ok, have the per-port tell the stack we're open for business 告訴我這個網絡棧,可以工作了 */ if (dhd_attach_net(bus->dhd, TRUE) != 0) { DHD_ERROR(("%s: Net attach failed!!\n", __FUNCTION__)); goto fail; } return NULL; }
我們先分析下dhd_attach
對DHD管理的每個硬件(狗)實例調用一次
dhd_attach(osl_t *osh, struct dhd_bus *bus, uint bus_hdrlen #ifdef BCMDBUS , void *data #endif ) { /* Passing NULL to dngl_name to ensure host gets if_name in dngl_name member * ifp->net = alloc_etherdev(DHD_DEV_PRIV_SIZE)分配etherdev,包括私有結構空間 * 取消注冊並釋放iflist和iflist中現有的net_device接口(如果有的話)分配一個新的。 * 槽位被重用。此函數不注冊Linux內核的新接口。Dhd_register_if負責這項工作 */ net = dhd_allocate_if(&dhd->pub, 0, if_name, NULL, 0, TRUE, NULL); if (net == NULL) { goto fail; } #if defined(RXFRAME_THREAD) dhd->rxthread_enabled = TRUE; #endif /* defined(RXFRAME_THREAD) */ /* Attach and link in the protocol */ if (dhd_prot_attach(&dhd->pub) != 0) { DHD_ERROR(("dhd_prot_attach failed\n")); goto fail; } dhd_state |= DHD_ATTACH_STATE_PROT_ATTACH; #ifdef WL_CFG80211 spin_lock_init(&dhd->pub.up_lock); /* Attach and link in the cfg80211 把設備注冊到cfg80211, * 操作函數是wl_cfg80211_ops, 注冊到rfkill*/ if (unlikely(wl_cfg80211_attach(net, &dhd->pub))) { DHD_ERROR(("wl_cfg80211_attach failed\n")); goto fail; } #if defined(WL_WIRELESS_EXT) /* Attach and link in the iw 加入到iw里面*/ if (wl_iw_attach(net, &dhd->pub) != 0) { DHD_ERROR(("wl_iw_attach failed\n")); goto fail; } dhd_state |= DHD_ATTACH_STATE_WL_ATTACH; #endif /* defined(WL_WIRELESS_EXT) */ /* Set up the bottom half handler */ if (dhd_dpc_prio >= 0) { /* Initialize DPC thread * Deferred Procedure Call 延遲函數,也就是中斷后半部 */ PROC_START(dhd_dpc_thread, dhd, &dhd->thr_dpc_ctl, 0, "dhd_dpc"); if (dhd->thr_dpc_ctl.thr_pid < 0) { goto fail; } } else { /* use tasklet for dpc */ tasklet_init(&dhd->tasklet, dhd_dpc, (ulong)dhd); dhd->thr_dpc_ctl.thr_pid = -1; } if (dhd->rxthread_enabled) { bzero(&dhd->pub.skbbuf[0], sizeof(void *) * MAXSKBPEND); /* Initialize RXF thread 網絡包接收線程 */ PROC_START(dhd_rxf_thread, dhd, &dhd->thr_rxf_ctl, 0, "dhd_rxf"); if (dhd->thr_rxf_ctl.thr_pid < 0) { goto fail; } } #endif /* !BCMDBUS */ return &dhd->pub; }
我們再來分析一下dhd_attach_net
dhd_attach_net(dhd_pub_t *dhdp, bool need_rtnl_lock) { struct net_device *primary_ndev; /* Register primary net device , 這里的need_rtnl_lock=true */ if (dhd_register_if(dhdp, 0, need_rtnl_lock) != 0) { return BCME_ERROR; } #if defined(WL_CFG80211) primary_ndev = dhd_linux_get_primary_netdev(dhdp); /* 加入到cfg80211里面,cfg80211是Linux 802.11用於管理配置的一套API, * 它是用戶和驅動之間的橋梁,替代了WEXT,提供和802.11相關的功能*/ if (wl_cfg80211_net_attach(primary_ndev) < 0) { /* fail the init */ dhd_remove_if(dhdp, 0, TRUE); return BCME_ERROR; } #endif /* WL_CFG80211 */ return BCME_OK; }
重點是dhd_register_if
dhd_register_if(dhd_pub_t *dhdp, int ifidx, bool need_rtnl_lock) { ifp = dhd->iflist[ifidx]; /* 首先從剛才添加的接口列表中取出net,然后進行下面的系列初始化工作*/ net = ifp->net; / net->netdev_ops = &dhd_ops_virt; /* Ok, link into the network layer... */ if (ifidx == 0) { /* * device functions for the primary interface only * 網絡設備注冊,啟用,停止,發送數據幀,選擇網卡隊列 *(對於支持網卡多隊列的),設置網絡設備mac地址 */ net->netdev_ops = &dhd_ops_pri; if (!ETHER_ISNULLADDR(dhd->pub.mac.octet)) memcpy(temp_addr, dhd->pub.mac.octet, ETHER_ADDR_LEN); } else { /* * We have to use the primary MAC for virtual interfaces */ memcpy(temp_addr, ifp->mac_addr, ETHER_ADDR_LEN); /* * Android sets the locally administered bit to indicate that this is a * portable hotspot. This will not work in simultaneous AP/STA mode, * nor with P2P. Need to set the Donlge's MAC address, and then use that. */ if (!memcmp(temp_addr, dhd->iflist[0]->mac_addr, ETHER_ADDR_LEN)) { DHD_ERROR(("%s interface [%s]: set locally administered bit in MAC\n", __func__, net->name)); temp_addr[0] |= 0x02; } } net->hard_header_len = ETH_HLEN + dhd->pub.hdrlen; /* ethtool操作函數,ethtool 是用於查詢及設置網卡參數的命令。*/ net->ethtool_ops = &dhd_ethtool_ops; #if defined(WL_WIRELESS_EXT) #if WIRELESS_EXT < 19 net->get_wireless_stats = dhd_get_wireless_stats; #endif /* WIRELESS_EXT < 19 */ #if WIRELESS_EXT > 12 /* 這里的初始化工作很重要,之后的ioctl流程會涉及到對它的使用 */ net->wireless_handlers = &wl_iw_handler_def; #endif /* WIRELESS_EXT > 12 */ #endif /* defined(WL_WIRELESS_EXT) */ dhd->pub.rxsz = DBUS_RX_BUFFER_SIZE_DHD(net); memcpy(net->dev_addr, temp_addr, ETHER_ADDR_LEN); if (ifidx == 0) printf("%s\n", dhd_version); else { #ifdef WL_EXT_IAPSTA wl_ext_iapsta_update_net_device(net, ifidx); #endif /* WL_EXT_IAPSTA */ if (dhd->pub.up == 1) { /* 設置mac地址 */ if (_dhd_set_mac_address(dhd, ifidx, net->dev_addr, FALSE) == 0) DHD_INFO(("%s: MACID is overwritten\n", __FUNCTION__)); else DHD_ERROR(("%s: _dhd_set_mac_address() failed\n", __FUNCTION__)); } } if (need_rtnl_lock) /* 注冊net設備*/ err = register_netdev(net); else err = register_netdevice(net); printf("Register interface [%s] MAC: "MACDBG"\n\n", net->name, MAC2STRDBG(net->dev_addr)); net->netdev_ops = NULL; return err; }
參考:
wifi底層學習之路:
wifi底層學習之路:
wifi詳解:
