2011
10.26

I’ve been able to get wireless working – well, sort of. The problem in my last post about the DHCP client failing to start was solved by adding wifi.interface=wlan0 in default.prop (doesn’t really matter where though; could have used system.prop for instance) as suggested by this thread. After fixing some more permission problems, I was able to connect to the open WiFi networks at my school:

Wireless settings in Gingerbread on the Nokia N810  Browser showing welcome page from wireless network on the Nokia N810

Wireless in Gingerbread (Android 2.3.7) on the Nokia N810

However, at our school, internet is provided only through an EAP-encrypted network (Purple Air in the above image). The school provides in addition two open WiFi networks. One, Purple Help in the above image, redirects all traffic to a help page which provides instructions on how to connect to the main EAP-encrypted network. The other, Guest in the above image, is for visiting guests to the college and will only give internet access if you knew a guest user name and password privately assigned to each visitor. The N810 is now able to connect to both of the two open networks, but is unable to connect to the EAP network. I am not surprised by this because our EAP network is set up so badly that I have not been able to make my Thinkpad laptop connect to it under Linux either, nor is the N810 able to connect to the network even using the proprietary driver under stock Maemo. Therefore, I have not been able to get internet on the device, but that is hardly the fault of Android, the driver, or the device.

I next tried tethering my phone as an AP, but while my laptop and tablet can connect to the phone fine, the N810 is not showing it in the list of scan results in Android. Using the proprietary driver for the wireless chip in stock Maemo, the N810 is able to connect to my phone. This means that there are still problems with the open source driver for the wireless chip in the N810, but I do know now that at least open WiFi networks on standard routers do work fine. Since I do not have a wireless router of my own, I have not been able to test further.

Share:
  • Reddit
  • Google Bookmarks
  • Facebook
  • Twitter
  • Digg
  • email
  • Print
  1. The progress you are making is awesome. I’ve dusted off and charged up my N810 in preparation for loading Android onto it.

  2. anyplace to dowload? do you have any source control for this? what abot steps on what you’ve done? maybe I can help

  3. Just come across this, great work! I’d really like to get Android working on my 810 so I can run MM-Tracker with my old .qct format OS maps. Have you had a chance to look at the GPS yet?

  4. @joe: Dev has old repositories on Github: https://github.com/jichuan89, but sadly doesn’t seem to have anything newer available.

    • here’s the latest source code: https://gitorious.org/nitdroid. however I dont know how to build and create a bootable img file. Any help? thanks,

  5. I too am interested in Gingerbread for n810.

    +1.

  6. Hi, I’m pretty interested in nitdroid on the n800, because i own one and maemo sucks compared to android, so far nitdroid project on the n800 was based on donut, android 1.6, but with no sound and external sd card mounting, so i am pretty interested on installing nitdroid whether is donut, eclair o even froyo but with sound and external sd card mouting, is there anyway to accomplish this?, i wanna revive my old n800 and android is the perfect answer

  7. good work. hoping for a public release. i have a nokia n810. thanks fo ryour work!

  8. Keep up the great work! I’m following this NITdroid blog since May or so and am looking forward to downloading a Gingerbread installer onto my N810 sometime soon :-)

  9. Hi all :-)

    any news about this ? where to found firmware or a documentation to do the same thing ?

    ps : sorry for my bad english

  10. Hi! im very interested about this too, could you do some How-To and put all the neccessary files in it too, you would have many testers for sure. =)

2011
10.25

I have been working on getting wireless to work on the N810 under Gingerbread. So far I have not been successful, but I have made a lot of progress along the way.

I started out basically follwing this great article on porting WiFi drivers to Android. For some reason, if I compiled the driver for the wireless chip on the N810 (p54spi) directly into the kernel, the kernel refuses to boot. Therefore, I compiled it as a module, grabbed the firmware required from the official site of the p54 driver and add the corresponding changes to Android.mk and BoardConfig.mk as described in the aforementioned article.

