分类: LINUX
2010-08-02 23:51:06
Porting WiFi drivers to Android
For we want to support several USB and miniPCI WiFi dongles, this guide provides a step by step explanation of what's involved in adding a new wifi driver and making wifi work in a custom Android build (this guide was written for android 2.1 but should be applicable to previous android releases and hopefully future releases).
Contents
0. Understand how Android WiFi works.
1. Enable building of wpa_supplicant in your BoardConfig.mk
2. (Optional) Enable debug for wpa_supplicant.
3. Provide a proper wpa_supplicant.conf for your device
4. Have the correct paths and permissions created from init.rc
5. Make sure your wpa_supplicant and dhcpcd (optional) are starting from init.rc
6. Provide your driver either as a module or built in kernel and proper kernel support for it and modify Android source code accordingly.
7. Provide a firmware if your module needs it.
8. Make your driver work with Android custom wpa_supplicant commands and SIOCSIWPRIV ioctl
Now onto details.
0. Understand how Android WiFi works.
Android uses a modified wpa_supplicant (external/wpa_supplicant) daemon for wifi support which is controlled through a socket by hardware/libhardware_legacy/wifi/wifi.c that gets controlled from Android UI through android.net.wifi package from frameworks/base/wifi/java/android/net/wifi/ and it's corresponding jni implementation in frameworks/base/core/jni/android_net_wifi_Wifi.cpp. Higher level network management is done in frameworks/base/core/java/android/net
1. Enable building of wpa_supplicant in your BoardConfig.mk
This is by simply adding: BOARD_WPA_SUPPLICANT_DRIVER := WEXT
to your BoardConfig.mk. This will set WPA_BUILD_SUPPLICANT to true in
external/wpa_supplicant/Android.mk enabling building of driver_wext.c
If you have a custom wpa_supplicant driver (like madwifi) you can set in BOARD_WPA_SUPPLICANT_DRIVER := MADWIFI.
2. (Optional) Enable debug for wpa_supplicant.
By default wpa_supplicant is set to MSG_INFO that doesn't tell much. To enable more messages:
2.1 modify common.c and set wpa_debug_level = MSG_DEBUG
2.2 modify common.h and change #define wpa_printf from #ifdef ANDROID to if ((level) >= MSG_DEBUG) instead of if ((level) >= MSG_INFO)
3. Provide a proper wpa_supplicant.conf for your device
Providing a wpa_supplicant.conf it's important because the control socket for android is specified in this file (ctrl_interface= ). This file should be copied by your AndroidBoard.mk to $(TARGET_OUT_ETC)/wifi (basically /system/etc/wifi/wpa_supplicant.conf).
This location should be double checked on init.rc for service wpa_supplicant as the wpa_supplicant config file.
Minimum required config options in wpa_supplicant.conf :ctrl_interface=DIR=/data/system/wpa_supplicant GROUP=wifi
update_config=1
Depending on your driver you might also want to add:ap_scan=1
If you have AP association problems with should change to ap_scan=0 to let the driver do the association instead of wpa_supplicant.
If you want to let wpa_supplicant connect to non-WPA or open wireless networks (by default it skips these kind) add:network={
key_mgmt=NONE
}
4. Have the correct permissions and paths created from init.rc
Incorrect permisions will result in wpa_supplicant not being able to create/open the control socket and libhardware_legacy/wifi/wifi.c won't connect.
Since Google modified wpa_supplicant to run as wifi user/group the directory structure and file ownership should belong to wifi user/group (see os_program_init() function in wpa_supplicant/os_unix.c).
Otherwise errors like:
E/WifiHW ( ): Unable to open connection to supplicant on "/data/system/wpa_supplicant/wlan0": No such file or directory will appear.
Also wpa_supplicant.conf should belong to user/group wifi because wpa_supplicant will want to modify this file. If your system has /system as read-only use a location like /data/misc/wifi/wpa_supplicant.conf and modify wpa_supplicant service in init.rc with new location.
Make sure the paths are correctly created in 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 0771 wifi wifi
chmod 0771 /data/system/wpa_supplicant
#wpa_supplicant control socket for android wifi.c
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
5. Make sure your wpa_supplicant and dhcpcd are starting from init.rc
For wpa_supplicant the init.rc startup like should be:service wpa_supplicant /system/bin/wpa_supplicant -dd -Dwext -iwlan0 -c /system/etc/wifi/wpa_supplicant.conf
group system wifi inet
disabled
oneshot
If your wifi driver creates a wifi interface with other name then wlan0 you will have to modify the above line accordingly.
You also should have dhcpcd starting from init.rcservice dhcpcd /system/bin/dhcpcd wlan0
group system dhcp
disabled
oneshot
6. Provide your driver either as a module or built in kernel and proper kernel support for it.
First make sure that CONFIG_PACKET and CONFIG_NET_RADIO (wireless extensions) are enabled in your kernel. The driver can be built as module (default android way) or built in kernel (if you want to rely in kernel auto probing to support multiple driver eg. USB wifi) but will require source code modifications (see below).
- As kernel module:
Define in your BoardConfig.mk:
1. WIFI_DRIVER_MODULE_PATH :=
You need to specify module name in that path too, usually should look something like /system/lib/modules/wlan.ko
2. WIFI_DRIVER_MODULE_NAME:=
3. WIFI_DRIVER_MODULE_ARG:= system/code/toolbox/hotplug.c) instead the init process takes care of firmware events and loads the firmware file from /etc/firmware (see: system/core/init/devices.c handle_firmware_event() function).
Firmware file name is defined by the driver and might also contain a folder like: RTL8192SU/rtl8192sfw.bin, entire file path should be available in /etc/firmware.
8. Make your driver work with Android custom wpa_supplicant commands and SIOCSIWPRIV ioctl.
Android uses SIOCSIWPRIV ioctl to send commands to driver and receive information like signal strength, mac address of the AP, link speed. This ioctl is usually not implemented in any known wireless drivers except bcm4329 which is in google .
The errors from not having this ioctl implemented will look like:
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
After 4, WEXT_NUMBER_SEQUENTIAL_ERRORS errors android will abort using the device.
To quickly test your wifi from interface you can disable error checking in external/wpa_supplicant/driver_wext.c by simply making ret = 0; in wpa_driver_priv_driver_cmd() function after the ioctl call. This will make all access points in android UI appear without signal or MAC address.
To proper implement the ioctl you will need to modify your kernel driver to reply to SIOCSIWPRIV ioctl with RSSI (signal strength) and MACADDR commands being the most important.
A better way is to add a custom driver_xxx.c to google external/wpa_supplicant/ implementing wpa_driver_priv_driver_cmd() function that will take care of RSSI and MACADDR through a call to SIOCGIWSTATS ioctl and the rest of the functions can be called from driver_wext.c.
Below is a link to a patch for wpa_supplicant that I did for Android build. It creates a new driver awext which "emulates" android driver commands using wireless extensions ioctls.
How to use the new driver:
1. In your BoardConfig.mk define: BOARD_WPA_SUPPLICANT_DRIVER := AWEXT
2. Change init.rc wpa_supplicant service command line by replacind -Dwext with -Dawext