解決 wifi 無法開啟的問題 – 軟體啟動流程發生錯亂

由於科技不斷發展,無線技術的應用已日趨普及,其中 wifi 當然是無線產品不可缺少的一個應用。本文主要描述在 wifi 開發過程中曾經遇到的問題以及分析,希望可以藉由此篇文章來幫助 wifi 開發者分析問題。

  • wifi 啟動流程

系統根據以下流程開啟 wifi

  1. 載入 wifi kernel module。在 wifi kernel module 中,主要是透過 ioctl 的方式與 user space 下運行的 wpa_supplicant 進行溝通。另外在硬體的實現上,由於 CPU 需要有另一個介面與 wifi chip 進行溝通,如 wifi firmware 的載入、command request 以及資料的傳送/接收。目前較為常用的界面為 4bit SDIO (Secure Digital I/O)。
  2. 啟動 wpa_supplicant。運行於 user space 下的一個 service,可以在 init.rc 下啟動並初始化這個 service。當然在啟動以及關閉 wifi 時也會一起啟動以及關閉。
  3. 建立 wpa_supplicant 以及 wifi kernel module 的 interface 作為實現 wifi 的各種操作如找尋適當 AP (Access Point)、secure method (WPA、WPA2 或 WEP) 或建立/解除與 AP 的連線。
  •  問題描述

在 user 透過 UI 去啟動 wifi 時,wifi 找不到任何 AP 且畫面一直停留在 scanning…的狀態。相信各位 wifi 開發者在 porting wifi driver 時應該經常會遇到這樣的問題。或許有人會說是 AP 訊號太弱或是 security method 不對,這些當然都有可能。但除了這些之外,本文想引出另一個思考的方向 — 軟體啟動流程也有可能出了問題。

  • 問題分析

重新檢視上述啟動流程,可以發現 wifi kernel module 是在 kernel space 進行載入而 wpa_supplicant 是在 user space 下啟動的。如果要正確地控制其啟動流程,較常見的方法是程式中透過 cat /proc/modules。如果可以在 /proc/modules 中讀取到  module name,代表 wifi kernel module 已經載入完成。但顯然在筆者的開發經驗上,這樣的方法上並不能適用於所有的平台。在這樣的情況下,如果在 user space 中因為對 wifi kernel module 確實的載入完成時間有任何的誤判而提前啟動 wpa_supplicant,將會使得 wpa_supplicant 找不到 wifi device 而會有如以下的 error logs 出現

I/wpa_supplicant(  677): rfkill: Cannot open RFKILL control device

E/wpa_supplicant(  677): Could not read interface wlan0 flags: No such device

E/wpa_supplicant(  677): WEXT: Could not set interface ‘wlan0′ UP

E/wpa_supplicant(  677): wlan0: Failed to initialize driver interface

  •  問題分析方法
  1. 可以在 adb shell 中透過手動方式運行啟動流程。這可以幫助分析以及釐清問題。假如可以利用手動方式啟動,則可以確認在軟體中可能是因為 kernel module 載入時間過長而發生問題。

a). 可利用以下 command load/unload/list module

$ insmod xxx.ko (Insert module)

$ rmmod xxx.ko (Remove module)

$ lsmod (list module)

b). 可利用以下 command start/stop wpa_supplicant

$ start wpa_supplicant

$ stop wpa_supplicant

  • 解決方法
  1. 在 kernel space 裡增加 flag 來檢查 kernel driver 是否已經載入完成。並且透過 set property 的方式記錄在 prop file 中, 這種方式可以確保 user space 啟動 wpa_supplicant 的時間。
  2. 如果你無法從 kernel space 去完成上述動作,那可能只能乖乖地加一些 Timeout 或是 delay 來解決這個問題。