But, as the same article explains, Android, rather than using the normal control functions in wpa_supplicant like a normal Linux system, expects wireless drivers to implement Android’s custom set of commands directly on top of SIOCSIWPRIV, and relies on those instead. I do not understand the rationale behind such a design decision; why reinvent the wheel when the wireless connectivity mechanism on modern Linux systems work just fine? But of course it is pointless arguing with Android, and I adapted the patch found in this article on porting WiFi drivers for the Nitrogen-E (which is a a Freescale i.MX51 / Cortex-A8 based board.

Unfortunately that was not enough either. As Android still thrashed around and refused to talk to the wireless chip, I discovered a line in my logcat output saying something like ioctl[SIOCSIWPRIV] (cscan): -1. Researching further lead me to this PDF (also archived here) which explained that Gingerbread added on top of Froyo yet another custom command over SIOCSIWPRIV, combo scan, that a driver needs to implement. In fact, Gingerbread will only use combo scanning to scan for APs when using WEXT. A patch for this is found here or, less elegantly, by removing combo scanning from the WEXT driver of wpa_supplicant as in this patch.

In addition, I had to patch libhardware_legacy to use wlan0 instead of sta (seriously, why is that hard-coded in?) which libhardware_legacy uses as the interface name to determine the state of the wpa_supplicant process. The patch is as follows:

diff --git a/wifi/wifi.c b/wifi/wifi.c
index 3f8708d..9d9d89c 100644
--- a/wifi/wifi.c
+++ b/wifi/wifi.c
@@ -60,7 +60,7 @@ static char iface[PROPERTY_VALUE_MAX];
 #ifndef WIFI_FIRMWARE_LOADER
 #define WIFI_FIRMWARE_LOADER		""
 #endif
-#define WIFI_TEST_INTERFACE		"sta"
+#define WIFI_TEST_INTERFACE		"wlan0"

 #define WIFI_DRIVER_LOADER_DELAY	1000000

Otherwise, I get errors like

Unable to open connection to supplicant on "sta": No such file or directory

After all this effort, the device is able to scan correctly and does give a list of APs in Android GUI. From the logcat output I can see that it is also able to successfully associate with the AP:

I/wpa_supplicant( 1205): Trying to associate with 1c:17:d3:fc:db:79 (SSID='Purple Help' freq=2412 MHz)
D/wpa_supplicant( 1205): Cancelling scan request
D/wpa_supplicant( 1205): WPA: clearing own WPA/RSN IE
D/wpa_supplicant( 1205): Automatic auth_alg selection: 0x1
D/wpa_supplicant( 1205): WPA: clearing AP WPA IE
D/wpa_supplicant( 1205): WPA: clearing AP RSN IE
D/wpa_supplicant( 1205): WPA: clearing own WPA/RSN IE
D/wpa_supplicant( 1205): No keys have been configured - skip key clearing
D/wpa_supplicant( 1205): wpa_driver_wext_set_drop_unencrypted
D/wpa_supplicant( 1205): State: SCANNING -> ASSOCIATING
I/wpa_supplicant( 1205): CTRL-EVENT-STATE-CHANGE id=-1 state=3 BSSID=1c:17:d3:fc:db:79
D/wpa_supplicant( 1205): wpa_driver_wext_set_operstate: operstate 0->0 (DORMANT)
D/wpa_supplicant( 1205): WEXT: Operstate: linkmode=-1, operstate=5
D/wpa_supplicant( 1205): wpa_driver_wext_associate
D/wpa_supplicant( 1205): wpa_driver_wext_set_psk
D/wpa_supplicant( 1205): Setting authentication timeout: 10 sec 0 usec
D/wpa_supplicant( 1205): EAPOL: External notification - EAP success=0
D/wpa_supplicant( 1205): EAPOL: External notification - EAP fail=0
D/wpa_supplicant( 1205): EAPOL: External notification - portControl=ForceAuthorized
D/wpa_supplicant( 1205): RTM_NEWLINK: operstate=0 ifi_flags=0x1003 ([UP])
D/wpa_supplicant( 1205): RTM_NEWLINK, IFLA_IFNAME: Interface 'wlan0' added
D/wpa_supplicant( 1205): Wireless event: cmd=0x8b06 len=8
D/wpa_supplicant( 1205): RTM_NEWLINK: operstate=0 ifi_flags=0x1003 ([UP])
D/wpa_supplicant( 1205): RTM_NEWLINK, IFLA_IFNAME: Interface 'wlan0' added
D/wpa_supplicant( 1205): Wireless event: cmd=0x8b04 len=12
D/wpa_supplicant( 1205): RTM_NEWLINK: operstate=0 ifi_flags=0x1003 ([UP])
D/wpa_supplicant( 1205): RTM_NEWLINK, IFLA_IFNAME: Interface 'wlan0' added
D/wpa_supplicant( 1205): Wireless event: cmd=0x8b1a len=19
D/wpa_supplicant( 1205): CMD: AP_SCAN 1
D/wpa_supplicant( 1205): ap_scan = 1
V/WifiMonitor(  925): Event [Trying to associate with 1c:17:d3:fc:db:79 (SSID='Purple Help' freq=2412 MHz)]
V/WifiMonitor(  925): Event [CTRL-EVENT-STATE-CHANGE id=-1 state=3 BSSID=1c:17:d3:fc:db:79]
D/wpa_supplicant( 1205): CMD: DRIVER SCAN-PASSIVE
D/wpa_supplicant( 1205): wpa_driver_priv_driver_cmd SCAN-PASSIVE len = 4096
D/wpa_supplicant( 1205): SCAN-PASSIVE not yet supported
V/WifiStateTracker(  925): Changing supplicant state: SCANNING ==> ASSOCIATING
D/wpa_supplicant( 1205): CMD: STATUS
D/wpa_supplicant( 1205): CMD: DRIVER RSSI-APPROX
D/wpa_supplicant( 1205): wpa_driver_priv_driver_cmd RSSI-APPROX len = 4096
D/wpa_supplicant( 1205): >>>. DRIVER EMULATE RSSI
E/wpa_supplicant( 1205): wpa_driver_priv_driver_cmd failed (-1): RSSI
D/wpa_supplicant( 1205): CMD: DRIVER LINKSPEED
D/wpa_supplicant( 1205): wpa_driver_priv_driver_cmd LINKSPEED len = 4096
D/wpa_supplicant( 1205): Link Speed command
E/wpa_supplicant( 1205): wpa_driver_priv_driver_cmd failed (-1): LINKSPEED
D/wpa_supplicant( 1205): EAPOL: disable timer tick
D/wpa_supplicant( 1205): RTM_NEWLINK: operstate=0 ifi_flags=0x11003 ([UP][LOWER_UP])
D/wpa_supplicant( 1205): RTM_NEWLINK, IFLA_IFNAME: Interface 'wlan0' added
D/wpa_supplicant( 1205): RTM_NEWLINK: operstate=0 ifi_flags=0x11003 ([UP][LOWER_UP])
D/wpa_supplicant( 1205): RTM_NEWLINK, IFLA_IFNAME: Interface 'wlan0' added
D/wpa_supplicant( 1205): Wireless event: cmd=0x8c08 len=50
D/wpa_supplicant( 1205): RTM_NEWLINK: operstate=0 ifi_flags=0x11003 ([UP][LOWER_UP])
D/wpa_supplicant( 1205): RTM_NEWLINK, IFLA_IFNAME: Interface 'wlan0' added
D/wpa_supplicant( 1205): Wireless event: cmd=0x8b15 len=20
D/wpa_supplicant( 1205): Wireless event: new AP: 1c:17:d3:fc:db:79
D/wpa_supplicant( 1205): Association info event
D/wpa_supplicant( 1205): WPA: clearing AP WPA IE
D/wpa_supplicant( 1205): WPA: clearing AP RSN IE
D/wpa_supplicant( 1205): State: ASSOCIATING -> ASSOCIATED
I/wpa_supplicant( 1205): CTRL-EVENT-STATE-CHANGE id=0 state=4 BSSID=1c:17:d3:fc:db:79
D/wpa_supplicant( 1205): wpa_driver_wext_set_operstate: operstate 0->0 (DORMANT)
D/wpa_supplicant( 1205): WEXT: Operstate: linkmode=-1, operstate=5
V/WifiMonitor(  925): Event [CTRL-EVENT-STATE-CHANGE id=0 state=4 BSSID=1c:17:d3:fc:db:79]
V/WifiStateTracker(  925): Changing supplicant state: ASSOCIATING ==> ASSOCIATED
D/wpa_supplicant( 1205): Associated to a new BSS: BSSID=1c:17:d3:fc:db:79
I/wpa_supplicant( 1205): Associated with 1c:17:d3:fc:db:79
D/wpa_supplicant( 1205): WPA: Association event - clear replay counter
D/wpa_supplicant( 1205): WPA: Clear old PTK
D/wpa_supplicant( 1205): EAPOL: External notification - portEnabled=0
D/wpa_supplicant( 1205): EAPOL: External notification - portValid=0
D/wpa_supplicant( 1205): EAPOL: External notification - portEnabled=1
D/wpa_supplicant( 1205): EAPOL: SUPP_PAE entering state S_FORCE_AUTH
D/wpa_supplicant( 1205): EAPOL: SUPP_BE entering state IDLE
D/wpa_supplicant( 1205): Cancelling authentication timeout
D/wpa_supplicant( 1205): State: ASSOCIATED -> COMPLETED
I/wpa_supplicant( 1205): CTRL-EVENT-STATE-CHANGE id=0 state=7 BSSID=00:00:00:00:00:00
I/wpa_supplicant( 1205): CTRL-EVENT-CONNECTED - Connection to 1c:17:d3:fc:db:79 completed (auth) [id=0 id_str=]
D/wpa_supplicant( 1205): wpa_driver_wext_set_operstate: operstate 0->1 (UP)
D/wpa_supplicant( 1205): WEXT: Operstate: linkmode=-1, operstate=6
D/wpa_supplicant( 1205): Cancelling scan request
D/wpa_supplicant( 1205): RTM_NEWLINK: operstate=1 ifi_flags=0x11043 ([UP][RUNNING][LOWER_UP])
D/wpa_supplicant( 1205): RTM_NEWLINK, IFLA_IFNAME: Interface 'wlan0' added
V/WifiMonitor(  925): Event [Associated with 1c:17:d3:fc:db:79]
V/WifiMonitor(  925): Event [CTRL-EVENT-STATE-CHANGE id=0 state=7 BSSID=00:00:00:00:00:00]
V/WifiMonitor(  925): Event [CTRL-EVENT-CONNECTED - Connection to 1c:17:d3:fc:db:79 completed (auth) [id=0 id_str=]]
V/WifiStateTracker(  925): Changing supplicant state: ASSOCIATED ==> COMPLETED
V/WifiStateTracker(  925): New network state is CONNECTED
D/wpa_supplicant( 1205): CMD: STATUS
D/wpa_supplicant( 1205): CMD: STATUS
D/wpa_supplicant( 1205): CMD: DRIVER RSSI-APPROX
D/wpa_supplicant( 1205): wpa_driver_priv_driver_cmd RSSI-APPROX len = 4096
D/wpa_supplicant( 1205): >>>. DRIVER EMULATE RSSI
D/wpa_supplicant( 1205): CMD: DRIVER LINKSPEED
D/wpa_supplicant( 1205): wpa_driver_priv_driver_cmd LINKSPEED len = 4096
D/wpa_supplicant( 1205): Link Speed command
D/wpa_supplicant( 1205): CMD: DRIVER BTCOEXMODE 1
D/wpa_supplicant( 1205): wpa_driver_priv_driver_cmd BTCOEXMODE 1 len = 4096
D/wpa_supplicant( 1205): BTCOEXMODE not yet supported
D/wpa_supplicant( 1205): CMD: DRIVER GETPOWER
D/wpa_supplicant( 1205): wpa_driver_priv_driver_cmd GETPOWER len = 4096
D/wpa_supplicant( 1205): GETPOWER not yet supported
D/wpa_supplicant( 1205): CMD: DRIVER POWERMODE 1
D/wpa_supplicant( 1205): wpa_driver_priv_driver_cmd POWERMODE 1 len = 4096
D/wpa_supplicant( 1205): POWERMODE not yet supported
D/WifiStateTracker(  925): DHCP request started

However, DHCP appears not to function and Android unloads the driver and reloads it only to restart the scan -> associate -> DHCP query cycle. The error message is

E/WifiStateTracker(  925): DHCP request failed: Timed out waiting for dhcpcd to start
D/wpa_supplicant( 1205): CMD: DRIVER POWERMODE 0
D/wpa_supplicant( 1205): wpa_driver_priv_driver_cmd POWERMODE 0 len = 4096
D/wpa_supplicant( 1205): POWERMODE not yet supported
D/wpa_supplicant( 1205): CMD: DRIVER BTCOEXMODE 2
D/wpa_supplicant( 1205): wpa_driver_priv_driver_cmd BTCOEXMODE 2 len = 4096
D/wpa_supplicant( 1205): BTCOEXMODE not yet supported
D/wpa_supplicant( 1205): CMD: DISCONNECT
D/wpa_supplicant( 1205): wpa_driver_wext_disassociate
D/wpa_supplicant( 1205): No keys have been configured - skip key clearing
D/wpa_supplicant( 1205): State: COMPLETED -> DISCONNECTED
I/wpa_supplicant( 1205): CTRL-EVENT-STATE-CHANGE id=0 state=8 BSSID=00:00:00:00:00:00
D/wpa_supplicant( 1205): wpa_driver_wext_set_operstate: operstate 1->0 (DORMANT)
D/wpa_supplicant( 1205): WEXT: Operstate: linkmode=-1, operstate=5
D/wpa_supplicant( 1205): EAPOL: External notification - portEnabled=0
D/wpa_supplicant( 1205): EAPOL: SUPP_PAE entering state DISCONNECTED

Given that it is 7AM, that I have class and homework due, and that I have been working on this for the past 10 hours I will have to figure this out next time…

My full patch for wpa_supplicant 0.6.x, as found in the external/wpa_supplicant_6 directory in the Android source tree, is as follows:

diff --git a/wpa_supplicant/Android.mk b/wpa_supplicant/Android.mk
index d3ab3dd..eb23985 100644
--- a/wpa_supplicant/Android.mk
+++ b/wpa_supplicant/Android.mk
@@ -32,6 +32,11 @@ ifeq ($(TARGET_ARCH),arm)
 L_CFLAGS += -mabi=aapcs-linux
 endif

+# Disable combo scan
+ifeq ($(BOARD_WEXT_NO_COMBO_SCAN),true)
+L_CFLAGS += -DWEXT_NO_COMBO_SCAN
+endif
+
 # To ignore possible wrong network configurations
 L_CFLAGS += -DWPA_IGNORE_CONFIG_ERRORS

diff --git a/wpa_supplicant/src/drivers/driver_wext.c b/wpa_supplicant/src/drivers/driver_wext.c
index bc5efff..36f28b5 100644
--- a/wpa_supplicant/src/drivers/driver_wext.c
+++ b/wpa_supplicant/src/drivers/driver_wext.c
@@ -253,7 +253,8 @@ 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;
+        os_strlcpy(drv->ssid, buf, sizeof(drv->ssid));

 	if (ioctl(drv->ioctl_sock, SIOCSIWESSID, &iwr) < 0) {
 		wpa_printf(MSG_ERROR, "ioctl[SIOCSIWESSID]");
@@ -969,6 +970,8 @@ void * wpa_driver_wext_init(void *ctx, const char *ifname)
 	drv->errors = 0;
 	drv->driver_is_started = TRUE;
 	drv->skip_disconnect = 0;
+        drv->ssid_len = 0;
+        os_memset(drv->ssid, 0, sizeof(drv->ssid));
 #endif
 	wpa_driver_wext_finish_drv_init(drv);

@@ -2559,12 +2562,207 @@ static int wpa_driver_priv_driver_cmd( void *priv, char *cmd, char *buf, size_t
 	ret = ioctl(drv->ioctl_sock, SIOCSIWPRIV, &iwr);

 	if (ret < 0) {
+#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 != '\0') {
+                    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));
+                }
+            }
+            if (ret < 0) {
+#endif
 		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
+            }
+#endif
 	} else {
 		drv->errors = 0;
 		ret = 0;
diff --git a/wpa_supplicant/src/drivers/driver_wext.h b/wpa_supplicant/src/drivers/driver_wext.h
index 29ef44b..a7cde94 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_started;
 	int skip_disconnect;
+        /* SIOCSIWPRIV support in WEXT for non-Android WiFi drivers. */
+        char ssid[33];
+        unsigned int ssid_len;
 #endif
 };

