學習android wifi開發已經一周了,今天開始立帖,將每天的學習成果貼出來,以備以後查閱,從framework到wpa_supplicant的適配層(wifi.c)網上介紹的文章很多,而且本身也並不複雜,其中framework部分需要注意的是wifiService和wifiMoniter兩部分,這兩快一個是轉寄AP的CMD另一個是接收來自wpa_supplicant的CMD。他們與本地庫的串連都是通過JNI方法,具體實現方法在android_net_wifi_Wifi.cpp中。在這個檔案中可以大致看出AP會給wpa_supplicant下哪些命令。這些命令通過wifi.c的wifi_command發送給wpa_supplicant,在發送命令的過程中實際是調用wpa_ctrl_request來完成命令發送的,wpa_ctrl_request是通過socket的方式與wpa_supplicant進行通訊的,然後通過wpa_ctrl_recv來接收來自wpa_supplicant的命令,並返回標識給wifi_wait_for_event。 但是命令發到wpa_supplicant後的流程網上提到的資料就非常少了,不過由於wpa_supplicant是一個標準的開源項目,已經被移植到很多平台上,它中間的過程我暫時還沒有去細看。比較關心的是wpa_supplicant在接收到上層的命令後是怎麼將命令發給DRIVER的,DRIVER在接收到命令後的解析的動作以及之後調用驅動功能函數的流程以及驅動對寄存器控制的細節。由於需要注意代碼保密,之後不會提及具體使用了哪塊WIFI晶片也不會提及此WIFI DRIVER是在什麼平台什麼產品。 先貼一張wpa_supplicant的標準結構框圖: 重點關注框圖的下半部分,即wpa_supplicant是如何與DRIVER進行聯絡的。整個過程暫以AP發出SCAN命令為主線。由於現在大部分WIFI DRIVER都支援wext,所以就假設我們的裝置走的是wext這條線,其實用ndis也一樣,流程感覺差不多。 首先要說的是,在Driver.h檔案中有個結構體wpa_driver_ops: /** * struct wpa_driver_ops - Driver interface API definition * * This structure defines the API that each driver interface needs to implement * for core wpa_supplicant code. All driver specific functionality is captured * in this wrapper. */struct wpa_driver_ops 這個結構體在Driver.c中被聲明為: #ifdef CONFIG_DRIVER_WEXTextern struct wpa_driver_ops wpa_driver_wext_ops; /* driver_wext.c */ 然後在driver_wext.c填寫了結構體的成員, const struct wpa_driver_ops wpa_driver_wext_ops = { .name = "wext", .desc = "Linux wireless extensions (generic)", .get_bssid = wpa_driver_wext_get_bssid, .get_ssid = wpa_driver_wext_get_ssid, .set_wpa = wpa_driver_wext_set_wpa, .set_key = wpa_driver_wext_set_key, .set_countermeasures = wpa_driver_wext_set_countermeasures, .set_drop_unencrypted = wpa_driver_wext_set_drop_unencrypted, .scan = wpa_driver_wext_scan, .combo_scan = wpa_driver_wext_combo_scan, .get_scan_results2 = wpa_driver_wext_get_scan_results, .deauthenticate = wpa_driver_wext_deauthenticate, .disassociate = wpa_driver_wext_disassociate, .set_mode = wpa_driver_wext_set_mode, .associate = wpa_driver_wext_associate, .set_auth_alg = wpa_driver_wext_set_auth_alg, .init = wpa_driver_wext_init, .deinit = wpa_driver_wext_deinit, .add_pmkid = wpa_driver_wext_add_pmkid, .remove_pmkid = wpa_driver_wext_remove_pmkid, .flush_pmkid = wpa_driver_wext_flush_pmkid, .get_capa = wpa_driver_wext_get_capa, .set_operstate = wpa_driver_wext_set_operstate,#ifdef ANDROID .driver_cmd = wpa_driver_priv_driver_cmd,#endif}; 這些成員其實都是驅動和wpa_supplicant的介面,以SCAN為例: int wpa_driver_wext_scan(void *priv, const u8 *ssid, size_t ssid_len) 中的LINE1174:if (ioctl(drv->ioctl_sock, SIOCSIWSCAN, &iwr) < 0)從這裡可以看出wpa_cupplicant是通過IOCTL來調用SOCKET與DRIVER進行通訊的,並給DRIVER下達SIOCSIWSCAN這個命令。 這樣,一個命令從AP到FRAMEWORK到C++本地庫再到wpa_supplicant適配層,再由wpa_supplicant下CMD給DRIVER的路線就打通了,寫起來雖然不多但也是一點小成果。 時間過得很快,畢業已經三周了,後悔當初在實驗室沒有去學習關於WIFI的知識,現在只好從頭看起。好在公司環境比較輕鬆,可以有時間抓抓細節,後面就要開始將DRIVER部分的結構和流程理理清楚了。 由於在這個項目中,WIFI模組是採用SDIO匯流排來控制的,所以先記錄下CLIENT DRIVER的SDIO部分的結構,這部分的SDIO分為三層:SdioDrv、SdioAdapter、SdioBusDrv。其中SdioBusDrv是Client Driver中SDIO與WIFI模組的介面,SdioAdapter是SdioDrv和SdioBusDrv之間的適配層,SdioDrv是Client Driver中SDIO與LINUX KERNEL中的MMC SDIO的介面。這三部分只需要關注一下SdioDrv就可以了,另外兩層都只是對它的封裝罷了。 在SdioDrv中提供了這幾個功能: (1)static struct sdio_driver tiwlan_sdio_drv = { .probe = tiwlan_sdio_probe, .remove = tiwlan_sdio_remove, .name = "sdio_tiwlan", .id_table = tiwl12xx_devices,}; (2)int sdioDrv_EnableFunction(unsigned int uFunc) (3)int sdioDrv_EnableInterrupt(unsigned int uFunc) (4)SDIO的讀寫,實際是調用了MMC\Core中的 static int mmc_io_rw_direct_host()功能。 SDIO功能部分簡單瞭解下就可以,一般HOST部分晶片廠商都會做好。我的主要任務還是WIFI模組。 首先從WIFI模組的入口函數wlanDrvIf_ModuleInit()看起,這裡調用了wlanDrvIf_Create()。 代碼主體部分: static int wlanDrvIf_Create (void){ TWlanDrvIfObj *drv; //這個結構體為代表裝置,包含LINUX網路裝置結構體net_device pDrvStaticHandle = drv; /* save for module destroy */ drv->pWorkQueue = create_singlethread_workqueue (TIWLAN_DRV_NAME);//建立了工作隊列 /* Setup driver network interface. */ rc = wlanDrvIf_SetupNetif (drv); //這個函數超級重要,後面詳細的看 drv->wl_sock = netlink_kernel_create( NETLINK_USERSOCK, 0, NULL, NULL, THIS_MODULE ); // 建立了接受wpa_supplicant的SOCKET介面 /* Create all driver modules and link their handles */ rc = drvMain_Create (drv, &drv->tCommon.hDrvMain, &drv->tCommon.hCmdHndlr, &drv->tCommon.hContext, &drv->tCommon.hTxDataQ, &drv->tCommon.hTxMgmtQ, &drv->tCommon.hTxCtrl, &drv->tCommon.hTWD, &drv->tCommon.hEvHandler, &drv->tCommon.hCmdDispatch, &drv->tCommon.hReport, &drv->tCommon.hPwrState); /* * Initialize interrupts (or polling mode for debug): */ /* Normal mode: Interrupts (the default mode) */ rc = hPlatform_initInterrupt (drv, (void*)wlanDrvIf_HandleInterrupt); return 0; } 在調用完wlanDrvIf_Create()這個函數後,實際上WIFI模組的初始化就結束了,下面分析如何初始化的。先看wlanDrvIf_SetupNetif (drv)這個函數的主體, static int wlanDrvIf_SetupNetif (TWlanDrvIfObj *drv){ struct net_device *dev; int res; /* Allocate network interface structure for the driver */ dev = alloc_etherdev (0);//申請LINUX網路裝置 if (dev == NULL) /* Setup the network interface */ ether_setup (dev);//建立網路介面 ,這兩個都是LINUX網路裝置驅動的標準函數 dev->netdev_ops = &wlan_netdev_ops; /* Initialize Wireless Extensions interface (WEXT) */ wlanDrvWext_Init (dev); res = register_netdev (dev); /* Setup power-management callbacks */ hPlatform_SetupPm(wlanDrvIf_Suspend, wlanDrvIf_Resume, pDrvStaticHandle); } 注意,在這裡初始化了wlanDrvWext_Inti(dev),這就說明wpa_supplicant與Driver直接的聯絡是走的WEXT這條路。也就是說event的接收,處理也應該是在WEXT部分來做的,確定這個,剩下的工作量頓減三分之一,哈哈哈。後面還註冊了網路裝置dev。而在wlan_netdev_ops中定義的功能如下: static const struct net_device_ops wlan_netdev_ops = { .ndo_open = wlanDrvIf_Open, .ndo_stop = wlanDrvIf_Release, .ndo_do_ioctl = NULL, .ndo_start_xmit = wlanDrvIf_Xmit, .ndo_get_stats = wlanDrvIf_NetGetStat, .ndo_validate_addr = NULL, }; 功能一看名字就知道了,不說了,這幾個對應的都是LINUX網路裝置驅動都有的命令字,詳見《LINUX裝置驅動開發詳解》第十六章。 在這之後,又調用了rc =drvMain_CreateI。 在這個函數裡完成了相關模組的初始化工作。具體不說了。接下來就是等待Android上層發送來的事件了。 WIFI已經可以工作了,大部分android wifisetting裡要求的功能也都實現了,不過還有兩個問題在這裡記錄一下: 1. Softap無法使用 2. 通過WPS連網的時候有一定幾率會失敗。 對於softap,當在setting中選下WIFI TETHERING時,softapcontroller就會給DRIVER發送私人命令,不過在發送私人命令前會先通過IOCTL發送SIOCGIWPRIV這個命令字給DRIVER。這個命令的作用是獲得當前DRIVER所支援的私人命令。(因為SOFTAP並不是standard cmd, 所以如果要支援的話必須放在私人命令中) 而DRIVER是否支援私人命令,或者說支援哪些私人命令就要看DRIVER中關於結構體iw_handler_def的賦值: const struct iw_handler_def wl_iw_handler_def ={ .num_standard = ARRAYSIZE(wl_iw_handler), .standard = (iw_handler *) wl_iw_handler, .num_private = ARRAYSIZE(wl_iw_priv_handler), .num_private_args = ARRAY_SIZE(wl_iw_priv_args), .private = (iw_handler *)wl_iw_priv_handler, .private_args = (void *) wl_iw_priv_args,}; 而我這邊由於不知到手上的模組支援哪些private cmd。所以這塊自己沒辦法加,只有聯絡供應廠商提供支援,現在暫時空下,等支援OK了再該過來。 可是對於問題2我就頭大了,有時候好有時候不好,不好的時候就是硬體返回了一個fail,這個讓我無從解起。不知道是不是WPS本來連網就不穩定還是其他什麼情況我也不得而知。這可能也是我沒有選擇在裝置商公司工作而是轉投到製造商的悲哀之一吧。一旦裝置本身出了問題,作為製造商只能等待裝置商自己來解決,除了催促其他什麼也做不了!可是在裝置商公司工作又無法接觸到這麼多好玩的終端產品這就交魚和熊掌不可兼得吧。