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 oneshotThe 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".
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
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) < 0) { perror("ioctl[SIOCSIWESSID]"); @@ -966,6 +968,8 @@ void * wpa_driver_wext_init(void *ctx, const char *ifname) drv->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)) < 0) { - perror("ioctl[SIOCSIWPRIV]"); - } + ret = ioctl(drv->ioctl_sock, SIOCSIWPRIV, &iwr); if (ret < 0) { - wpa_printf(MSG_ERROR, "%s failed (%d): %s", __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"); +#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) < 0) { + perror("ioctl[SIOCGIWSTATS]"); + ret = -1; + } else { + if (stats.qual.updated & IW_QUAL_DBM) { + /* Values in dBm, stored in u8 with range 63 : -192 */ + rssi = ( stats.qual.level > 63 ) ? + stats.qual.level - 0x100 : + stats.qual.level; + } else + rssi = stats.qual.level; + + if (drv->ssid_len != 0 && + drv->ssid_len < buf_len) { + os_memcpy((void *) buf, (void *) + (drv->ssid), drv->ssid_len); + ret = drv->ssid_len; + ret += snprintf(&buf[ret], buf_len-ret, + " rssi %dn", rssi); + if (ret < (int)buf_len) + return ret; + ret = -1; + } + } + } else if (os_strncasecmp(cmd, "START", 5) == 0) { + os_sleep(0, WPA_DRIVER_WEXT_WAIT_US); + wpa_msg(drv->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) < 0) { + perror("ioctl[SIOCGIWRATE]"); + ret = -1; + } else { + linkspeed = wrq.u.bitrate.value / 1000000; + ret = snprintf(buf, buf_len, "LinkSpeed %dn", + linkspeed); + } + } else if (os_strncasecmp(cmd, "SNR", 3) == 0) { + struct iwreq wrq; + struct iw_statistics stats; + int snr, rssi, noise; + + wrq.u.data.pointer = (caddr_t) &stats; + wrq.u.data.length = sizeof(stats); + wrq.u.data.flags = 1; /* Clear updated flag */ + strncpy(wrq.ifr_name, drv->ifname, IFNAMSIZ); + + if (ioctl(drv->ioctl_sock, SIOCGIWSTATS, &wrq) < 0) { + perror("ioctl[SIOCGIWSTATS]"); + ret = -1; + } else { + if (stats.qual.updated & IW_QUAL_DBM) { + /* Values in dBm, stored in u8 with + * range 63 : -192 */ + rssi = ( stats.qual.level > 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 < (int)buf_len) + return ret; + } + } else if (os_strncasecmp(cmd, "SET-RTS-THRESHOLD", 17) == 0) { + struct iwreq wrq; + unsigned int rtsThreshold; + char *cp = cmd + 17; + char *endp; + + strncpy(wrq.ifr_name, drv->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) < 0) { + perror("ioctl[SIOCGIWRTS]"); + ret = -1; + } else { + rtsThreshold = wrq.u.rts.value; + wpa_printf(MSG_DEBUG,"Set RTS Threshold command = %d", rtsThreshold); + ret = 0; + } + } + } + } else if (os_strncasecmp(cmd, "GET-RTS-THRESHOLD", 17) == 0) { + struct iwreq wrq; + unsigned int rtsThreshold; + + strncpy(wrq.ifr_name, drv->ifname, IFNAMSIZ); + + if (ioctl(drv->ioctl_sock, SIOCGIWRTS, &wrq) < 0) { + perror("ioctl[SIOCGIWRTS]"); + ret = -1; + } else { + rtsThreshold = wrq.u.rts.value; + wpa_printf(MSG_DEBUG,"Get RTS Threshold command = %d", + rtsThreshold); + ret = snprintf(buf, buf_len, "rts-threshold = %un", + rtsThreshold); + if (ret < (int)buf_len) + return ret; + } + } else if (os_strncasecmp(cmd, "SCAN-ACTIVE", 11) == 0) { + wpa_printf(MSG_DEBUG,"SCAN-ACTIVE not yet supportedn"); + ret = 0 ; + } else if (os_strncasecmp(cmd, "SCAN-PASSIVE", 12) == 0) { + wpa_printf(MSG_DEBUG,"SCAN-PASSIVE not yet supportedn"); + ret = 0 ; + } else if (os_strncasecmp(cmd, "SCAN-CHANNELS", 13) == 0) { + wpa_printf(MSG_DEBUG,"SCAN-CHANNELS not yet supportedn"); + ret = 0 ; + } else if (os_strncasecmp(cmd, "BTCOEXSCAN-START", 16) == 0) { + wpa_printf(MSG_DEBUG,"BTCOEXSCAN-START not yet supportedn"); + ret = 0 ; + } else if (os_strncasecmp(cmd, "BTCOEXSCAN-STOP", 15) == 0) { + wpa_printf(MSG_DEBUG,"BTCOEXSCAN-STOP not yet supportedn"); + ret = 0 ; + } else if (os_strncasecmp(cmd, "RXFILTER-START", 14) == 0) { + wpa_printf(MSG_DEBUG,"RXFILTER-START not yet supportedn"); + ret = 0 ; + } else if (os_strncasecmp(cmd, "RXFILTER-STOP", 13) == 0) { + wpa_printf(MSG_DEBUG,"RXFILTER-STOP not yet supportedn"); + ret = 0 ; + } else if (os_strncasecmp(cmd, "RXFILTER-STATISTICS", 19) == 0) { + wpa_printf(MSG_DEBUG,"RXFILTER-STATISTICS not yet supportedn"); + ret = 0 ; + } else if (os_strncasecmp(cmd, "RXFILTER-ADD", 12) == 0) { + wpa_printf(MSG_DEBUG,"RXFILTER-ADD not yet supportedn"); + ret = 0 ; + } else if (os_strncasecmp(cmd, "RXFILTER-REMOVE", 15) == 0) { + wpa_printf(MSG_DEBUG,"RXFILTER-REMOVE not yet supportedn"); + ret = 0 ; + } else if (os_strncasecmp(cmd, "BTCOEXMODE", 10) == 0) { + wpa_printf(MSG_DEBUG,"BTCOEXMODE not yet supportedn"); + ret = 0 ; + } else if (os_strncasecmp(cmd, "BTCOEXSTAT", 10) == 0) { + wpa_printf(MSG_DEBUG,"BTCOEXSTAT not yet supportedn"); + ret = 0 ; + } else if (os_strncasecmp(cmd, "POWERMODE", 9) == 0) { + wpa_printf(MSG_DEBUG,"POWERMODE not yet supportedn"); + ret = 0 ; + } else if (os_strncasecmp(cmd, "GETPOWER", 8 ) == 0) { + wpa_printf(MSG_DEBUG,"GETPOWER not yet supportedn"); + ret = 0 ; + } else if (os_strncasecmp(cmd, "MACADDR", 7) == 0) { + /* MACADDR */ + struct ifreq ifr; + os_memset(&ifr, 0, sizeof(ifr)); + os_strncpy(ifr.ifr_name, drv->ifname, IFNAMSIZ); + + if (ioctl(drv->ioctl_sock, SIOCGIFHWADDR, &ifr) < 0) { + perror("ioctl[SIOCGIFHWADDR]"); + ret = -1; + } else { + u8 *macaddr = (u8 *) ifr.ifr_hwaddr.sa_data; + ret = snprintf(buf, buf_len, "Macaddr = " MACSTR "n", + MAC2STR(macaddr)); + } + } +#endif + 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"); + } } } 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.