diff --git a/wpa_supplicant/wpa_supplicant.c b/wpa_supplicant/wpa_supplicant.c
index beec16e..ed40642 100644
--- a/wpa_supplicant/wpa_supplicant.c
+++ b/wpa_supplicant/wpa_supplicant.c
@@ -1490,6 +1490,9 @@ int wpa_drv_scan(struct wpa_supplicant *wpa_s, struct wpa_ssid **ssid_ptr)
 	size_t ssid_len = 0;
 	int ret = -1;

+#ifdef WEXT_NO_COMBO_SCAN
+	if (wpa_s->driver->scan) {
+#else
 	if (wpa_s->driver->combo_scan) {
 		ret = wpa_s->driver->combo_scan(wpa_s->drv_priv, ssid_ptr,
 						wpa_s->conf->ssid);
@@ -1499,6 +1502,7 @@ int wpa_drv_scan(struct wpa_supplicant *wpa_s, struct wpa_ssid **ssid_ptr)
 		}
 	}
 	else if (wpa_s->driver->scan) {
+#endif
 		if (*ssid_ptr) {
 			ssid_nm = (*ssid_ptr)->ssid;
 			ssid_len = (*ssid_ptr)->ssid_len;

Credit goes to Michael Trimarchi as the changes are totally adapted from his patch.

I have added a repository for userland patches which can be accessed here.

Share:
  • Reddit
  • Google Bookmarks
  • Facebook
  • Twitter
  • Digg
  • email
  • Print

2011
10.24

In short – I compiled Gingerbread (2.3.7_r1) without modification and it boots on the N810 out of the box. That was SO much easier than with Froyo…

Gingerbread (2.3.7 r1) on the Nokia N810

Gingerbread (2.3.7 r1) on the Nokia N810


I uploaded my device configuration files for Android to this Git repository. To build, the files should be kept in a folder with any name, say nitdroid_device, under $ANDROID_SOURCE/device. To build, run make PRODUCT-nitdroid-eng or a variant from the root of the source tree.

I also calibrated the TSC2005 touchscreen. The code I pulled off this thread back in May resulted in weird coordinates on my touchscreen since when I was running Froyo, and I took the time to correct the numeric values by trial and error on my touchscreen. The final diff from the tsc2005.c created by the OpenWRT patches is:

--- a/drivers/input/touchscreen/tsc2005.c
+++ b/drivers/input/touchscreen/tsc2005.c
@@ -304,8 +304,8 @@ static void tsc2005_ts_update_pen_state(struct tsc2005 *ts,
 					int x, int y, int pressure)
 {
 	if (pressure) {
+                x = abs((x - 260) * 800 / 3550);
+                y = abs((3600 - y) * 480 / 3750);
 		input_report_abs(ts->idev, ABS_X, x);
 		input_report_abs(ts->idev, ABS_Y, y);
 		input_report_abs(ts->idev, ABS_PRESSURE, pressure);

I have committed it to my kernel patches repository.

There are still a million issues though…sound and wireless don’t work yet, and most importantly the system panics after about a minute of inaction, complaining (untruthfully) that the SD card had been removed. But it’s a start.

Share:
  • Reddit
  • Google Bookmarks
  • Facebook
  • Twitter
  • Digg
  • email
  • Print
  1. THANKS. THAINKS! THANKS!! THANKS DUDE!! Please keep te great work. I just want to be a device on my hands and not only a reliq. Again, thanks. I hope we could have a repository for that soon.

  2. Man, and the root files ? Or just the NITDROID Root and your kernel ? Thanks

2011
10.23

While working on a port of the Gingerbread (2.3.7) userland of Android for the N810, I had to figure out how to add support for an audio chip with a working ALSA driver in an Android build. The information was out there but it took me a while to figure everything out, so here’s a brief summary of the process.

First, of course, ALSA drivers for the chip must be enabled in the kernel; to check whether they are working, see if /dev/snd is properly populated. If not, check if the driver has been compiled into the kernel and not as a module.

There are three libraries that act as intermediate layers between an ALSA sound device and the Android userland. Due to the recent relocation of the Android source tree, I used alternative URLs for the three libraries. The code (shamelessly stolen and modified from this Armadeus Wiki page) is as follows, assuming $ANDROID_SOURCE points to the root of the checked-out Android source tree:


$ cd $ANDROID_SOURCE/external
$ git clone git://git.omapzoom.org/platform/external/alsa-lib.git
$ git clone git://git.omapzoom.org/platform/external/alsa-utils.git
$ cd $ANDROID_SOURCE/hardware
$ git clone git://git.omapzoom.org/platform/hardware/alsa_sound.git

However, a bogus commit to the last library in platform/hardware/alsa_sound breaks the build (as explained in this thread), hence:


$ cd $ANDROID_SOURCE/hardware/alsa_sound
$ git checkout ece3f1b6f1e6a67d02e42490eca6c7de62220b57

Then, modify the BoardConfig.mk of your build to include:


HAVE_HTC_AUDIO_DRIVER := false
BOARD_USES_GENERIC_AUDIO := false
BOARD_USES_ALSA_AUDIO := true
BUILD_WITH_ALSA_UTILS := true

And that should do the trick.

Share:
  • Reddit
  • Google Bookmarks
  • Facebook
  • Twitter
  • Digg
  • email
  • Print
  1. Is there any way to have this working with Android 4.0 ?

2011
10.21

Thanks to the amazing comment by bhaskar on my previous post, I am glad to announce that the LM8323 keyboard is now functional in the 2.6.38 kernel after applying the threaded IRQ patch by Felipe and the level-triggered IRQ patch by Leigh found on this thread on the LM8323.

I collected all the patches I used against the Android-OMAP 2.6.38 kernel at this new Git repository for NITDroid kernel patches for better organization. I figured maintaining and working with patches was better than the entire kernel tree, and it also makes the changes easier for someone else to incorporate into their own kernel.

A recent problem I have had with this particular kernel is that it is not able to boot Maemo anymore, but fails with weird errors about watchdog processes. This is not a huge problem since I am much more interested in getting Android to work, but it does complicate my testing process as I am now no longer able to test the kernel with a known working user software stack. So before continuing with the kernel, I am going to resume working on the userland again, and see what I can get done there. In particular, I am going to try switching gears to Gingerbread, and see if I can get its userland to boot instead. From what I have been able to read the differences between the Froyo and Gingerbread userlands should not be so large as to cause serious porting issues. But we will see in the following weeks whether that is true.

Share:
  • Reddit
  • Google Bookmarks
  • Facebook
  • Twitter
  • Digg
  • email
  • Print
  1. wow. great job. hope that someone picks up nitdroid for N900

  2. Good job Cji. You have done some impressive work. Wish you luck in the next development phase.

2011
10.20

I have made some progress with the 2.6.38 kernel on the Nokia N810. It now boots fine to the Android GUI:

Froyo home screen on the Nokia N810, running kernel 2.6.38 File Expert app on the Nokia N810
Froyo home screen on the Nokia N810, running kernel 2.6.38 File Expert app on the Nokia N810
Android's Built-in Calc application on the Nokia N810
Android’s Built-in Calc application on the Nokia N810

But unfortunately it’s still too early to celebrate. There is a very bizarre bug in my kernel: upon boot-up, the keyboard works fine, but after an indeterminate number of key presses, the keyboard stops working. Other input sources, such as the touchscreen, are not affected, while hardware buttons like the back, home and menu keys, which are attached to the same keyboard device (LM8323), stop functioning as well. I inserted debug statements into the keyboard driver and booted to the console to watch kernel messages as I pressed keys. I was very surprised to discover that after the first few key presses, not even the IRQ handler registered by the keyboard driver is invoked anymore. I do not see at all how that is even possible; I will need to investigate further next time.

Incidentally, I also came across this wonderful article on Android porting from an engineer at ARM. It is the most detailed and well-written text on the subject that I have ever read. My approach does differ from the one described in the article though.

Share:
  • Reddit
  • Google Bookmarks
  • Facebook
  • Twitter
  • Digg
  • email
  • Print
  1. The keyboard issue might be due to trigger settings being incorrect for the LM8323. The n810 keyboard locks up if keys are pressed in quick succession. The reason is that the LM8323 triggers are level triggered and the default code might be setting them to be edge triggered. Here is the discussion and patch that fixes the issue: http://comments.gmane.org/gmane.linux.ports.arm.omap/58910 . Please take a look at the code and see if that is the case.

    • Hi bhaskar,

      Wow thanks! The patches in the thread you linked to completely solved the keyboard problem. I’ll post an update in a bit.

      Thanks again for the tip! That was amazing.

      cji

  2. wow
    Keep doing what you doing! Looks good!

2011
10.19

I have always been curious how rooting actually worked behind the scenes. After recently acquiring a new Eee Pad Slider, a Honeycomb tablet that so far no one has been able to root, the frustration of being locked out of this amazing piece of hardware with so much potential led me to finally sit down and figure out what exactly rooting means, what it entails from a technical perspective, and how hackers out in the wild are approaching the rooting of a new device. Although all this information is out there, I have not been able to find a good article that had both the level of technical detail that I wanted and an appropriate introduction to the big picture, and so I decided to write my own.

This is NOT a noob-friendly guide to rooting a particular Android device. Rather, it is a general explanation of how stock Android ROMs try to prevent unprivileged access, how hackers attack this problem and how rooting software leverage various exploits to defeat these security mechanisms.

I. The Goal

Let us first take a step back and consider exactly what we mean by rooting. Forget flashing custom ROMs, enabling WiFi tethering or installing Superuser.apk; fundamentally, rooting is about obtaining root access to the underlying Linux system beneath Android and thus gaining absolute control over the software that is running on the device. Things that require root access on a typical Linux system — mounting and unmounting file systems, starting your favorite SSH or HTTP or DHCP or DNS or proxy servers, killing system processes, chroot-ing, etc., — require root access on Android as well. Being able to run arbitrary commands as the root user allows you to do absolutely anything on a Linux / Android system, and this is real goal of rooting.

Stock OEM Android builds typically do not allow users to execute arbitrary code as root. This essentially means that you as a user are granted only limited control over your own device; you can make your device do task X only if the manufacturer explicitly decided to allow it and shipped a program to do it. You will not be able to use third-party apps to accomplish a task that your manufacturer does not wish you to do. WiFi tethering is a good example of this. Cell phone carriers obviously do not want you to tether your phone without paying them additional charges. Therefore, many phones come pre-packaged with their own proprietary WiFi tethering apps that demand extraneous fees. But without root access, you will not be able to install a free alternative like Wireless Tether For Root Users. Why this is accepted practice in the industry is a mystery to me. The only difference between cell phones, tablets and computers is their form factor; but while a PC vendor would fail spectacularly if they tried to prevent users from running arbitrary programs on their machines, cell phone vendors are clearly not judged along the same lines. But such arguments would belong to another article.

I. The Enemy: Protection Mechanisms On A Stock OEM Android ROM

1. Bootloader and Recovery

The bootloader, the first piece of code executed when your device is powered on, is responsible for loading the Android OS and the recovery system and flashing a new ROM. People refer to some bootloaders as “unlocked” if a user can flash and boot arbitrary ROMs without hacking; unfortunately, many Android devices have locked bootloaders that you would have to hack around in order to make them do anything other than boot the stock ROM. A Samsung smartphone I had used some months ago had an unlocked bootloader; I could press a certain combination of hardware keys on the phone, connect it to my computer, and flash any custom ROM onto it using Samsung’s utilities without having to circumvent any protection mechanisms. The same is not true for my Motorola Droid 2 Global; the bootloader, as far as I know, cannot be hacked. The Eee Pad Slider, on the other hand, is an interesting beast; as with other nVidia Tegra 2 based devices, its bootloader is controllable through the nvflash utility, but only if you know the secure boot key (SBK) of the device. (The SBK is a private AES key used to encrypt the commands sent to the bootloader; the bootloader will only accept the command if it has been encrypted by the particular key of the device.) Currently, as the SBK of the Eee Pad Slider is not publicly known, the bootloader remains inaccessible.

System recovery is the second piece of low-level code on board any Android device. It is separate from the Android userland and is typically located on its own partition; it is usually booted by the bootloader when you press a certain combination of hardware keys. It is important to understand that it is a totally independent program; Linux and the Android userland is not loaded when you boot into recovery, and any high-level concept such as root does not exist here. It is simple program that really is a very primitive OS, and it has absolute control over the system and will do anything you want as long as the code to do it is built in. Stock recovery varies with the manufacturer, but often includes functionalities like reformatting the /data partition (factory reset) and flashing an update ROM (update.zip, located at the root of the external microSD card) signed by the manufacturer. Note I said signed by the manufacturer; typically it is not possible to flash custom update files unless you obtain the private key of the manufacturer and sign your custom update with it, which is both impossible for most and illegal under certain jurisdictions. However, since recovery is stored in a partition just like /system, /data and /cache (more about that later), you can replace it with a custom recovery if you have root access in Linux / Android. Most people do just that upon rooting their device; ClockworkMod Recovery is a popular third-party recovery image, and allows you to flash arbitrary ROMs, backup and restore partitions, and lots of other magic.

2. ADB

ADB (see the official documentation for ADB) allows a PC or a Mac to connect to an Android device and perform certain operations. One such operation is to launch a simple shell on the device, using the command adb shell. The real question is what user do the commands executed by that shell process run as. It turns out that it depends on the value of an Android system property, named ro.secure. (You can view the value of this property by typing getprop ro.secure either through an ADB shell or on a terminal emulator on the device.) If ro.secure=0, an ADB shell will run commands as the root user on the device. But if ro.secure=1, an ADB shell will run commands as an unprivileged user on the device. Guess what ro.secure is set to on almost every stock OEM Android build. But can we change the value of ro.secure on a system? The answer is no, as implied by the ro in the name of the property. The value of this property is set at boot time from the default.prop file in the root directory. The contents of the root directory are essentially copied from a partition in the internal storage on boot, but you cannot write to the partition if you are not already root. In other words, this property denies root access via ADB, and the only way you could change it is by gaining root access in the first place. Thus, it is secure.

3. Android UI

On an Android system, all Android applications that you can see or interact with directly are running as unprivileged users in sandboxes. Logically, a program running as an unprivileged user cannot start another program that is run as the privileged user; otherwise any program can simply start another copy of itself in privileged mode and gain privileged access to everything. On the other hand, a program running as root can start another program as root or as an unprivileged user. On Linux, privilege escalation is usually accomplished via the su and sudo programs; they are often the only programs in the system that are able to execute the system call setuid(0) that changes the current program from running as an unprivileged user to running as root. Apps that label themselves as requiring root are in reality just executing other programs (often just native binaries packaged with the app) through su. Unsurprisingly, stock OEM ROMs never come with these su. You cannot just download it or copy it over either; it needs to have its SUID bit set, which indicates to the system that the programs this allowed to escalate its runtime privileges to root. But of course, if you are not root, you cannot set the SUID bit on a program. To summarize, what this means is that any program that you can interact with on Android (and hence running in unprivileged mode) is unable to either 1) gain privileged access and execute in privileged mode, or 2) start another program that executes in privileged mode. If this holds, the Android system by itself is pretty much immune to privilege escalation attempts. We will see the loophole exploited by on-device rooting applications in the next section.

II. Fighting the System

So how the hell do you root an Android? Well, from the security mechanisms described above, we can figure out how to attack each component in turn.

If your device happens to have an unlocked bootloader, you’re pretty much done. An example is the Samsung phone that I had had. Since the bootloader allowed the flashing of arbitrary ROMs, somebody essentially pulled the stock ROM from the phone (using dd), added su, and repackaged it into a modified ROM. All I as a user needed to do was to power off the phone, press a certain combination of hardware keys to start the phone in flashing mode, and use Samsung’s utilities to flash the modified ROM onto the phone.

Believe it or not, certain manufacturers don’t actually set ro.secure to 1. If that is the case, rooting is even easier; just plug the phone into your computer and run ADB, and you now have a shell that can execute any program as root. You can then mount /system as read-write, install su and all your dreams have come true.

But many other Android devices have locked bootloaders and ro.secure set. As explained above, they should not be root-able because you can only interact with unprivileged programs on the system and they cannot help you execute any privileged code. So what’s the solution?

The key is that a number of important programs, including low-level system services, must run as root even on Android in order to access hardware resources. Typing ps on an Android shell (either via ADB or a terminal emulator on the device) will give you an idea. These programs are started by the init process, the first process started by the kernel (I often feel that the kernel and the init process are kind of analogous to Adam and Eve — the kernel spawns init in a particular fashion, and init then goes on and spawns all other processes) which has to run as root because it needs to start other privileged system processes. If you can hack / trick one of these system processes running in privileged mode to execute your arbitrary code, you have just gained privileged access to the system. This how all one-click-root methods work, including z4root, gingerbreak, and so on. If you are truly curious, I highly recommend this excellent presentation on the various exploits used by current rooting tools, but the details are not as relevant here as the simple idea behind them. That idea is that there are vulnerabilities in the system processes running as root in the background that, if exploited, will allow us to execute arbitrary code as root. Well, that “arbitrary code” is most certainly a piece of code that mounts /system in read-write mode and installs a copy of su permanently on the system, so that from then on we don’t need to jump through the hoops to run the programs we really wanted to run in the first place. Since Android is open source as is Linux, what people have done is to scrutinize and reason about the source code of the various system services until they find a security hole they can leverage. This becomes increasingly hard as Google and the maintainers of the various pieces of code fix those particular vulnerabilities when they are discovered and published, which means that the exploits will eventually become obsolete with newer devices. But the good news is that manufacturers are not stupid enough to push OTA updates to fix a vulnerability just to prevent rooting as it is very expensive for them; in addition, devices in the market are always lagging behind the newest software releases. Thus, it takes quite some time before these rooting tools are rendered useless by new patches, and by then hopefully other exploits would have been discovered.

Share:
  • Reddit
  • Google Bookmarks
  • Facebook
  • Twitter
  • Digg
  • email
  • Print
  1. ThankU so much…..this was xtremly informative…if u dnt mind pls mail me links to the reference articles u had gone through…….Thnx in advance

  2. Very nice and informative writeup! Thanks!

  3. thanks, you just stopped me wasting my time on ADB

  4. good info thanks

2011
10.18

I recently acquired an Eee Pad Slider, a truly groundbreaking Android tablet running Honeycomb 3.2. Its unique sliding keyboard allows it to transcend the conventional definition of tablets as content consumption devices while preserving to perfection the intimacy of touch interface interaction. To be honest, however, up until last week I had always seen tablets as these flashy toys whose only purpose in life was to satisfy the vanity of a few mindless and unfortunately privileged gadget fanboys. But I fell in love with the Eee Pad Slider at first sight. This was a device that perfectly bridged that gap between my laptop and my smartphone; this was a device that was as immediately accessible, friendly, and visually appealing as my smartphone for casual use, but as pragmatic and dependable as my Thinkpad T400 when I need to get down and serious and reply to that one email or fix that one bug in my code. It’s simply the most versatile mobile device between a phone and a full blown PC.

Before I am dismissed as a crazed and dangerous fanatic, however, I should admit that I fully understand the fact that the Slider is not the best tablet for everyone. The Slider is probably twice as thick as the iPad or the Samsung Galaxy Tab and about the same price as either one of the two. Its keys are much less comfortable than those on your typical ultraportable laptop. It is also brown, a color I would not place very high on my list of good colors for electronic devices. It is probably not the best consumer lifestyle gadget you can find on the market, nor the most suitable for mobile office computing. But it is a very innovative and intelligent cross of the two worlds, and that happened to be exactly that one device I could not find in the hugely diverse and volatile market of mobile devices of today.

Back, then, to the real deal. I am a computer geek and I cannot live very well without SSH. On Android the SSH client that I (and it looks like everyone else) have been using is ConnectBot. So among the first apps I installed on my brand new Eee Pad Slider was ConnectBot; finally I thought, there was a mobile keyboard that had real control, shift, and alt keys that I could use. I was very surprised, therefore, when I realized that none of them worked. Given that the Slider has only been released for a month, forum threads and other helpful resources are nonexistent. I dug up the discussion on this document and found Lorant Kurthy’s fork of ConnectBot on GitHub that fixed the same issues for the Eee Pad Transformer. I cloned his repository and, with a one-line change in the source code, enabled his fix for the Slider as well. So there we go – here is my version of ConnectBot with fixes for the Eee Pad Transformer and Eee Pad Slider hardware keyboards. As far as I can tell it works fine; in fact, I am typing this post on the Eee Pad Slider using Vim in an SSH session to my laptop via my version of ConnectBot. All the credit goes to Lorant Kurthy though.

Share:
  • Reddit
  • Google Bookmarks
  • Facebook
  • Twitter
  • Digg
  • email
  • Print
  1. : )

2011
09.20

It’s been a while since I last updated this blog – the summer has been unsurprisingly busy, and as school has started again my hiatus is now officially over. But first, a HUGE thanks to all who inquired about and commented on the NITDroid project in the meantime, as even though I haven’t been able to get back to you individually your support has meant a lot to me.

The first challenge I decided to tackle was improving the kernel I used. The kernel I had gotten working was based on Android OMAP 2.6.32 and contained many patches and copied files from various sources that I did not understand and could not hope to port to a newer kernel if needed. Looking through the diff files revealed many unnecessary or irrelevant changes that made them essentially useless beyond Android OMAP 2.6.32, and given the availability of Gingerbread, I figured that it would be beneficial in the long run if I could 1) minimize the modifications to the kernel required, and 2) port these changes to a newer version of the kernel.

