Android WLAN – the rest of the story
Published on September 2, 2010
R.I.P. Paul Harvey.
It seems I was a bit naive when calling the WLAN updates completes in my earlier blog post. While the previous post did show the process of getting and installing the driver, and is sufficient to get wpa_supplicant and wpa_cli up and running from the command-line, the updates are not enough to make Android happy.
The first update needed fixes the permissions in device/fsl/imx5x/init.rc
.
commit 1c7df8972d769e28680a57836f9a9d0b9112d5ee Author: Eric Nelson Date: Thu Sep 2 14:41:29 2010 -0700 fix groups for wpa_supplicant and dhcpcd diff --git a/imx5x/init.rc b/imx5x/init.rc index 349f897..3fd0bc5 100755 --- a/imx5x/init.rc +++ b/imx5x/init.rc @@ -472,13 +472,15 @@ service dumpstate /system/bin/dumpstate -s oneshot service dhcpcd /system/bin/logwrapper /system/bin/dhcpcd -d -B wlan0 + group system dhcp disabled oneshot service wpa_supplicant /system/bin/logwrapper /system/bin/wpa_supplicant -Dwext -iwlan0 -c/data/misc/wifi/wpa_supplicant.conf user root - group wifi inet + group system wifi inet dhcp socket wpa_wlan0 dgram 660 wifi wifi + disabled oneshot
The updated group
lines are needed to allow wpa_supplicant
to open sockets and dhcpcd
to do much of anything.
Once these are fixed, I found that there's a more pernicious problem, though. This little snippet of code from external/wpa_supplicant_6/wpa_supplicant/src/drivers/drivers_wext.c
shows what happens with the Unigen driver:
if (0 > ret) { perror("ioctl[SIOCSIWPRIV]"); wpa_printf(MSG_ERROR, "%s failed (%d): %s:%m", __func__, ret, cmd); drv->errors++; if (drv->errors > WEXT_NUMBER_SEQUENTIAL_ERRORS) { drv->errors = 0; wpa_msg(drv->ctx, MSG_INFO, WPA_EVENT_DRIVER_STATE "HANGED"); } }
In English, if some number of consecutive driver errors occurs, the network interface reports to be HANGED
.
Dang! What could be causing this?
After a frenzied bout of debugging and a number of bum steers chasing down which driver, I'll detail what I've learned.
It's probably best to start with some background information.
- Android uses something called "wpa_supplicant" to manage WLAN devices.
- wpa_supplicant has two main interfaces as illustrated here: -- the "client" side talks to GUIs, including Android, and -- the "driver" side which talks to device drivers
- The wpa_supplicant package contains a command-line interface named "wpa_cli" that is also installed as a part of Android
- The "client" side of wpa_supplicant has a number of choices of interfaces, including Named Pipes under Windows, Unix sockets, UDP sockets, and Android sockets
- The "driver" side of wpa_supplicant also has a number of choices, including "wext", "ipw", and "Atmel".
I was seriously confused by the fact that I could configure the WLAN and connect using wpa_cli perfectly well without patches. I also went on a number of goose chases trying to figure out which client interface to use and which driver interface to use (the Unigen driver supports the IPW interface as well as "wext"), finally determining that neither of these made any difference and that the problem lay elsewhere. Once settling on "Android" and "wext", I got "HANGED" and as so often happens, I also found some answers on the 'Net.
This thread on the "Android Porting" mailing list is the best discussion I've found (Thanks Nicu!).
It turns out that in addition to the well-formed command set that wpa_supplicant supplies in the wpa_cli package, it's also possible to send custom messages straight from a GUI to the driver with minimal involvement by wpa_supplicant. In the "wext" driver case, these messages are passed as literal text through the SIOCSIWPRIV
ioctl. It also turns out that Android uses this
"feature" extensively, requiring modifications to each wireless LAN driver that wants to play in the Android space. It also turns out that some of these "custom" commands can be satisfied with standard "wext" calls. In the mailing list thread mentioned above, two patches are presented:
- Nicu Pavel implemented a modified "wext" driver named "awext"
- Michael Trimarchi patched the "wext" driver, resulting in a somewhat smaller patch
Unfortunately, neither of these applied cleanly to Froyo. Mostly because of its' smaller size, I chose to use Michael's implementation as a baseline and trimmed it a bit more to produce the following patch. This approach also just seems "righter" (more "righteous"?). I want to thank both guys for posting their code.
diff --git a/wpa_supplicant/src/drivers/driver_wext.c b/wpa_supplicant/src/drivers/driver_wext.c index 2f0771d..2c0a122 100644 --- a/wpa_supplicant/src/drivers/driver_wext.c +++ b/wpa_supplicant/src/drivers/driver_wext.c @@ -253,7 +253,9 @@ int wpa_driver_wext_set_ssid(void *priv, const u8 *ssid, size_t ssid_len) if (ssid_len) ssid_len++; } - iwr.u.essid.length = ssid_len; + drv->ssid_len = iwr.u.essid.length = ssid_len; + memcpy(drv->ssid,buf,ssid_len); + drv->ssid[ssid_len] = 0 ; if (ioctl(drv->ioctl_sock, SIOCSIWESSID, &iwr) errors = 0; drv->driver_is_loaded = TRUE; drv->skip_disconnect = 0; + drv->ssid_len = 0 ; + drv->ssid[0] = 0 ; #endif wpa_driver_wext_finish_drv_init(drv); @@ -2462,16 +2466,208 @@ static int wpa_driver_priv_driver_cmd( void *priv, char *cmd, char *buf, size_t iwr.u.data.pointer = buf; iwr.u.data.length = buf_len; - if ((ret = ioctl(drv->ioctl_sock, SIOCSIWPRIV, &iwr)) ioctl_sock, SIOCSIWPRIV, &iwr); if (ret errors++; - if (drv->errors > WEXT_NUMBER_SEQUENTIAL_ERRORS) { - drv->errors = 0; - wpa_msg(drv->ctx, MSG_INFO, WPA_EVENT_DRIVER_STATE "HANGED"); +#ifdef ANDROID + if (os_strcasecmp(cmd, "RSSI") == 0) { + struct iwreq wrq; + struct iw_statistics stats; + signed int rssi; + wpa_printf(MSG_DEBUG, ">>>. DRIVER EMULATE RSSI "); + wrq.u.data.pointer = (caddr_t) &stats; + wrq.u.data.length = sizeof(stats); + /* Clear updated flag */ + wrq.u.data.flags = 1; + strncpy(wrq.ifr_name, drv->ifname, IFNAMSIZ); + + if (ioctl(drv->ioctl_sock, SIOCGIWSTATS, &wrq) 63 ) ? + stats.qual.level - 0x100 : + stats.qual.level; + } else + rssi = stats.qual.level; + + if (drv->ssid_len != 0 && + drv->ssid_len ssid), drv->ssid_len); + ret = drv->ssid_len; + ret += snprintf(&buf[ret], buf_len-ret, + " rssi %dn", rssi); + if (ret ctx, MSG_INFO, WPA_EVENT_DRIVER_STATE "STARTED"); + } else if (os_strncasecmp(cmd, "STOP", 4) == 0) { + wpa_msg(drv->ctx, MSG_INFO, WPA_EVENT_DRIVER_STATE "STOPPED"); + } else if (os_strncasecmp(cmd, "LINKSPEED", 9) == 0) { + struct iwreq wrq; + unsigned int linkspeed; + os_strncpy(wrq.ifr_name, drv->ifname, IFNAMSIZ); + wpa_printf(MSG_DEBUG,"Link Speed command"); + if (ioctl(drv->ioctl_sock, SIOCGIWRATE, &wrq) ifname, IFNAMSIZ); + + if (ioctl(drv->ioctl_sock, SIOCGIWSTATS, &wrq) 63 ) ? + stats.qual.level - 0x100 : + stats.qual.level; + noise = ( stats.qual.noise > 63 ) ? + stats.qual.noise - 0x100 : + stats.qual.noise; + } else { + rssi = stats.qual.level; + noise = stats.qual.noise; + } + + snr = rssi - noise; + + ret = snprintf(buf, buf_len, "snr = %un", + (unsigned int)snr); + if (ret ifname, IFNAMSIZ); + + if (*cp != ' ') { + rtsThreshold = (unsigned int)strtol(cp, &endp, 0); + if (endp != cp) { + wrq.u.rts.value = rtsThreshold; + wrq.u.rts.fixed = 1; + wrq.u.rts.disabled = 0; + + if (ioctl(drv->ioctl_sock, SIOCSIWRTS, + &wrq) ifname, IFNAMSIZ); + + if (ioctl(drv->ioctl_sock, SIOCGIWRTS, &wrq) ifname, IFNAMSIZ); + + if (ioctl(drv->ioctl_sock, SIOCGIFHWADDR, &ifr) ret) { + perror("ioctl[SIOCSIWPRIV]"); + wpa_printf(MSG_ERROR, "%s failed (%d): %s:%m", __func__, ret, cmd); + drv->errors++; + if (drv->errors > WEXT_NUMBER_SEQUENTIAL_ERRORS) { + drv->errors = 0; + wpa_msg(drv->ctx, MSG_INFO, WPA_EVENT_DRIVER_STATE "HANGED"); + } } } else { diff --git a/wpa_supplicant/src/drivers/driver_wext.h b/wpa_supplicant/src/drivers/driver_wext.h index f6b76a9..423b60f 100644 --- a/wpa_supplicant/src/drivers/driver_wext.h +++ b/wpa_supplicant/src/drivers/driver_wext.h @@ -47,6 +47,9 @@ struct wpa_driver_wext_data { int errors; int driver_is_loaded; int skip_disconnect; + /* used for emulate system call */ + u8 ssid[32]; + unsigned int ssid_len; #endif };
This patch is 90+ percent Michael's. I did collapse his wpa_emulate_priv_android_cmd()
call into the body of wpa_driver_priv_driver_cmd
in order to eliminate one level of if (os_strcasecmp())
calls.