Android Wifi的工作流程
一、WIFI工作相关部分
Wifi 网卡状态
1. WIFI_STATE_DISABLED:WIFI网卡不可用
2. WIFI_STATE_DISABLING:WIFI正在关闭
3. WIFI_STATE_ENABLED:WIFI网卡可用
4. WIFI_STATE_ENABLING:WIFI网卡正在打开
5. WIFI_STATE_UNKNOWN:未知网卡状态
WIFI 访问网络需要的权限
"android.permission.CHANGE_NETWORK_STATE">
修改网络状态的权限
android:name="android.permission.CHANGE_WIFI_STATE">
修改WIFI状态的权限
"android.permission.ACCESS_NETWORK_STATE">
访问网络权限
"android.permission.ACCESS_WIFI_STATE">
访问WIFI权限
WIFI 核心模块
n WifiService
由SystemServer启动的时候生成的ConnecttivityService创建,负责启动关闭wpa_supplicant,启动和关闭WifiMonitor线程,把命令下发给wpa_supplicant以及跟新WIFI的状态
n WifiMonitor
负责从wpa_supplicant接收事件通知
n Wpa_supplicant
1、读取配置文件
2、初始化配置参数,驱动函数
3、让驱动scan当前所有的bssid
4、检查扫描的参数是否和用户设置的想否
5、如果相符,通知驱动进行权限 认证操作
6、连上AP
n Wifi驱动模块
厂商提供的source,主要进行load firmware和kernel的wireless进行通信
n Wifi电源管理模块
主要控制硬件的GPIO和上下电,让CPU和Wifi模组之间通过sdio接口通信
二、Wifi工作步骤
1 Wifi模块初期化
2 Wifi启动
3 查找热点(AP)
4 配置AP
5 配置AP参数
6 Wifi连接
7 IP地址配置
三、WIFI的架构和流程
1、WIFI的基本架构
1、wifi用户空间的程序和库:
external/wpa_supplicant/
生成库libwpaclient.so和守护进程wpa_supplicant
2、hardware/libhardware_legary/wifi/是wifi管理库
3、JNI部分:
frameworks/base/core/jni/android_net_wifi_Wifi.cpp
4、JAVA部分:
frameworks/base/services/java/com/android/server/
frameworks/base/wifi/java/android/net/wifi/
5、WIFI Settings应用程序位于:
packages/apps/Settings/src/com/android/settings/wifi/
6、WIFI 驱动模块 wlan.ko
wpa_supplicant通过wireless_ext接口和驱动通信
7、WIFI 硬件模块
2、WIFI在Android中如何工作
Android使用一个修改版wpa_supplicant作为daemon来控制WIFI,代码位于external/wpa_supplicant。wpa_supplicant是通过socket与hardware/libhardware_legacy/wifi/wifi.c通信。UI通过android.net.wifipackage(frameworks/base/wifi/java/android/net/wifi/)发送命令给wifi.c。相应的JNI实现位于frameworks/base/core/jni/android_net_wifi_Wifi.cpp。更高一级的网络管理位于frameworks/base/core/java/android/net。
3、配置Android支持WIFI
在BoardConfig.mk中添加:
BOARD_HAVE_WIFI := true
BOARD_WPA_SUPPLICANT_DRIVER :=WEXT
这将在external/wpa_supplicant/Android.mk设置WPA_BUILD_SUPPLICANT为true,默认使用驱动driver_wext.c。
如果使用定制的wpa_supplicant驱动(例如 wlan0),可以设置:
BOARD_WPA_SUPPLICANT_DRIVER := wlan0
4、使能wpa_supplicant调试信息
默认wpa_supplicant设置为MSG_INFO,为了输出更多信息,可修改:
1、在common.c中设置wpa_debug_level= MSG_DEBUG;
2、在common.c中把#definewpa_printf宏中的
if ((level) >= MSG_INFO)
改为
if ((level) >= MSG_DEBUG)
5、配置wpa_supplicant.conf
wpa_supplicant是通过wpa_supplicant.conf中的ctrl_interface=来指定控制socket的,应该在AndroidBoard.mk中配置好复制到$(TARGET_OUT_ETC)/wifi(也就是/system/etc/wifi/wpa_supplicant.conf)这个位置会在init.rc中再次检测的。
一般的wpa_supplicant.conf配置为:
ctrl_interface=DIR=/data/system/wpa_supplicantGROUP=wifi
update_config=1
fast_reauth=1
有时,驱动需要增加:
ap_scan=1
如果遇到AP连接问题,需要修改ap_scan=0来让驱动连接,代替wpa_supplicant。
如果要连接到non-WPA or open wirelessnetworks,要增加:
network={
key_mgmt=NONE
}
6、配置路径和权限
Google修改的wpa_supplicant要运行在wifi用户和组下的。代码可见wpa_supplicant/os_unix.c
中的os_program_init()函数。
如果配置不对,会出现下面错误:
E/WifiHW ( ): Unableto open connection to supplicant on
"/data/system/wpa_supplicant/wlan0":No such file or directory will appear.
确认init.rc中有如下配置:
mkdir /system/etc/wifi 0770 wifi wifi
chmod 0770 /system/etc/wifi
chmod 0660/system/etc/wifi/wpa_supplicant.conf
chown wifi wifi/system/etc/wifi/wpa_supplicant.conf
# wpa_supplicant socket
mkdir /data/system/wpa_supplicant 0771wifi wifi
chmod 0771 /data/system/wpa_supplicant
#wpa_supplicant control socket forandroid wifi.c
mkdir /data/misc/wifi 0770 wifi wifi
mkdir /data/misc/wifi/sockets 0770wifi wifi
chmod 0770 /data/misc/wifi
chmod 0660/data/misc/wifi/wpa_supplicant.conf
如果系统的/system目录为只读,那应该使用路径/data/misc/wifi/wpa_supplicant.conf。
7、运行wpa_supplicant和dhcpcd
在init.rc中确保有如下语句:
service wpa_supplicant/system/bin/logwrapper /system/bin/wpa_supplicant -dd
-Dwext -iwlan0 -c/data/misc/wifi/wpa_supplicant.conf
user root
group wifi inet
socket wpa_wlan0 dgram 660 wifiwifi
oneshot
service dhcpcd/system/bin/logwrapper /system/bin/dhcpcd -d -B wlan0
disabled
oneshot
根据所用的WIFI驱动名字,修改wlan0为自己驱动的名字。
8、编译WIFI驱动为module或kernel built in
1、编译为module
在BoardConfig.mk中添加:
WIFI_DRIVER_MODULE_PATH :="/system/lib/modules/xxxx.ko"
WIFI_DRIVER_MODULE_ARG := "" #for example nohwcrypt
WIFI_DRIVER_MODULE_NAME :="xxxx" #for example wlan0
WIFI_FIRMWARE_LOADER := ""
2、编译为kernel built in
1)在hardware/libhardware_legacy/wifi/wifi.c要修改interface名字,
2)在init.rc中添加:
setprop wifi.interface"wlan0"
3)在hardware/libhardware_legacy/wifi/wifi.c中当insmod/rmmod时,
直接return 0。
9、WIFI需要的firmware
Android不使用标准的hotplug binary,WIFI需要的firmware要复制到/etc/firmware。
或者复制到WIFI驱动指定的位置,然后WIFI驱动会自动加载。
10、修改WIFI驱动适合Android
Google修改的wpa_supplicant要求SIOCSIWPRIVioctl发送命令到驱动,及接收信息,例如signalstrength, mac address of the AP, link speed等。所以要正确实现WIFI驱动,需要从SIOCSIWPRIVioctl返回RSSI(signal strength)和MACADDR信息。
如果没实现这个ioctl,会出现如下错误:
E/wpa_supplicant( ):wpa_driver_priv_driver_cmd failed
wpa_driver_priv_driver_cmd RSSI len = 4096
E/wpa_supplicant( ):wpa_driver_priv_driver_cmd failed
D/wpa_supplicant( ):wpa_driver_priv_driver_cmd LINKSPEED len = 4096
E/wpa_supplicant( ):wpa_driver_priv_driver_cmd failed
I/wpa_supplicant( ):CTRL-EVENT-DRIVER-STATE HANGED
10、设置dhcpcd.conf
一般/system/etc/dhcpcd/dhcpcd.conf的配置为:
interface wlan0
option subnet_mask, routers,domain_name_server
四、WIFI模块分析
Wifi模块学习流程
最近研究Wifi模块,查了不少的相关资料,但发现基本上是基于android2.0版本的的分析,而现在研发的android移动平台基本上都是2.3的版本,跟2.0版本的差别,在Wifi模块上也是显而易见的。2.3版本Wifi模块没有了WifiLayer,之前的WifiLayer主要负责一些复杂的Wifi功能,如AP选择等以提供给用户自定义,而新的版本里面的这块内容基本上被WifiSettings所代替
本文就是基于android2.3版本的Wifi分析,主要分为两部分来分别说明:
(a) Wifi的启动流程(有代码供参考分析)
(b) Wifi模块相关文件的解析
(c) Wpa_supplicant解析
Awifi的基本运行流程(针对代码而言)
首先给一张我网上down下来的图,针对2.3版本之前的,由于不怎么擅长画这些,大家也就将就点,只要能助理解就可以了
(一)初始化
a.流程
1.在SystemServer启动的时候会生成一个ConnectivityService的实例
2.ConnectivityService的构造函数会创建WifiService
3.WifiStateTracker会创建WifiMonitor接受来自底层的事件,WifiService和WifiMonitor是整个wifi模块的核心,WifiService负责启动和关闭wpa_supplicant,启动和关闭WifiMonitor监视线程和把命令下方给wpa_supplicant,而WifiMonitor则负责从wpa_supplicant接受事件通知
b.代码分析
要想使用Wifi模块,必须首先使能Wifi,当你第一次按下Wifi使能按钮时,WirelessSettings会实例化一个WifiEnabler对象,实例化代码如下:
packages/apps/settings/src/com/android/settings/WirelessSettings.java
[java] view plaincopy
1. protected void onCreate(Bundle savedInstanceState) {
2.
3. super.onCreate(savedInstanceState);
4. ……
5. CheckBoxPreferencewifi = (CheckBoxPreference) findPreference(KEY_TOGGLE_WIFI);
6.
7. mWifiEnabler= new WifiEnabler(this, wifi);
8. ……
9. }
WifiEnabler类的定义大致如下,它实现了一个监听接口,当WifiEnabler对象被初始化后,它监听到你按键的动作,会调用响应函数 onPreferenceChange(),这个函数会调用WifiManager的setWifiEnabled()函数。
[java] view plaincopy
1. public class WifiEnabler implementsPreference.OnPreferenceChangeListener {
2. ……
3. public boolean onPreferenceChange(Preference preference,Object value) {
4. booleanenable = (Boolean) value;
5. ……
6. if (mWifiManager.setWifiEnabled(enable)) {
7. mCheckBox.setEnabled(false);
8. ……
9. }
10. ……
11. }
我们都知道Wifimanager只是个服务代理,所以它会调用WifiService的setWifiEnabled()函数,而这个函数会调用 sendEnableMessage()函数,了解android消息处理机制的都知道,这个函数最终会给自己发送一个 MESSAGE_ENABLE_WIFI的消息,被WifiService里面定义的handlermessage()函数处理,会调用 setWifiEnabledBlocking()函数。下面是调用流程:
mWifiEnabler.onpreferencechange()===>mWifiManage.setWifienabled()===>mWifiService.setWifiEnabled()===>mWifiService.sendEnableMessage()
===>mWifiService.handleMessage()===>mWifiService.setWifiEnabledBlocking().
在setWifiEnabledBlocking()函数中主要做如下工作:加载Wifi驱动,启动wpa_supplicant,注册广播接收器,启动WifiThread监听线程。代码如下:
1. ……
2. if (enable) {
3. if (!mWifiStateTracker.loadDriver()) {
4. Slog.e(TAG, "Failed toload Wi-Fi driver.");
5. setWifiEnabledState(WIFI_STATE_UNKNOWN, uid);
6. return false;
7. }
8. if (!mWifiStateTracker.startSupplicant()) {
9. mWifiStateTracker.unloadDriver();
10. Slog.e(TAG, "Failed tostart supplicant daemon.");
11. setWifiEnabledState(WIFI_STATE_UNKNOWN, uid);
12. return false;
13. }
14. registerForBroadcasts();
15. mWifiStateTracker.startEventLoop();
16. ……
至此,Wifi使能(开启)结束,自动进入扫描阶段。
(二)扫描AP
当驱动加载成功后,如果配置文件的AP_SCAN = 1,扫描会自动开始,WifiMonitor将会从supplicant收到一个消息EVENT_DRIVER_STATE_CHANGED,调用handleDriverEvent(),然后调用mWifiStateTracker.notifyDriverStarted(),该函数向消息队列添加EVENT_DRIVER_STATE_CHANGED,handlermessage()函数处理消息时调用scan()函数,并通过 WifiNative将扫描命令发送到wpa_supplicant。
看代码Frameworks/base/wifi/java/android/net/wifi/WifiMonitor.java
[java] view plaincopy
1. private void handleDriverEvent(Stringstate) {
2. if (state == null) {
3. return;
4. }
5. if (state.equals("STOPPED")) {
6. mWifiStateTracker.notifyDriverStopped();
7. } else if (state.equals("STARTED")) {
8. mWifiStateTracker.notifyDriverStarted();
9. } else if (state.equals("HANGED")) {
10. mWifiStateTracker.notifyDriverHung();
11. }
12. }
Frameworks/base/wifi/java/android/net/wifi/WifiStateTracker.java
[java] view plaincopy
1. ...
2. case EVENT_DRIVER_STATE_CHANGED:
3. switch(msg.arg1) {
4. case DRIVER_STARTED:
5.
6. setNumAllowedChannels();
7. synchronized (this) {
8. if (mRunState == RUN_STATE_STARTING) {
9. mRunState = RUN_STATE_RUNNING;
10. if (!mIsScanOnly) {
11. reconnectCommand();
12. } else {
13. // In somesituations, supplicant needs to be kickstarted to
14. // start thebackground scanning
15. scan(true);
16. }
17. }
18. }
19. break;
20. ...
上面是启动Wifi时,自动进行的AP的扫描,用户当然也可以手动扫描AP,这部分实现在WifiService里面,WifiService通过startScan()接口函数发送扫描命令到supplicant。
看代码:Frameworks/base/wifi/java/android/net/wifi/WifiStateTracker.java
[java] view plaincopy
1. public boolean startScan(booleanforceActive) {
2. enforceChangePermission();
3. switch (mWifiStateTracker.getSupplicantState()) {
4. case DISCONNECTED:
5. case INACTIVE:
6. case SCANNING:
7. case DORMANT:
8. break;
9. default:
10. mWifiStateTracker.setScanResultHandling(
11. WifiStateTracker.SUPPL_SCAN_HANDLING_LIST_ONLY);
12. break;
13. }
14. return mWifiStateTracker.scan(forceActive);
15. }
然后下面的流程同上面的自动扫描,我们来分析一下手动扫描从哪里开始的。我们应该知道手动扫描是通过菜单键的扫描键来响应的,而响应该动作的应该是 WifiSettings类中Scanner类的handlerMessage()函数,它调用WifiManager的 startScanActive(),这才调用WifiService的startScan()。
如代码:packages/apps/Settings/src/com/android/settings/wifiwifisettings.java
[java] view plaincopy
1. public boolean onCreateOptionsMenu(Menu menu) {
2. menu.add(Menu.NONE, MENU_ID_SCAN, 0, R.string.wifi_menu_scan)
3. .setIcon(R.drawable.ic_menu_scan_network);
4. menu.add(Menu.NONE, MENU_ID_ADVANCED, 0, R.string.wifi_menu_advanced)
5. .setIcon(android.R.drawable.ic_menu_manage);
6. return super.onCreateOptionsMenu(menu);
7. }
当按下菜单键时,WifiSettings就会调用这个函数绘制菜单。如果选择扫描按钮,WifiSettings会调用onOptionsItemSelected()。
packages/apps/Settings/src/com/android/settings/wifiwifisettings.java
1. public booleanonOptionsItemSelected(MenuItem item) {
2. switch (item.getItemId()) {
3. case MENU_ID_SCAN:
4. if(mWifiManager.isWifiEnabled()) {
5. mScanner.resume();
6. }
7. return true;
8. case MENU_ID_ADVANCED:
9. startActivity(new Intent(this,AdvancedSettings.class));
10. return true;
11. }
12. return super.onOptionsItemSelected(item);
13. }
Handler类:
1. private class Scanner extends Handler {
2. private int mRetry = 0;
3. void resume() {
4. if (!hasMessages(0)) {
5. sendEmptyMessage(0);
6. }
7. }
8. void pause() {
9. mRetry = 0;
10. mAccessPoints.setProgress(false);
11. removeMessages(0);
12. }
13. @Override
14. public void handleMessage(Message message) {
15. if (mWifiManager.startScanActive()){
16. mRetry = 0;
17. } else if (++mRetry >= 3) {
18. mRetry = 0;
19. Toast.makeText(WifiSettings.this, R.string.wifi_fail_to_scan,
20. Toast.LENGTH_LONG).show();
21. return;
22. }
23. mAccessPoints.setProgress(mRetry != 0);
24. sendEmptyMessageDelayed(0, 6000);
25. }
26. }
这里的mWifiManager.startScanActive()就会调用WifiService里的startScan()函数,下面的流程和上面的一样,这里不赘述。
当supplicant完成了这个扫描命令后,它会发送一个消息给上层,提醒他们扫描已经完成,WifiMonitor会接收到这消息,然后再发送给WifiStateTracker。
Frameworks/base/wifi/java/android/net/wifi/WifiMonitor.java
1. void handleEvent(int event, String remainder) {
2. switch (event) {
3. caseDISCONNECTED:
4. handleNetworkStateChange(NetworkInfo.DetailedState.DISCONNECTED,remainder);
5. break;
6. case CONNECTED:
7. handleNetworkStateChange(NetworkInfo.DetailedState.CONNECTED,remainder);
8. break;
9. case SCAN_RESULTS:
10. mWifiStateTracker.notifyScanResultsAvailable();
11. break;
12. case UNKNOWN:
13. break;
14. }
15. }
WifiStateTracker将会广播SCAN_RESULTS_AVAILABLE_ACTION消息:
代码如:Frameworks/base/wifi/java/android/net/wifi/WifiStateTracker.java
1. public voidhandleMessage(Message msg) {
2. Intent intent;
3. ……
4. case EVENT_SCAN_RESULTS_AVAILABLE:
5. if(ActivityManagerNative.isSystemReady()) {
6. mContext.sendBroadcast(new Intent(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION));
7. }
8. sendScanResultsAvailable();
9.
10. setScanMode(false);
11. break;
12. ……
13. }
由于WifiSettings类注册了intent,能够处理SCAN_RESULTS_AVAILABLE_ACTION消息,它会调用handleEvent(),调用流程如下所示。
WifiSettings.handleEvent() ====>WifiSettings.updateAccessPoints() ====> mWifiManager.getScanResults()====> mService.getScanResults()====>mWifiStateTracker.scanResults() ====> WifiNative.scanResultsCommand()……
将获取AP列表的命令发送到supplicant,然后supplicant通过Socket发送扫描结果,由上层接收并显示。这和前面的消息获取流程基本相同。
(三)配置,连接AP
当用户选择一个活跃的AP时,WifiSettings响应打开一个对话框来配置AP,比如加密方法和连接AP的验证模式。配置好AP后,WifiService添加或更新网络连接到特定的AP。
代码如:packages/apps/settings/src/com/android/settings/wifi/WifiSetttings.java
1. public booleanonPreferenceTreeClick(PreferenceScreen screen, Preference preference) {
2. if (preference instanceof AccessPoint) {
3. mSelected = (AccessPoint) preference;
4. showDialog(mSelected, false);
5. } else if (preference == mAddNetwork) {
6. mSelected = null;
7. showDialog(null, true);
8. } else if (preference == mNotifyOpenNetworks) {
9. Secure.putInt(getContentResolver(),
10. Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON,
11. mNotifyOpenNetworks.isChecked() ? 1 : 0);
12. } else {
13. return super.onPreferenceTreeClick(screen, preference);
14. }
15. return true;
16. }
配置好以后,当按下“Connect Press”时,WifiSettings通过发送LIST_NETWORK命令到supplicant来检查该网络是否配置。如果没有该网络或没有配置它,WifiService调用addorUpdateNetwork()函数来添加或更新网络,然后发送命令给supplicant,连接到这个网络。下面是从响应连接按钮到WifiService发送连接命令的代码:
packages/apps/settings/src/com/android/settings/wifi/WifiSetttings.java
[java] view plaincopy
1. public void onClick(DialogInterfacedialogInterface, int button) {
2. if (button == WifiDialog.BUTTON_FORGET && mSelected != null) {
3. forget(mSelected.networkId);
4. } else if (button == WifiDialog.BUTTON_SUBMIT && mDialog !=null) {
5. WifiConfiguration config = mDialog.getConfig();
6. if (config == null) {
7. if (mSelected != null&& !requireKeyStore(mSelected.getConfig())) {
8. connect(mSelected.networkId);
9. }
10. } else if (config.networkId != -1) {
11. if (mSelected != null) {
12. mWifiManager.updateNetwork(config);
13.
14. saveNetworks();
15. }
16. } else {
17. int networkId =mWifiManager.addNetwork(config);
18. if (networkId != -1) {
19. mWifiManager.enableNetwork(networkId, false);
20. config.networkId =networkId;
21. if (mDialog.edit || requireKeyStore(config)){
22. saveNetworks();
23. } else {
24. connect(networkId);
25. }
26. }
27. }
28. }
29. }
Frameworks\base\wifi\java\android\net\wifi\WifiManager.java
1. public intupdateNetwork(WifiConfiguration config) {
2. if(config == null || config.networkId < 0) {
3. return -1;
4. }
5. return addOrUpdateNetwork(config);
6. }
7. private intaddOrUpdateNetwork(WifiConfiguration config) {
8. try {
9. return mService.addOrUpdateNetwork(config);
10. } catch (RemoteException e) {
11. return -1;
12. }
13. }
WifiService.addOrUpdateNetwork()通过调用mWifiStateTracker.setNetworkVariable()将连接命令发送到Wpa_supplicant。
(四)获取IP地址
当连接到supplicant后,WifiMonitor就会通知WifiStateTracker。
代码如:Frameworks/base/wifi/java/android/net/wifi/WifiMonitor.java
1. Public void Run(){
2. if (connectToSupplicant()) {
3. // Send a message indicatingthat it is now possible to send commands
4. // to the supplicant
5. mWifiStateTracker.notifySupplicantConnection();
6. } else {
7. mWifiStateTracker.notifySupplicantLost();
8. return;
9. }
10. ……
11. }
WifiStateTracker发送EVENT_SUPPLICANT_CONNECTION消息到消息队列,这个消息有自己的handlermessage()函数处理,它会启动一个DHCP线程,而这个线程会一直等待一个消息事件,来启动DHCP协议分配IP地址。
frameworks/base/wifi/java/android/net/wifi/WifiStateTracker.java
1. void notifySupplicantConnection() {
2. sendEmptyMessage(EVENT_SUPPLICANT_CONNECTION);
3. }
4. public void handleMessage(Message msg) {
5. Intent intent;
6. switch (msg.what) {
7. case EVENT_SUPPLICANT_CONNECTION:
8. ……
9. HandlerThread dhcpThread = newHandlerThread("DHCP Handler Thread");
10. dhcpThread.start();
11. mDhcpTarget = newDhcpHandler(dhcpThread.getLooper(), this);
12. ……
13. ……
14. "code" class="java"> case EVENT_NETWORK_STATE_CHANGED:
15. ……
16. configureInterface();
17. ……
18. }}
19. "code" class="java">private void configureInterface() {
20. checkPollTimer();
21. mLastSignalLevel = -1;
22. if(!mUseStaticIp) { //使用DHCP线程动态IP
23. if(!mHaveIpAddress && !mObtainingIpAddress) {
24. mObtainingIpAddress = true;
25. //发送启动DHCP线程获取IP
26. mDhcpTarget.sendEmptyMessage(EVENT_DHCP_START);
27. }
28. } else { //使用静态IP,IP信息从mDhcpInfo中获取
29. intevent;
30. if(NetworkUtils.configureInterface(mInterfaceName, mDhcpInfo)) {
31. mHaveIpAddress = true;
32. event = EVENT_INTERFACE_CONFIGURATION_SUCCEEDED;
33. if (LOCAL_LOGD) Log.v(TAG, "Static IP configurationsucceeded");
34. }else {
35. mHaveIpAddress = false;
36. event = EVENT_INTERFACE_CONFIGURATION_FAILED;
37. if (LOCAL_LOGD) Log.v(TAG, "Static IP configuration failed");
38. }
39. sendEmptyMessage(event); //发送IP获得成功消息事件
40. }
41. 当Wpa_supplicant连接到AP后,它会发送一个消息给上层来通知连接成功,WifiMonitor会接受到这个消息并上报给WifiStateTracker。
42. Frameworks/base/wifi/java/android/net/wifi/WifiMonitor.java
43. "code" class="java">void handleEvent(int event, String remainder) {
44. switch (event) {
45. case DISCONNECTED:
46. handleNetworkStateChange(NetworkInfo.DetailedState.DISCONNECTED,remainder);
47. break;
48. case CONNECTED:
49. handleNetworkStateChange(NetworkInfo.DetailedState.CONNECTED,remainder);
50. break;
51. ……
52. }
53. private void handleNetworkStateChange(NetworkInfo.DetailedState newState, String data) {
54. StringBSSID = null;
55. intnetworkId = -1;
56. if(newState == NetworkInfo.DetailedState.CONNECTED) {
57. Matcher match = mConnectedEventPattern.matcher(data);
58. if(!match.find()) {
59. if (Config.LOGD) Log.d(TAG, "Could not find BSSID in CONNECTEDevent string");
60. }else {
61. BSSID = match.group(1);
62. try {
63. networkId = Integer.parseInt(match.group(2));
64. } catch (NumberFormatException e) {
65. networkId = -1;
66. }
67. }
68. }
69. mWifiStateTracker.notifyStateChange(newState,BSSID, networkId);
70. }
71. void notifyStateChange(DetailedState newState, StringBSSID, int networkId) {
72. Messagemsg = Message.obtain(
73. this, EVENT_NETWORK_STATE_CHANGED,
74. newNetworkStateChangeResult(newState, BSSID, networkId));
75. msg.sendToTarget();
76. }
77. DhcpThread获取EVENT_DHCP_START消息事件后,调用handleMessage()函数,启动DHCP获取IP地址的服务。
78. frameworks/base/wifi/java/android/net/wifi/WifiStateTracker.java
79.
80. "code" class="java">public void handleMessage(Message msg) {
81. intevent;
82. switch (msg.what) {
83. case EVENT_DHCP_START:
84. ……
85. Log.d(TAG, "DhcpHandler: DHCP requeststarted");
86. //启动一个DHCPclient的精灵进程,为mInterfaceName请求分配一个IP地//址
87. if (NetworkUtils.runDhcp(mInterfaceName, mDhcpInfo)) {
88. event= EVENT_INTERFACE_CONFIGURATION_SUCCEEDED;
89. if(LOCAL_LOGD) Log.v(TAG, "DhcpHandler: DHCP request succeeded");
90. } else {
91. event= EVENT_INTERFACE_CONFIGURATION_FAILED;
五、WIFI连接流程分析
Wifi 连接部分
当用户选择一个AP时会弹出一个AP参数配置对话框,此对话框会显示当前选择的AP信号强度,若此AP设置了密码则需要用户输入密码才能登录。WifiSettings中的 onPreferenceTreeClick会被调用 @Override
public boolean onPreferenceTreeClick(PreferenceScreen screen, Preference preference) {
//点击AP响应函数
if (preference instanceof AccessPoint) {
mSelected = (AccessPoint) preference;
showDialog(mSelected, false);
} else if (preference == mAddNetwork) {
mSelected = null;
showDialog(null, true);
} else if (preference == mNotifyOpenNetworks) {
Secure.putInt(getContentResolver(),
Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON,
mNotifyOpenNetworks.isChecked() ? 1 : 0);
} else {
return super.onPreferenceTreeClick(screen, preference);
}
return true;
}
用户配置好之后点击连接按钮,onClick函数会被调用。
public void onClick(DialogInterface dialogInterface, int button) {
//点击连接按钮的响应函数
if (button == WifiDialog.BUTTON_FORGET && mSelected != null) {
forget(mSelected.networkId);
} else if (button == WifiDialog.BUTTON_SUBMIT && mDialog != null) {
WifiConfiguration config = mDialog.getConfig();
if (config == null) {
if (mSelected != null && !requireKeyStore(mSelected.getConfig())) {
connect(mSelected.networkId);
}
} else if (config.networkId != -1) {
if (mSelected != null) {
mWifiManager.updateNetwork(config);
saveNetworks();
}
} else {
int networkId = mWifiManager.addNetwork(config);
if (networkId != -1) {
mWifiManager.enableNetwork(networkId, false);
config.networkId = networkId;
if (mDialog.edit || requireKeyStore(config)) {
saveNetworks();
} else {
connect(networkId);
}
}
}
}
连接请求部分
一.Settings的connect函数响应连接,更新网络保存配置,更新设置当前选择的优先级最高,并
保存。然后通过enableNetwork使得其他网络不可用来进行连接。最后调用WifiManager的
reconnect函数连接当前选择的网络。
二.WifiManager的reconnect函数通过AIDL的Binder机制,调用WifiService的reconnect函数
三.然后会调用 WifiStateTracker的reconnectCommand函数,通过JNI(android_net_wifi_Wifi)的
android_net_wifi_reconnectCommand 函数向WPA_WPASUPPLICANT发送 RECONNECT命令。
四. android_net_wifi_Wifi通过 doCommand(命令名,响应缓冲,响应缓存大小)调用wifi.c中的
wifi_command函数来发送命令。
五.最后通过 wpa_ctrl的wpa_ctrl_request函数向控制通道发送连接命令。
返回请求部分
六.当连接上之后WPA_SUPPLICANT会向控制通道发送连接成功命令。wifi.c的
wifi_wait_for_event函数阻塞调用并返回这个命令的字符串(CONNECTED).
七.而后WifiMonitor会被执行来处理这个事件,WifiMonitor 再调用 WifiStateTracker的
notifyStateChange,WifiStateTracker则接着会往自身发送EVENT_DHCP_START 消息来启动
DHCP 去获取 IP 地址,然后广播NETWORK_STATE_CHANGED_ACTION消息,最后由
WifiSettings类来响应,改变状态和界面信息。
关键函数功能介绍
一.connect函数功能
1.updateNetwork:updateNetwork(config)会将当前选择连接的AP配置信息
信息传递进去,配置信息有(网络ID等)。如果网络ID为-1则重新添加网络配置,然后向
wpa_supplicant 发送SET_NETWORK命令(即通过这个网络ID设置其他一些相关信息,设置
SSID,密码等)如果网络配置不为-1则直接执行后面步骤即发送SET_NETWORK命令。
2.saveNetwork:告诉supplicant保存当前网络配置并更新列表。SaveNetwork会调用WifiService的
saveConfiguration向wpa_supplicant发送SAVE_CONFIG命令保存当前网络配置信息,
如果返回false,则向wpa_supplicant重新发送RECONFIGURE命令获取配置信息,如果获取信
息成功后,会Intent一个 NETWORK_IDS_CHANGED_ACTION事件WifiSettings会注册接受
这个时间并更新列表。
3.enableNetwork函数,向系统获取接口名并使得该接口有效。由于之前传递的disableOthers
为true则向wpa_supplicant发送SELECT_NETWORK(如果传递的为false则发送
ENABLE_NETWORK命令),
4.reconnect函数:连接AP
二.reconnect函数功能:connect函数会调用WifiManager的reconnect然后通过Binder机制调用
WifiService的reconnect,再由WifiStateTracke调用WifiNative向wpa_supplicant发送
RECONNECT命令去连接网络,当连接上wpa_supplicant之后会向控制通道发送连接成功的命
令,
wifi_wait_for_event函数阻塞等待该事件的发生,并返回这个命令的字符串(CONNECTED)
三.android_net_wifi_Wifi函数的doCommand函数会调用wifi.c的wifi_command函数将上层的命
令向wpa_supplicant发送。
四.wifi_wait_for_event函数以阻塞的方式,等待控制通道传递的事件。当有事件传递过来的时候
该函数会通过wpa_ctrl的wpa_ctrl_recv函数读取该事件,并以字符串形式返回该事件名。
int wifi_wait_for_event(char *buf, size_t buflen)
{
.......
result =wpa_ctrl_recv(monitor_conn, buf, &nread);
if (result < 0) {
LOGD("wpa_ctrl_recv failed: %s/n", strerror(errno));
strncpy(buf,WPA_EVENT_TERMINATING "- recv error", buflen-1);
buf[buflen-1]= '/0';
return strlen(buf);
}
buf[nread] = '/0';
if (result == 0 && nread == 0) {
LOGD("Received EOF on supplicant socket/n");
strncpy(buf,WPA_EVENT_TERMINATING "- signal 0 received",buflen-1);
buf[buflen-1]= '/0';
return strlen(buf);
}
if (buf[0] == '<') {
char *match= strchr(buf, '>');
if (match != NULL) {
nread-= (match+1-buf);
memmove(buf,match+1, nread+1);
}
}
return nread;
}
五.wpa_ctrl_request,通过socket方式向wpa_supplicant发送命令,以select模式阻塞在
wpa_supplicant发送和接收。
int wpa_ctrl_request(struct wpa_ctrl *ctrl, const char *cmd, size_t cmd_len,char *reply, size_t *reply_len,void(*msg_cb)(char *msg,size_t len))
{
.......
res= select(ctrl->s + 1, &rfds, NULL, NULL, &tv);
if (FD_ISSET(ctrl->s,&rfds)) {
res= recv(ctrl->s, reply, *reply_len, 0);
if (res < 0)
return res;
if (res > 0&& reply[0] == '<') {
if (msg_cb) {
if ((size_t) res ==*reply_len)
res= (*reply_len) - 1;
reply[res]= '/0';
msg_cb(reply,res);
}
continue;
}
*reply_len= res;
break;
} else {
return -2;
}
}
return 0;
}
六.WifiMonitor 维护一个监视线程分发处理底层返回上来的事件
void handleEvent(int event, String remainder) {
switch (event) {
case DISCONNECTED:
handleNetworkStateChange(NetworkInfo.DetailedState.DISCONNECTED, remainder);
break;
case CONNECTED:
handleNetworkStateChange(NetworkInfo.DetailedState.CONNECTED, remainder);
break;
case SCAN_RESULTS:
mWifiStateTracker.notifyScanResultsAvailable();
break;
case UNKNOWN:
break;
}
}
此时返回的事件是CONNECTED因此 handleNetworkStateChange会被调用,验证一下BSSID,重新获得networkId
,然后调用WifiStateTracke的notifyStateChange通知状态改变了的消息(EVENT_NETWORK_STATE_CHANGED)
接着处理这个消息,会移除可用网络通告,然后通过 configureInterface()的动态获取IP地址。最后
发送一个NETWORK_STATE_CHANGED_ACTIONIntent,WifiSetings注册了此Intent因此会响应该它。由updateConnectionState函数响应。
七.updateConnectionState 获取连接信息,更新列表状态,设置为Connected,然后设置当前网络为可用状态
private void updateConnectionState(DetailedState state) {
if (!mWifiManager.isWifiEnabled()) {
mScanner.pause();
return;
}
if (state == DetailedState.OBTAINING_IPADDR) {
mScanner.pause();
} else {
mScanner.resume();
}
mLastInfo = mWifiManager.getConnectionInfo();
if (state != null) {
mLastState = state;
}
for (int i = mAccessPoints.getPreferenceCount() - 1; i >= 0; --i) {
((AccessPoint) mAccessPoints.getPreference(i)).update(mLastInfo, mLastState);
}
if (mResetNetworks && (state == DetailedState.CONNECTED ||
state == DetailedState.DISCONNECTED || state == DetailedState.FAILED)) {
updateAccessPoints();
enableNetworks();
}
}
流程图对应的源代码路径为:
WifiEnabler,WifiSettings对应的路径如下:
froyo/packages/apps/Settings/src/com/android/settings/
WifiManager,WifiMonitor,WifiStateTracker,WifiNative.对应的源代码路径如下:
froyo/frameworrks/base/wifi/java/android/net/wifi/
WifiService 对应代码的位置
froyo/frameworks/base/services/java/com/android/server/
android_net_wifi_Wifi源代码路径如下:
froyo/frameworks/base/core/jni/
wifi_command,wifi_wait_for_envent源代码路径如下:
/hardware/libhardware_legacy/wifi/wifi.c
wpa_ctrl_源代码路径如下:
/external/wpa_supplicant/wpa_ctrl.c
wpa_supplicant源代码路径如下:
froyo/external/wpa_supplicant/
六、WIFI移植
手动加载驱动
驱动加载
modprobelibertas
modprobe libertas_sdio
加载第二行时出错拉
#modprobe libertas_sdio
libertas_sdio: Libertas SDIO driver
libertas_sdio: Copyright Pierre Ossman
model=0xb
sd8686_helper.bin sd8686.bin
init: untracked pid 958 exited
过了一会出现:
libertas: can't load helper firmware
libertas: failed to load helper firmware
libertas_sdio: probe of mmc2:0001:1 failed with error -2
但是用lsmod又能看到
# lsmod
libertas_sdio 8776 0 - Live 0xbf022000
libertas 97416 1 libertas_sdio, Live 0xbf009000
usbserial 30256 0 - Live 0xbf000000
###使用insmod试一下
insmod /lib/modules/2.6.24.7/kernel/drivers/net/wireless/libertas/libertas.ko
insmod/lib/modules/2.6.24.7/kernel/drivers/net/wireless/libertas/libertas_sdio.ko
还是老样子
###发现系统中没有提到的文件fireware文件
从华恒的romfs/lib把fireware拷贝到/nfs/rootfs/lib下
$$还是不行
###把fireware拷贝到system/etc/下面,终于可以加载了
如下:
# modprobe libertas_sdio
libertas_sdio: Libertas SDIO driver
libertas_sdio: Copyright Pierre Ossman
model=0xb
sd8686_helper.bin sd8686.bin
init: untracked pid 714 exited
init: untracked pid 717 exited
libertas: eth1: Marvell WLAN 802.11 adapter
$$$建议
Title:AndroidINIT not loading firmware
android员工答:Youneed to run the insmod in a separate process launched by init.
#########
理解原理
Android uses a modified wpa_supplicant (external/wpa_supplicant) daemon forwifi support which is controlled through a socket byhardware/libhardware_legacy/wifi/wifi.c (WiFiHW) that gets controlled fromAndroid UI through android.net.wifi package fromframeworks/base/wifi/java/android/net/wifi/ and it's corresponding jniimplementation in frameworks/base/core/jni/android_net_wifi_Wifi.cpp Higherlevel network management is done in frameworks/base/core/java/android/net
1.在build/target/board/generic/BoardConfig.mk中增加
BOARD_WPA_SUPPLICANT_DRIVER := WEXT
2.打开调试信息--可选
默认是不打开调试的
2.1 modify external/wpa_supplicant/common.c and set wpa_debug_level = MSG_DEBUG
2.2 modify common.h and change #define wpa_printf from if ((level) >=MSG_INFO) to if ((level) >= MSG_DEBUG)
3.创建system/etc/wifi/wpa_supplicant.conf
有2种socket
一种是androidprivate socket
ctrl_interface=eth1
update_config=1
ap_scan=1 ###取决于wifi驱动,如果不行,则改为0试试
另一种是unix 标准socket,这里先选用第二种
ctrl_interface=DIR=/data/system/wpa_supplicant GROUP=wifi
update_config=1
ap_scan=1 ###取决于wifi驱动,如果不行,则改为0试试
4在init.rc中增加以下语句
#@qiu
mkdir/system/etc/wifi 0777 wifi wifi
chmod 0777/system/etc/wifi
chmod 0777/system/etc/wifi/wpa_supplicant.conf
chown wifiwifi /system/etc/wifi/wpa_supplicant.conf
mkdir /data/misc/wifi 0777 wifi wifi
mkdir /data/misc/wifi/sockets 0770 wifi wifi
chmod 0777 /data/misc/wifi
chmod 0777 /data/misc/wifi/wpa_supplicant.conf
chown wifi wifi /data/misc/wifi
chown wifi wifi /data/misc/wifi/wpa_supplicant.conf
# wpa_supplicant socket (unix socketmode)
mkdir/data/system/wpa_supplicant 0777 wifi wifi
chmod 0777/data/system/wpa_supplicant
chown wifiwifi /data/system/wpa_supplicant
#qiu@
##如果是privatesocket,则是
mkdir /system/etc/wifi 0777 wifi wifi
chmod 0777 /system/etc/wifi
chmod 0777 /system/etc/wifi/wpa_supplicant.conf
chown wifi wifi /system/etc/wifi/wpa_supplicant.conf
#wpa_supplicantcontrol socket for android wifi.c (android private socket)
mkdir /data/misc/wifi 0777 wifi wifi
mkdir /data/misc/wifi/sockets 0770 wifi wifi
chmod 0777 /data/misc/wifi
chmod 0777 /data/misc/wifi/wpa_supplicant.conf
chown wifi wifi /data/misc/wifi
chown wifi wifi /data/misc/wifi/wpa_supplicant.conf
5.在init.rc中添加wpa_supplicant和dhcpcd启动服务
service wpa_supplicant /system/bin/wpa_supplicant -dd -Dwext -ieth1 -c/system/etc/wifi/wpa_supplicant.conf
group system wifi inet
disabled
oneshot
##如果是pricvatesocket则需要在group上面增加一行:
socket wpa_eth1 dgram 777 wifi wifi
servicedhcpcd /system/bin/dhcpcd -f /system/etc/dhcpcd/dhcpcd.conf -d eth1
group system dhcp wifi
disabled
oneshot
5.5增加/system/etc/dhcpcd/dhcpcd.conf文件内容
interface eth1
option subnet_mask, routers, domain_name_servers
6.把wifi驱动编译进内核
首先init.rc中增加
setprop wifi.interface "eth1"
setprop wlan.driver.status "ok"
其次在修改hardware/libhardware_legacy/wifi/wifi.c
新的out/target/product/generic/system/lib/libhardware_legacy.so---v2
在函数wifi_load_driver()的体的开头增加
//@qiu
LOGE("Weber@wifi driverloaded !");
return 0;
//qiu@
在函数wifi_unload_driver()体的开头增加
//@qiu
LOGE("Weber@wifi driverunloaded!");
return 0;
//qiu@
####17th,Jul
把驱动从M改为*,得zImage-vvv39
开始启动:
加载firmware的时候出错
libertas: can't load helper firmware
libertas: failed to load helper firmware
libertas_sdio: probe of mmc2:0001:1 failed with error -2
看来是无法加载fireware
###尝试修改wifi.c让它自动加载模块
修改
#ifndef WIFI_DRIVER_MODULE_PATH1
#defineWIFI_DRIVER_MODULE_PATH1 "/system/lib/modules/libertas.ko"
#endif
#ifndef WIFI_DRIVER_MODULE_PATH2
#defineWIFI_DRIVER_MODULE_PATH2 "/system/lib/modules/libertas_sdio.ko"
#endif
#ifndef WIFI_DRIVER_MODULE_NAME1
#defineWIFI_DRIVER_MODULE_NAME1 "libertas"
#endif
#ifndef WIFI_DRIVER_MODULE_NAME2
#defineWIFI_DRIVER_MODULE_NAME2 "libertas_sdio"
#endif
并在此文件的后面代码中做一定修改,wifi_load_driver()函数中也做相应修改
编译得到libhardware_legacy.so---v3
-启动后在设置中点击wifi
串口显示如下,看来是能加载了
# libertas_sdio: Libertas SDIO driver
libertas_sdio: Copyright Pierre Ossman
model=0xb
sd8686_helper.bin sd8686.bin
init: untracked pid 954 exited
init: untracked pid 957 exited
libertas: eth1: Marvell WLAN 802.11 adapter
但是wifi图标下面显示wifi不可用
查看logcat的main:
7899 E/WifiHW ( 708): Cannot access"/data/misc/wifi/wpa_supplicant.conf": Permission denied
7900 E/WifiHW ( 708): Wi-Fi will not be enabled
7901 W/WifiHW ( 708): Weber@wifi driver unloaded!
7902 E/WifiService( 708):Failed to start supplicant daemon.
7903 D/SettingsWifiEnabler( 791):Received wifi state changed from Enabling to Unknown
于是增加以下语句到init.rc
mkdir /data/misc/wifi 0770 wifi wifi
mkdir /data/misc/wifi/sockets 0770 wifi wifi
chmod 0770 /data/misc/wifi
chmod 0660 /data/misc/wifi/wpa_supplicant.conf
chown wifi wifi /data/misc/wifi
chown wifi wifi /data/misc/wifi/wpa_supplicant.conf
并且cpsystem/etc/wifi/wpa_supplicant.conf ./data/misc/wifi/
启动,终于能打开wifi,但还是无法打开网页
查看logcat
4564 E/wpa_supplicant( 917):Failed to read or parse configuration '/system/etc/wifi/wpa_supplicant.conf'.
4565 E/WifiHW ( 708): Unable to open connection tosupplicant on "/data/system/wpa_supplicant/sta": No such file or directory
4566 D/WifiService( 708):ACTION_BATTERY_CHANGED pluggedType: 1
4899 E/WifiHW ( 708): Supplicant not running, cannotconnect
5574 E/WifiHW ( 708): Supplicant not running, cannotconnect
5575 V/WifiStateTracker( 708):Supplicant died unexpectedly
5576 D/dalvikvm( 934):DexOpt: load 3102ms, verify 1519ms, opt 24ms
5577 D/installd( 665):DexInv: --- END '/system/app/Browser.apk' (success) ---
5578 D/NetworkStateTracker( 708):setDetailed state, old =IDLE and new state=DISCONNECTED
5579 D/ConnectivityService( 708):ConnectivityChange for WIFI: DISCONNECTED/DISCONNECTED
##在init.rc中把wpa_supplicant.conf权限修改为777,
还是有错误:
5573 E/WifiHW ( 708): Unable to open connection tosupplicant on "/data/system/wpa_supplicant/sta": No such file ordirector
##在init.rc中增加以下几条语句
setpropwifi.interface "eth1"
setpropwlan.driver.status "ok"
setpropwlan.interface "eth1"
结果还是不行logcat显示
E/WifiHW ( 708):Unable toopen connection to supplicant on "/data/system/wpa_supplicant/eth1":No such file or directory
##在init.rc中dhcp服务启动是为eth1的增加wifi组权限
又出现新错误
如果开始就enablewifi就会出现以下错误:
E/WifiHW ( 709): Unable to open connection tosupplicant on "/data/system/wpa_supplicant/eth1": Connection refused
因为这是开机默认启动wifi,如果不起动,错误则是和上一条相同
###18th,Jul
##尝试在wpa_supplicant.conf设置GROUP=system,还是不行,只好改回去
ls/data/system/wpa_supplicant/eth1 -l
srwxrwx--- 11010 1010 0 Jul 18 08:22/data/system/wpa_supplicant/eth1
#ls /data/system/wpa_supplicant/eth1 -l
srwxrwxrwx 11010 1010 0 Jul 18 08:22 /data/system/wpa_supplicant/eth1
###18th,Jul手动调试
# wpa_supplicant -dd -Dwext -ieth1 -c /system/etc/wifi/wpa_supplicant.conf&
# ioctl[SIOCSIWPMKSA]: Invalid argument《《《《输出的错误
#ls /data/system/wpa_supplicant/eth1 -l
srwxrwxrwx 11010 1010 0 Jul 18 13:14/data/system/wpa_supplicant/eth1
#wpa_cli-i eth1 -p /data/system/wpa_supplicant
不知道该如何设置ap
bash# wpa_cli -ieth1 scan //搜索无线网
bash# wpa_cli -ieth1 scan_results //显示搜索结果
bash# wpa_cli -ieth1 add_network
bash# iwconfig eth1 essid "you_wifi_net"
bash# wpa_cli -ieth1 password 0 "password"
bash# wpa_cli -ieth1 enable_network
###19th,Jul
修改viframeworks/base/wifi/java/android/net/wifi/WifiStateTracker.java
把tiwlan0改eth1
暂时未编译
###开始没有选择第二步的debug选项,选上,在external/wpa_supplicant目录下有mm编译
得到
system/bin/wpa_cli---vvv1
system/lib/libwpa_client.so---vvv1
system/bin/wpa_supplicant---vvv1
###试一下androidprivate socket
修改visystem/etc/wifi/wpa_supplicant.conf
vi data/misc/wifi/wpa_supplicant.conf
修改init.rc
在chmod那里把unixsocket的那段注释
在启动wpa_supplicant的时候增加
socket wpa_eth1 dgram 660 wifi wifi
结果还是不行
###只好在external/wpa_supplicant/wpa_ctrl.c中添加
LOGW,注意得先加入头文件和LOG_TAG
17 #define LOG_TAG "WifiQiu"
18 #include "cutils/log.h"
mm编译得到
system/bin/wpa_cli---vvv2
system/lib/libwpa_client.so---vvv2
但是竟然没有WifiQiu输出,不知道是不是没有用make而是mm的缘故
###20th,Jul
1.BoardConfig.mk增加了HAVE_CUSTOM_WIFI_DRIVER_2:= true
2.修改wifi/wifi.c中使得检查和卸载驱动的两个函数,用make编译得到
libhardware_legacy.so---v4
system/bin/wpa_cli---vvv2.1
system/bin/wpa_supplicant-v2
system/lib/libwpa_client.so---vvv2.1
###上面的版本还没有来的及测试,有发现问题
1.突然发现external/wpa_supplicant/wpa_ctrl.c中LOG不够全面,
一共有三种情况,漏了一个函数没有加,于是增加LOGW并使之可以区分
2.根据手动stracewpa_cli的提示
access("/data/misc/wifi/wpa_supplicant", F_OK) = -1 ENOENT (No suchfile or directory)
用grep 搜索到所在文件为external/wpa_supplicant/wpa_cli.c
把它修改为:/data/misc/wifi
用mm分别在external/wpa_supplicant和hardware/libhardware_legacy中编译
得到
system/bin/wpa_cli---vvvvv3
system/lib/libwpa_client.so---vvv3
libhardware_legacy.so---v5
启动之后发现是在对socket进行连接的时候出错了
connect(ctrl->s, (struct sockaddr *) &ctrl->dest,sizeof(ctrl->dest))< 0)
而且不能关闭wifi
只好重新修改wifi.c直接在unload函数中返回0,使得wifi能被关闭
libhardware_legacy.so---v5.1
###21th,Jul使用private的时候再次手动调试
# wpa_supplicant -dd -Dwext -ieth1 -c /system/etc/wifi/wpa_supplicant.conf&
# ioctl[SIOCSIWPMKSA]: Invalid argument
mkdir[ctrl_interface]: Permission denied
[1]+Done(255) wpa_supplicant -dd -Dwext -ieth1 -c/system/etc/wifi/wpa_supplicant.conf
查找mkdir发现是在ctrl_iface_unix.c中
错误是出现在#ifdefANDROID之外阿,诡异
###用donut中external/wpa_supplicant下的driver_wext.c替换地掉eclair中的
mm重新编译,得到:
wpa_supplicant---vvv3
$$$还是不行
###22th,Jul
把donut的wpa_supplicant文件夹拷贝到eclair下
用mm编译得到
system/bin/wpa_cli---vvvvv4
system/bin/wpa_supplicant---vvv4
system/lib/libwpa_client.so---vvv4
wpa_supplicant---vvv3
终于可以搜索到网络了,泪奔
但是,不能获取到ip地址呵呵
logcat提示:
I/WifiStateTracker( 713):DhcpHandler: DHCP request failed: Timed out waiting for DHCP to finish
###手动启动dhcp提示
eth1: open `/data/misc/dhcp/dhcpcd-eth1.pid': No such file or directory
在init.rc中增加
mkdir /data/misc/dhcp 0777 wifi wifi
chmod 0777 /data/misc/dhcp
chown wifi wifi /data/misc/dhcp
还是不能获得地址
eth1: flock `/data/misc/dhcp/dhcpcd-eth1.pid': Try again
后来把上面的wifi 改为dhcp,还是不能获得IP地址
$$
把dhcpcd杀了,手动启动
# dhcpcd -f /system/etc/dhcpcd/dhcpcd.conf -d eth1
eth1: dhcpcd 4.0.1 starting
eth1: hardware address = 00:1a:6b:a2:38:65
eth1: executing `/system/etc/dhcpcd/dhcpcd-run-hooks', reason PREINIT
eth1: /system/etc/dhcpcd/dhcpcd-run-hooks: Permission denied
eth1: waiting for carrier
eth1: host does not support a monotonic clock - timing can skew
eth1: timed out
eth1: executing `/system/etc/dhcpcd/dhcpcd-run-hooks', reason FAIL
eth1: /system/etc/dhcpcd/dhcpcd-run-hooks: Permission denied
$$把权限都改为777,再次执行
# dhcpcd -f /system/etc/dhcpcd/dhcpcd.conf -d eth1
eth1: dhcpcd 4.0.1 starting
eth1: hardware address = 00:1a:6b:a2:38:65
eth1: executing `/system/etc/dhcpcd/dhcpcd-run-hooks', reason PREINIT
eth1: waiting for carrier
eth1: host does not support a monotonic clock - timing can skew
eth1: timed out
eth1: executing `/system/etc/dhcpcd/dhcpcd-run-hooks', reason FAIL
$$还没有关闭android机呢,重新打开wifi,嘿嘿,能分配到地址了
但是...但是,还是不能上网,估计是由于地址的原因吧
把eth0禁用之后就可以上网了,呵呵
wifi移植告一段落
七、Wifi模块代码总结
1 Wifi Application代码
packages/apps/Settings/src/com/android/settings/wifi
2 Wifi Framework
frameworks/base/wifi/java/android/net/wifi
frameworks/base/services/java/com/android/server
3 Wifi JNI
frameworks/base/core/jni/android_net_wifi_Wifi.cpp
4 Wifi Hardware
hardware/libhardware_legacy/wifi/wifi.c
5 Wifi tool
external/wpa_supplicant
6 Wifi kernel
net/wireless drivers/wlan_sd8688 arch/arm/mach-pxa/wlan_pm.c
Wifi模块的初始化:
在SystemServer 启动的时候,会生成一个ConnectivityService 的实例, ConnectivityService 的构造函数会创建WifiService,WifiStateTracker 会创建WifiMonitor 接收来自底层的事件,WifiService 和WifiMonitor 是整个模块的核心。WifiService 负责启动关闭wpa_supplicant、启动关闭WifiMonitor监视线程和把命令下发给wpa_supplicant,而WifiMonitor 则负责从wpa_supplicant 接收事件通知。
Wifi模块的启动:
WirelessSettings在初始化的时候配置了由WifiEnabler 来处理Wifi 按钮,
当用户按下Wifi按钮后,Android 会调用WifiEnabler 的onPreferenceChange,再由WifiEnabler调用WifiManager 的setWifiEnabled接口函数,通过AIDL,实际调用的是WifiService 的setWifiEnabled函数,WifiService 接着向自身发送一条MESSAGE_ENABLE_WIFI 消息,在处理该消息的代码中做真正的使能工作:首先装载WIFI 内核模块(该模块的位置硬编码为"/system/lib/modules/wlan.ko"), 然后启动wpa_supplicant ( 配置文件硬编码为"/data/misc/wifi/wpa_supplicant.conf"),再通过WifiStateTracker 来启动WifiMonitor 中的监视线程。
查找热点(AP):
(Wifi开启)中讲到Wifi模块开启后会对外发送WIFI_STATE_CHANGED_ACTION。WifiLayer中注册了Action的Receiver。当WifiLayer收到此Action后开始scan的流程,具体如下
当wpa_supplicant处理完SCAN 命令后,它会向控制通道发送事件通知扫描完成,从wifi_wait_for_event 函数会接收到该事件,由此WifiMonitor 中的MonitorThread 会被执行来出来这个事件:
配置 AP 参数:
当用户在WifiSettings 界面上选择了一个AP 后,会显示配置AP 参数的一个对话框:
Wifi连接:
IP地址的配置:
八、wifi新增功能
Wifi Direct技术简介
Wi-Fi Direct标准是指允许无线网络中的设备无需通过无线路由器即可相互连接。与 蓝牙技术类似,这种标准允许无线设备以点对点形式互连,不过在传输速度与传输距离方面 则比蓝牙有大幅提升。
Wi-Fi Direct技术特点
移动性与便携性:Wi-Fi Direct设备能够随时随地实现互相连接。由于不需要Wi-Fi 路由器或接入点,因此Wi-Fi设备可以在任何地点实现连接。
即时可用性:用户将得以利用带回家的第一部Wi-Fi Direct认证设备建立直接连接。 例如,一部新购买的Wi-Fi Direct笔记本可以与用户已有的传统Wi-Fi设备创建直接连接。
易用性:Wi-Fi Direct设备发现(Device Discovery )与服务发现(Service Discovery)功能帮助用户确定可用的设备与服务,然后建立连接。例如,如果用户想要打 印文件,他们可以通过上述服务了解到哪个Wi-Fi网络拥有打印机。
简单而安全的连接:Wi-Fi Direct设备采用Wi-Fi Protected Setup™简化了在设备 之间创建安全连接的过程。用户可以按下任一设备上的按钮,也可以输入PIN码(即设备显 示的PIN码),轻松创建安全连接。
Wi-Fi Direct 主要优点:传输速率高,兼容原有设备。
Wi-Fi Direct 主要缺点:耗电量高(较之蓝牙)。
Android P2P源码学习
①:WifiP2pSettings Wi Fi P2P设置
方法 | 解释 |
mReceiver | 处理接收到的各种Action方法: |
onCreate | 创建时调用 获得系统服务WifiP2pManager和 WifiP2pManager.Channel; mConnectListener/mDisconnectListener 为 wifiP2pDialog 设置 Listener; setHasOptionsMenu(true)设置操作菜单 |
onResume() | 注册 mReceiver; discoverPeers()探索附近设备 |
onPause() | 注销 mReceiver; |
onOptionsItemSelected | menu事件响应: |
onPreferenceTreeClick | 点击一个设备,根据设备状态弹出相关对话框 |
updateDevicePref | 更新设备属性 |
onPeersAvailable | 更新UI |
②
WifiP2pDialog方法 | 解释 |
getConfig() | 获得P2P配置信息,获得device地址和 wps |
onCreate | 创建时候调用,设置device的address和name等信息。 |
③
WifiP2pEnabler
方法 | 理解 |
mReceiver | 处理接收到的各种Action方法: |
handleP2pStateChanged | 根据状态设置checkbox的属性值 |
onPreferenceChange | 根据checkbox的属性变化,开启/关闭wifi p2p |
resume/pause | 注册/注销mReceiver,设置/取消checkbox的Listener |
④
WifiP2pPeer
方法 | 理解 |
onBindView | 设置基本配置信息(名称,地址,rssi和信号量的图片),调 |
compareTo | 比较某device是否和本地的device相同 |
getLevel | 获得信号等级 |
九、Android Wi-Fi Display(Miracast)介绍
2012年11月中旬,Google发布了Android 4.2。虽然它和Android 4.1同属Jelly Bean系列,但却添加了很多新的功能。其中,在显示部分,Android 4.2在Project Butter基础上再接再厉,新增了对Wi-Fi Display功能的支持。由此也导致整个显示架构发生了较大的变化。
本文首先介绍Wi-FiDisplay的背景知识,然后再结合代码对Android 4.2中Wi-Fi Display的实现进行介绍。
一背景知识介绍
Wi-Fi Display经常和Miracast联系在一起。实际上,Miracast是Wi-Fi联盟(Wi-Fi Alliance)对支持Wi-Fi Display功能的设备的认证名称。通过Miracast认证的设备将在最大程度内保持对Wi-Fi Display功能的支持和兼容。由此可知,Miracast考察的就是Wi-Fi Display(本文后续将不再区分Miracast和Wi-Fi Display)。而Wi-Fi Display的核心功能就是让设备之间通过Wi-Fi无线网络来分享视音频数据。以一个简单的应用场景为例:有了Wi-Fi Display后,手机和电视机之间可以直接借助Wi-Fi,而无需硬连线(如HDMI)就可将手机中的视频投递到TV上去显示[①]。以目前智能设备的发展趋势来看,Wi-Fi Display极有可能在较短时间内帮助我们真正实现多屏互动。
从技术角度来说,Wi-FiDisplay并非另起炉灶,而是充分利用了现有的Wi-Fi技术。图1所示为Wi-Fi Display中使用的其他Wi-Fi技术项。
图1 Miracast的支撑体系结构
由图1可知,Miracast依赖的Wi-Fi技术项[②]有:
· Wi-Fi Direct,也就是Wi-Fi P2P。它支持在没有AP(Access Point)的情况下,两个Wi-Fi设备直连并通信。
· Wi-Fi Protected Setup:用于帮助用户自动配置Wi-Fi网络、添加Wi-Fi设备等。
· 11n/WMM/WPA2:其中,11n就是802.11n协议,它将11a和11g提供的Wi-Fi传输速率从56Mbps提升到300甚至600Mbps。WMM是Wi-Fi Multimedia的缩写,是一种针对实时视音频数据的QoS服务。而WPA2意为Wi-Fi Protected Acess第二版,主要用来给传输的数据进行加密保护。
上述的Wi-Fi技术中,绝大部分功能由硬件厂商实现。而在Android中,对Miracast来说最重要的是两个基础技术:
· Wi-Fi Direct:该功能由Android中的WifiP2pService来管理和控制。
· Wi-Fi Multimedia:为了支持Miracast,Android 4.2对MultiMedia系统也进行了修改。
下边我们对Miracast几个重要知识点进行介绍,首先是拓扑结构和视音频格式方面的内容。
Miracast一个重要功能就是支持Wi-Fi Direct。但它也考虑了无线网络环境中存在AP设备的情况下,设备之间的互联问题。读者可参考如图2所示的四种拓扑结构。
图2 Miracast的四种拓扑结构
图2所示内容比较简单,此处就不再详述。另外,在Wi-Fi Display规范中,还存在着Source将Video和Audio内容分别传送给不同Render Device的情况。感兴趣的读者可参考Wi-Fi Display技术规范。
另外,Miracast对所支持的视音频格式也进行了规定,如表1所示。
表1 Miracast 视音频格式支持
分辨率 | 17种 CEA格式,分辨率从640*480到1920*1080,帧率从24到60 29种VESA格式,分辨率从800*600到1920*1200,帧率从30到60 12种手持设备格式,分辨率从640*360到960*540,帧率从30到60 |
视频 | H.264高清 |
音频 | 必选:LPCM 16bits,48kHz采样率,双声道 可选: LPCM 16bits,44.1kHz采样率,双声道 Advanced Audio coding Dolby Advanced Codec 3 |
最后,我们简单介绍一下Miracast的大体工作流程。Miracast以session为单位来管理两个设备之间的交互的工作,主要步骤包括(按顺序):
· Device Discovery:通过Wi-Fi P2P来查找附近的支持Wi-Fi P2P的设备。
· Device Selection:当设备A发现设备B后,A设备需要提示用户。用户可根据需要选择是否和设备B配对。
· Connection Setup:Source和Display设备之间通过Wi-Fi P2P建立连接。根据Wi-Fi Direct技术规范,这个步骤包括建立一个Group Owner和一个Client。此后,这两个设备将建立一个TCP连接,同时一个用于RTSP协议的端口将被创建用于后续的Session管理和控制工作。
· Capability Negotiation:在正式传输视音频数据前,Source和Display设备需要交换一些Miracast参数信息,例如双方所支持的视音频格式等。二者协商成功后,才能继续后面的流程。
· Session Establishment andstreaming:上一步工作完成后,Source和Display设备将建立一个Miracast Session。而后就可以开始传输视音频数据。Source端的视音频数据将经由MPEG2TS编码后通过RTP协议传给Display设备。Display设备将解码收到的数据,并最终显示出来。
· User Input back channelsetup:这是一个可选步骤。主要用于在传输过程中处理用户发起的一些控制操作。这些控制数据将通过TCP在Source和Display设备之间传递。
· Payload Control:传输过程中,设备可根据无线信号的强弱,甚至设备的电量状况来动态调整传输数据和格式。可调整的内容包括压缩率,视音频格式,分辨率等内容。
· Session teardown:停止整个Session。
通过对上面背景知识的介绍,读者可以发现:
· Miracast本质就是一个基于Wi-Fi的网络应用。这个应用包括服务端和客户端。
· 服务端和客户端必须支持RTP/RTSP等网络协议和相应的编解码技术。
二 Android 4.2 Miracast功能实现介绍
Miracast的Android实现涉及到系统的多个模块,包括:
· MediaPlayerService及相关模块:原因很明显,因为Miracast本身就牵扯到RTP/RTSP及相应的编解码技术。
· SurfaceFlinger及相关模块:SurfaceFlinger的作用是将各层UI数据混屏并投递到显示设备中去显示。现在,SurfaceFlinger将支持多个显示设备。而支持Miracast的远端设备也做为一个独立的显示设备存在于系统中。
· WindowManagerService及相关模块:WindowManagerService用于管理系统中各个UI层的位置和属性。由于并非所有的UI层都会通过Miracast投递到远端设备上。例如手机中的视频可投递到远端设备上去显示,但假如在播放过程中,突然弹出一个密码输入框(可能是某个后台应用程序发起的),则这个密码输入框就不能投递到远端设备上去显示。所以,WindowManagerService也需要修改以适应Miracast的需要。
· DisplayManagerService及相关模块:DisplayManagerService服务是Android 4.2新增的,用于管理系统中所有的Display设备。
由于篇幅原因,本文将重点关注SurfaceFlinger和DisplayManagerService以及Miracast的动态工作流程。
2.1 SurfaceFlinger对Miracast的支持
相比前面的版本,Android4.2中SurfaceFlinger的最大变化就是增加了一个名为DisplayDevice的抽象层。相关结构如图3所示:
图3 SurfaceFlinger家族类图
由图3可知:
· Surface系统定义了一个DisplayType的枚举,其中有代表手机屏幕的DISPLAY_PRIMARY和代表HDMI等外接设备的DISPLAY_EXTERNAL。比较有意思的是,作为Wi-Fi Display,它的设备类型是DISPLAY_VIRTUAL。
· 再来看SurfaceFlinger类,其内部有一个名为mDisplays的变量,它保存了系统中当前所有的显示设备(DisplayDevice)。另外,SurfaceFlinger通过mCurrentState和mDrawingState来控制显示层的状态。其中,mDrawingState用来控制当前正在绘制的显示层的状态,mCurrentState表示当前所有显示层的状态。有这两种State显示层的原因是不论是Miracast还是HDMI设备,其在系统中存在的时间是不确定的。例如用户可以随时选择连接一个Miracast显示设备。为了不破坏当前正在显示的内容,这个新显示设备的一些信息将保存到CurrentState中。等到SurfaceFlinger下次混屏前再集中处理。
· mCurrentState和mDrawingState的类型都是SurfaceFlinger的内部类State。由图3可知,State首先通过layerSortedByZ变量保存了一个按Z轴排序的显示层数组(在Android中,显示层的基类是LayerBase),另外还通过displays变量保存了每个显示层对应的DisplayDeviceState。
· DisplayDeviceState的作用是保存对应显示层的DisplayDevice的属性以及一个ISurfaceTexure接口。这个接口最终将传递给DisplayDevice。
· DisplayDevice代表显示设备,它有两个重要的变量,一个是mFrameBufferSurface和mNativeWindow。mFrameBufferSurace是FrameBufferSurface类型,当显示设备不属于VIRTUAL类型的话,则该变量不为空。对于Miracast来说,显示数据是通过网络传递给真正的显示设备的,所有在Source端的SurfaceFlinger来说,就不存在FrameBuffer。故当设备为VIRTUAL时,其对应的mFrameBufferSurface就为空。而ANativeWindow是Android显示系统的老员工了。该结构体在多媒体的视频I/O、OpenGL ES等地方用得较多。而在普通的UI绘制中,ISurfaceTexture接口用得较多。不过早在Android 2.3,Google开发人员就通过函数指针将ANativeWindow的各项操作和ISurfaceTexture接口统一起来。
作为VIRTUAL的Miracast设备是如何通过DisplayDevice这一层抽象来加入到Surface系统中来的呢?下面这段代码对理解DisplayDevice的抽象作用极为重要。如图4所示。
图4 SurfaceFlinger代码片段
由图4代码可知:
· 对于非Virtual设备,DisplayDevice的FrameBufferSurface不为空。而且SurfaceTextureClient的构造参数来自于FrameBufferSurface的getBufferQueue函数。
· 如果是Virtual设备,SurfaceTextureClient直接使用了State信息中携带的surface变量。
凭着上面这两点不同,我们可以推测出如图5所示的DisplayDevice的作用
图5 DisplayDevice的隔离示意图
最后再来看一下SurfaceFlinger中混屏操作的实现,代码如图6所示:
图6 SurfaceFilnger的混屏操作
由图5可知,SurfaceFlinger将遍历系统中所有的DisplayDevice来完成各自的混屏工作。
2.2 Framework对Miracast的支持
为了彻底解决多显示设备的问题,Android 4.2干脆在Framework中新增了一个名为DisplayManagerService的服务,用来统一管理系统中的显示设备。DisplayManagerService和系统其它几个服务都有交互。整体结构如图7所示。
图7 DisplayManagerService及相关类图
由图7可知:
· DisplayManagerService主要实现了IDisplayManager接口。这个接口的大部分函数都和Wi-Fi Display操作相关。
· 另外,DisplayManagerService和WindowManagerService交互紧密。因为WindowManagerService管理系统所有UI显示,包括属性,Z轴位置等等。而且,WindowManagerService是系统内部和SurfaceFlinger交互的重要通道。
· DisplayManagerService通过mDisplayAdapters来和DisplayDevice交互。每一个DisplayDevice都对应有一个DisplayAdapter。
· 系统定义了四种DisplayAdapter。HeadlessDisplayAdapter和OverlayDisplayAdapter针对的都是Fake设备。其中OverlayDisplay用于帮助开发者模拟多屏幕之用。LocalDisplayAdapter代表主屏幕,而WifiDisplayAdapter代表Wi-Fi Display。
2.3 Android中Miracast动态工作流程介绍
当用户从Settings程序中选择开启Miracast并找到匹配的Device后[③],系统将通过WifiDisplayController的requestConnect函数向匹配设备发起连接。代码如图8所示:
图8 requestConnect函数实现
图8中,最终将调用connect函数去连接指定的设备。connect函数比较中,其中最重要的是updateConnection函数,我们抽取其中部分代码来看,如图9所示:
图9 updateConnection函数片段
在图8所示的代码中,系统创建了一个RemoteDisplay,并在这个Display上监听(listen)。从注释中可知,该RemoteDisplay就是和远端Device交互的RTP/RTSP通道。而且,一旦有远端Device连接上,还会通过onDisplayConnected返回一个Surface对象。
根据前面对SurfaceFlinger的介绍,读者可以猜测出Miracast的重头好戏就在RemoteDisplay以及它返回的这个Surface上了。
确实如此,RemoteDisplay将调用MediaPlayerService的listenForRemoteDisplay函数,最终会得到一个Native的RemoteDisplay对象。相关类图如图10所示。
图10 RemoteDisplay类图
由图10可知,RemoteDisplay有三个重要成员变量:
· mLooper,指向一个ALooper对象。这表明RemoteDisplay是一个基于消息派发和处理的系统。
· mNetSession指向一个ANetWorkSession对象。从它的API来看,ANetworkSession提供大部分的网络操作。
· mSource指向一个WifiDisplaySource对象。它从AHandler派生,故它就是mLooper中消息的处理者。注意,图中的M1、M3、M5等都是Wi-Fi Display技术规范中指定的消息名。
RemoteDisplay构造函数中,WifiDisplaySource的start函数将被调用。如此,一个类型为kWhatStart的消息被加到消息队列中。该消息最终被WifiDisplaySource处理,结果是一个RTSPServer被创建。代码如图11所示:
图11 kWhatStart消息的处理结果
以后,客户端发送的数据都将通过类型为kWhatRTSPNotify的消息加入到系统中来。而这个消息的处理核心在onReceiveClientData函数中,它囊括了设备之间网络交互的所有细节。其核心代码如图12所示:
图12 onReceiveClientData核心代码示意
图12的内容较多,建议读者根据需要自行研究。
根据前面的背景知识介绍,设备之间的交互将由Session来管理。在代码中,Session的概念由WifiSource的内部类PlaybackSession来表示。先来看和其相关的类图结构,如图13所示:
图13 PlaybackSession及相关类图
由图13可知:
· PlaybackSession及其内部类Track都从AHandler派生。故它们的工作也依赖于消息循环和处理。Track代表视频流或音频流。
· Track内部通过mMediaPull变量指向一个MediaPull对象。而MediaPull对象则保存了一个MediaSource对象。在PlaybackSession中,此MediaSource的真正类型为SurfaceMediaSource。它表明该Media的源来自Surface。
· BufferQueue从ISurfaceTexure中派生,根据前面对SurfaceFlinger的介绍,它就是SurfaceFlinger代码示例中代表虚拟设备的State的surface变量。
当双方设备准备就绪后,MediaPull会通过kWhatPull消息处理不断调用MediaSource的read函数。在SurfaceMediaSource实现的read函数中,来自SurfaceFlinger的混屏后的数据经由BufferQueue传递到MediaPull中。代码如图14所示:
图14 MediaPull和SurfaceMediaSource的代码示意
从图13可知:
· 左图中,MediaPull通过kWhatPull消息不断调用MediaSource的read函数。
· 右图中,SurfaceMediaSource的read函数由通过mBufferQueue来读取数据。
那么mBufferQueue的数据来自什么地方呢?对,正是来自图4的SurfaceFlinger。
当然,PlaybackSession拿到这些数据后还需要做编码,然后才能发送给远端设备。由于篇幅关系,本文就不再讨论这些问题了。
三总结
本文对Miracast的背景知识以及Android系统中Miracast的实现进行了一番简单介绍。从笔者个人角度来看,有以下几个点值得感兴趣的读者注意:
· 一定要结合Wi-Fi的相关协议去理解Miracast。重点关注的协议包括Wi-Fi P2p和WMM。
· Android Miracast的实现中,需要重点理解SurfaceFlinger和RemoteDisplay模块。这部分的实现不仅代码量大,而且类之间,以及线程之间关系复杂。
· 其他需要注意的点就是DisplayManagerService及相关模块。这部分内容在SDK中有相关API。应用开发者应关注这些新API是否能帮助自己开发出更有新意的应用程序。
另外,Android的进化速度非常快,尤其在几个重要的功能点上。作者在此也希望国内的手机厂商或那些感兴趣的移动互联网厂商能真正投入力量做一些更有深度和价值的研发工作。
十、wifi和蓝牙
首先交代开发环境——硬件平台:高通MSM8225,OS:Android2.3.5,无线模块:brodcom BCM4330。
一、WIFI:
首先保证上好电:在platform/kernel/arch/arm/mach-msm/board-msm7x30.c中实现GPIO管脚的配置,也要配置好休眠唤醒的脚。再就是在板级__init里面配置好WIFI的上电以及下电时序问题,还有因为我们的无线芯片是基于SDIO进行通讯的,所以SDIO的driver也要配置好。
再就是WIFI本身的驱动的开发了,我们是用博通公司提供的代码,在dhd_custemer_gpio.c中修改好相应的Power_en和reset脚的配置,然后配置wlan linux相关的驱动。最终编译生成dhd.ko文件。
这个dhd.ko文件和nvram.txt以及sdio.bin文件共同加载了wlan0的驱动。在中间层有一个hardware/library_lib/wifi/wifi.c,就是把一些配置文件的加载,DHCP文件的文件路径等等综合在一起。
在wifi移植过程中一定要注意MAC地址的有效性和可用性。这样才能连接热点已经被分配到IP地址。
二、Bluetooth:
首先还是和wifi一样,上电是必须的!在platform/kernel/arch/arm/mach-msm/board-msm7x30.c中实现bluetooth_power_init,注意是在参数初始化的函数__initmsm7x30_init函数中完成。
由于BT的协议层,在开发的时候基本不用动什么,所以我们直接找跟芯片相关的文件进行修改。brodcom BT相关的文件在system/bluetooth/brcm_patchram_plus/
brcm_patchram_plus.c 中,里面主要完成了如下事件:1、读取BT的地址。2、执行parse相关命令。3、重启HCI服务,4、设置蓝牙通信的波特率。5、设置hci_pcm
再就是开启BT的服务。目录在system/core/rootdir/A1/etc/init.qcom.bt.sh 中,实现start_hciattach和kill_hciattach的功能。
网上关于BT的驱动很少,所以我在开发过程中把其中的步骤记录下来。供大家相互学习讨论。
一、关于BT driver的移植:
1. Enablebluetootch in BoadConfig.mk
BOARD_HAVE_BLUETOOTH:= true
2.实现BT电源管理rfkill驱动。
Kernel/driver/bluetooth/bluetooth-power.c 高通的这个文件基本上不用动。
在kernel\arch\arm\mach_msm7x27.c:static int bluetooth_power(int on)中
实现:上电:把bt_reset pin 和bt_reg_on pin 拉低
mdelay(10);
把bt_resetpin 和bt_reg_on pin 拉高
mdelay(150)
下电:把bt_resetpin 和bt_reg_on pin 拉低
3. RebuildAndroid image and reboot
命令行测试:
echo 0 >/sys/class/rfkill/rfkill0/state //BT下电
echo 1 >/sys/class/rfkill/rfkill0/state //BT上电
brcm_patchram_plus-d --patchram/etc/firmware/BCM4329B1_002.002.023.0061.0062.hcd/dev/ttyHS0
hciattach -s115200 /dev/ttyHS0 any
没任何错误提示是可以用以下测试
hciconfig hci0up
hcitool scan
4.实现BT睡眠唤醒机制
Kernel\drivers\bluetooth\bluesleep.c一般来说这个文件改动比较少,但可能逻辑上会有些问题。需要小的改动。
在kernel\arch\arm\mach_xxx/board_xxx.c:bluesleep_resources中定义gpio_host_wake(BT唤醒host脚)、gpio_ext_wake(host唤醒BT脚)、host_wake(BT唤醒host的中断号)。
注:各个平台的board_xxx.c文件名字不同,请客户确认
5.系统集成
1)在init.qcom.rc中确认有下面的内容:
service hciattach/system/bin/sh /system/etc/init.qcom.bt.sh
user bluetooth
group qcom_oncrpc bluetoothnet_bt_admin
disabled
oneshot
2)修改init.qcom.bt.sh
确认有:
BLUETOOTH_SLEEP_PATH=/proc/bluetooth/sleep/proto
echo 1 >$BLUETOOTH_SLEEP_PATH
/system/bin/hciattach-n /dev/ttyHS0 any 3000000 flow & 改为:
./brcm_patchram_plus--enable_lpm –enable_hci--patchram /system/etc/wifi/BCM4329BT.hcd --baudrate3000000 /dev/ttyHS0 &
注掉:高通下载firmware的命令。
6.重新编译system。此时BT应该能运行了。
二、BT的休眠唤醒配置
BT的休眠在driver/bluetooth/bluesleep.c中,首先驱动的名字叫“bluesleep”与arch/arm/mach-msm/board-msm7x30.c相匹配就执行platform_driver_probe(&bluesleep_driver, bluesleep_probe)然后调用static int __init bluesleep_probe(struct platform_device *pdev),这里会配置两个引脚HOST_WAKE_BT & BT_WAKE_HOST
bsi = kzalloc(sizeof(struct bluesleep_info), GFP_KERNEL);
if (!bsi)
return-ENOMEM;
res =platform_get_resource_byname(pdev, IORESOURCE_IO,
"gpio_host_wake");
if (!res) {
BT_ERR("couldn'tfind host_wake gpio\n");
ret =-ENODEV;
gotofree_bsi;
}
bsi->host_wake= res->start;
//[SIMT-zhangmin-111230]change the configuration of BT sleep gpio from bt_power to here {
gpio_tlmm_config(GPIO_CFG(bsi->host_wake,0, GPIO_CFG_INPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA),GPIO_CFG_ENABLE);
//[SIMT-zhangmin-111230]}
ret =gpio_request(bsi->host_wake, "bt_host_wake");
if (ret)
gotofree_bsi;
ret =gpio_direction_input(bsi->host_wake);
bsi->host_wake_irq = platform_get_irq_byname(pdev,"host_wake");
如上代码所示,主要将HOST_WAKE_BT设置为输出脚,BT_WAKE_HOST 设置为输入脚并也设置为中断脚,等待BT芯片的唤醒。
然后再bluesleep_init函数中建立BT目录/proc/bluetooth/sleep/读btwake中HOST_WAKE_BT的状态。读出的状态值为0或1。
或在BT目录/proc/bluetooth/sleep/写btwake中HOST_WAKE_BT的状态。写出状态值为0或1。
Part 1
问:打开wifi,连接wifi热点,提示连接成功,但headbar上不显示wifi图标,back退出wifi设置,再进入,提示wifi已断开。
答:首先现象复现,当现象复现时进入adb shell,然后输入ifconfig查看是否有wlan0 端口,然后在adb shell 中ping 192.168.1.101(嵌入式设备的IP),如果能Ping通则说明底层wifi设备与AP是连接通的。所以把问题转向上层。上层在frameworks/base/services/java/com/android/server/ConnectivityService.java文件中查看private void handleDisconnect(NetworkInfo info)函数的实现,问题可能出现在这里。
Part 2
问:出现新热点,需要重启WIFI才可以扫描到。
问:20s内无法连接到WIFI热点。
问:已连接的热点关闭,不会自动连接下一个热点。
答:这些问题都是供应商芯片的firmware没有配置好,所以直接找FAE换掉。
Part 3
问:调试中如果遇到dhd_sdio_probe fail 。
问:发现sdio register timeout 。
答:因为我们用到的WIFI数据通道是SDIO接口,所以要在log信息中查看sdcard是否有加载。没加载的话就看看WIFI芯片的上电(如WIFI_REG_ON这个PIN脚)。
Part 4
问:如果在wifi的设置里面选中wifi选项出现ERRO(或错误)的提示。
答:首先在adb shell中lsmod查看.ko文件是否已经加载。如没加载cat /proc/kmsg查看是否是版本匹配的问题。如遇版本匹配则在kernel/scripts/setlocalversion中将echo “+”中去掉。如果顺利加载了驱动,则要看看MAC地址是否有,并且是否合理。
Part 5
问:发现在WIFI设置选项中有已经选上了wifi并且勾应打上了,过一会出现wifi的勾自动消失。
答:这种情况在adb shell中用ifconfig查看Wlan的接口用的是否是wlan0,有可能是eth0。如果是eth0则在hardware/libhardware_legacy/wifi/wifi.c中的#define WIFI_DRIVER_MODULE_ARG "firmware_path=/system/etc/firmware/wlan/sdio.binnvram_path=/data/simcom/nvram.txt iface_name=wlan0" 中查看iface_name=wlan0是否已经加上。
Part 6
问:有的路由器不能扫描的到。
答:查看wlan的设置的channel是否在1-14这个频段,因为如果wlan设置成USA模式则channel的范围在1-11之间,channel12,13,14就不会收索的到。所以要在nvram.txt中修改成ccode=ALL,并且在gqcom_cfg.ini中把APCntryCode=ALL。这样应该就可以扫描到所有的channel了。当然如果上层还是没有收索到的话,在Settings.java中也要做相应的修改。
常用的命令:wl channels_in_country
wl chanlist
wl channels
wl country
Part 7
问:【预置条件】:wifi 已连接 wifi sleep policy模式为When screen turns off
【操作步骤】:wifi连接网络,静置屏幕熄灭 大约5分钟,再次点亮屏幕
【测试结果】:wifi一直处在scanning状态,无法连接网络,关闭再打开恢复正常
【预期结果】:点亮屏幕后,wifi自动连接成功
答:调试分析记录:
1.在frameworks/base/services/java/com/android/server/WifiService.java中修改LCD灭屏后关掉WIFI的时间长短,由以前的2分钟修改为现在的1秒钟。
结果是短时间休眠后唤醒可以顺利连上AP,但经过长时间休眠(如15分钟)后唤醒机器还是无法连接AP。
2.检查Kernel中POWER_ON使能脚,看是否有其它地方被占用过,结果是屏蔽所有其它用到过该脚的代码还是无用。
3.检查CP侧代码,将CP侧POWER_ON引脚由以前的某种特殊功能设置成普通的Output模式,然后AP就可以自由的控制其高低电平了。
修改后的结果还是以无效告终。
4.检查external/wpa_supplicant_6/wpa_supplicant/文件夹下面的代码,检查WEXT驱动代码。结果还是失败。
5.最后在机器休眠15分钟后,用底层命令行来scan、连接等动作的操作,结果根本无法扫描到AP。故把问题集中在驱动底层。经过修改dhd.ko、sdio.bin文件
现在此Bug终于得解。
6.也有问题不出现在驱动dhd.ko文件中,可能出现在mmc驱动中的sdio通道。在函数mmc_pm_notify中当出现PM_POST_SUSPEND或PM_POST_HIBERNATION的情况时就有可能移除掉了sdio通道,所以函数mmc_detect_change(host, 0)要加一个判断,当sdio作为wifi通道时就不能移除。移除后的后果就是此Bug。