I started working, therefore, with Android OMAP 2.6.38 (since kernel.org is down due to a security breach, I had to use a mirror at git.omapzoom.org). I spent two days porting each necessary change manually, but to no avail. I was able to fix a problem introduced in 2.6.38 where the LCD was being reset by inserting for (;;) {} to determine the source of the problem; only later did I discover this email by Michael Buesch which proposed a much simpler solution. But even then I did not get far; I was still stuck with the Nokia logo screen and could go no further. I had forgotten how painful debugging kernel builds were: there are no error messages or debugging output, and the only thing observable would be a frozen Nokia logo.

Then, at wit’s end a couple of hours ago, I searched for the author of said email on Google and found Michael Buesch’s page about his and others’ work on OpenWRT on the N810. I followed the instructions and was able to build a working kernel for the N810 with some minor fixes. The truly marvellous thing about this work, however, is that the exact patches that are used against a vanilla kernel are provided; they can be viewed in the OpenWRT SVN repo for instance. I went ahead and applied all their patches to a vanilla Android OMAP 2.6.38 kernel, but the resulting kernel still refused to boot. I ended up comparing each affected file painstakingly to its counterpart in the OpenWRT tree and was finally able to find the culprit: arch/arm/mach-omap2/clock2420_data.c; the problem was so dumb that I cannot believe it has been checked in at all. Here’s the diff:

