Android WLAN – the rest of the story

Published on September 2, 2010

Archived Notice

This article has been archived and may contain broken links, photos and out-of-date information. If you have any questions, please Contact Us.

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:
  1. Nicu Pavel implemented a modified "wext" driver named "awext"
  2. 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) < 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.