diff --git a/arch/arm/mach-omap2/clock2420_data.c b/arch/arm/mach-omap2/clock2420_data.c
index 3c1712b..0a992bc 100644
--- a/arch/arm/mach-omap2/clock2420_data.c
+++ b/arch/arm/mach-omap2/clock2420_data.c
@@ -1786,10 +1786,10 @@ static struct omap_clk omap2420_clks[] = {
        CLK(NULL,       "gfx_2d_fck",   &gfx_2d_fck,    CK_242X),
        CLK(NULL,       "gfx_ick",      &gfx_ick,       CK_242X),
        /* DSS domain clocks */
-       CLK("omap_dss", "ick",          &dss_ick,       CK_242X),
-       CLK("omap_dss", "fck",          &dss1_fck,      CK_242X),
-       CLK("omap_dss", "sys_clk",      &dss2_fck,      CK_242X),
-       CLK("omap_dss", "tv_clk",       &dss_54m_fck,   CK_242X),
+       CLK("omapdss",  "ick",          &dss_ick,       CK_242X),
+       CLK("omapdss",  "dss1_fck",     &dss1_fck,      CK_242X),
+       CLK("omapdss",  "dss2_fck",     &dss2_fck,      CK_242X),
+       CLK("omapdss",  "tv_fck",       &dss_54m_fck,   CK_242X),
        /* L3 domain clocks */
        CLK(NULL,       "core_l3_ck",   &core_l3_ck,    CK_242X),
        CLK(NULL,       "ssi_fck",      &ssi_ssr_sst_fck, CK_242X),

With all of these changes, and essentially the same n810_defconfig from before, the produced kernel boots up to Android, but dies when it tries to initialize the GUI. I have not found a solution to that yet, but at least it boots to console and prints out error messages. I will need to investigate the issue further next time.

From now on, I think I will no longer reinvent the wheel and rely on OpenWRT patches for the N810 as much as possible. Thanks, Michael Buesch and everyone at OpenWRT!

Share:
  • Reddit
  • Google Bookmarks
  • Facebook
  • Twitter
  • Digg
  • email
  • Print

2011
05.18

I had to fight against a “feature” in QEMU today as I was screwing around. Normally when QEMU ARM boots a Linux kernel it sets up a tag list with all sorts of good information like available memory regions as explained here. This is the Linux’s standard for ARM and any ARM bootloader that loads Linux must do this. However, when I was booting my custom kernel with qemu-system-arm I discovered that R1 and R2 were empty upon boot using the switch -monitor stdio:

QEMU 0.13.0 monitor - type 'help' for more information
(qemu) info registers
R00=00000000 R01=00000000 R02=00000000 R03=00000000
R04=00000000 R05=00000000 R06=00000000 R07=00000000
R08=00000000 R09=00000000 R10=00000000 R11=00000000
R12=00000000 R13=00000000 R14=00000000 R15=00000020
PSR=400001d3 -Z-- A svc32

This meant of course that the kernel refused to load at all. In addition, nowhere in memory could the tag list be found, as I saw with a quick assembly search through memory space.

So I downloaded the QEMU source code and found the relevant code in hw/arm_boot.c. The code there is pretty straightforward, and it turns out that QEMU ARM will only install a tag list if it determines kernel to be Linux. This is fine in itself, but the way it figures out if a given kernel is Linux is really stupid – as a comment in the file reads:

/* Assume that raw images are linux kernels, and ELF images are not.  */

This is really silly as one just objcopy -O binary any kernel into a raw image anyway. So I just set the initial assignment to is_linux = 1; in arm_load_kernel(...), compiled the ARM version, and copied it into my /usr/bin. Sigh.

Share:
  • Reddit
  • Google Bookmarks
  • Facebook
  • Twitter
  • Digg
  • email
  • Print

2011
05.14

I was able to get hardware keys to work last night. After a deep dive into the Android event handling system (see my previous post), I inserted log outputs in strategic positions and determined that the hardware keys were indeed sending events through the keyboard controller like the keys on the hardware keyboard. This made my life immensely easier; all I had to do was create a custom keyboard layout and set the corresponding key codes to HOME, BACK and so on.

The scan codes I found for the hardware keys through my experiments and the corresponding functions I assigned them under Android are summarized in the following table:

Key Code Mapped to
Switch application 63 Home
Back 1 Back
Full screen 64 Menu
Minus sign 66 Vol down
Plus sign 65 vol up
Power 116 Power
Menu 62 Menu

So I created a new board and a new product in the build/ directory and defined the affected keys as follows in a new keyboard layout file, following the above results:

key 1     BACK              WAKE_DROPPED
key 62    MENU              WAKE_DROPPED
key 63    HOME              WAKE
key 64    MENU              WAKE_DROPPED
key 65    VOLUME_UP
key 66    VOLUME_DOWN
key 116   POWER             WAKE

I also had to investigate how Android determined which keyboard layout file to look for. My search turned up this code in EventHub::openDevice(...) in frameworks/base/libs/ui/EventHub.cpp:

int EventHub::open_device(const char *deviceName)
{
    ...
    if(ioctl(fd, EVIOCGNAME(sizeof(name) - 1), &name) < 1) {
        //fprintf(stderr, "could not get device name for %s, %sn", deviceName, strerror(errno));
        name[0] = '\0';
    }

    ...
    if ((device->classes&CLASS_KEYBOARD) != 0) {
        char tmpfn[sizeof(name)];
        char keylayoutFilename[300];

        // a more descriptive name
        device->name = name;

        // replace all the spaces with underscores
        strcpy(tmpfn, name);
        for (char *p = strchr(tmpfn, ' '); p && *p; p = strchr(tmpfn, ' '))
            *p = '_';

        // find the .kl file we need for this device
        const char* root = getenv("ANDROID_ROOT");
        snprintf(keylayoutFilename, sizeof(keylayoutFilename),
                 "%s/usr/keylayout/%s.kl", root, tmpfn);
        bool defaultKeymap = false;
        if (access(keylayoutFilename, R_OK)) {
            snprintf(keylayoutFilename, sizeof(keylayoutFilename),
                     "%s/usr/keylayout/%s", root, "qwerty.kl");
            defaultKeymap = true;
        }
        device->layoutMap->load(keylayoutFilename);
    ...
}

In other words, Android gets the device name from the device itself using ioctl(2), replaces all spaces in it by underscores, and tries to load the keyboard layout file /system/usr/keylayout/device_name.kl. Originally in my kernel the N8x0 board initialization routines were overriding the device name to be “Internal Keyboard”; I felt that was a bit awkward and instead let the LM8323 driver itself assign the name to the default “LM8323 keypad”. Therefore, my keyboard layout has to be named LM8323_keypad.kl. The Android build process automatically (well, I had to specify it in BoardConfig.mk) copies the keyboard layout file over to the correct location.

Now when I tried building using my newly created board and product definitions, the build process was failing with the following error:

target thumb C++: libcameraservice <= frameworks/base/camera/libcameraservice/CameraService.cpp
make: *** No rule to make target `out/target/product/n8x0/obj/lib/libcamera.so', needed by `out/target/product/n8x0/obj/SHARED_LIBRARIES/libcameraservice_intermediates/LINKED/libcameraservice.so'.  Stop.

It took me quite a while to figure out what was going on. Apparently, Android expects every new board to provide a camera / audio implementation; otherwise, one has to enable the stub camera implementation with the following line in BoardConfig.mk:

USE_CAMERA_STUB := true

I found this after a long search in this thread. The annoying thing is that while the generic board explicitly uses the stub audio implementation, it does not say anything about the stub camera implementation; why then does it compile thus without a problem whereas my custom board doesn't?

Finally, after a silly incident where the build system failed to copy over my modified keyboard layout file, I was able to use the hardware keys without further problems.

As a safeguard, I set up some projects on GitHub to track my work on NITDroid. Since they are also public they're also effectively hosting my work. The projects are:

Moving forward, I will next try to work out power. A stable power management implementation is the basis for a stable system; without it the system is essentially a house of cards. But that will have to wait until next week.

Share:
  • Reddit
  • Google Bookmarks
  • Facebook
  • Twitter
  • Digg
  • email
  • Print

2011
05.14

While figuring out hardware buttons for my NITDroid project, I had the opportunity of exploring the way Linux and Android handle input events internally before passing them through to the user application. This post traces the propagation of an input event from the Linux kernel through the Android userspace as far as I understand it. Although the principles are likely the same for essentially any input device, I will be drawing on my investigations of the drivers for the LM8323 hardware keyboard (drivers/input/keyboard/lm8323.c) and the TSC2005 touchscreen (drivers/input/touchscreen/tsc2005.c) which are both found inside the Nokia N810.

I. Inside the Linux kernel

Firstly, Linux exposes externally a uniform input event interface for each device as /dev/input/eventX where X is an integer. This means these “devices” can be polled in the same way and the events they produce are in the same uniform format. To accomplish this, Linux has a standard set of routines that every device driver uses to register / unregister the hardware it manages and publish input events it receives.

When the driver module of an input device is first loaded into the kernel, its initialization routine usually sets up some sort of probing to detect the presence of the types of hardware it is supposed to manage. This probing is of course device-specific; however, if it is successful, the module will eventually invoke the function input_register_device(...) in include/linux/input.h which sets up a file representing the physical device as /dev/input/eventX where X is some integer. The module will also register a function to handle IRQs originating from the hardware it manages via request_irq(...) (include/linux/interrupt.h) so that the module will be notified when the user interacts with the physical device it manages.

When the user physically interacts with the hardware (for instance by pushing / releasing a key or exerting / lifting pressure on the touchscreen), an IRQ is fired and Linux invokes the IRQ handler registered by the corresponding device driver. However, IRQ handlers by custom must return quickly as they essentially block the entire system when executing and thus cannot perform any lengthy processing; typically, therefore, an IRQ handler would merely 1) save the data carried by the IRQ, 2) ask the kernel to schedule a method that would process the event later on when we have exited IRQ mode, and 3) tell the kernel we have handled the IRQ and exit. This could be very straightforward, as the IRQ handler in the driver for the LM8323 keyboard inside the N810:

/*
 * We cannot use I2C in interrupt context, so we just schedule work.
 */
static irqreturn_t lm8323_irq(int irq, void *data)
{
        struct lm8323_chip *lm = data;

        schedule_work(&lm->work);

        return IRQ_HANDLED;
}

It could also be more complex as the one in the driver of the TSC2005 touchscreen controller (tsc2005_ts_irq_handler(...)) as it integrates into the SPI framework (which I have never looked into…).

Some time later, the kernel executes the scheduled method to process the recently saved event. Invariably, this method would report the event in a standard format by calling one or more of the input_* functions in include/linux/input.h; these include input_event(...) (general purpose), input_report_key(...) (for key down and key up events), input_report_abs(...) (for position events e.g. from a touchscreen) among others. Note that the input_report_*(...) functions are really just convenience functions that call input_event(...) internally, as defined in include/linux/input.h. It is likely that a lot of processing happens before the event is published via these methods; the LM8323 driver for instance does an internal key code mapping step and the TSC2005 driver goes through this crazy arithmetic involving Ohms (to calculate a pressure index from resistance data?). Furthermore, one physical IRQ could correspond to multiple published input events, and vice versa. Finally, when all event publishing is finished, the event processing method calls input_sync(...) to flush the event out. The event is now ready to be accessed by the userspace at /dev/input/eventX.

II. Inside the Android userspace

When the Android GUI starts up, an instance of the class WindowManagerService (frameworks/base/services/java/com/android/server/WindowManagerservice.java) is created. This class, when constructed, initializes the member field

final KeyQ mQueue;

where KeyQ, defined as a private class inside the same file, extends Android’s basic input handling class, the abstract class KeyInputQueue (frameworks/base/services/java/com/android/server/KeyInputQueue.java and frameworks/base/services/jni/com_android_server_KeyInputQueue.cpp). As mQueue is instantiated, it of course calls the constructor of KeyInputQueue; the latter, inconspicuously, starts an anonymous thread it owns that is at the heart of the event handling system in Android:

Thread mThread = new Thread("InputDeviceReader") {
    public void run() {
        ...
        RawInputEvent ev = new RawInputEvent();
        while (true) {
            try {
                readEvent(ev);  // block, doesn't release the monitor

                boolean send = false;
                ...
                if (ev.type == RawInputEvent.EV_DEVICE_ADDED) {
                    ...
                } else if (ev.type == RawInputEvent.EV_DEVICE_REMOVED) {
                    ...
                } else {
                    di = getInputDevice(ev.deviceId);
                    ...
                    // first crack at it
                    send = preprocessEvent(di, ev);
                }
                ...
                if (!send) {
                    continue;
                }
                synchronized (mFirst) {
                    ...
                    // Is it a key event?
                    if (type == RawInputEvent.EV_KEY &&
                            (classes&RawInputEvent.CLASS_KEYBOARD) != 0 &&
                            (scancode < RawInputEvent.BTN_FIRST ||
                                    scancode > RawInputEvent.BTN_LAST)) {
                        boolean down;
                        if (ev.value != 0) {
                            down = true;
                            di.mKeyDownTime = curTime;
                        } else {
                            down = false;
                        }
                        int keycode = rotateKeyCodeLocked(ev.keycode);
                        addLocked(di, curTimeNano, ev.flags,
                                RawInputEvent.CLASS_KEYBOARD,
                                newKeyEvent(di, di.mKeyDownTime, curTime, down,
                                        keycode, 0, scancode,
                                        ((ev.flags & WindowManagerPolicy.FLAG_WOKE_HERE) != 0)
                                         ? KeyEvent.FLAG_WOKE_HERE : 0));
                    } else if (ev.type == RawInputEvent.EV_KEY) {
                        ...
                    } else if (ev.type == RawInputEvent.EV_ABS &&
                            (classes&RawInputEvent.CLASS_TOUCHSCREEN_MT) != 0) {
                        // Process position events from multitouch protocol.
                        ...
                    } else if (ev.type == RawInputEvent.EV_ABS &&
                            (classes&RawInputEvent.CLASS_TOUCHSCREEN) != 0) {
                        // Process position events from single touch protocol.
                        ...
                    } else if (ev.type == RawInputEvent.EV_REL &&
                            (classes&RawInputEvent.CLASS_TRACKBALL) != 0) {
                        // Process movement events from trackball (mouse) protocol.
                        ...
                    }
                    ...
                }

            } catch (RuntimeException exc) {
                Slog.e(TAG, "InputReaderThread uncaught exception", exc);
            }
        }
    }
};

I have removed most of this ~350 lined function that is irrelevant to our discussion and reformatted the code for easier reading. The key idea is that this independent thread will

  1. Read an event
  2. Call the preprocess(...) method of its derived class, offering the latter a chance to prevent the event from being propagated further
  3. Add it to the event queue owned by the class

This InputDeviceReader thread started by WindowManagerService (indirectly via KeyInputQueue's constructor) is thus THE event loop of the Android UI.

But we are still missing the link from the kernel to this InputDeviceReader. What exactly is this magical readEvent(...)? It turns out that this is actually a native method implemented in the C++ half of KeyInputQueue:

static Mutex gLock;
static sp gHub;

static jboolean
android_server_KeyInputQueue_readEvent(JNIEnv* env, jobject clazz,
                                          jobject event)
{
    gLock.lock();
    sp hub = gHub;
    if (hub == NULL) {
        hub = new EventHub;
        gHub = hub;
    }
    gLock.unlock();

    ...
    bool res = hub->getEvent(&deviceId, &type, &scancode, &keycode,
            &flags, &value, &when);
    ...

    return res;
}

Ah, so readEvent is really just a proxy for EventHub::getEvent(...). If we proceed to look up EventHub in frameworks/base/libs/ui/EventHub.cpp, we find

int EventHub::scan_dir(const char *dirname)
{
    ...
    dir = opendir(dirname);
    ...
    while((de = readdir(dir))) {
        ...
        open_device(devname);
    }
    closedir(dir);
    return 0;
}
...

static const char *device_path = "/dev/input";
...

bool EventHub::openPlatformInput(void)
{
    ...
    res = scan_dir(device_path);
    ...
    return true;
}

bool EventHub::getEvent(int32_t* outDeviceId, int32_t* outType,
        int32_t* outScancode, int32_t* outKeycode, uint32_t *outFlags,
        int32_t* outValue, nsecs_t* outWhen)
{
    ...
    if (!mOpened) {
        mError = openPlatformInput() ? NO_ERROR : UNKNOWN_ERROR;
        mOpened = true;
    }

    while(1) {
        // First, report any devices that had last been added/removed.
        if (mClosingDevices != NULL) {
            ...
            *outType = DEVICE_REMOVED;
            delete device;
            return true;
        }
        if (mOpeningDevices != NULL) {
            ...
            *outType = DEVICE_ADDED;
            return true;
        }

        ...
        pollres = poll(mFDs, mFDCount, -1);
        ...

        // mFDs[0] is used for inotify, so process regular events starting at mFDs[1]
        for(i = 1; i < mFDCount; i++) {
            if(mFDs[i].revents) {
                if(mFDs[i].revents & POLLIN) {
                    res = read(mFDs[i].fd, &iev, sizeof(iev));
                    if (res == sizeof(iev)) {
                        ...
                        *outType = iev.type;
                        *outScancode = iev.code;
                        if (iev.type == EV_KEY) {
                            err = mDevices[i]->layoutMap->map(iev.code, outKeycode, outFlags);
                            ...
                        } else {
                            *outKeycode = iev.code;
                        }
                        ...
                        return true;
                    } else {
                        // Error handling
                        ...
                        continue;
                    }
                }
            }
        }
        ...
    }
}

Again, most of the details have been stripped out from the above code, but we now see how readEvent() in KeyInputQueue is getting these events from Linux: on first call, EventHub::getEvent scans the directory /dev/input for input devices, opens them and saves their file descriptors in an array called mFDs. Then whenever it is called again, it tries to read from each of these input devices by simply calling the read(2) Linux system call.

OK, now we know how an event propagates through EventHub::getEvent(...) to KeyInputQueue::readEvent(...) then to InputDeviceReader.run(...) where it could get queued inside WindowManagerService.mQueue (which, as a reminder, extends the otherwise abstract KeyInputQueue). But what happens then? How does that event get to the client application?

Well, it turns out that WindowManagerService has yet another private member class that handles just that:

private final class InputDispatcherThread extends Thread {
    ...
    @Override public void run() {
        while (true) {
            try {
                process();
            } catch (Exception e) {
                Slog.e(TAG, "Exception in input dispatcher", e);
            }
        }
    }

    private void process() {
        android.os.Process.setThreadPriority(
                android.os.Process.THREAD_PRIORITY_URGENT_DISPLAY);
        ...
        while (true) {
            ...
            // Retrieve next event, waiting only as long as the next
            // repeat timeout.  If the configuration has changed, then
            // don't wait at all -- we'll report the change as soon as
            // we have processed all events.
            QueuedEvent ev = mQueue.getEvent(
                (int)((!configChanged && curTime < nextKeyTime)
                        ? (nextKeyTime-curTime) : 0));
            ...
            try {
                if (ev != null) {
                    curTime = SystemClock.uptimeMillis();
                    int eventType;
                    if (ev.classType == RawInputEvent.CLASS_TOUCHSCREEN) {
                        eventType = eventType((MotionEvent)ev.event);
                    } else if (ev.classType == RawInputEvent.CLASS_KEYBOARD ||
                                ev.classType == RawInputEvent.CLASS_TRACKBALL) {
                        eventType = LocalPowerManager.BUTTON_EVENT;
                    } else {
                        eventType = LocalPowerManager.OTHER_EVENT;
                    }
                    ...
                    switch (ev.classType) {
                        case RawInputEvent.CLASS_KEYBOARD:
                            KeyEvent ke = (KeyEvent)ev.event;
                            if (ke.isDown()) {
                                lastKey = ke;
                                downTime = curTime;
                                keyRepeatCount = 0;
                                lastKeyTime = curTime;
                                nextKeyTime = lastKeyTime
                                        + ViewConfiguration.getLongPressTimeout();
                            } else {
                                lastKey = null;
                                downTime = 0;
                                // Arbitrary long timeout.
                                lastKeyTime = curTime;
                                nextKeyTime = curTime + LONG_WAIT;
                            }
                            dispatchKey((KeyEvent)ev.event, 0, 0);
                            mQueue.recycleEvent(ev);
                            break;
                        case RawInputEvent.CLASS_TOUCHSCREEN:
                            dispatchPointer(ev, (MotionEvent)ev.event, 0, 0);
                            break;
                        case RawInputEvent.CLASS_TRACKBALL:
                            dispatchTrackball(ev, (MotionEvent)ev.event, 0, 0);
                            break;
                        case RawInputEvent.CLASS_CONFIGURATION_CHANGED:
                            configChanged = true;
                            break;
                        default:
                            mQueue.recycleEvent(ev);
                        break;
                    }
                } else if (configChanged) {
                    ...
                } else if (lastKey != null) {
                    ...
                } else {
                    ...
                }
            } catch (Exception e) {
                Slog.e(TAG,
                    "Input thread received uncaught exception: " + e, e);
            }
        }
    }
}

As we can see, this thread started by WindowManagerService is very simple; all it does is

  1. Grabs events queued into WindowManagerService.mQueue
  2. Calls WindowManagerService.dispatchKey(...) when appropriate.

If we next inspect WindowManagerService.dispatchKey(...), we would see that it checks the currently focused window, and calls android.view.IWindow.dispatchKey(...) on that window. The event is now in the user space.

I put together some nice diagrams that illustrate these interactions. The conceptual model:

Event propagation flow on Android - simple version

Event propagation flow on Android - simple version


The full model:
Event propagation flow on Android

Event propagation flow on Android


The yellow boxes are Java implementations, and the blue boxes native or other.

Share:
  • Reddit
  • Google Bookmarks
  • Facebook
  • Twitter
  • Digg
  • email
  • Print
  1. Its really a very good post, I was also trying to find and stuck at the event hub. But now i am very clear how input devices reports are propagated to the upper layers. Simply super.
    thanks a lot for sharing the same.

  2. Excellent analysis.

  3. Your article is great but this mechanism only applies until Froyo. Gingerbread has a different implementation which now uses InputManager. KeyInputQueue is already gone.

    • Interesting…will have to look into that. Thanks!

  4. Thanks you very much. Just awesome.

  5. Yes great post. I’m working from the driver up and it’s helpful to have this breakdown.

2011
05.13

I came across a quite puzzling phenomenon while working on an Android application today. Although my renderer (GLSurfaceView.Renderer) had implemented its onSurfaceChanged() method which, according to the documentation, should be called when the device is rotated among other things, it wasn’t happening according to my logs. Instead, with rotation, my entire application gets paused (Activity.onPause()), stopped (Activity.onStop()), destroyed (Activity.onDestroy()), then recreated (Activity.onCreate()), started (Activity.onStart()), and resumed (Activity.onResume()). It was as if the user had quit the application and killed it and launched it again. It was crashing my application as nowhere in the documentation had I ever read that rotating the deviced would cause such a strange chain of events to happen.

It turns out that the activity tags in the AndroidManifest.xml have this crazy attribute configChanges that specifies which of a number of events the application is set up to handle itself; any of the events, if not explicitly listed in AndroidManifest.xml, would cause Android to destroy and then restart the application anew. (See official documentation.) These include notably the orientation and the hardware keyboard state. I guess in a way this makes sense; if an application did not include adequate event handlers, all of these events could potentially cause the application to crash if left running on its own; therefore Android just kills it and restarts it so that it would presumably correctly initialize itself in the new environment when it starts up again. But this attribute really ought to be heavily publicized in any introductory text as it is likely to cause noobs like me quite some bewilderment.

Share:
  • Reddit
  • Google Bookmarks
  • Facebook
  • Twitter
  • Digg
  • email
  • Print

2011
05.10

I randomly came across this just now and thought I’d make a little note for myself. Bash 4 apparently introduced a new environment variable called PROMPT_DIRTRIM that allows the shortening of paths displayed in its prompts (via w in PS1. I added

export PROMPT_DIRTRIM=3

to my ~/.bashrc and whereas Bash used to display this prompt:

[user@host /media/D/Projects/nitdroid/ul/out/target/product/generic/system/framework]$

It now displays

[user@host .../generic/system/framework]$

which is a huge improvement especially when working with the Android source code and its deep directory structure.

Share:
  • Reddit
  • Google Bookmarks
  • Facebook
  • Twitter
  • Digg
  • email
  • Print

2011
05.09

I figured out the touchscreen today. I started off with this thread from almost exactly 3 years ago. It turns out that most of the suggested kernel patch is unnecessary; for some unknown reason, the patch tries to merge the keyboard input device and the touchscreen input device into one single device in /dev/input/. Based on this understanding of the patch, I hypothesized that the reason Android was ignoring my touchscreen events while perfectly responding to my keyboard events was that it expected only one composite input device from the kernel, and was only polling on the keyboard device instead of the touchscreen device. So I commented out the calls to input_register_device(...) in both drivers/input/keyboard/lm8323.c and drivers/cbus/retu-pwrbutton.c and tried again; still no good.

I knew the touchscreen driver was definitely sending events to /dev/input/event0 because I had inserted printk calls in the driver and could see the appropriate events being generated. The problem thus lay with the Android userland. Based on this thread, I next sprinkled frameworks/base/services/java/KeyInputQueue.java and frameworks/base/services/WindowManagerService.java with Slog calls. After a number of painfully slow [ change source code -> recompile -> copy to microSD card -> unmount microSD card -> remove microSD card from SD card adapter -> insert microSD card into miniSD card adapter -> insert microSD card into the N810 -> boot up the N810 -> write on the touchscreen -> open up back cover -> take out battery -> reattach battery -> reattach back cover -> remove microSD card from miniSD card adapter -> insert microSD card into SD card adapter -> insert SD card into computer -> mount microSD card -> repair file system on microSD card -> checkout log ] cycles, it turns out that the input event handler thread (KeyInputQueue.mThread) was getting all the events and was passing them to WindowManagerService.preprocessEvent(), which, unfortunately, told it to throw them away because our good friend the PowerManagerService believes the screen was off. So for now I made PowerManagerService.isScreenOn() in frameworks/base/services/PowerManagerService.java to just return true, and the touchscreen started working.

So the conclusion is that of the changes in the patch suggested in the above thread, only those recalculating the x and y values in the function tsc2005_ts_update_pen_state(...) and the lines setting x_max and y_max to 800 and 480 respectively in tsc2005_ts_init(...) are necessary. Both changes are in drivers/input/touchscreen/tsc2005.c. All the other suggested changes are irrelevant and should be disregarded. In the Android userland code, PowerManagerService needs to be figured out.

And here’s a short video I put on YouTube showing my Froyo build booting up and responding to the touchscreen:

Share:
  • Reddit
  • Google Bookmarks
  • Facebook
  • Twitter
  • Digg
  • email
  • Print