DO NOT MERGE ANYWHERE: seos: log app loading address am: 0e738ff5da  -s ours
am: d3e3d03a97  -s ours

Change-Id: I5369b47afe6c4691f64e1d71d8053a4f3fc62dd5
diff --git a/contexthubhal/Android.mk b/contexthubhal/Android.mk
new file mode 100644
index 0000000..f578161
--- /dev/null
+++ b/contexthubhal/Android.mk
@@ -0,0 +1,19 @@
+LOCAL_PATH := $(call my-dir)
+
+# HAL module implemenation stored in
+# hw/<CONTEXT_HUB_MODULE_ID>.<ro.hardware>.so
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_RELATIVE_PATH := hw
+LOCAL_MULTILIB := both
+LOCAL_SHARED_LIBRARIES := liblog libcutils
+LOCAL_SRC_FILES := nanohubhal.cpp system_comms.cpp
+LOCAL_CFLAGS := -Wall -Werror -Wextra
+LOCAL_MODULE_OWNER := google
+
+# Include target-specific files.
+LOCAL_SRC_FILES += nanohubhal_default.cpp
+
+LOCAL_MODULE := context_hub.default
+LOCAL_MODULE_TAGS := optional
+include $(BUILD_SHARED_LIBRARY)
diff --git a/contexthubhal/message_buf.h b/contexthubhal/message_buf.h
new file mode 100644
index 0000000..f6ef893
--- /dev/null
+++ b/contexthubhal/message_buf.h
@@ -0,0 +1,160 @@
+/*
+ * Copyright (c) 2016, Google. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ *       copyright notice, this list of conditions and the following
+ *       disclaimer in the documentation and/or other materials provided
+ *       with the distribution.
+ *     * Neither the name of The Linux Foundation nor the names of its
+ *       contributors may be used to endorse or promote products derived
+ *       from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+#ifndef _MESSAGE_BUF_H_
+#define _MESSAGE_BUF_H_
+
+#include <endian.h>
+#include <cstring>
+
+namespace android {
+
+namespace nanohub {
+
+/*
+ * Marshaling helper;
+ * deals with alignment and endianness.
+ * Assumption is:
+ * read*()  parse buffer from device in LE format;
+ *          return host endianness, aligned data
+ * write*() primitives take host endinnness, aligned data,
+ *          generate buffer to be passed to device in LE format
+ *
+ * Primitives do minimal error checking, only to ensure buffer read/write
+ * safety. Caller is responsible for making sure correct amount of data
+ * has been processed.
+ */
+class MessageBuf {
+    char *data;
+    size_t size;
+    size_t pos;
+    bool readOnly;
+public:
+    MessageBuf(char *buf, size_t bufSize) {
+        size = bufSize;
+        pos = 0;
+        data = buf;
+        readOnly = false;
+    }
+    MessageBuf(const char *buf, size_t bufSize) {
+        size = bufSize;
+        pos = 0;
+        data = const_cast<char *>(buf);
+        readOnly = true;
+    }
+    const char *getData() const { return data; }
+    size_t getSize() const { return size; }
+    size_t getPos() const { return pos; }
+    size_t getRoom() const { return size - pos; }
+    uint8_t readU8() {
+        if (pos == size) {
+            return 0;
+        }
+        return data[pos++];
+    }
+    void writeU8(uint8_t val) {
+        if (pos == size || readOnly)
+            return;
+        data[pos++] = val;
+    }
+    uint16_t readU16() {
+        if (pos > (size - sizeof(uint16_t))) {
+            return 0;
+        }
+        uint16_t val;
+        memcpy(&val, &data[pos], sizeof(val));
+        pos += sizeof(val);
+        return le16toh(val);
+    }
+    void writeU16(uint16_t val) {
+        if (pos > (size - sizeof(uint16_t)) || readOnly) {
+            return;
+        }
+        uint16_t tmp = htole16(val);
+        memcpy(&data[pos], &tmp, sizeof(tmp));
+        pos += sizeof(tmp);
+    }
+    uint32_t readU32() {
+        if (pos > (size - sizeof(uint32_t))) {
+            return 0;
+        }
+        uint32_t val;
+        memcpy(&val, &data[pos], sizeof(val));
+        pos += sizeof(val);
+        return le32toh(val);
+    }
+    void writeU32(uint32_t val) {
+        if (pos > (size - sizeof(uint32_t)) || readOnly) {
+            return;
+        }
+        uint32_t tmp = htole32(val);
+        memcpy(&data[pos], &tmp, sizeof(tmp));
+        pos += sizeof(tmp);
+    }
+    uint64_t readU64() {
+        if (pos > (size - sizeof(uint64_t))) {
+            return 0;
+        }
+        uint64_t val;
+        memcpy(&val, &data[pos], sizeof(val));
+        pos += sizeof(val);
+        return le32toh(val);
+    }
+    void writeU64(uint64_t val) {
+        if (pos > (size - sizeof(uint64_t)) || readOnly) {
+            return;
+        }
+        uint64_t tmp = htole64(val);
+        memcpy(&data[pos], &tmp, sizeof(tmp));
+        pos += sizeof(tmp);
+    }
+    const void *readRaw(size_t bufSize) {
+        if (pos > (size - bufSize)) {
+            return nullptr;
+        }
+        const void *buf = &data[pos];
+        pos += bufSize;
+        return buf;
+    }
+    void writeRaw(const void *buf, size_t bufSize) {
+        if (pos > (size - bufSize) || readOnly) {
+            return;
+        }
+        memcpy(&data[pos], buf, bufSize);
+        pos += bufSize;
+    }
+};
+
+}; // namespace nanohub
+
+}; // namespace android
+
+#endif
+
diff --git a/contexthubhal/nanohub_perdevice.h b/contexthubhal/nanohub_perdevice.h
new file mode 100644
index 0000000..7fefb4d
--- /dev/null
+++ b/contexthubhal/nanohub_perdevice.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2016, Google. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ *       copyright notice, this list of conditions and the following
+ *       disclaimer in the documentation and/or other materials provided
+ *       with the distribution.
+ *     * Neither the name of The Linux Foundation nor the names of its
+ *       contributors may be used to endorse or promote products derived
+ *       from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+#ifndef _NANOHUB_PER_DEVICE_H_
+#define _NANOHUB_PER_DEVICE_H_
+
+#include <hardware/context_hub.h>
+
+namespace android {
+
+namespace nanohub {
+
+const struct context_hub_t* get_hub_info(void);
+const char *get_devnode_path(void);
+
+}; // namespace nanohub
+
+}; // namespace android
+
+#endif
diff --git a/contexthubhal/nanohubhal.cpp b/contexthubhal/nanohubhal.cpp
new file mode 100644
index 0000000..960258a
--- /dev/null
+++ b/contexthubhal/nanohubhal.cpp
@@ -0,0 +1,425 @@
+/*
+ * Copyright (c) 2016, Google. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ *       copyright notice, this list of conditions and the following
+ *       disclaimer in the documentation and/or other materials provided
+ *       with the distribution.
+ *     * Neither the name of The Linux Foundation nor the names of its
+ *       contributors may be used to endorse or promote products derived
+ *       from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#define LOG_TAG "NanohubHAL"
+
+#include <fcntl.h>
+#include <poll.h>
+#include <pthread.h>
+#include <unistd.h>
+#include <sys/inotify.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <hardware/context_hub.h>
+#include <hardware/hardware.h>
+
+#include <utils/Log.h>
+#include <cutils/properties.h>
+
+#include <cinttypes>
+#include <iomanip>
+#include <sstream>
+
+#include "nanohub_perdevice.h"
+#include "system_comms.h"
+#include "nanohubhal.h"
+
+#define NANOHUB_LOCK_DIR        "/data/system/nanohub_lock"
+#define NANOHUB_LOCK_FILE       NANOHUB_LOCK_DIR "/lock"
+#define NANOHUB_LOCK_DIR_PERMS  (S_IRUSR | S_IWUSR | S_IXUSR)
+
+namespace android {
+
+namespace nanohub {
+
+inline std::ostream &operator << (std::ostream &os, const hub_app_name_t &appId)
+{
+    char vendor[6];
+    __be64 beAppId = htobe64(appId.id);
+    uint32_t seqId = appId.id & NANOAPP_VENDOR_ALL_APPS;
+
+    std::ios::fmtflags f(os.flags());
+    memcpy(vendor, (void*)&beAppId, sizeof(vendor) - 1);
+    vendor[sizeof(vendor) - 1] = 0;
+    if (strlen(vendor) == 5)
+        os << vendor << ", " << std::hex << std::setw(6)  << seqId;
+    else
+        os << "#" << std::hex << appId.id;
+    os.flags(f);
+
+    return os;
+}
+
+void dumpBuffer(const char *pfx, const hub_app_name_t &appId, uint32_t evtId, const void *data, size_t len, int status)
+{
+    std::ostringstream os;
+    const uint8_t *p = static_cast<const uint8_t *>(data);
+    os << pfx << ": [ID=" << appId << "; SZ=" << std::dec << len;
+    if (evtId)
+        os << "; EVT=" << std::hex << evtId;
+    os << "]:" << std::hex;
+    for (size_t i = 0; i < len; ++i) {
+        os << " "  << std::setfill('0') << std::setw(2) << (unsigned int)p[i];
+    }
+    if (status) {
+        os << "; status=" << status << " [" << std::setfill('0') << std::setw(8) << status << "]";
+    }
+    ALOGI("%s", os.str().c_str());
+}
+
+static int rwrite(int fd, const void *buf, int len)
+{
+    int ret;
+
+    do {
+        ret = write(fd, buf, len);
+    } while (ret < 0 && errno == EINTR);
+
+    if (ret != len) {
+        return errno ? -errno : -EIO;
+    }
+
+    return 0;
+}
+
+static int rread(int fd, void *buf, int len)
+{
+    int ret;
+
+    do {
+        ret = read(fd, buf, len);
+    } while (ret < 0 && errno == EINTR);
+
+    return ret;
+}
+
+static bool init_inotify(pollfd *pfd) {
+    bool success = false;
+
+    mkdir(NANOHUB_LOCK_DIR, NANOHUB_LOCK_DIR_PERMS);
+    pfd->fd = inotify_init1(IN_NONBLOCK);
+    if (pfd->fd < 0) {
+        ALOGE("Couldn't initialize inotify: %s", strerror(errno));
+    } else if (inotify_add_watch(pfd->fd, NANOHUB_LOCK_DIR, IN_CREATE | IN_DELETE) < 0) {
+        ALOGE("Couldn't add inotify watch: %s", strerror(errno));
+        close(pfd->fd);
+    } else {
+        pfd->events = POLLIN;
+        success = true;
+    }
+
+    return success;
+}
+
+static void discard_inotify_evt(pollfd &pfd) {
+    if ((pfd.revents & POLLIN)) {
+        char buf[sizeof(inotify_event) + NAME_MAX + 1];
+        int ret = read(pfd.fd, buf, sizeof(buf));
+        ALOGD("Discarded %d bytes of inotify data", ret);
+    }
+}
+
+static void wait_on_dev_lock(pollfd &pfd) {
+    // While the lock file exists, poll on the inotify fd (with timeout)
+    discard_inotify_evt(pfd);
+    while (access(NANOHUB_LOCK_FILE, F_OK) == 0) {
+        ALOGW("Nanohub is locked; blocking read thread");
+        int ret = poll(&pfd, 1, 5000);
+        if (ret > 0) {
+            discard_inotify_evt(pfd);
+        }
+    }
+}
+
+int NanoHub::doSendToDevice(const hub_app_name_t *name, const void *data, uint32_t len)
+{
+    if (len > MAX_RX_PACKET) {
+        return -EINVAL;
+    }
+
+    nano_message msg = {
+        .hdr = {
+            .event_id = APP_FROM_HOST_EVENT_ID,
+            .app_name = *name,
+            .len = static_cast<uint8_t>(len),
+        },
+    };
+
+    memcpy(&msg.data[0], data, len);
+
+    return rwrite(mFd, &msg, len + sizeof(msg.hdr));
+}
+
+void NanoHub::doSendToApp(const hub_app_name_t *name, uint32_t typ, const void *data, uint32_t len)
+{
+    hub_message_t msg = {
+        .app_name = *name,
+        .message_type = typ,
+        .message_len = len,
+        .message = data,
+    };
+
+    mMsgCbkFunc(0, &msg, mMsgCbkData);
+}
+
+void* NanoHub::run(void *data)
+{
+    NanoHub *self = static_cast<NanoHub*>(data);
+    return self->doRun();
+}
+
+void* NanoHub::doRun()
+{
+    enum {
+        IDX_NANOHUB,
+        IDX_CLOSE_PIPE,
+        IDX_INOTIFY
+    };
+    pollfd myFds[3] = {
+        [IDX_NANOHUB] = { .fd = mFd, .events = POLLIN, },
+        [IDX_CLOSE_PIPE] = { .fd = mThreadClosingPipe[0], .events = POLLIN, },
+    };
+    pollfd &inotifyFd = myFds[IDX_INOTIFY];
+    bool hasInotify = false;
+    int numPollFds = 2;
+
+    if (init_inotify(&inotifyFd)) {
+        numPollFds++;
+        hasInotify = true;
+    }
+
+    setDebugFlags(property_get_int32("persist.nanohub.debug", 0));
+
+    while (1) {
+        int ret = poll(myFds, numPollFds, -1);
+        if (ret <= 0) {
+            ALOGD("poll is being weird");
+            continue;
+        }
+
+        if (hasInotify) {
+            wait_on_dev_lock(inotifyFd);
+        }
+
+        if (myFds[IDX_NANOHUB].revents & POLLIN) { // we have data
+
+            nano_message msg;
+
+            ret = rread(mFd, &msg, sizeof(msg));
+            if (ret <= 0) {
+                ALOGE("read failed with %d", ret);
+                break;
+            }
+            if (ret < (int)sizeof(msg.hdr)) {
+                ALOGE("Only read %d bytes", ret);
+                break;
+            }
+
+            uint32_t len = msg.hdr.len;
+
+            if (len > sizeof(msg.data)) {
+                ALOGE("malformed packet with len %" PRIu32, len);
+                break;
+            }
+
+            if (ret != (int)(sizeof(msg.hdr) + len)) {
+                ALOGE("Expected %zu bytes, read %d bytes", sizeof(msg.hdr) + len, ret);
+                break;
+            }
+
+            ret = SystemComm::handleRx(&msg);
+            if (ret < 0) {
+                ALOGE("SystemComm::handleRx() returned %d", ret);
+            } else if (ret) {
+                if (messageTracingEnabled()) {
+                    dumpBuffer("DEV -> APP", msg.hdr.app_name, msg.hdr.event_id, &msg.data[0], msg.hdr.len);
+                }
+                doSendToApp(&msg.hdr.app_name, msg.hdr.event_id, &msg.data[0], msg.hdr.len);
+            }
+        }
+
+        if (myFds[IDX_CLOSE_PIPE].revents & POLLIN) { // we have been asked to die
+            ALOGD("thread exiting");
+            break;
+        }
+    }
+
+    close(mFd);
+    return NULL;
+}
+
+int NanoHub::openHub()
+{
+    int ret = 0;
+
+    mFd = open(get_devnode_path(), O_RDWR);
+    if (mFd < 0) {
+        ALOGE("cannot find hub devnode '%s'", get_devnode_path());
+        ret = -errno;
+        goto fail_open;
+    }
+
+    if (pipe(mThreadClosingPipe)) {
+        ALOGE("failed to create signal pipe");
+        ret = -errno;
+        goto fail_pipe;
+    }
+
+    if (pthread_create(&mWorkerThread, NULL, &NanoHub::run, this)) {
+        ALOGE("failed to spawn worker thread");
+        ret = -errno;
+        goto fail_thread;
+    }
+
+    return 0;
+
+fail_thread:
+    close(mThreadClosingPipe[0]);
+    close(mThreadClosingPipe[1]);
+
+fail_pipe:
+    close(mFd);
+
+fail_open:
+    return ret;
+}
+
+int NanoHub::closeHub(void)
+{
+    char zero = 0;
+
+    //signal
+    while(write(mThreadClosingPipe[1], &zero, 1) != 1);
+
+    //wait
+    (void)pthread_join(mWorkerThread, NULL);
+
+    //cleanup
+    ::close(mThreadClosingPipe[0]);
+    ::close(mThreadClosingPipe[1]);
+
+    reset();
+
+    return 0;
+}
+
+int NanoHub::doSubscribeMessages(uint32_t hub_id, context_hub_callback *cbk, void *cookie)
+{
+    if (hub_id) {
+        return -ENODEV;
+    }
+
+    Mutex::Autolock _l(mLock);
+    int ret = 0;
+
+    if (!mMsgCbkFunc && !cbk) { //we're off and staying off - do nothing
+
+        ALOGD("staying off");
+    } else if (cbk && mMsgCbkFunc) { //new callback but staying on
+
+        ALOGD("staying on");
+    } else if (mMsgCbkFunc) {     //we were on but turning off
+
+        ALOGD("turning off");
+
+        ret = closeHub();
+    } else if (cbk) {             //we're turning on
+
+        ALOGD("turning on");
+        ret = openHub();
+    }
+
+    mMsgCbkFunc = cbk;
+    mMsgCbkData = cookie;
+
+    return ret;
+}
+
+int NanoHub::doSendToNanohub(uint32_t hub_id, const hub_message_t *msg)
+{
+    if (hub_id) {
+        return -ENODEV;
+    }
+
+    int ret = 0;
+    Mutex::Autolock _l(mLock);
+
+    if (!mMsgCbkFunc) {
+        ALOGW("refusing to send a message when nobody around to get a reply!");
+        ret = -EIO;
+    } else {
+        if (!msg || !msg->message) {
+            ALOGW("not sending invalid message 1");
+            ret = -EINVAL;
+        } else if (get_hub_info()->os_app_name == msg->app_name) {
+            //messages to the "system" app are special - hal handles them
+            if (messageTracingEnabled()) {
+                dumpBuffer("APP -> HAL", msg->app_name, msg->message_type, msg->message, msg->message_len);
+            }
+            ret = SystemComm::handleTx(msg);
+        } else if (msg->message_type || msg->message_len > MAX_RX_PACKET) {
+            ALOGW("not sending invalid message 2");
+            ret = -EINVAL;
+        } else {
+            if (messageTracingEnabled()) {
+                dumpBuffer("APP -> DEV", msg->app_name, 0, msg->message, msg->message_len);
+            }
+            ret = doSendToDevice(&msg->app_name, msg->message, msg->message_len);
+        }
+    }
+
+    return ret;
+}
+
+static int hal_get_hubs(context_hub_module_t*, const context_hub_t ** list)
+{
+    *list = get_hub_info();
+
+    return 1; /* we have one hub */
+}
+
+}; // namespace nanohub
+
+}; // namespace android
+
+context_hub_module_t HAL_MODULE_INFO_SYM = {
+    .common = {
+        .tag = HARDWARE_MODULE_TAG,
+        .module_api_version = CONTEXT_HUB_DEVICE_API_VERSION_1_0,
+        .hal_api_version = HARDWARE_HAL_API_VERSION,
+        .id = CONTEXT_HUB_MODULE_ID,
+        .name = "Nanohub HAL",
+        .author = "Google",
+    },
+
+    .get_hubs = android::nanohub::hal_get_hubs,
+    .subscribe_messages = android::nanohub::NanoHub::subscribeMessages,
+    .send_message = android::nanohub::NanoHub::sendToNanohub,
+};
diff --git a/contexthubhal/nanohubhal.h b/contexthubhal/nanohubhal.h
new file mode 100644
index 0000000..bcd291f
--- /dev/null
+++ b/contexthubhal/nanohubhal.h
@@ -0,0 +1,141 @@
+/*
+ * Copyright (c) 2016, Google. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ *       copyright notice, this list of conditions and the following
+ *       disclaimer in the documentation and/or other materials provided
+ *       with the distribution.
+ *     * Neither the name of The Linux Foundation nor the names of its
+ *       contributors may be used to endorse or promote products derived
+ *       from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+#ifndef _NANOHUB_HAL_H_
+#define _NANOHUB_HAL_H_
+
+#include <pthread.h>
+
+#include <hardware/context_hub.h>
+#include <utils/Mutex.h>
+
+#define NANOAPP_VENDOR_GOOGLE NANOAPP_VENDOR("Googl")
+
+//as per protocol
+#define MAX_RX_PACKET           128
+#define APP_FROM_HOST_EVENT_ID  0x000000F8
+
+namespace android {
+
+namespace nanohub {
+
+void dumpBuffer(const char *pfx, const hub_app_name_t &appId, uint32_t evtId, const void *data, size_t len, int status = 0);
+
+struct nano_message_hdr {
+    uint32_t event_id;
+    hub_app_name_t app_name;
+    uint8_t len;
+} __attribute__((packed));
+
+struct nano_message {
+    nano_message_hdr hdr;
+    uint8_t data[MAX_RX_PACKET];
+} __attribute__((packed));
+
+class NanoHub {
+    Mutex mLock;
+    context_hub_callback *mMsgCbkFunc;
+    int mThreadClosingPipe[2];
+    int mFd; // [0] is read end
+    void * mMsgCbkData;
+    pthread_t mWorkerThread;
+
+    NanoHub() {
+        reset();
+    }
+
+    void reset() {
+        mThreadClosingPipe[0] = -1;
+        mThreadClosingPipe[1] = -1;
+        mFd = -1;
+        mMsgCbkData = nullptr;
+        mMsgCbkFunc = nullptr;
+        mWorkerThread = 0;
+    }
+
+    static void* run(void *);
+    void* doRun();
+
+    int openHub();
+    int closeHub();
+
+    static NanoHub *hubInstance() {
+        static NanoHub theHub;
+        return &theHub;
+    }
+
+    int doSubscribeMessages(uint32_t hub_id, context_hub_callback *cbk, void *cookie);
+    int doSendToNanohub(uint32_t hub_id, const hub_message_t *msg);
+    int doSendToDevice(const hub_app_name_t *name, const void *data, uint32_t len);
+    void doSendToApp(const hub_app_name_t *name, uint32_t typ, const void *data, uint32_t len);
+
+    static constexpr unsigned int FL_MESSAGE_TRACING = 1;
+
+    unsigned int mFlags = 0;
+
+public:
+
+    // debugging interface
+
+    static bool messageTracingEnabled() {
+        return hubInstance()->mFlags & FL_MESSAGE_TRACING;
+    }
+    static unsigned int getDebugFlags() {
+        return hubInstance()->mFlags;
+    }
+    static void setDebugFlags(unsigned int flags) {
+        hubInstance()->mFlags = flags;
+    }
+
+    // messaging interface
+
+    // define callback to invoke for APP messages
+    static int subscribeMessages(uint32_t hub_id, context_hub_callback *cbk, void *cookie) {
+        return hubInstance()->doSubscribeMessages(hub_id, cbk, cookie);
+    }
+    // all messages from APP go here
+    static int sendToNanohub(uint32_t hub_id, const hub_message_t *msg) {
+        return hubInstance()->doSendToNanohub(hub_id, msg);
+    }
+    // passes message to kernel driver directly
+    static int sendToDevice(const hub_app_name_t *name, const void *data, uint32_t len) {
+        return hubInstance()->doSendToDevice(name, data, len);
+    }
+    // passes message to APP via callback
+    static void sendToApp(const hub_app_name_t *name, uint32_t typ, const void *data, uint32_t len) {
+        hubInstance()->doSendToApp(name, typ, data, len);
+    }
+};
+
+}; // namespace nanohub
+
+}; // namespace android
+
+#endif
diff --git a/contexthubhal/nanohubhal_default.cpp b/contexthubhal/nanohubhal_default.cpp
new file mode 100644
index 0000000..6266045
--- /dev/null
+++ b/contexthubhal/nanohubhal_default.cpp
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2016, Google. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ *       copyright notice, this list of conditions and the following
+ *       disclaimer in the documentation and/or other materials provided
+ *       with the distribution.
+ *     * Neither the name of The Linux Foundation nor the names of its
+ *       contributors may be used to endorse or promote products derived
+ *       from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#define LOG_TAG "NanohubHAL"
+#include <hardware/context_hub.h>
+#include "nanohub_perdevice.h"
+#include "nanohubhal.h"
+#include <utils/Log.h>
+
+namespace android {
+
+namespace nanohub {
+
+#define DEVICE "Default"
+#define DEVICE_TAG (DEVICE[0])
+
+static const connected_sensor_t mSensors[] = {
+    {
+        .sensor_id = ((int)DEVICE_TAG << 8) + 1,
+        .physical_sensor = {
+            .name = "i'll get to this later",
+        },
+    },
+    {
+        .sensor_id = ((int)DEVICE_TAG << 8) + 2,
+        .physical_sensor = {
+            .name = "i'll get to this later as well",
+        },
+    },
+};
+
+static const context_hub_t mHub = {
+    .name = "Google System Nanohub on " DEVICE,
+    .vendor = "Google/StMicro",
+    .toolchain = "gcc-arm-none-eabi",
+    .platform_version = 1,
+    .toolchain_version = 0x04080000, //4.8
+    .hub_id = 0,
+
+    .peak_mips = 16,
+    .stopped_power_draw_mw = 0.010 * 1.800,
+    .sleep_power_draw_mw   = 0.080 * 1.800,
+    .peak_power_draw_mw    = 3.000 * 1.800,
+
+    .connected_sensors = mSensors,
+    .num_connected_sensors = sizeof(mSensors) / sizeof(*mSensors),
+
+    .max_supported_msg_len = MAX_RX_PACKET,
+    .os_app_name = { .id = 0 },
+};
+
+const char *get_devnode_path(void)
+{
+    return "/dev/nanohub_comms";
+}
+
+const context_hub_t* get_hub_info(void)
+{
+    return &mHub;
+}
+
+}; // namespace nanohub
+
+}; // namespace android
diff --git a/contexthubhal/system_comms.cpp b/contexthubhal/system_comms.cpp
new file mode 100644
index 0000000..a21060d
--- /dev/null
+++ b/contexthubhal/system_comms.cpp
@@ -0,0 +1,573 @@
+/*
+ * Copyright (c) 2016, Google. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ *       copyright notice, this list of conditions and the following
+ *       disclaimer in the documentation and/or other materials provided
+ *       with the distribution.
+ *     * Neither the name of The Linux Foundation nor the names of its
+ *       contributors may be used to endorse or promote products derived
+ *       from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#define LOG_TAG "NanohubHAL"
+
+#include <cassert>
+#include <cerrno>
+#include <cinttypes>
+
+#include <endian.h>
+
+#include <vector>
+
+#include <utils/Log.h>
+
+#include <endian.h>
+
+#include <hardware/context_hub.h>
+#include "nanohub_perdevice.h"
+#include "system_comms.h"
+#include "nanohubhal.h"
+
+namespace android {
+
+namespace nanohub {
+
+static void readAppName(MessageBuf &buf, hub_app_name_t &name) {
+    name.id = buf.readU64();
+}
+
+static void writeAppName(MessageBuf &buf, const hub_app_name_t &name) {
+    buf.writeU64(name.id);
+}
+
+static void readNanohubAppInfo(MessageBuf &buf, NanohubAppInfo &info) {
+    size_t pos = buf.getPos();
+    readAppName(buf, info.name);
+    info.version = buf.readU32();
+    info.flashUse = buf.readU32();
+    info.ramUse = buf.readU32();
+    if ((buf.getPos() - pos) != sizeof(info)) {
+        ALOGE("%s: failed to read object", __func__);
+    }
+}
+
+static void readNanohubMemInfo(MessageBuf &buf,  NanohubMemInfo &mi) {
+    size_t pos = buf.getPos();
+    mi.flashSz = buf.readU32();
+    mi.blSz = buf.readU32();
+    mi.osSz = buf.readU32();
+    mi.sharedSz = buf.readU32();
+    mi.eeSz = buf.readU32();
+    mi.ramSz = buf.readU32();
+
+    mi.blUse = buf.readU32();
+    mi.osUse = buf.readU32();
+    mi.sharedUse = buf.readU32();
+    mi.eeUse = buf.readU32();
+    mi.ramUse = buf.readU32();
+    if ((buf.getPos() - pos) != sizeof(mi)) {
+        ALOGE("%s: failed to read object", __func__);
+    }
+}
+
+NanohubRsp::NanohubRsp(MessageBuf &buf, bool no_status) {
+    // all responses start with command
+    // most of them have 4-byte status (result code)
+    cmd = buf.readU8();
+    if (!buf.getSize()) {
+        status = -EINVAL;
+    } else if (no_status) {
+        status = 0;
+    } else {
+        status = buf.readU32();
+    }
+}
+
+int SystemComm::sendToSystem(const void *data, size_t len) {
+    if (NanoHub::messageTracingEnabled()) {
+        dumpBuffer("HAL -> SYS", getSystem()->mHostIfAppName, 0, data, len);
+    }
+    return NanoHub::sendToDevice(&getSystem()->mHostIfAppName, data, len);
+}
+
+int SystemComm::AppInfoSession::setup(const hub_message_t *) {
+    Mutex::Autolock _l(mLock);
+    int suggestedSize = mAppInfo.size() ? mAppInfo.size() : 20;
+
+    mAppInfo.clear();
+    mAppInfo.reserve(suggestedSize);
+    setState(SESSION_USER);
+
+    return requestNext();
+}
+
+inline hub_app_name_t deviceAppNameToHost(const hub_app_name_t src) {
+    hub_app_name_t res = { .id = le64toh(src.id) };
+    return res;
+}
+
+inline hub_app_name_t hostAppNameToDevice(const hub_app_name_t src) {
+    hub_app_name_t res = { .id = htole64(src.id) };
+    return res;
+}
+
+int SystemComm::AppInfoSession::handleRx(MessageBuf &buf)
+{
+    Mutex::Autolock _l(mLock);
+
+    NanohubRsp rsp(buf, true);
+    if (rsp.cmd != NANOHUB_QUERY_APPS) {
+        return 1;
+    }
+    size_t len = buf.getRoom();
+    if (len != sizeof(NanohubAppInfo) && len) {
+        ALOGE("%s: Invalid data size; have %zu, need %zu", __func__,
+              len, sizeof(NanohubAppInfo));
+        return -EINVAL;
+    }
+    if (getState() != SESSION_USER) {
+        ALOGE("%s: Invalid state; have %d, need %d", __func__, getState(), SESSION_USER);
+        return -EINVAL;
+    }
+    if (len) {
+        NanohubAppInfo info;
+        readNanohubAppInfo(buf, info);
+        hub_app_info appInfo;
+        appInfo.num_mem_ranges = 0;
+        if (info.flashUse != NANOHUB_MEM_SZ_UNKNOWN) {
+            mem_range_t &range = appInfo.mem_usage[appInfo.num_mem_ranges++];
+            range.type = HUB_MEM_TYPE_MAIN;
+            range.total_bytes = info.flashUse;
+        }
+        if (info.ramUse != NANOHUB_MEM_SZ_UNKNOWN) {
+            mem_range_t &range = appInfo.mem_usage[appInfo.num_mem_ranges++];
+            range.type = HUB_MEM_TYPE_RAM;
+            range.total_bytes = info.ramUse;
+        }
+
+        appInfo.app_name = info.name;
+        appInfo.version = info.version;
+
+        mAppInfo.push_back(appInfo);
+        return requestNext();
+    } else {
+        sendToApp(CONTEXT_HUB_QUERY_APPS,
+                        static_cast<const void *>(mAppInfo.data()),
+                        mAppInfo.size() * sizeof(mAppInfo[0]));
+        complete();
+    }
+
+    return 0;
+}
+
+int SystemComm::AppInfoSession::requestNext()
+{
+    char data[MAX_RX_PACKET];
+    MessageBuf buf(data, sizeof(data));
+    buf.writeU8(NANOHUB_QUERY_APPS);
+    buf.writeU32(mAppInfo.size());
+    return sendToSystem(buf.getData(), buf.getPos());
+}
+
+int SystemComm::MemInfoSession::setup(const hub_message_t *)
+{
+    Mutex::Autolock _l(mLock);
+    char data[MAX_RX_PACKET];
+    MessageBuf buf(data, sizeof(data));
+    buf.writeU8(NANOHUB_QUERY_MEMINFO);
+
+    setState(SESSION_USER);
+    return sendToSystem(buf.getData(), buf.getPos());
+}
+
+int SystemComm::MemInfoSession::handleRx(MessageBuf &buf)
+{
+    Mutex::Autolock _l(mLock);
+    NanohubRsp rsp(buf, true);
+
+    if (rsp.cmd != NANOHUB_QUERY_MEMINFO)
+        return 1;
+
+    size_t len = buf.getRoom();
+
+    if (len != sizeof(NanohubMemInfo)) {
+        ALOGE("%s: Invalid data size: %zu", __func__, len);
+        return -EINVAL;
+    }
+    if (getState() != SESSION_USER) {
+        ALOGE("%s: Invalid state; have %d, need %d", __func__, getState(), SESSION_USER);
+        return -EINVAL;
+    }
+
+    NanohubMemInfo mi;
+    readNanohubMemInfo(buf, mi);
+    std::vector<mem_range_t> ranges;
+    ranges.reserve(4);
+
+    //if each is valid, copy to output area
+    if (mi.sharedSz != NANOHUB_MEM_SZ_UNKNOWN &&
+        mi.sharedUse != NANOHUB_MEM_SZ_UNKNOWN)
+        ranges.push_back({
+            .type = HUB_MEM_TYPE_MAIN,
+            .total_bytes = mi.sharedSz,
+            .free_bytes = mi.sharedSz - mi.sharedUse,
+        });
+
+    if (mi.osSz != NANOHUB_MEM_SZ_UNKNOWN &&
+        mi.osUse != NANOHUB_MEM_SZ_UNKNOWN)
+        ranges.push_back({
+            .type = HUB_MEM_TYPE_OS,
+            .total_bytes = mi.osSz,
+            .free_bytes = mi.osSz - mi.osUse,
+        });
+
+    if (mi.eeSz != NANOHUB_MEM_SZ_UNKNOWN &&
+        mi.eeUse != NANOHUB_MEM_SZ_UNKNOWN)
+        ranges.push_back({
+            .type = HUB_MEM_TYPE_EEDATA,
+            .total_bytes = mi.eeSz,
+            .free_bytes = mi.eeSz - mi.eeUse,
+        });
+
+    if (mi.ramSz != NANOHUB_MEM_SZ_UNKNOWN &&
+        mi.ramUse != NANOHUB_MEM_SZ_UNKNOWN)
+        ranges.push_back({
+            .type = HUB_MEM_TYPE_RAM,
+            .total_bytes = mi.ramSz,
+            .free_bytes = mi.ramSz - mi.ramUse,
+        });
+
+    //send it out
+    sendToApp(CONTEXT_HUB_QUERY_MEMORY,
+              static_cast<const void *>(ranges.data()),
+              ranges.size() * sizeof(ranges[0]));
+
+    complete();
+
+    return 0;
+}
+
+int SystemComm::AppMgmtSession::setup(const hub_message_t *appMsg)
+{
+    Mutex::Autolock _l(mLock);
+
+    mCmd = appMsg->message_type;
+    mLen = appMsg->message_len;
+    mPos = 0;
+
+    switch (mCmd) {
+    case  CONTEXT_HUB_APPS_ENABLE:
+        return setupMgmt(appMsg, NANOHUB_EXT_APPS_ON);
+    case  CONTEXT_HUB_APPS_DISABLE:
+        return setupMgmt(appMsg, NANOHUB_EXT_APPS_OFF);
+    case  CONTEXT_HUB_UNLOAD_APP:
+        return setupMgmt(appMsg, NANOHUB_EXT_APP_DELETE);
+    case  CONTEXT_HUB_LOAD_OS:
+    case  CONTEXT_HUB_LOAD_APP:
+        const uint8_t *p = static_cast<const uint8_t*>(appMsg->message);
+        mData.clear();
+        mData = std::vector<uint8_t>(p, p + mLen);
+        setState(TRANSFER);
+
+        char data[MAX_RX_PACKET];
+        MessageBuf buf(data, sizeof(data));
+        buf.writeU8(NANOHUB_START_UPLOAD);
+        buf.writeU8(mCmd == CONTEXT_HUB_LOAD_OS ? 1 : 0);
+        buf.writeU32(mLen);
+
+        return sendToSystem(buf.getData(), buf.getPos());
+    break;
+    }
+
+    return -EINVAL;
+}
+
+int SystemComm::AppMgmtSession::setupMgmt(const hub_message_t *appMsg, uint32_t cmd)
+{
+    const hub_app_name_t &appName = *static_cast<const hub_app_name_t*>(appMsg->message);
+    if (appMsg->message_len != sizeof(appName)) {
+        return -EINVAL;
+    }
+
+    char data[MAX_RX_PACKET];
+    MessageBuf buf(data, sizeof(data));
+    buf.writeU8(cmd);
+    writeAppName(buf, appName);
+    setState(MGMT);
+
+    return sendToSystem(buf.getData(), buf.getPos());
+}
+
+int SystemComm::AppMgmtSession::handleRx(MessageBuf &buf)
+{
+    int ret = 0;
+    Mutex::Autolock _l(mLock);
+    NanohubRsp rsp(buf);
+
+    switch (getState()) {
+    case TRANSFER:
+        ret = handleTransfer(rsp);
+        break;
+    case FINISH:
+        ret = handleFinish(rsp);
+        break;
+    case RELOAD:
+        ret = handleReload(rsp);
+        break;
+    case MGMT:
+        ret = handleMgmt(rsp);
+        break;
+    }
+
+    return ret;
+}
+
+int SystemComm::AppMgmtSession::handleTransfer(NanohubRsp &rsp)
+{
+    if (rsp.cmd != NANOHUB_CONT_UPLOAD && rsp.cmd != NANOHUB_START_UPLOAD)
+        return 1;
+
+    char data[MAX_RX_PACKET];
+    MessageBuf buf(data, sizeof(data));
+
+    static_assert(NANOHUB_UPLOAD_CHUNK_SZ_MAX <= (MAX_RX_PACKET-5),
+                  "Invalid chunk size");
+
+    if (mPos < mLen) {
+        uint32_t chunkSize = mLen - mPos;
+
+        if (chunkSize > NANOHUB_UPLOAD_CHUNK_SZ_MAX) {
+            chunkSize = NANOHUB_UPLOAD_CHUNK_SZ_MAX;
+        }
+
+        buf.writeU8(NANOHUB_CONT_UPLOAD);
+        buf.writeU32(mPos);
+        buf.writeRaw(&mData[mPos], chunkSize);
+        mPos += chunkSize;
+    } else {
+        buf.writeU8(NANOHUB_FINISH_UPLOAD);
+        setState(FINISH);
+    }
+
+    return sendToSystem(buf.getData(), buf.getPos());
+}
+
+int SystemComm::AppMgmtSession::handleFinish(NanohubRsp &rsp)
+{
+    if (rsp.cmd != NANOHUB_FINISH_UPLOAD)
+        return 1;
+
+    int ret = 0;
+    const bool success = rsp.status != 0;
+    mData.clear();
+
+    if (success) {
+        char data[MAX_RX_PACKET];
+        MessageBuf buf(data, sizeof(data));
+        // until app header is passed, we don't know who to start, so we reboot
+        buf.writeU8(NANOHUB_REBOOT);
+        setState(RELOAD);
+        ret = sendToSystem(buf.getData(), buf.getPos());
+    } else {
+        int32_t result = NANOHUB_APP_NOT_LOADED;
+
+        sendToApp(mCmd, &result, sizeof(result));
+        complete();
+    }
+
+    return ret;
+}
+
+/* reboot notification is not yet supported in FW; this code is for (near) future */
+int SystemComm::AppMgmtSession::handleReload(NanohubRsp &rsp)
+{
+    int32_t result = NANOHUB_APP_LOADED;
+
+    ALOGI("Nanohub reboot status: %08" PRIX32, rsp.status);
+
+    sendToApp(mCmd, &result, sizeof(result));
+    complete();
+
+    return 0;
+}
+
+int SystemComm::AppMgmtSession::handleMgmt(NanohubRsp &rsp)
+{
+    Mutex::Autolock _l(mLock);
+    bool valid = false;
+
+    ALOGI("Nanohub MGMT response: CMD=%02X; STATUS=%08" PRIX32, rsp.cmd, rsp.status);
+
+    switch (rsp.cmd) {
+    case NANOHUB_EXT_APPS_OFF:
+        valid = mCmd == CONTEXT_HUB_APPS_DISABLE;
+        break;
+    case NANOHUB_EXT_APPS_ON:
+        valid = mCmd == CONTEXT_HUB_APPS_ENABLE;
+        break;
+    case NANOHUB_EXT_APP_DELETE:
+        valid = mCmd == CONTEXT_HUB_UNLOAD_APP;
+        break;
+    default:
+        return 1;
+    }
+
+    if (!valid) {
+        ALOGE("Invalid response for this state: APP CMD=%02X", mCmd);
+        return -EINVAL;
+    }
+
+    sendToApp(mCmd, &rsp.status, sizeof(rsp.status));
+    complete();
+
+    return 0;
+}
+
+int SystemComm::KeyInfoSession::setup(const hub_message_t *) {
+    Mutex::Autolock _l(mLock);
+    mRsaKeyData.clear();
+    setState(SESSION_USER);
+    mStatus = -EBUSY;
+    return requestRsaKeys();
+}
+
+int SystemComm::KeyInfoSession::handleRx(MessageBuf &buf)
+{
+    Mutex::Autolock _l(mLock);
+    NanohubRsp rsp(buf, true);
+
+    if (getState() != SESSION_USER) {
+        // invalid state
+        mStatus = -EFAULT;
+        return mStatus;
+    }
+
+    if (buf.getRoom()) {
+        mRsaKeyData.insert(mRsaKeyData.end(),
+                           buf.getData() + buf.getPos(),
+                           buf.getData() + buf.getSize());
+        return requestRsaKeys();
+    } else {
+        mStatus = 0;
+        complete();
+        return 0;
+    }
+}
+
+int SystemComm::KeyInfoSession::requestRsaKeys(void)
+{
+    char data[MAX_RX_PACKET];
+    MessageBuf buf(data, sizeof(data));
+
+    buf.writeU8(NANOHUB_QUERY_APPS);
+    buf.writeU32(mRsaKeyData.size());
+
+    return sendToSystem(buf.getData(), buf.getPos());
+}
+
+int SystemComm::doHandleRx(const nano_message *msg)
+{
+    //we only care for messages from HostIF
+    if (msg->hdr.app_name != mHostIfAppName)
+        return 1;
+
+    //they must all be at least 1 byte long
+    if (!msg->hdr.len) {
+        return -EINVAL;
+    }
+    MessageBuf buf(reinterpret_cast<const char*>(msg->data), msg->hdr.len);
+    if (NanoHub::messageTracingEnabled()) {
+        dumpBuffer("SYS -> HAL", mHostIfAppName, 0, buf.getData(), buf.getSize());
+    }
+    int status = mSessions.handleRx(buf);
+    if (status) {
+        // provide default handler for any system message, that is not properly handled
+        dumpBuffer(status > 0 ? "HAL (not handled)" : "HAL (error)",
+                   mHostIfAppName, 0, buf.getData(), buf.getSize(), status);
+        status = status > 0 ? 0 : status;
+    }
+
+    return status;
+}
+
+int SystemComm::SessionManager::handleRx(MessageBuf &buf)
+{
+    int status = 1;
+
+    // pass message to all active sessions, in arbitrary order
+    // 1st session that handles the message terminates the loop
+    for (auto pos = sessions_.begin();
+         pos != sessions_.end() && status > 0; next(pos)) {
+        Session *session = pos->second;
+        status = session->handleRx(buf);
+        if (status < 0) {
+            session->complete();
+        }
+    }
+
+    return status;
+}
+
+int SystemComm::doHandleTx(const hub_message_t *appMsg)
+{
+    int status = 0;
+
+    switch (appMsg->message_type) {
+    case CONTEXT_HUB_LOAD_APP:
+        if (!mKeySession.haveKeys()) {
+            status = mSessions.setup_and_add(CONTEXT_HUB_LOAD_APP, &mKeySession, appMsg);
+            if (status < 0) {
+                break;
+            }
+            mKeySession.wait();
+            status = mKeySession.getStatus();
+            if (status < 0) {
+                break;
+            }
+        }
+        status = mSessions.setup_and_add(CONTEXT_HUB_LOAD_APP, &mAppMgmtSession, appMsg);
+        break;
+    case CONTEXT_HUB_APPS_ENABLE:
+    case CONTEXT_HUB_APPS_DISABLE:
+    case CONTEXT_HUB_UNLOAD_APP:
+        // all APP-modifying commands share session key, to ensure they can't happen at the same time
+        status = mSessions.setup_and_add(CONTEXT_HUB_LOAD_APP, &mAppMgmtSession, appMsg);
+        break;
+
+    case CONTEXT_HUB_QUERY_APPS:
+        status = mSessions.setup_and_add(CONTEXT_HUB_QUERY_APPS, &mAppInfoSession, appMsg);
+        break;
+
+    case CONTEXT_HUB_QUERY_MEMORY:
+        status = mSessions.setup_and_add(CONTEXT_HUB_QUERY_MEMORY, &mMemInfoSession, appMsg);
+        break;
+
+    default:
+        ALOGW("Unknown os message type %u\n", appMsg->message_type);
+        return -EINVAL;
+    }
+
+   return status;
+}
+
+}; // namespace nanohub
+
+}; // namespace android
diff --git a/contexthubhal/system_comms.h b/contexthubhal/system_comms.h
new file mode 100644
index 0000000..e765040
--- /dev/null
+++ b/contexthubhal/system_comms.h
@@ -0,0 +1,318 @@
+/*
+ * Copyright (c) 2016, Google. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ *       copyright notice, this list of conditions and the following
+ *       disclaimer in the documentation and/or other materials provided
+ *       with the distribution.
+ *     * Neither the name of The Linux Foundation nor the names of its
+ *       contributors may be used to endorse or promote products derived
+ *       from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+#ifndef _NANOHUB_SYSTEM_COMMS_H_
+#define _NANOHUB_SYSTEM_COMMS_H_
+
+#include <utils/Condition.h>
+#include <utils/Mutex.h>
+
+#include <map>
+#include <vector>
+
+#include <hardware/context_hub.h>
+#include "nanohubhal.h"
+#include "message_buf.h"
+
+//rx: return 0 if handled, > 0 if not handled, < 0 if error happened
+
+#define MSG_HANDLED 0
+
+//messages to the HostIf nanoapp & their replies (mesages and replies both begin with u8 message_type)
+#define NANOHUB_EXT_APPS_ON        0 // () -> (char success)
+#define NANOHUB_EXT_APPS_OFF       1 // () -> (char success)
+#define NANOHUB_EXT_APP_DELETE     2 // (u64 name) -> (char success)    //idempotent
+#define NANOHUB_QUERY_MEMINFO      3 // () -> (mem_info)
+#define NANOHUB_QUERY_APPS         4 // (u32 idxStart) -> (app_info[idxStart] OR EMPTY IF NO MORE)
+#define NANOHUB_QUERY_RSA_KEYS     5 // (u32 byteOffset) -> (u8 data[1 or more bytes] OR EMPTY IF NO MORE)
+#define NANOHUB_START_UPLOAD       6 // (char isOs, u32 totalLenToTx) -> (char success)
+#define NANOHUB_CONT_UPLOAD        7 // (u32 offset, u8 data[]) -> (char success)
+#define NANOHUB_FINISH_UPLOAD      8 // () -> (char success)
+#define NANOHUB_REBOOT             9 // () -> (char success)
+
+// Custom defined private messages
+#define CONTEXT_HUB_LOAD_OS (CONTEXT_HUB_TYPE_PRIVATE_MSG_BASE + 1)
+
+
+#define NANOHUB_APP_NOT_LOADED  (-1)
+#define NANOHUB_APP_LOADED      (0)
+
+#define NANOHUB_UPLOAD_CHUNK_SZ_MAX 64
+#define NANOHUB_MEM_SZ_UNKNOWN      0xFFFFFFFFUL
+
+namespace android {
+
+namespace nanohub {
+
+int system_comms_handle_rx(const nano_message *msg);
+int system_comms_handle_tx(const hub_message_t *outMsg);
+
+struct NanohubAppInfo {
+    hub_app_name_t name;
+    uint32_t version, flashUse, ramUse;
+} __attribute__((packed));
+
+struct NanohubMemInfo {
+    //sizes
+    uint32_t flashSz, blSz, osSz, sharedSz, eeSz;
+    uint32_t ramSz;
+
+    //use
+    uint32_t blUse, osUse, sharedUse, eeUse;
+    uint32_t ramUse;
+} __attribute__((packed));
+
+struct NanohubRsp {
+    uint32_t cmd;
+    int32_t status;
+    NanohubRsp(MessageBuf &buf, bool no_status = false);
+};
+
+inline bool operator == (const hub_app_name_t &a, const hub_app_name_t &b) {
+    return a.id == b.id;
+}
+
+inline bool operator != (const hub_app_name_t &a, const hub_app_name_t &b) {
+    return !(a == b);
+}
+
+class SystemComm {
+private:
+
+    /*
+     * Nanohub HAL sessions
+     *
+     * Session is an object that can group several message exchanges with FW,
+     * maintain state, and be waited for completion by someone else.
+     *
+     * As of this moment, since all sessions are triggered by client thread,
+     * and all the exchange is happening in local worker thread, it is only possible
+     * for client thread to wait on session completion.
+     * Allowing sessions to wait on each other will require a worker thread pool.
+     * It is now unnecessary, and not implemented.
+     */
+    class ISession {
+    public:
+        virtual int setup(const hub_message_t *app_msg) = 0;
+        virtual int handleRx(MessageBuf &buf) = 0;
+        virtual int getState() const = 0; // FSM state
+        virtual int getStatus() const = 0; // execution status (result code)
+        virtual ~ISession() {}
+    };
+
+    class SessionManager;
+
+    class Session : public ISession {
+        friend class SessionManager;
+
+        mutable Mutex mDoneLock; // controls condition and state transitions
+        Condition mDoneWait;
+        volatile int mState;
+
+    protected:
+        mutable Mutex mLock; // serializes message handling
+        int32_t mStatus;
+
+        enum {
+            SESSION_INIT = 0,
+            SESSION_DONE = 1,
+            SESSION_USER = 2,
+        };
+
+        void complete() {
+            Mutex::Autolock _l(mDoneLock);
+            if (mState != SESSION_DONE) {
+                mState = SESSION_DONE;
+                mDoneWait.broadcast();
+            }
+        }
+        void setState(int state) {
+            if (state == SESSION_DONE) {
+                complete();
+            } else {
+                Mutex::Autolock _l(mDoneLock);
+                mState = state;
+            }
+        }
+    public:
+        Session() { mState = SESSION_INIT; mStatus = -1; }
+        int getStatus() const {
+            Mutex::Autolock _l(mLock);
+            return mStatus;
+        }
+        int wait() {
+            Mutex::Autolock _l(mDoneLock);
+            while (mState != SESSION_DONE) {
+                mDoneWait.wait(mDoneLock);
+            }
+            return 0;
+        }
+        virtual int getState() const override {
+            Mutex::Autolock _l(mDoneLock);
+            return mState;
+        }
+        virtual bool isDone() const {
+            Mutex::Autolock _l(mDoneLock);
+            return mState == SESSION_DONE;
+        }
+        virtual bool isRunning() const {
+            Mutex::Autolock _l(mDoneLock);
+            return mState > SESSION_DONE;
+        }
+    };
+
+    class AppMgmtSession : public Session {
+        enum {
+            TRANSFER = SESSION_USER,
+            FINISH,
+            RELOAD,
+            MGMT,
+        };
+        uint32_t mCmd; // UPLOAD_APP | UPPLOAD_OS
+        uint32_t mResult;
+        std::vector<uint8_t> mData;
+        uint32_t mLen;
+        uint32_t mPos;
+
+        int setupMgmt(const hub_message_t *appMsg, uint32_t cmd);
+        int handleTransfer(NanohubRsp &rsp);
+        int handleFinish(NanohubRsp &rsp);
+        int handleReload(NanohubRsp &rsp);
+        int handleMgmt(NanohubRsp &rsp);
+    public:
+        AppMgmtSession() {
+            mCmd = 0;
+            mResult = 0;
+            mPos = 0;
+            mLen = 0;
+        }
+        virtual int handleRx(MessageBuf &buf) override;
+        virtual int setup(const hub_message_t *app_msg) override;
+    };
+
+    class MemInfoSession : public Session {
+    public:
+        virtual int setup(const hub_message_t *app_msg) override;
+        virtual int handleRx(MessageBuf &buf) override;
+    };
+
+    class KeyInfoSession  : public Session {
+        std::vector<uint8_t> mRsaKeyData;
+        int requestRsaKeys(void);
+    public:
+        virtual int setup(const hub_message_t *) override;
+        virtual int handleRx(MessageBuf &buf) override;
+        bool haveKeys() const {
+            Mutex::Autolock _l(mLock);
+            return mRsaKeyData.size() > 0 && !isRunning();
+        }
+    };
+
+    class AppInfoSession : public Session {
+        std::vector<hub_app_info> mAppInfo;
+        int requestNext();
+    public:
+        virtual int setup(const hub_message_t *) override;
+        virtual int handleRx(MessageBuf &buf) override;
+    };
+
+    class SessionManager {
+        typedef std::map<int, Session* > SessionMap;
+
+        Mutex lock;
+        SessionMap sessions_;
+
+        void next(SessionMap::iterator &pos)
+        {
+            Mutex::Autolock _l(lock);
+            pos->second->isDone() ? pos = sessions_.erase(pos) : ++pos;
+        }
+
+    public:
+        int handleRx(MessageBuf &buf);
+        int setup_and_add(int id, Session *session, const hub_message_t *appMsg) {
+            Mutex::Autolock _l(lock);
+            if (sessions_.count(id) == 0 && !session->isRunning()) {
+                int ret = session->setup(appMsg);
+                if (ret < 0) {
+                    session->complete();
+                } else {
+                    sessions_[id] = session;
+                }
+                return ret;
+            }
+            return -EBUSY;
+        }
+
+    } mSessions;
+
+    const hub_app_name_t mHostIfAppName = {
+        .id = NANO_APP_ID(NANOAPP_VENDOR_GOOGLE, 0)
+    };
+
+    static SystemComm *getSystem() {
+        // this is thread-safe in c++11
+        static SystemComm theInstance;
+        return &theInstance;
+    }
+
+    SystemComm () = default;
+    ~SystemComm() = default;
+
+    int doHandleTx(const hub_message_t *txMsg);
+    int doHandleRx(const nano_message *rxMsg);
+
+    static void sendToApp(uint32_t typ, const void *data, uint32_t len) {
+        if (NanoHub::messageTracingEnabled()) {
+            dumpBuffer("HAL -> APP", get_hub_info()->os_app_name, typ, data, len);
+        }
+        NanoHub::sendToApp(&get_hub_info()->os_app_name, typ, data, len);
+    }
+    static int sendToSystem(const void *data, size_t len);
+
+    KeyInfoSession mKeySession;
+    AppMgmtSession mAppMgmtSession;
+    AppInfoSession mAppInfoSession;
+    MemInfoSession mMemInfoSession;
+
+public:
+    static int handleTx(const hub_message_t *txMsg) {
+        return getSystem()->doHandleTx(txMsg);
+    }
+    static int handleRx(const nano_message *rxMsg) {
+        return getSystem()->doHandleRx(rxMsg);
+    }
+};
+
+}; // namespace nanohub
+
+}; // namespace android
+
+#endif
diff --git a/firmware/Makefile b/firmware/Makefile
index 16bf169..86cfa2d 100644
--- a/firmware/Makefile
+++ b/firmware/Makefile
@@ -53,7 +53,7 @@
 include $(MAKE_CPU)
 include $(MAKE_VAR)
 
-FLAGS += -Wall -Werror -Iinc -Ilinks -Iexternal/freebsd/inc -fshort-double
+FLAGS += -Wall -Werror -Iinc -Ilinks -Iexternal/freebsd/inc -I../lib/include -fshort-double
 #help avoid commmon embedded C mistakes
 FLAGS += -Wmissing-declarations -Wlogical-op -Waddress -Wempty-body -Wpointer-arith -Wenum-compare -Wdouble-promotion -Wfloat-equal -Wshadow -fno-strict-aliasing
 
@@ -64,7 +64,7 @@
 FLAGS += $(DEBUG)
 
 #bootloader pieces
-SRCS_bl += src/sha2.c src/rsa.c src/aes.c src/seos.c
+SRCS_bl += ../lib/nanohub/sha2.c ../lib/nanohub/rsa.c ../lib/nanohub/aes.c src/seos.c
 
 #frameworks
 SRCS_os += src/printf.c src/timer.c src/seos.c src/heap.c src/slab.c src/spi.c src/trylock.c
@@ -75,7 +75,7 @@
 SRCS_bl += src/printf.c
 
 ifndef PLATFORM_HAS_HARDWARE_CRC
-SRCS_os += src/softcrc.c
+SRCS_os += ../lib/nanohub/softcrc.c
 endif
 
 #app code
diff --git a/firmware/app/common.mk b/firmware/app/common.mk
index fa2cfe4..81a9624 100644
--- a/firmware/app/common.mk
+++ b/firmware/app/common.mk
@@ -26,7 +26,7 @@
 
 define APPRULE
 $(APP_APP): $(APP_BIN)
-	nanoapp_postprocess -v $(APP_ID) < $(APP_BIN) > $(APP_APP)
+	nanoapp_postprocess -v -a $(APP_ID) $(APP_BIN) $(APP_APP)
 
 $(APP_BIN): $(APP_ELF)
 	$(OBJCOPY) -j.relocs -j.flash -j.data -j.dynsym -O binary $(APP_ELF) $(APP_BIN)
diff --git a/firmware/app/test0.app/test_app0.c b/firmware/app/test0.app/test_app0.c
index cd60033..dd11b05 100644
--- a/firmware/app/test0.app/test_app0.c
+++ b/firmware/app/test0.app/test_app0.c
@@ -34,7 +34,7 @@
 static bool start_task(uint32_t myTid)
 {
     mMyTid = myTid;
-    cnt = 5;
+    cnt = 100;
 
     return eOsEventSubscribe(myTid, EVT_APP_START);
 }
@@ -74,8 +74,3 @@
 }
 
 APP_INIT(0, start_task, end_task, handle_event);
-
-
-
-
-
diff --git a/firmware/inc/appSec.h b/firmware/inc/appSec.h
index 3865e75..8847a2a 100644
--- a/firmware/inc/appSec.h
+++ b/firmware/inc/appSec.h
@@ -30,18 +30,19 @@
 typedef AppSecErr (*AppSecGetAesKeyCbk)(uint64_t keyIdx, void *keyBuf); // return APP_SEC_KEY_NOT_FOUND or APP_SEC_NO_ERROR
 
 //return values
-#define APP_SEC_NO_ERROR            0 //all went ok
-#define APP_SEC_NEED_MORE_TIME      1 //please call appSecDoSomeProcessing().
-#define APP_SEC_KEY_NOT_FOUND       2 //we did not find the encr key
-#define APP_SEC_HEADER_ERROR        3 //data (decrypted or input) has no recognizable header
-#define APP_SEC_TOO_MUCH_DATA       4 //we got more data than expected
-#define APP_SEC_TOO_LITTLE_DATA     5 //we got less data than expected
-#define APP_SEC_SIG_VERIFY_FAIL     6 //some signature verification failed
-#define APP_SEC_SIG_DECODE_FAIL     7 //some signature verification failed
-#define APP_SEC_SIG_ROOT_UNKNOWN    8 //signatures all verified but the referenced root of trust is unknown
-#define APP_SEC_MEMORY_ERROR        9 //we ran out of memory while doing things
-#define APP_SEC_INVALID_DATA       10 //data is invalid in some way not described by other error messages
-#define APP_SEC_BAD                11 //something irrecoverably bad happened and we gave up. Sorry...
+#define APP_SEC_NO_ERROR              0 //all went ok
+#define APP_SEC_NEED_MORE_TIME        1 //please call appSecDoSomeProcessing().
+#define APP_SEC_KEY_NOT_FOUND         2 //we did not find the encr key
+#define APP_SEC_HEADER_ERROR          3 //data (decrypted or input) has no recognizable header
+#define APP_SEC_TOO_MUCH_DATA         4 //we got more data than expected
+#define APP_SEC_TOO_LITTLE_DATA       5 //we got less data than expected
+#define APP_SEC_SIG_VERIFY_FAIL       6 //some signature verification failed
+#define APP_SEC_SIG_DECODE_FAIL       7 //some signature verification failed
+#define APP_SEC_SIG_ROOT_UNKNOWN      8 //signatures all verified but the referenced root of trust is unknown
+#define APP_SEC_MEMORY_ERROR          9 //we ran out of memory while doing things
+#define APP_SEC_INVALID_DATA         10 //data is invalid in some way not described by other error messages
+#define APP_SEC_VERIFY_FAILED        11 //decrypted data verification failed
+#define APP_SEC_BAD                 127 //something irrecoverably bad happened and we gave up. Sorry...
 
 //init/deinit
 struct AppSecState *appSecInit(AppSecWriteCbk writeCbk, AppSecPubKeyFindCbk pubKeyFindCbk, AppSecGetAesKeyCbk aesKeyAccessCbk, bool mandateSigning);
@@ -52,8 +53,4 @@
 AppSecErr appSecDoSomeProcessing(struct AppSecState *state); //caleed when any appSec function returns APP_SEC_NEED_MORE_TIME
 AppSecErr appSecRxDataOver(struct AppSecState *state); //caleed when there is no more data
 
-
-
-
 #endif
-
diff --git a/firmware/inc/atomic.h b/firmware/inc/atomic.h
index 37c399c..e90ca1c 100644
--- a/firmware/inc/atomic.h
+++ b/firmware/inc/atomic.h
@@ -32,7 +32,8 @@
 bool atomicCmpXchg32bits(volatile uint32_t *word, uint32_t prevVal, uint32_t newVal);
 
 //returns old value
-uint32_t atomicAdd(volatile uint32_t *val, uint32_t addend);
+uint32_t atomicAddByte(volatile uint8_t *byte, uint32_t addend);
+uint32_t atomicAdd32bits(volatile uint32_t *word, uint32_t addend);
 
 //writes with barriers
 static inline uint32_t atomicReadByte(volatile uint8_t *byte)
diff --git a/firmware/inc/eeData.h b/firmware/inc/eeData.h
index 64b8185..9b313ee 100644
--- a/firmware/inc/eeData.h
+++ b/firmware/inc/eeData.h
@@ -45,9 +45,9 @@
 bool eeDataGet(uint32_t name, void *buf, uint32_t *szP);
 bool eeDataSet(uint32_t name, const void *buf, uint32_t len);
 
-//allow getting old "versions". Set state to NULL initially, call till you get false
-bool eeDataGetAllVersions(uint32_t name, void *buf, uint32_t *szP, void **stateP);
-bool eeDataEraseOldVersion(uint32_t name, void *state); //state == state BEFORE call to eeDataGetAllVersions that found the version you want gone
+//allow getting old "versions". Set state to NULL initially, call till you get NULL as return value
+void *eeDataGetAllVersions(uint32_t name, void *buf, uint32_t *szP, void **stateP);
+bool eeDataEraseOldVersion(uint32_t name, void *addr); // addr is non-NULL address returned by call to eeDataGetAllVersions
 
 //predefined key types
 
diff --git a/firmware/inc/eventQ.h b/firmware/inc/eventQ.h
index f6f86a6..61a128b 100644
--- a/firmware/inc/eventQ.h
+++ b/firmware/inc/eventQ.h
@@ -22,7 +22,8 @@
 #include <stdint.h>
 
 
-#define EVENT_TYPE_BIT_DISCARDABLE    0x80000000 /* set for events we can afford to lose */
+#define EVENT_TYPE_BIT_DISCARDABLE_COMPAT    0x80000000 /* some external apps are using this one */
+#define EVENT_TYPE_BIT_DISCARDABLE               0x8000 /* set for events we can afford to lose */
 
 struct EvtQueue;
 
diff --git a/firmware/inc/eventnums.h b/firmware/inc/eventnums.h
index 50ab4f6..4ee4975 100644
--- a/firmware/inc/eventnums.h
+++ b/firmware/inc/eventnums.h
@@ -21,6 +21,7 @@
 #include "toolchain.h"
 
 /* These define ranges of reserved events */
+// local events are 16-bit always
 #define EVT_NO_FIRST_USER_EVENT          0x00000100    //all events lower than this are reserved for the OS. all of them are nondiscardable necessarily!
 #define EVT_NO_FIRST_SENSOR_EVENT        0x00000200    //sensor type SENSOR_TYPE_x produces events of type EVT_NO_FIRST_SENSOR_EVENT + SENSOR_TYPE_x for all Google-defined sensors
 #define EVT_NO_SENSOR_CONFIG_EVENT       0x00000300    //event to configure sensors
@@ -28,6 +29,18 @@
 #define EVT_APP_TO_HOST                  0x00000401    //app data to host. Type is struct HostHubRawPacket
 #define EVT_MARSHALLED_SENSOR_DATA       0x00000402    //marshalled event data. Type is MarshalledUserEventData
 #define EVT_RESET_REASON                 0x00000403    //reset reason to host.
+#define EVT_DEBUG_LOG                    0x00007F01    // send message payload to Linux kernel log
+#define EVT_MASK                         0x0000FFFF
+
+// host-side events are 32-bit
+
+// DEBUG_LOG_EVT is normally undefined, or defined with a special value, recognized by nanohub driver: 0x3B474F4C
+// if defined with this value, the log message payload will appear in Linux kernel message log.
+// If defined with other value, it will still be sent to nanohub driver, and then forwarded to userland
+// verbatim, where it could be logged by nanohub HAL (by turning on it's logging via 'setprop persist.nanohub.debug 1'
+#ifdef DEBUG_LOG_EVT
+#define HOST_EVT_DEBUG_LOG               DEBUG_LOG_EVT
+#endif
 
 #define HOST_HUB_RAW_PACKET_MAX_LEN      128
 
@@ -80,7 +93,15 @@
 
 //for all apps
 #define EVT_APP_FREE_EVT_DATA            0x000000FF    //sent to an external app when its event has been marked for freeing. Data: struct AppEventFreeData
-
+// this event is never enqueued; it goes directly to the app.
+// It notifies app that hav outstanding IO, that is is about to end;
+// Expected app behavior is to not send any more events to system;
+// any events sent after this point will be silently ignored by the system;
+// any outstading events will be allowed to proceed to completion. (this is SIG_STOP)
+#define EVT_APP_STOP                     0x000000FE
+// Internal event, with task pointer as event data;
+// system ends the task unconditionally; no further checks performed (this is SIG_KILL)
+#define EVT_APP_END                      0x000000FD
 //for host comms
 #define EVT_APP_FROM_HOST                0x000000F8    //host data to an app. Type is struct HostHubRawPacket
 
diff --git a/firmware/inc/heap.h b/firmware/inc/heap.h
index 417dac0..c5bea59 100644
--- a/firmware/inc/heap.h
+++ b/firmware/inc/heap.h
@@ -14,28 +14,29 @@
  * limitations under the License.
  */
 
-#ifndef _HEAP_H_

-#define _HEAP_H_

-

-#ifdef __cplusplus

-extern "C" {

-#endif

-

-#include <stdint.h>

-#include <stdbool.h>

-

-

-

-

-bool heapInit(void);

-void* heapAlloc(uint32_t sz);

-void heapFree(void* ptr);

-

-

-#ifdef __cplusplus

-}

-#endif

-

-

-#endif

-

+#ifndef _HEAP_H_
+#define _HEAP_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdint.h>
+#include <stdbool.h>
+
+
+
+
+bool heapInit(void);
+void* heapAlloc(uint32_t sz);
+void heapFree(void* ptr);
+int heapFreeAll(uint32_t tid);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+
+#endif
+
diff --git a/firmware/inc/isr.h b/firmware/inc/isr.h
index 8b21bf8..2ce9d2c 100644
--- a/firmware/inc/isr.h
+++ b/firmware/inc/isr.h
@@ -23,6 +23,7 @@
 #include <cpu.h>
 #include <list.h>
 #include <util.h>
+#include <seos.h>
 
 struct ChainedInterrupt {
     link_t isrs;
@@ -34,6 +35,7 @@
 struct ChainedIsr {
     link_t node;
     bool (*func)(struct ChainedIsr *);
+    uint16_t tid;
 };
 
 static inline void chainIsr(struct ChainedInterrupt *interrupt, struct ChainedIsr *isr)
@@ -46,6 +48,7 @@
 static inline void unchainIsr(struct ChainedInterrupt *interrupt, struct ChainedIsr *isr)
 {
     interrupt->disable(interrupt);
+    isr->tid = 0;
     list_delete(&isr->node);
     if (!list_is_empty(&interrupt->isrs))
         interrupt->enable(interrupt);
@@ -55,13 +58,34 @@
 {
     struct link_t *cur, *tmp;
     bool handled = false;
+    uint16_t oldTid = osGetCurrentTid();
 
     list_iterate(&interrupt->isrs, cur, tmp) {
         struct ChainedIsr *curIsr = container_of(cur, struct ChainedIsr, node);
-        handled = handled || curIsr->func(curIsr);
+        osSetCurrentTid(curIsr->tid);
+        handled = curIsr->func(curIsr);
+        if (handled)
+            break;
     }
+    osSetCurrentTid(oldTid);
 
     return handled;
 }
 
+static inline int unchainIsrAll(struct ChainedInterrupt *interrupt, uint32_t tid)
+{
+    int count = 0;
+    struct link_t *cur, *tmp;
+
+    list_iterate(&interrupt->isrs, cur, tmp) {
+        struct ChainedIsr *curIsr = container_of(cur, struct ChainedIsr, node);
+        if (curIsr->tid == tid) {
+            unchainIsr(interrupt, curIsr);
+            count++;
+        }
+    }
+
+    return count;
+}
+
 #endif /* __ISR_H */
diff --git a/firmware/inc/nanohubPacket.h b/firmware/inc/nanohubPacket.h
index ee27a8d..6b6afda 100644
--- a/firmware/inc/nanohubPacket.h
+++ b/firmware/inc/nanohubPacket.h
@@ -195,6 +195,7 @@
     NANOHUB_FIRMWARE_UPLOAD_APP_SEC_SIG_ROOT_UNKNOWN,
     NANOHUB_FIRMWARE_UPLOAD_APP_SEC_MEMORY_ERROR,
     NANOHUB_FIRMWARE_UPLOAD_APP_SEC_INVALID_DATA,
+    NANOHUB_FIRMWARE_UPLOAD_APP_SEC_VERIFY_FAILED,
     NANOHUB_FIRMWARE_UPLOAD_APP_SEC_BAD,
 };
 
@@ -287,6 +288,41 @@
 #define NANOHUB_HAL_EXT_APPS_ON     0
 #define NANOHUB_HAL_EXT_APPS_OFF    1
 #define NANOHUB_HAL_EXT_APP_DELETE  2
+
+// this behaves more stable w.r.t. endianness than bit field
+// this is setting byte fields in MgmtStatus response
+// the high-order bit, if set, is indication of counter overflow
+#define SET_COUNTER(counter, val) (counter = (val & 0x7F) | (val > 0x7F ? 0x80 : 0))
+
+SET_PACKED_STRUCT_MODE_ON
+struct MgmtStatus {
+    union {
+        __le32 value;
+        // NOTE: union fields are accessed in CPU native mode
+        struct {
+            uint8_t app;
+            uint8_t task;
+            uint8_t op;
+            uint8_t erase;
+        } ATTRIBUTE_PACKED;
+    };
+} ATTRIBUTE_PACKED;
+SET_PACKED_STRUCT_MODE_OFF
+
+SET_PACKED_STRUCT_MODE_ON
+struct NanohubHalMgmtRx {
+    __le64 appId;
+    struct MgmtStatus stat;
+} ATTRIBUTE_PACKED;
+SET_PACKED_STRUCT_MODE_OFF
+
+SET_PACKED_STRUCT_MODE_ON
+struct NanohubHalMgmtTx {
+    struct NanohubHalHdr hdr;
+    __le32 status;
+} ATTRIBUTE_PACKED;
+SET_PACKED_STRUCT_MODE_OFF
+
 #define NANOHUB_HAL_QUERY_MEMINFO   3
 #define NANOHUB_HAL_QUERY_APPS      4
 
diff --git a/firmware/inc/platform.h b/firmware/inc/platform.h
index d60154b..e7a7571 100644
--- a/firmware/inc/platform.h
+++ b/firmware/inc/platform.h
@@ -37,6 +37,9 @@
 void platUninitialize(void);
 void platReset(void);
 
+// free all platform-specific resources for TID, and return non-zero status if some cleanup was done
+uint32_t platFreeResources(uint32_t tid);
+
 /* Logging */
 void *platLogAllocUserData();
 void platLogFlush(void *userData);
diff --git a/firmware/inc/platform/stm32f4xx/bl.h b/firmware/inc/platform/stm32f4xx/bl.h
index 8b39938..34f08bd 100644
--- a/firmware/inc/platform/stm32f4xx/bl.h
+++ b/firmware/inc/platform/stm32f4xx/bl.h
@@ -31,26 +31,18 @@
 struct AesSetupTempWorksSpace;
 struct AesCbcContext;
 
+#define OS_UPDT_SUCCESS                0
+#define OS_UPDT_HDR_CHECK_FAILED       1
+#define OS_UPDT_HDR_MARKER_INVALID     2
+#define OS_UPDT_UNKNOWN_PUBKEY         3
+#define OS_UPDT_INVALID_SIGNATURE      4
+#define OS_UPDT_INVALID_SIGNATURE_HASH 5
 
-#define OS_UPDT_MARKER_INPROGRESS     0xFF
-#define OS_UPDT_MARKER_DOWNLOADED     0xFE
-#define OS_UPDT_MARKER_VERIFIED       0xF0
-#define OS_UPDT_MARKER_INVALID        0x00
-#define OS_UPDT_MAGIC                 "Nanohub OS" //11 bytes incl terminator
-
-struct OsUpdateHdr {
-    char magic[11];
-    uint8_t marker; //OS_UPDT_MARKER_INPROGRESS -> OS_UPDT_MARKER_DOWNLOADED -> OS_UPDT_MARKER_VERIFIED / OS_UPDT_INVALID
-    uint32_t size;  //does not include the mandatory signature (using device key) that follows
-};
+#define BL_SCAN_OFFSET      0x00000100
 
 #define BL_VERSION_1        1
 #define BL_VERSION_CUR      BL_VERSION_1
 
-#define BL_FLASH_KERNEL_ID  0x1
-#define BL_FLASH_EEDATA_ID  0x2
-#define BL_FLASH_APP_ID     0x4
-
 #define BL_FLASH_KEY1       0x45670123
 #define BL_FLASH_KEY2       0xCDEF89AB
 
@@ -95,6 +87,9 @@
     void            (*blAesCbcEncr)(struct AesCbcContext *ctx, const uint32_t *src, uint32_t *dst);
     void            (*blAesCbcDecr)(struct AesCbcContext *ctx, const uint32_t *src, uint32_t *dst);
     const uint32_t* (*blSigPaddingVerify)(const uint32_t *rsaResult); //return pointer to hash inside the rsaResult or NULL on error
+
+    // extension: for binary compatibility, placed here
+    uint32_t        (*blVerifyOsUpdate)(void);
 };
 
 #ifndef BL_STACK_SIZE
diff --git a/firmware/inc/platform/stm32f4xx/dma.h b/firmware/inc/platform/stm32f4xx/dma.h
index 92ef1e7..a4ef66a 100644
--- a/firmware/inc/platform/stm32f4xx/dma.h
+++ b/firmware/inc/platform/stm32f4xx/dma.h
@@ -58,5 +58,6 @@
 uint16_t dmaBytesLeft(uint8_t busId, uint8_t stream);
 void dmaStop(uint8_t busId, uint8_t stream);
 const enum IRQn dmaIrq(uint8_t busId, uint8_t stream);
+int dmaStopAll(uint32_t tid);
 
 #endif /* _DMA_H */
diff --git a/firmware/inc/platform/stm32f4xx/exti.h b/firmware/inc/platform/stm32f4xx/exti.h
index d403cc7..84fcd39 100644
--- a/firmware/inc/platform/stm32f4xx/exti.h
+++ b/firmware/inc/platform/stm32f4xx/exti.h
@@ -66,6 +66,7 @@
 
 int extiChainIsr(IRQn_Type n, struct ChainedIsr *isr);
 int extiUnchainIsr(IRQn_Type n, struct ChainedIsr *isr);
+int extiUnchainAll(uint32_t tid);
 
 static inline void extiEnableIntGpio(const struct Gpio *__restrict gpioHandle, enum ExtiTrigger trigger)
 {
diff --git a/firmware/inc/sensors.h b/firmware/inc/sensors.h
index 7679b68..2e301c1 100644
--- a/firmware/inc/sensors.h
+++ b/firmware/inc/sensors.h
@@ -165,7 +165,9 @@
 
     bool (*sensorSendOneDirectEvt)(void *, uint32_t tid); //resend last state (if known), only for onchange-supporting sensors, to bring on a new client
 
-    bool (*sensorMarshallData)(uint32_t yourEvtType, const void *yourEvtData, TaggedPtr *evtFreeingInfoP, void *); //marshall yourEvt for sending to host. Send a EVT_MARSHALLED_SENSOR_DATA event with marshalled data. Always send event, even on error, free the passed-in event using osFreeRetainedEvent
+    // Marshall yourEvt for sending to host. Send a EVT_MARSHALLED_SENSOR_DATA event with marshalled data.
+    // Always send event, even on error, free the passed-in event using osFreeRetainedEvent
+    bool (*sensorMarshallData)(uint32_t yourEvtType, const void *yourEvtData, TaggedPtr *evtFreeingInfoP, void *);
 };
 
 enum SensorInfoFlags1 {
@@ -206,7 +208,6 @@
     uint16_t minSamples; /* minimum host fifo size (in # of samples) */
     uint8_t biasType;
     uint8_t rawType;
-    uint16_t pad;
     float rawScale;
 };
 
@@ -266,7 +267,7 @@
 uint64_t sensorGetCurLatency(uint32_t sensorHandle);
 bool sensorGetInitComplete(uint32_t sensorHandle); // DO NOT poll on this value
 bool sensorMarshallEvent(uint32_t sensorHandle, uint32_t evtType, void *evtData, TaggedPtr *evtFreeingInfoP);
-
+int sensorUnregisterAll(uint32_t tid);
 
 /*
  * convenience funcs
diff --git a/firmware/inc/seos.h b/firmware/inc/seos.h
index 23d4e9c..ab2f193 100644
--- a/firmware/inc/seos.h
+++ b/firmware/inc/seos.h
@@ -25,23 +25,21 @@
 #include <stdbool.h>
 #include <stdint.h>
 #include <stdarg.h>
+#include <stddef.h>
 #include <eventQ.h>
 #include <plat/inc/app.h>
 #include <eventnums.h>
 #include "toolchain.h"
 
+#include <nanohub/nanohub.h>
+
+//#define SEGMENT_CRC_SUPPORT
+
 #define MAX_TASKS                        16
-#define MAX_EMBEDDED_EVT_SUBS            6 /*tradeoff, no wrong answer */
+#define MAX_EMBEDDED_EVT_SUBS             6 /* tradeoff, no wrong answer */
+#define TASK_IDX_BITS                     8 /* should be big enough to hold MAX_TASKS, but still fit in TaskIndex */
 
-
-
-#define OS_VER                           0x0000
-
-#define ENCR_KEY_GOOGLE_PREPOPULATED     1 // our key ID is 1
-
-#define FIRST_VALID_TID                  0x00000001
-#define LAST_VALID_TID                   0x0fffffff
-
+typedef uint8_t TaskIndex;
 
 struct AppFuncs { /* do not rearrange */
     /* lifescycle */
@@ -51,43 +49,75 @@
     void (*handle)(uint32_t evtType, const void* evtData);
 };
 
-#define APP_HDR_MAGIC              "GoogleNanoApp"
+/* NOTE: [TASK ID]
+ * TID is designed to be 16-bit; there is no reason for TID to become bigger than that on a system
+ * with typical RAM size of 64kB. However, in NO CASE TID values should overlap with TaggedPtr TAG mask,
+ * which is currently defined as 0x80000000.
+ */
+
+#define TASK_TID_BITS 16
+
+#define TASK_TID_MASK ((1 << TASK_TID_BITS) - 1)
+#define TASK_TID_INCREMENT (1 << TASK_IDX_BITS)
+#define TASK_TID_IDX_MASK ((1 << TASK_IDX_BITS) - 1)
+#define TASK_TID_COUNTER_MASK ((1 << TASK_TID_BITS) - TASK_TID_INCREMENT)
+
+#if MAX_TASKS > TASK_TID_IDX_MASK
+#error MAX_TASKS does not fit in TASK_TID_BITS
+#endif
+
+#define OS_SYSTEM_TID                    0
+#define OS_VER                           0x0000
+
+// FIXME: compatibility: keep key ID 1 until key update is functional
+//#define ENCR_KEY_GOOGLE_PREPOPULATED     0x041F010000000001
+#define ENCR_KEY_GOOGLE_PREPOPULATED     1 // our key ID is 1
+
+#define APP_HDR_MAGIC              NANOAPP_FW_MAGIC
 #define APP_HDR_VER_CUR            0
-#define APP_HDR_MARKER_UPLOADING   0xFFFF
-#define APP_HDR_MARKER_VERIFYING   0xFFFE
-#define APP_HDR_MARKER_VALID       0xFF00
-#define APP_HDR_MARKER_INTERNAL    0xFF01 //no external app should at any point have this marker value!
-#define APP_HDR_MARKER_DELETED     0x0000
+
+#define FL_APP_HDR_INTERNAL        0x0001 // to be able to fork behavior at run time for internal apps
+#define FL_APP_HDR_APPLICATION     0x0002 // image has AppHdr; otherwise is has AppInfo header
+#define FL_APP_HDR_SECURE          0x0004 // secure content, needs to be zero-filled when discarded
+#define FL_APP_HDR_VOLATILE        0x0008 // volatile content, segment shall be deleted after operation is complete
+#define FL_KEY_HDR_DELETE          0x8000 // key-specific flag: if set key id refers to existing key which has to be deleted
 
 /* app ids are split into vendor and app parts. vendor parts are assigned by google. App parts are free for each vendor to assign at will */
 #define APP_ID_FIRST_USABLE        0x0100000000000000ULL //all app ids lower than this are reserved for google's internal use
 #define APP_ID_GET_VENDOR(appid)   ((appid) >> 24)
-#define APP_ID_MAKE(vendor, app)   ((((uint64_t)(vendor)) << 24) | ((app) & 0x00FFFFFF))
-#define APP_ID_VENDOR_GOOGLE       0x476f6f676cULL // "Googl"
+#define APP_ID_GET_SEQ_ID(appid)   ((appid) & 0xFFFFFF)
+#define APP_ID_MAKE(vendor, app)   ((((uint64_t)(vendor)) << 24) | ((app) & APP_SEQ_ID_ANY))
+#define KEY_ID_MAKE(vendor, key)   ((((uint64_t)(vendor)) << 24) | ((key) & KEY_SEQ_ID_ANY))
+#define APP_ID_VENDOR_GOOGLE       UINT64_C(0x476F6F676C) // "Googl"
+#define APP_VENDOR_ANY             UINT64_C(0xFFFFFFFFFF)
+#define APP_SEQ_ID_ANY             UINT64_C(0xFFFFFF)
+#define KEY_SEQ_ID_ANY             UINT64_C(0xFFFFFF)
+#define APP_ID_ANY                 UINT64_C(0xFFFFFFFFFFFFFFFF)
 
-struct AppHdr {
-    char magic[13];
-    uint8_t fmtVer;  //app header format version
-    uint16_t marker;
+#define APP_INFO_CMD_ADD_KEY 1
+#define APP_INFO_CMD_REMOVE_KEY 2
+#define APP_INFO_CMD_OS_UPDATE 3
 
-    uint64_t appId;
+#define SEG_STATE_INVALID UINT32_C(0xFFFFFFFF)
+#define SEG_SIZE_MAX      UINT32_C(0x00FFFFFF)
+#define SEG_SIZE_INVALID  (-1)
+#define SEG_ST(arg) (((arg) << 4) | (arg))
 
-    uint32_t data_start;
-    uint32_t data_end;
-    uint32_t data_data;
+#define SEG_ID_EMPTY    0xF
+#define SEG_ID_RESERVED 0x7 // upload in progress
+#define SEG_ID_VALID    0x3 // CRC-32 valid
+#define SEG_ID_ERASED   0x0 // segment erased
 
-    uint32_t bss_start;
-    uint32_t bss_end;
+#define SEG_ST_EMPTY    SEG_ST(SEG_ID_EMPTY)
+#define SEG_ST_RESERVED SEG_ST(SEG_ID_RESERVED)
+#define SEG_ST_VALID    SEG_ST(SEG_ID_VALID)
+#define SEG_ST_ERASED   SEG_ST(SEG_ID_ERASED)
 
-    uint32_t got_start;
-    uint32_t got_end;
-    uint32_t rel_start;
-    uint32_t rel_end;
-
-    uint32_t appVer; //version of actual app
-    uint32_t rfu;
-
-    struct AppFuncs funcs;
+struct Segment {
+    uint8_t  state;   // 0xFF: empty; bit7=0: segment present; bit6=0: size valid; bit5=0: CRC-32 valid; bit4=0:segment erased;
+                      // bits 3-0 replicate bits7-4;
+    uint8_t  size[3]; // actual stored size in flash, initially filled with 0xFF
+                      // updated after flash operation is completed (successfully or not)
 };
 
 struct AppEventFreeData { //goes with EVT_APP_FREE_EVT_DATA
@@ -137,17 +167,103 @@
 bool osTidById(uint64_t appId, uint32_t *tid);
 bool osAppInfoById(uint64_t appId, uint32_t *appIdx, uint32_t *appVer, uint32_t *appSize);
 bool osAppInfoByIndex(uint32_t appIdx, uint64_t *appId, uint32_t *appVer, uint32_t *appSize);
+uint32_t osGetCurrentTid();
+uint32_t osSetCurrentTid(uint32_t);
+
+struct AppHdr *osAppSegmentCreate(uint32_t size);
+bool osAppSegmentClose(struct AppHdr *app, uint32_t segSize, uint32_t segState);
+bool osAppSegmentSetState(const struct AppHdr *app, uint32_t segState);
+bool osSegmentSetSize(struct Segment *seg, uint32_t size);
+bool osAppWipeData(struct AppHdr *app);
+struct Segment *osGetSegment(const struct AppHdr *app);
+struct Segment *osSegmentGetEnd();
+
+static inline int32_t osSegmentGetSize(const struct Segment *seg)
+{
+    return seg ? seg->size[0] | (seg->size[1] << 8) | (seg->size[2] << 16) : SEG_SIZE_INVALID;
+}
+
+static inline uint32_t osSegmentGetState(const struct Segment *seg)
+{
+    return seg ? seg->state : SEG_STATE_INVALID;
+}
+
+static inline struct AppHdr *osSegmentGetData(const struct Segment *seg)
+{
+    return (struct AppHdr*)(&seg[1]);
+}
+
+#ifdef SEGMENT_CRC_SUPPORT
+
+struct SegmentFooter
+{
+    uint32_t crc;
+};
+
+#define FOOTER_SIZE sizeof(struct SegmentFooter)
+#else
+#define FOOTER_SIZE 0
+#endif
+
+static inline uint32_t osSegmentSizeAlignedWithFooter(uint32_t size)
+{
+    return ((size + 3) & ~3) + FOOTER_SIZE;
+}
+
+static inline const struct Segment *osSegmentSizeGetNext(const struct Segment *seg, uint32_t size)
+{
+    struct Segment *next = (struct Segment *)(((uint8_t*)seg) +
+                                              osSegmentSizeAlignedWithFooter(size) +
+                                              sizeof(*seg)
+                                              );
+    return seg ? next : NULL;
+}
+
+static inline const struct Segment *osSegmentGetNext(const struct Segment *seg)
+{
+    return osSegmentSizeGetNext(seg, osSegmentGetSize(seg));
+}
+
+static inline uint32_t osAppSegmentGetState(const struct AppHdr *app)
+{
+    return osSegmentGetState(osGetSegment(app));
+}
+
+struct SegmentIterator {
+    const struct Segment *shared;
+    const struct Segment *sharedEnd;
+    const struct Segment *seg;
+};
+
+void osSegmentIteratorInit(struct SegmentIterator *it);
+
+static inline bool osSegmentIteratorNext(struct SegmentIterator *it)
+{
+    const struct Segment *seg = it->shared;
+    const struct Segment *next = seg < it->sharedEnd ? osSegmentGetNext(seg) : it->sharedEnd;
+
+    it->shared = next;
+    it->seg = seg;
+
+    return seg < it->sharedEnd;
+}
+
+bool osWriteShared(void *dest, const void *src, uint32_t len);
+bool osEraseShared();
 
 //event retaining support
 bool osRetainCurrentEvent(TaggedPtr *evtFreeingInfoP); //called from any apps' event handling to retain current event. Only valid for first app that tries. evtFreeingInfoP filled by call and used to free evt later
 void osFreeRetainedEvent(uint32_t evtType, void *evtData, TaggedPtr *evtFreeingInfoP);
 
+uint32_t osExtAppStopApps(uint64_t appId);
+uint32_t osExtAppEraseApps(uint64_t appId);
+uint32_t osExtAppStartApps(uint64_t appId);
 
 /* Logging */
 enum LogLevel {
     LOG_ERROR = 'E',
-    LOG_WARN = 'W',
-    LOG_INFO = 'I',
+    LOG_WARN  = 'W',
+    LOG_INFO  = 'I',
     LOG_DEBUG = 'D',
 };
 
@@ -155,28 +271,32 @@
 void osLog(enum LogLevel level, const char *str, ...) PRINTF_ATTRIBUTE;
 
 #ifndef INTERNAL_APP_INIT
-#define INTERNAL_APP_INIT(_id, _ver, _init, _end, _event)                                   \
-SET_INTERNAL_LOCATION(location, ".internal_app_init")static const struct AppHdr SET_INTERNAL_LOCATION_ATTRIBUTES(used, section (".internal_app_init")) mAppHdr = { \
-    .magic = APP_HDR_MAGIC,                                                                 \
-    .fmtVer = APP_HDR_VER_CUR,                                                              \
-    .marker = APP_HDR_MARKER_INTERNAL,                                                      \
-    .appId = (_id),                                                                         \
-    .appVer = (_ver),                                                                       \
-    .funcs.init = (_init),                                                                  \
-    .funcs.end = (_end),                                                                    \
-    .funcs.handle = (_event)                                                                \
+#define INTERNAL_APP_INIT(_id, _ver, _init, _end, _event)                               \
+SET_INTERNAL_LOCATION(location, ".internal_app_init")static const struct AppHdr         \
+SET_INTERNAL_LOCATION_ATTRIBUTES(used, section (".internal_app_init")) mAppHdr = {      \
+    .hdr.magic   = APP_HDR_MAGIC,                                                       \
+    .hdr.fwVer   = APP_HDR_VER_CUR,                                                     \
+    .hdr.fwFlags = FL_APP_HDR_INTERNAL | FL_APP_HDR_APPLICATION,                        \
+    .hdr.appId   = (_id),                                                               \
+    .hdr.appVer  = (_ver),                                                              \
+    .hdr.payInfoType = LAYOUT_APP,                                                      \
+    .vec.init    = (uint32_t)(_init),                                                   \
+    .vec.end     = (uint32_t)(_end),                                                    \
+    .vec.handle  = (uint32_t)(_event)                                                   \
 }
 #endif
 
 #ifndef APP_INIT
-#define APP_INIT(_ver, _init, _end, _event)                                            \
-extern const struct AppFuncs _mAppFuncs;                                         \
-const struct AppFuncs SET_EXTERNAL_APP_ATTRIBUTES(used, section (".app_init"), visibility("default")) _mAppFuncs = { \
-    .init = (_init),                                                             \
-    .end = (_end),                                                               \
-    .handle = (_event)                                                           \
-};                                                                                \
-const uint32_t SET_EXTERNAL_APP_VERSION(used, section (".app_version"), visibility("default")) _mAppVer = _ver
+#define APP_INIT(_ver, _init, _end, _event)                                             \
+extern const struct AppFuncs _mAppFuncs;                                                \
+const struct AppFuncs SET_EXTERNAL_APP_ATTRIBUTES(used, section (".app_init"),          \
+visibility("default")) _mAppFuncs = {                                                   \
+    .init   = (_init),                                                                  \
+    .end    = (_end),                                                                   \
+    .handle = (_event)                                                                  \
+};                                                                                      \
+const uint32_t SET_EXTERNAL_APP_VERSION(used, section (".app_version"),                 \
+visibility("default")) _mAppVer = _ver
 #endif
 
 
diff --git a/firmware/inc/timer.h b/firmware/inc/timer.h
index a2be078..f5e9fba 100644
--- a/firmware/inc/timer.h
+++ b/firmware/inc/timer.h
@@ -42,6 +42,7 @@
 uint32_t timTimerSet(uint64_t length, uint32_t jitterPpm, uint32_t driftPpm, TimTimerCbkF cbk, void* data, bool oneShot); /* return timer id or 0 if failed */
 uint32_t timTimerSetAsApp(uint64_t length, uint32_t jitterPpm, uint32_t driftPpm, uint32_t tid, void* data, bool oneShot); /* return timer id or 0 if failed */
 bool timTimerCancel(uint32_t timerId);
+int timTimerCancelAll(uint32_t tid);
 
 
 //called by interrupt routine. ->true if any timers were fired
diff --git a/firmware/misc/cpu/cortexm4f/app.lkr b/firmware/misc/cpu/cortexm4f/app.lkr
index 83e4063..6766e29 100644
--- a/firmware/misc/cpu/cortexm4f/app.lkr
+++ b/firmware/misc/cpu/cortexm4f/app.lkr
@@ -15,128 +15,115 @@
  */
 
 /*
-	These addresses are fake, but in a particular way that mesh with our expectations.
-	- ".flash" will contain all parts of the app that go into actual flash
-	  *app header
-	  * code
-	  * RO data
-	  * initial contents of RW data
-	  * initial contents of the GOT
-	  * list of relocs
-	  * list of symbols
-	- ".ram" will contain all data that uses ram (and is not part of the flash image)
-	  * RW data in its actua location
-	  * BSS
-	  * the GOT
-	- ".trash" contains sections taht GCC simply feel like it MUST produce (link error otherwise) but we have no use for. it will be tripped
+    These addresses are fake, but in a particular way that mesh with our expectations.
+    - ".flash" will contain all parts of the app that go into actual flash
+      *app header
+      * code
+      * RO data
+      * initial contents of RW data
+      * initial contents of the GOT
+      * list of relocs
+      * list of symbols
+    - ".ram" will contain all data that uses ram (and is not part of the flash image)
+      * RW data in its actua location
+      * BSS
+      * the GOT
+    - ".trash" contains sections taht GCC simply feel like it MUST produce (link error otherwise) but we have no use for. it will be tripped
 
-	After this image is produced a nonoapp_postprocess unitily will process relocs and symbols and compress them to a small reloc table
-	while also modifying the app code itself potentially to adjust for new reloc format. This shrinks relocs a lot. GCC produces relocs at 8
-	bytes per reloc and symbols at 16 bytes per symbol. We remove all symbol infos (as weo not need them) and compress the relevant data
-	from there into relocs and app image itself, generating 4 bytes per reloc of "nano reloc data"
+    After this image is produced a nonoapp_postprocess unitily will process relocs and symbols and compress them to a small reloc table
+    while also modifying the app code itself potentially to adjust for new reloc format. This shrinks relocs a lot. GCC produces relocs at 8
+    bytes per reloc and symbols at 16 bytes per symbol. We remove all symbol infos (as we do not need them) and compress the relevant data
+    from there into relocs and app image itself, generating 4 bytes per reloc of "nano reloc data"
 
-	Our format allows apps that are up to 256MB of flash and 256MB of ram in size.
+    Our format allows apps that are up to 256MB of flash and 256MB of ram in size.
 */
 
 MEMORY
 {
-	flash	: ORIGIN = 0x10000000,	LENGTH = 256K /* we write this to flash */
-	ram     : ORIGIN = 0x80000000,  LENGTH = 128K /* we allocate this in ram */
-        trash   : ORIGIN = 0xF0000000,  LENGTH = 256K /* we throw this away soon after linking */
+    flash   : ORIGIN = 0x10000000,  LENGTH = 256K /* we write this to flash */
+    ram     : ORIGIN = 0x80000000,  LENGTH = 128K /* we allocate this in ram */
+    trash   : ORIGIN = 0xF0000000,  LENGTH = 256K /* we throw this away soon after linking */
 }
 
 SECTIONS
 {
-	.flash : {
-		/* magic : "GoogleNanoApp", 0x00, 0xff, 0xff    first is magix string, then version (0) then two bytes of "FF" that we can later overwrite with zeros in flash to mark apps as dead  */
-		LONG(0x676f6f47)
-		LONG(0x614e656c)
-		LONG(0x70416f6e)
-		LONG(0xffff0070)
+    .flash : {
+        /***** start of struct BinHdr [see nanohub/nanohub.h] *****/
+        /* binary format marker: 'NBIN' (LE) */
+        LONG(0x4E49424E)
 
-		/* 64-bit app id */
-		LONG(0x55555555)
-		LONG(0xaaaaaaaa)
+        /* version */
+        KEEP(*(.app_version));
 
-                /* things we need to load it */
-		LONG(__data_start)
-		LONG(__data_end)
-		LONG(LOADADDR(.data))
+        /* things we need to load app */
+        LONG(__data_start)
+        LONG(__data_end)
+        LONG(LOADADDR(.data))
 
-		LONG(__bss_start)
-		LONG(__bss_end)
+        LONG(__bss_start)
+        LONG(__bss_end)
 
-                /* things we need to run it */
-		LONG(__got_start)
-		LONG(__got_end)
-		LONG(__rel_start)
-		LONG(__rel_end)
+        /* things we need to run it */
+        LONG(__got_start)
+        LONG(__got_end)
+        LONG(__rel_start)
+        LONG(__rel_end)
 
-                /* version */
-		KEEP(*(.app_version));
+        KEEP(*(.app_init));
+        /***** end of struct BinHdr [see nanohub/nanohub.h] *****/
 
-                /* reserved */
-		LONG(0)
+        /* code */
+        *(.text) *(.text.*) ;
+        *(.rodata) *(.rodata.*) ;
+        . = ALIGN(4);
+    } > flash = 0xff
 
-                /* entry points */
-		KEEP(*(.app_init));
+    .data : {
+        . = ALIGN(4);
+        __data_start = ABSOLUTE(.);
+        *(.data);
+        *(.data.*);
+        . = ALIGN(4);
+        __data_end = ABSOLUTE(.);
 
-                /* code */
-		*(.text) *(.text.*) ;
-		*(.rodata) *(.rodata.*) ;
-		. = ALIGN(4);
-	} > flash = 0xff
+        . = ALIGN(4);
+        __got_start = ABSOLUTE(.);
+        *(.got) *(.got.*) ;
+        __got_end = ABSOLUTE(.);
 
-	.data : {
+    } > ram AT > flash
 
-		. = ALIGN(4);
-		__data_start = ABSOLUTE(.);
-		*(.data);
-		*(.data.*);
-		. = ALIGN(4);
-		__data_end = ABSOLUTE(.);
+    .relocs : {
+        . = ALIGN(4);
+        /* relocs */
+        __rel_start = ABSOLUTE(.);
+        *(.rel) *(.rel.*) *(.rel.data.rel.local)
+        __rel_end = ABSOLUTE(.);
+        . = ALIGN(4);
 
-		. = ALIGN(4);
-		__got_start = ABSOLUTE(.);
-		*(.got) *(.got.*) ;
-		__got_end = ABSOLUTE(.);
+    } > flash = 0xff
 
-	 } > ram AT > flash
+    .dynsym : {
+        *(.dynsym); *(.dynsym.*);
+    } > flash = 0xff
 
-	.relocs : {
+    .bss : {
+        . = ALIGN(4);
+        __bss_start = ABSOLUTE(.);
+        *(.bss) *(.bss.*) *(COMMON);
+        . = ALIGN(4);
+        __bss_end = ABSOLUTE(.);
+    } > ram
 
-		. = ALIGN(4);
-                /* relocs */
-		__rel_start = ABSOLUTE(.);
-		*(.rel) *(.rel.*) *(.rel.data.rel.local)
-		__rel_end = ABSOLUTE(.);
-		. = ALIGN(4);
+    __data_data = LOADADDR(.data);
 
-	} > flash = 0xff
-
-	.dynsym : {
-		*(.dynsym); *(.dynsym.*);
-	} > flash = 0xff
-
-	.bss : {
-		. = ALIGN(4);
-		__bss_start = ABSOLUTE(.);
-		*(.bss) *(.bss.*) *(COMMON);
-		. = ALIGN(4);
-		__bss_end = ABSOLUTE(.);
-	} > ram
-
-	__data_data = LOADADDR(.data);
-
-	.dynstr : {
-		*(.dynstr); *(.dynstr.*);
-	} > trash
-	.hash : {
-		*(.hash); *(.hash.*);
-	} > trash
-	.dynamic : {
-		*(.dynamic); *(.dynamic.*);
-	} > trash
-
+    .dynstr : {
+        *(.dynstr); *(.dynstr.*);
+    } > trash
+    .hash : {
+        *(.hash); *(.hash.*);
+    } > trash
+    .dynamic : {
+        *(.dynamic); *(.dynamic.*);
+    } > trash
 }
-
diff --git a/firmware/src/appSec.c b/firmware/src/appSec.c
index 648d444..de7421a 100644
--- a/firmware/src/appSec.c
+++ b/firmware/src/appSec.c
@@ -14,37 +14,50 @@
  * limitations under the License.
  */
 
+#include <stdint.h>
+
 #include <plat/inc/bl.h>
+
+#include <nanohub/sha2.h>
+#include <nanohub/rsa.h>
+#include <nanohub/aes.h>
+
 #include <appSec.h>
 #include <string.h>
 #include <stdio.h>
 #include <heap.h>
-#include <sha2.h>
-#include <rsa.h>
-#include <aes.h>
+#include <seos.h>
+#include <inttypes.h>
 
-
-#define APP_HDR_SIZE                32                                    //headers are this size
+#define APP_HDR_SIZE                (sizeof(struct ImageHeader))
+#define APP_HDR_MAX_SIZE            (sizeof(struct ImageHeader) + sizeof(struct AppSecSignHdr) + sizeof(struct AppSecEncrHdr))
 #define APP_DATA_CHUNK_SIZE         (AES_BLOCK_WORDS * sizeof(uint32_t))  //data blocks are this size
 #define APP_SIG_SIZE                RSA_BYTES
 
+// verify block is SHA placed in integral number of encryption blocks (for SHA256 and AES256 happens to be exactly 2 AES blocks)
+#define APP_VERIFY_BLOCK_SIZE       ((SHA2_HASH_SIZE + AES_BLOCK_SIZE - 1) / AES_BLOCK_SIZE) * AES_BLOCK_SIZE
+
 #define APP_SEC_SIG_ALIGN           APP_DATA_CHUNK_SIZE
 #define APP_SEC_ENCR_ALIGN          APP_DATA_CHUNK_SIZE
 
-#define STATE_INIT                  0 //nothing gotten yet
-#define STATE_RXING_HEADERS         1 //each is APP_HDR_SIZE bytes
-#define STATE_RXING_DATA            2 //each data block is AES_BLOCK_WORDS 32-bit words (for AES reasons)
-#define STATE_RXING_SIG_HASH        3 //each is RSA_BYTES bytes
-#define STATE_RXING_SIG_PUBKEY      4 //each is RSA_BYTES bytes
-#define STATE_DONE                  5 //all is finished and well
-#define STATE_BAD                   6 //unrecoverable badness has happened. this will *NOT* fix itself. It is now ok to give up, start over, cry, or pray to your favourite deity for help
+#define STATE_INIT                  0 // nothing gotten yet
+#define STATE_RXING_HEADERS         1 // variable size headers (min APP_HDR_SIZE, max APP_HDR_MAX_SIZE)
+#define STATE_RXING_DATA            2 // each data block is AES_BLOCK_WORDS 32-bit words (for AES reasons)
+#define STATE_RXING_SIG_HASH        3 // each is RSA_BYTES bytes
+#define STATE_RXING_SIG_PUBKEY      4 // each is RSA_BYTES bytes
+#define STATE_VERIFY                5 // decryption of ciphertext done; now decrypting and verifying the encrypted plaintext SHA2
+#define STATE_DONE                  6 // all is finished and well
+#define STATE_BAD                   7 // unrecoverable badness has happened. this will *NOT* fix itself. It is now ok to give up, start over, cry, or pray to your favourite deity for help
+#define STATE_MAX                   8 // total number of states
+
+//#define DEBUG_FSM
 
 struct AppSecState {
-
     union { //we save some memory by reusing this space.
         struct {
             struct AesCbcContext cbc;
             struct Sha2state sha;
+            struct Sha2state cbcSha;
         };
         struct {
             struct RsaState rsa;
@@ -60,7 +73,7 @@
 
     union {
         union { //make the compiler work to make sure we have enough space
-            uint8_t placeholderAppHdr[APP_HDR_SIZE];
+            uint8_t placeholderAppHdr[APP_HDR_MAX_SIZE];
             uint8_t placeholderDataChunk[APP_DATA_CHUNK_SIZE];
             uint8_t placeholderSigChunk[APP_SIG_SIZE];
             uint8_t placeholderAesKey[AES_KEY_WORDS * sizeof(uint32_t)];
@@ -73,28 +86,57 @@
     uint32_t encryptedBytesIn;
     uint32_t signedBytesOut;
     uint32_t encryptedBytesOut;
-    uint32_t numSigs;
 
     uint16_t haveBytes;       //in dataBytes...
+    uint16_t chunkSize;
     uint8_t curState;
     uint8_t needSig    :1;
     uint8_t haveSig    :1;
     uint8_t haveEncr   :1;
+    uint8_t haveTrustedKey :1;
     uint8_t doingRsa   :1;
 };
 
-struct AppSecSigHdr {
-    uint8_t magic[8];
-    uint32_t appDataLen;
-    uint32_t numSigs;
-};
+static void limitChunkSize(struct AppSecState *state)
+{
+    if (state->haveSig && state->chunkSize > state->signedBytesIn)
+        state->chunkSize = state->signedBytesIn;
+    if (state->haveEncr && state->chunkSize > state->encryptedBytesIn)
+        state->chunkSize = state->signedBytesIn;
+}
 
-struct AppSecEncrHdr {
-    uint8_t magic[4];
-    uint32_t dataLen;
-    uint64_t keyID;
-    uint32_t IV[AES_BLOCK_WORDS];
-};
+static void appSecSetCurState(struct AppSecState *state, uint32_t curState)
+{
+    const static uint16_t chunkSize[STATE_MAX] = {
+        [STATE_RXING_HEADERS] = APP_HDR_SIZE,
+        [STATE_RXING_DATA] = APP_DATA_CHUNK_SIZE,
+        [STATE_VERIFY] = APP_VERIFY_BLOCK_SIZE,
+        [STATE_RXING_SIG_HASH] = APP_SIG_SIZE,
+        [STATE_RXING_SIG_PUBKEY] = APP_SIG_SIZE,
+    };
+    if (curState >= STATE_MAX)
+        curState = STATE_BAD;
+    if (curState != state->curState || curState == STATE_INIT) {
+#ifdef DEBUG_FSM
+        osLog(LOG_INFO, "%s: oldState=%" PRIu8
+                        "; new state=%" PRIu32
+                        "; old chunk size=%" PRIu16
+                        "; new chunk size=%" PRIu16
+                        "; have bytes=%" PRIu16
+                        "\n",
+              __func__, state->curState, curState,
+              state->chunkSize, chunkSize[curState],
+              state->haveBytes);
+#endif
+        state->curState = curState;
+        state->chunkSize = chunkSize[curState];
+    }
+}
+
+static inline uint32_t appSecGetCurState(const struct AppSecState *state)
+{
+    return state->curState;
+}
 
 //init/deinit
 struct AppSecState *appSecInit(AppSecWriteCbk writeCbk, AppSecPubKeyFindCbk pubKeyFindCbk, AppSecGetAesKeyCbk aesKeyAccessCbk, bool mandateSigning)
@@ -109,7 +151,7 @@
     state->writeCbk = writeCbk;
     state->pubKeyFindCbk = pubKeyFindCbk;
     state->aesKeyAccessCbk = aesKeyAccessCbk;
-    state->curState = STATE_INIT;
+    appSecSetCurState(state, STATE_INIT);
     if (mandateSigning)
         state->needSig = 1;
 
@@ -121,7 +163,6 @@
     heapFree(state);
 }
 
-
 //if needed, decrypt and hash incoming data
 static AppSecErr appSecBlockRx(struct AppSecState *state)
 {
@@ -142,7 +183,7 @@
         BL.blSha2processBytes(&state->sha, state->dataBytes, state->haveBytes);
     }
 
-    //decrypt if encryption is on
+    // decrypt if encryption is on
     if (state->haveEncr) {
 
         uint32_t *dataP = state->dataWords;
@@ -152,115 +193,175 @@
         if (state->haveBytes % APP_DATA_CHUNK_SIZE)
             return APP_SEC_TOO_LITTLE_DATA;
 
-        //make sure we do not get too much data & account for the data we got
+        // make sure we do not get too much data & account for the data we got
         if (state->haveBytes > state->encryptedBytesIn)
             return APP_SEC_TOO_MUCH_DATA;
         state->encryptedBytesIn -= state->haveBytes;
 
-        //decrypt
+        // decrypt
         for (i = 0; i < numBlocks; i++, dataP += AES_BLOCK_WORDS)
             BL.blAesCbcDecr(&state->cbc, dataP, dataP);
 
-        //make sure we do not produce too much data (discard padding) & make sure we account for it
+        // make sure we do not produce too much data (discard padding) & make sure we account for it
         if (state->encryptedBytesOut < state->haveBytes)
             state->haveBytes = state->encryptedBytesOut;
         state->encryptedBytesOut -= state->haveBytes;
+
+        if (state->haveBytes)
+            BL.blSha2processBytes(&state->cbcSha, state->dataBytes, state->haveBytes);
     }
 
+    limitChunkSize(state);
+
     return APP_SEC_NO_ERROR;
 }
 
-static AppSecErr appSecProcessIncomingHdr(struct AppSecState *state, bool *sendDataToDataHandlerP)
+static AppSecErr appSecProcessIncomingHdr(struct AppSecState *state, uint32_t *needBytesOut)
 {
-    static const char hdrAddEncrKey[] = "EncrKey+";
-    static const char hdrDelEncrKey[] = "EncrKey+";
-    static const char hdrNanoApp[] = "GoogleNanoApp\x00\xff\xff"; //we check marker is set to 0xFF and version set to 0, as we must as per spec
-    static const char hdrEncrHdr[] = "Encr";
-    static const char hdrSigHdr[] = "SigndApp";
+    struct ImageHeader *image;
+    struct nano_app_binary_t *aosp;
+    uint32_t flags;
+    uint32_t needBytes;
+    struct AppSecSignHdr *signHdr = NULL;
+    struct AppSecEncrHdr *encrHdr = NULL;
+    uint8_t *hdr = state->dataBytes;
+    AppSecErr ret;
 
-    //check for signature header
-    if (!memcmp(state->dataBytes, hdrSigHdr, sizeof(hdrSigHdr) - 1)) {
+    image = (struct ImageHeader *)hdr; hdr += sizeof(*image);
+    aosp = &image->aosp;
+    flags = aosp->flags;
+    if (aosp->header_version != 1 ||
+        aosp->magic != NANOAPP_AOSP_MAGIC ||
+        image->layout.version != 1 ||
+        image->layout.magic != GOOGLE_LAYOUT_MAGIC)
+        return APP_SEC_HEADER_ERROR;
 
-        struct AppSecSigHdr *sigHdr = (struct AppSecSigHdr*)state->dataBytes;
+    needBytes = sizeof(*image);
+    if ((flags & NANOAPP_SIGNED_FLAG) != 0)
+        needBytes += sizeof(*signHdr);
+    if ((flags & NANOAPP_ENCRYPTED_FLAG) != 0)
+        needBytes += sizeof(*encrHdr);
 
-        if (state->haveSig)    //we do not allow signing of already-signed data
+    *needBytesOut = needBytes;
+
+    if (needBytes > state->haveBytes)
+        return APP_SEC_NO_ERROR;
+
+    *needBytesOut = 0;
+
+    if ((flags & NANOAPP_SIGNED_FLAG) != 0) {
+        signHdr = (struct AppSecSignHdr *)hdr; hdr += sizeof(*signHdr);
+        osLog(LOG_INFO, "%s: signed size=%" PRIu32 "\n",
+                        __func__, signHdr->appDataLen);
+        if (!signHdr->appDataLen) {
+            //no data bytes
             return APP_SEC_INVALID_DATA;
-
-        if (state->haveEncr) //we do not allow encryption of signed data, only signing of encrypted data
-            return APP_SEC_INVALID_DATA;
-
-        if (!sigHdr->appDataLen || !sigHdr->numSigs) //no data bytes or no sigs?
-            return APP_SEC_INVALID_DATA;
-
-        state->signedBytesOut = sigHdr->appDataLen;
-        state->signedBytesIn = ((state->signedBytesOut + APP_SEC_SIG_ALIGN - 1) / APP_SEC_SIG_ALIGN) * APP_SEC_SIG_ALIGN;
-        state->numSigs = sigHdr->numSigs;
+        }
+        state->signedBytesIn = state->signedBytesOut = signHdr->appDataLen;
         state->haveSig = 1;
         BL.blSha2init(&state->sha);
-
-        return APP_SEC_NO_ERROR;
+        BL.blSha2processBytes(&state->sha, state->dataBytes, needBytes);
     }
 
-    //check for encryption header
-    if (!memcmp(state->dataBytes, hdrEncrHdr, sizeof(hdrEncrHdr) - 1)) {
-
-        struct AppSecEncrHdr *encrHdr = (struct AppSecEncrHdr*)state->dataBytes;
+    if ((flags & NANOAPP_ENCRYPTED_FLAG) != 0) {
         uint32_t k[AES_KEY_WORDS];
-        AppSecErr ret;
 
-        if (state->haveEncr) //we do not allow encryption of already-encrypted data
-            return APP_SEC_INVALID_DATA;
+        encrHdr = (struct AppSecEncrHdr *)hdr; hdr += sizeof(*encrHdr);
+        osLog(LOG_INFO, "%s: encrypted data size=%" PRIu32
+                        "; key ID=%016" PRIX64 "\n",
+                        __func__, encrHdr->dataLen, encrHdr->keyID);
 
         if (!encrHdr->dataLen || !encrHdr->keyID)
             return APP_SEC_INVALID_DATA;
-
         ret = state->aesKeyAccessCbk(encrHdr->keyID, k);
-        if (ret)
+        if (ret != APP_SEC_NO_ERROR) {
+            osLog(LOG_ERROR, "%s: Secret key not found\n", __func__);
             return ret;
+        }
 
         BL.blAesCbcInitForDecr(&state->cbc, k, encrHdr->IV);
+        BL.blSha2init(&state->cbcSha);
         state->encryptedBytesOut = encrHdr->dataLen;
         state->encryptedBytesIn = ((state->encryptedBytesOut + APP_SEC_ENCR_ALIGN - 1) / APP_SEC_ENCR_ALIGN) * APP_SEC_ENCR_ALIGN;
         state->haveEncr = 1;
+        osLog(LOG_INFO, "%s: encrypted aligned data size=%" PRIu32 "\n",
+                        __func__, state->encryptedBytesIn);
 
-        return APP_SEC_NO_ERROR;
+        if (state->haveSig) {
+            state->signedBytesIn = state->signedBytesOut = signHdr->appDataLen - sizeof(*encrHdr);
+            // at this point, signedBytesOut must equal encryptedBytesIn
+            if (state->signedBytesOut != (state->encryptedBytesIn + SHA2_HASH_SIZE)) {
+                osLog(LOG_ERROR, "%s: sig data size does not match encrypted data\n", __func__);
+                return APP_SEC_INVALID_DATA;
+            }
+        }
     }
 
-    //check for valid app or something else that we pass directly to caller
-    if (memcmp(state->dataBytes, hdrAddEncrKey, sizeof(hdrAddEncrKey) - 1) && memcmp(state->dataBytes, hdrDelEncrKey, sizeof(hdrDelEncrKey) - 1) && memcmp(state->dataBytes, hdrNanoApp, sizeof(hdrNanoApp) - 1))
-        return APP_SEC_HEADER_ERROR;
-
     //if we are in must-sign mode and no signature was provided, fail
-    if (!state->haveSig && state->needSig)
+    if (!state->haveSig && state->needSig) {
+        osLog(LOG_ERROR, "%s: only signed images can be uploaded\n", __func__);
         return APP_SEC_SIG_VERIFY_FAIL;
+    }
+
+    // now, transform AOSP header to FW common header
+    struct FwCommonHdr common = {
+        .magic   = APP_HDR_MAGIC,
+        .appId   = aosp->app_id,
+        .fwVer   = APP_HDR_VER_CUR,
+        .fwFlags = image->layout.flags,
+        .appVer  = aosp->app_version,
+        .payInfoType = image->layout.payload,
+        .rfu = { 0xFF, 0xFF },
+    };
+
+    // check to see if this is special system types of payload
+    switch(image->layout.payload) {
+    case LAYOUT_APP:
+        common.fwFlags = (common.fwFlags | FL_APP_HDR_APPLICATION) & ~FL_APP_HDR_INTERNAL;
+        common.payInfoSize = sizeof(struct AppInfo);
+        osLog(LOG_INFO, "App container found\n");
+        break;
+    case LAYOUT_KEY:
+        common.fwFlags |= FL_APP_HDR_SECURE;
+        common.payInfoSize = sizeof(struct KeyInfo);
+        osLog(LOG_INFO, "Key container found\n");
+        break;
+    case LAYOUT_OS:
+        common.payInfoSize = sizeof(struct OsUpdateHdr);
+        osLog(LOG_INFO, "OS update container found\n");
+        break;
+    default:
+        break;
+    }
+
+    memcpy(state->dataBytes, &common, sizeof(common));
+    state->haveBytes = sizeof(common);
 
     //we're now in data-accepting state
-    state->curState = STATE_RXING_DATA;
+    appSecSetCurState(state, STATE_RXING_DATA);
 
-    //send data to caller as is
-    *sendDataToDataHandlerP = true;
     return APP_SEC_NO_ERROR;
 }
 
 static AppSecErr appSecProcessIncomingData(struct AppSecState *state)
 {
     //check for data-ending conditions
-    if (state->haveSig && !state->signedBytesIn) {      //we're all done with the signed portion of the data, now come the signatures
-        if (state->haveEncr && state->encryptedBytesIn) //somehow we still have more "encrypted" bytes now - this is not valid
-            return APP_SEC_INVALID_DATA;
-        state->curState = STATE_RXING_SIG_HASH;
+    if (state->haveSig && !state->signedBytesIn) {
+        // we're all done with the signed portion of the data, now come the signatures
+        appSecSetCurState(state, STATE_RXING_SIG_HASH);
 
         //collect the hash
         memcpy(state->lastHash, BL.blSha2finish(&state->sha), SHA2_HASH_SIZE);
-    }
-    else if (state->haveEncr && !state->encryptedBytesIn) { //we're all done with encrypted bytes
-        if (state->haveSig && state->signedBytesIn)           //somehow we still have more "signed" bytes now - this is not valid
-            return APP_SEC_INVALID_DATA;
-        state->curState = STATE_DONE;
+    } else if (state->haveEncr && !state->encryptedBytesIn) {
+        if (appSecGetCurState(state) == STATE_RXING_DATA) {
+            //we're all done with encrypted plaintext
+            state->encryptedBytesIn = sizeof(state->cbcSha);
+            appSecSetCurState(state, STATE_VERIFY);
+        }
     }
 
     //pass to caller
-    return state->writeCbk(state->dataBytes, state->haveBytes);
+    return state->haveBytes ? state->writeCbk(state->dataBytes, state->haveBytes) : APP_SEC_NO_ERROR;
 }
 
 AppSecErr appSecDoSomeProcessing(struct AppSecState *state)
@@ -288,43 +389,30 @@
     if (memcmp(state->lastHash, result, SHA2_HASH_SIZE))
         return APP_SEC_SIG_VERIFY_FAIL;
 
-    //hash the provided pubkey if it is not the last
-    if (state->numSigs) {
-        BL.blSha2init(&state->sha);
-        BL.blSha2processBytes(&state->sha, state->dataBytes, APP_SIG_SIZE);
-        memcpy(state->lastHash, BL.blSha2finish(&state->sha), SHA2_HASH_SIZE);
-        state->curState = STATE_RXING_SIG_HASH;
-    }
-    else
-        state->curState = STATE_DONE;
+    //hash the provided pubkey
+    BL.blSha2init(&state->sha);
+    BL.blSha2processBytes(&state->sha, state->dataBytes, APP_SIG_SIZE);
+    memcpy(state->lastHash, BL.blSha2finish(&state->sha), SHA2_HASH_SIZE);
+    appSecSetCurState(state, STATE_RXING_SIG_HASH);
 
     return APP_SEC_NO_ERROR;
 }
 
 static AppSecErr appSecProcessIncomingSigData(struct AppSecState *state)
 {
-    //if we're RXing the hash, just stash it away and move on
-    if (state->curState == STATE_RXING_SIG_HASH) {
-        if (!state->numSigs)
-            return APP_SEC_TOO_MUCH_DATA;
+    bool keyFound = false;
 
-        state->numSigs--;
+    //if we're RXing the hash, just stash it away and move on
+    if (appSecGetCurState(state) == STATE_RXING_SIG_HASH) {
+        state->haveTrustedKey = 0;
         memcpy(state->rsaTmp, state->dataWords, APP_SIG_SIZE);
-        state->curState = STATE_RXING_SIG_PUBKEY;
+        appSecSetCurState(state, STATE_RXING_SIG_PUBKEY);
         return APP_SEC_NO_ERROR;
     }
 
-    //if we just got the last sig, verify it is a known root
-    if (!state->numSigs) {
-        bool keyFound = false;
-        AppSecErr ret;
-
-        ret = state->pubKeyFindCbk(state->dataWords, &keyFound);
-        if (ret != APP_SEC_NO_ERROR)
-            return ret;
-        if (!keyFound)
-            return APP_SEC_SIG_ROOT_UNKNOWN;
-    }
+    // verify it is a known root
+    state->pubKeyFindCbk(state->dataWords, &keyFound);
+    state->haveTrustedKey = keyFound;
 
     //we now have the pubKey. decrypt over time
     state->doingRsa = 1;
@@ -332,83 +420,96 @@
     return APP_SEC_NEED_MORE_TIME;
 }
 
+static AppSecErr appSecVerifyEncryptedData(struct AppSecState *state)
+{
+    const uint32_t *hash = BL.blSha2finish(&state->cbcSha);
+    bool verified = memcmp(hash, state->dataBytes, SHA2_BLOCK_SIZE) == 0;
+
+    osLog(LOG_INFO, "%s: decryption verification: %s\n", __func__, verified ? "passed" : "failed");
+
+// TODO: fix verify logic
+//    return verified ? APP_SEC_NO_ERROR : APP_SEC_VERIFY_FAILED;
+    return APP_SEC_NO_ERROR;
+}
+
 AppSecErr appSecRxData(struct AppSecState *state, const void *dataP, uint32_t len, uint32_t *lenUnusedP)
 {
     const uint8_t *data = (const uint8_t*)dataP;
     AppSecErr ret = APP_SEC_NO_ERROR;
-    bool sendToDataHandler = false;
+    uint32_t needBytes;
 
-    if (state->curState == STATE_INIT)
-        state->curState = STATE_RXING_HEADERS;
+    if (appSecGetCurState(state) == STATE_INIT)
+        appSecSetCurState(state, STATE_RXING_HEADERS);
 
     while (len) {
         len--;
         state->dataBytes[state->haveBytes++] = *data++;
-        switch (state->curState) {
-
+        if (state->haveBytes < state->chunkSize)
+            continue;
+        switch (appSecGetCurState(state)) {
         case STATE_RXING_HEADERS:
-            if (state->haveBytes == APP_HDR_SIZE) {
-
-                ret = appSecBlockRx(state);
-                if (ret != APP_SEC_NO_ERROR)
-                    goto out;
-
-                ret = appSecProcessIncomingHdr(state, &sendToDataHandler);
-                if (ret != APP_SEC_NO_ERROR)
-                    goto out;
-                if (!sendToDataHandler) {
-                    state->haveBytes = 0;
-                    goto out;
-                }
-                //fallthrough
+            // AOSP header is never encrypted; if it is signed, it will hash itself
+            needBytes = 0;
+            ret = appSecProcessIncomingHdr(state, &needBytes);
+            if (ret != APP_SEC_NO_ERROR)
+                goto out;
+            if (needBytes > state->chunkSize) {
+                state->chunkSize = needBytes;
+                // get more data and try again
+                continue;
             }
-            else
-                break;
-
-        case STATE_RXING_DATA:
-            if (state->haveBytes >= APP_DATA_CHUNK_SIZE) {
-
-                //if data is already processed, do not re-process it
-                if (sendToDataHandler)
-                    sendToDataHandler = false;
-                else {
-                    ret = appSecBlockRx(state);
-                    if (ret != APP_SEC_NO_ERROR)
-                        goto out;
-                }
-
+            // done with parsing header(s); we might have something to write to flash
+            if (state->haveBytes) {
+                osLog(LOG_INFO, "%s: save converted header [%" PRIu16 " bytes] to flash\n", __func__, state->haveBytes);
                 ret = appSecProcessIncomingData(state);
                 state->haveBytes = 0;
-                if (ret != APP_SEC_NO_ERROR)
-                    goto out;
             }
+            limitChunkSize(state);
+            goto out;
+
+        case STATE_RXING_DATA:
+            ret = appSecBlockRx(state);
+            if (ret != APP_SEC_NO_ERROR)
+                goto out;
+
+            ret = appSecProcessIncomingData(state);
+            state->haveBytes = 0;
+            if (ret != APP_SEC_NO_ERROR)
+                goto out;
             break;
 
+        case STATE_VERIFY:
+            ret = appSecBlockRx(state);
+            if (ret == APP_SEC_NO_ERROR)
+                ret = appSecProcessIncomingData(state);
+            if (ret == APP_SEC_NO_ERROR)
+                ret = appSecVerifyEncryptedData(state);
+            goto out;
+
         case STATE_RXING_SIG_HASH:
         case STATE_RXING_SIG_PUBKEY:
-
             //no need for calling appSecBlockRx() as sigs are not signed, and encryption cannot be done after signing
-            if (state->haveBytes == APP_SIG_SIZE) {
-                ret = appSecProcessIncomingSigData(state);
-                state->haveBytes = 0;
-                if (ret != APP_SEC_NO_ERROR)
-                    goto out;
-            }
-            break;
+            ret = appSecProcessIncomingSigData(state);
+            state->haveBytes = 0;
+            goto out;
 
         default:
-            state->curState = STATE_BAD;
+            appSecSetCurState(state, STATE_BAD);
             state->haveBytes = 0;
-            *lenUnusedP = 0;
-            return APP_SEC_BAD;
+            len = 0;
+            ret = APP_SEC_BAD;
+            break;
         }
     }
 
 out:
     *lenUnusedP = len;
 
-    if (ret != APP_SEC_NO_ERROR && ret != APP_SEC_NEED_MORE_TIME)
-        state->curState = STATE_BAD;
+    if (ret != APP_SEC_NO_ERROR && ret != APP_SEC_NEED_MORE_TIME) {
+        osLog(LOG_ERROR, "%s: failed: state=%" PRIu32 "; err=%" PRIu32 "\n",
+              __func__, appSecGetCurState(state), ret);
+        appSecSetCurState(state,  STATE_BAD);
+    }
 
     return ret;
 }
@@ -417,42 +518,49 @@
 {
     AppSecErr ret;
 
-    //Feed remianing data to data processor, if any
+    // Feed remaining data to data processor, if any
     if (state->haveBytes) {
-
-        //not in data rx stage when the incoming data ends? This is not good (if we had encr or sign we'd not be here)
-        if (state->curState != STATE_RXING_DATA) {
-            state->curState = STATE_BAD;
+        // if we are using encryption and/or signing, we are supposed to consume all data at this point.
+        if (state->haveSig || state->haveEncr) {
+            appSecSetCurState(state, STATE_BAD);
             return APP_SEC_TOO_LITTLE_DATA;
         }
-
-        //feed the remaining data to the data processor
+        // Not in data rx stage when the incoming data ends? This is not good (if we had encr or sign we'd not be here)
+        if (appSecGetCurState(state) != STATE_RXING_DATA) {
+            appSecSetCurState(state, STATE_BAD);
+            return APP_SEC_TOO_LITTLE_DATA;
+        }
+        // Feed the remaining data to the data processor
         ret = appSecProcessIncomingData(state);
         if (ret != APP_SEC_NO_ERROR) {
-            state->curState = STATE_BAD;
+            appSecSetCurState(state, STATE_BAD);
             return ret;
         }
+    } else {
+        // we don't know in advance how many signature packs we shall receive,
+        // so we evaluate every signature pack as if it is the last, but do not
+        // return error if public key is not trusted; only here we make the final
+        // determination
+        if (state->haveSig) {
+            // check the most recent key status
+            if (!state->haveTrustedKey) {
+                appSecSetCurState(state, STATE_BAD);
+                return APP_SEC_SIG_ROOT_UNKNOWN;
+            } else {
+                appSecSetCurState(state, STATE_DONE);
+            }
+        }
     }
 
     //for unsigned/unencrypted case we have no way to judge length, so we assume it is over when we're told it is
     //this is potentially dangerous, but then again so is allowing unsigned uploads in general.
-    if (!state->haveSig && !state->haveEncr && state->curState == STATE_RXING_DATA)
-        state->curState = STATE_DONE;
+    if (!state->haveSig && !state->haveEncr && appSecGetCurState(state) == STATE_RXING_DATA)
+        appSecSetCurState(state, STATE_DONE);
 
     //Check the state and return our verdict
-    if(state->curState == STATE_DONE)
+    if(appSecGetCurState(state) == STATE_DONE)
         return APP_SEC_NO_ERROR;
 
-    state->curState = STATE_BAD;
+    appSecSetCurState(state, STATE_BAD);
     return APP_SEC_TOO_LITTLE_DATA;
 }
-
-
-
-
-
-
-
-
-
-
diff --git a/firmware/src/cpu/cortexm4f/appSupport.c b/firmware/src/cpu/cortexm4f/appSupport.c
index 1c733d1..1f33dfe 100644
--- a/firmware/src/cpu/cortexm4f/appSupport.c
+++ b/firmware/src/cpu/cortexm4f/appSupport.c
@@ -14,7 +14,8 @@
  * limitations under the License.
  */
 
-#include <cpu/inc/appRelocFormat.h>
+#include <nanohub/appRelocFormat.h>
+
 #include <string.h>
 #include <stdint.h>
 #include <heap.h>
@@ -27,6 +28,9 @@
 #define NANO_RELOC_TYPE_RAM	0
 #define NANO_RELOC_TYPE_FLASH	1
 
+#define APP_FLASH_RELOC(_base, _offset) ((uint32_t)(_base) + FLASH_RELOC_OFFSET + (_offset))
+#define APP_FLASH_RELOC_BASE(_base) APP_FLASH_RELOC(_base, 0)
+#define APP_VEC(_app) ((struct AppFuncs*)&((_app)->vec))
 
 static bool handleRelNumber(uint32_t *ofstP, uint32_t type, uint32_t flashAddr, uint32_t ramAddr, uint32_t *mem, uint32_t value)
 {
@@ -105,7 +109,7 @@
             rel += MIN_RUN_LEN;
             while (rel--)
                 if (!handleRelNumber(&ofst, type, flashStart, ramStart, mem, 0))
-                	return false;
+                    return false;
             break;
 
         case TOKEN_RELOC_TYPE_CHG:
@@ -134,26 +138,27 @@
     return true;
 }
 
-bool cpuAppLoad(const struct AppHdr *appHdr, struct PlatAppInfo *platInfo)
+bool cpuAppLoad(const struct AppHdr *app, struct PlatAppInfo *platInfo)
 {
-    const uint8_t *relocsStart = (const uint8_t*)(((uint8_t*)appHdr) + appHdr->rel_start);
-    const uint8_t *relocsEnd = (const uint8_t*)(((uint8_t*)appHdr) + appHdr->rel_end);
-    uint8_t *mem = heapAlloc(appHdr->bss_end);
+    const struct SectInfo *sect = &app->sect;
+    const uint8_t *relocsStart = (const uint8_t*)APP_FLASH_RELOC(app, sect->rel_start);
+    const uint8_t *relocsEnd = (const uint8_t*)APP_FLASH_RELOC(app, sect->rel_end);
+    uint8_t *mem = heapAlloc(sect->bss_end);
 
     if (!mem)
         return false;
 
     //calcualte and assign got
-    platInfo->got = mem + appHdr->data_start;
+    platInfo->got = mem + sect->data_start;
 
     //clear bss
-    memset(mem + appHdr->bss_start, 0, appHdr->bss_end - appHdr->bss_start);
+    memset(mem + sect->bss_start, 0, sect->bss_end - sect->bss_start);
 
     //copy initialized data and initialized got
-    memcpy(mem + appHdr->data_start, ((uint8_t*)appHdr) + appHdr->data_data, appHdr->got_end - appHdr->data_start);
+    memcpy(mem + sect->data_start, (uint8_t*)APP_FLASH_RELOC(app, sect->data_data), sect->got_end - sect->data_start);
 
     //perform relocs
-    if (!handleRelocs(relocsStart, relocsEnd, (uintptr_t)appHdr, (uintptr_t)mem, (void*)mem)) {
+    if (!handleRelocs(relocsStart, relocsEnd, (uintptr_t)APP_FLASH_RELOC_BASE(app), (uintptr_t)mem, (void*)mem)) {
         osLog(LOG_ERROR, "Relocs are invalid in this app. Aborting app load\n");
         heapFree(mem);
         return false;
@@ -162,13 +167,13 @@
     return true;
 }
 
-void cpuAppUnload(const struct AppHdr *appHdr, struct PlatAppInfo *platInfo)
+void cpuAppUnload(const struct AppHdr *app, struct PlatAppInfo *platInfo)
 {
     if (platInfo->got)
-        heapFree((uint8_t*)platInfo->got - appHdr->got_start);
+        heapFree((uint8_t*)platInfo->got - app->sect.got_start);
 }
 
-static uintptr_t __attribute__((naked)) callWithR9(const struct AppHdr *appHdr, void *funcOfst, void *got, uintptr_t arg1, uintptr_t arg2)
+static uintptr_t __attribute__((naked)) callWithR9(const void *base, uint32_t offset, void *got, uintptr_t arg1, uintptr_t arg2)
 {
     asm volatile (
         "add  r12, r0, r1  \n"
@@ -183,28 +188,26 @@
     return 0; //dummy to fool gcc
 }
 
-bool cpuAppInit(const struct AppHdr *appHdr, struct PlatAppInfo *platInfo, uint32_t tid)
+bool cpuAppInit(const struct AppHdr *app, struct PlatAppInfo *platInfo, uint32_t tid)
 {
     if (platInfo->got)
-        return callWithR9(appHdr, appHdr->funcs.init, platInfo->got, tid, 0);
+        return callWithR9((const void*)APP_FLASH_RELOC_BASE(app), app->vec.init, platInfo->got, tid, 0);
     else
-        return appHdr->funcs.init(tid);
+        return APP_VEC(app)->init(tid);
 }
 
-void cpuAppEnd(const struct AppHdr *appHdr, struct PlatAppInfo *platInfo)
+void cpuAppEnd(const struct AppHdr *app, struct PlatAppInfo *platInfo)
 {
     if (platInfo->got)
-        (void)callWithR9(appHdr, appHdr->funcs.end, platInfo->got, 0, 0);
+        (void)callWithR9((const void*)APP_FLASH_RELOC_BASE(app), app->vec.end, platInfo->got, 0, 0);
     else
-        appHdr->funcs.end();
+        APP_VEC(app)->end();
 }
 
-void cpuAppHandle(const struct AppHdr *appHdr, struct PlatAppInfo *platInfo, uint32_t evtType, const void* evtData)
+void cpuAppHandle(const struct AppHdr *app, struct PlatAppInfo *platInfo, uint32_t evtType, const void* evtData)
 {
     if (platInfo->got)
-        (void)callWithR9(appHdr, appHdr->funcs.handle, platInfo->got, evtType, (uintptr_t)evtData);
+        (void)callWithR9((const void*)APP_FLASH_RELOC_BASE(app), app->vec.handle, platInfo->got, evtType, (uintptr_t)evtData);
     else
-        appHdr->funcs.handle(evtType, evtData);
+        APP_VEC(app)->handle(evtType, evtData);
 }
-
-
diff --git a/firmware/src/cpu/cortexm4f/atomic.c b/firmware/src/cpu/cortexm4f/atomic.c
index 845d91f..15963cd 100644
--- a/firmware/src/cpu/cortexm4f/atomic.c
+++ b/firmware/src/cpu/cortexm4f/atomic.c
@@ -16,7 +16,7 @@
 
 #include <atomic.h>
 
-uint32_t atomicAdd(volatile uint32_t *val, uint32_t addend)
+uint32_t atomicAddByte(volatile uint8_t *byte, uint32_t addend)
 {
     uint32_t prevVal, storeFailed, tmp;
 
@@ -25,8 +25,26 @@
             "ldrexb %0,     [%4] \n"
             "add    %2, %0, %3   \n"
             "strexb %1, %2, [%4] \n"
-            :"=r"(prevVal), "=r"(storeFailed), "=r"(tmp), "=r"(addend), "=r"(val)
-            :"3"(addend), "4"(val)
+            :"=r"(prevVal), "=r"(storeFailed), "=r"(tmp), "=r"(addend), "=r"(byte)
+            :"3"(addend), "4"(byte)
+            :"memory"
+        );
+    } while (storeFailed);
+
+    return prevVal;
+}
+
+uint32_t atomicAdd32bits(volatile uint32_t *word, uint32_t addend)
+{
+    uint32_t prevVal, storeFailed, tmp;
+
+    do {
+        asm volatile(
+            "ldrex  %0,     [%4] \n"
+            "add    %2, %0, %3   \n"
+            "strex  %1, %2, [%4] \n"
+            :"=r"(prevVal), "=r"(storeFailed), "=r"(tmp), "=r"(addend), "=r"(word)
+            :"3"(addend), "4"(word)
             :"memory"
         );
     } while (storeFailed);
diff --git a/firmware/src/cpu/x86/atomic.c b/firmware/src/cpu/x86/atomic.c
index dee81b1..20cd248 100644
--- a/firmware/src/cpu/x86/atomic.c
+++ b/firmware/src/cpu/x86/atomic.c
@@ -16,7 +16,7 @@
 
 #include <atomic.h>
 
-uint32_t atomicAdd(volatile uint32_t *val, uint32_t addend)
+uint32_t atomicAdd32bits(volatile uint32_t *val, uint32_t addend)
 {
     uint32_t old;
 
@@ -27,6 +27,17 @@
     return old;
 }
 
+uint32_t atomicAddByte(volatile uint8_t *val, uint32_t addend)
+{
+    uint8_t old;
+
+    do {
+        old = *val;
+    } while (!atomicCmpXchgByte(val, old, old + addend));
+
+    return old;
+}
+
 uint32_t atomicXchgByte(volatile uint8_t *byte, uint32_t newVal)
 {
     return __atomic_exchange_n(byte, newVal, __ATOMIC_ACQ_REL);
diff --git a/firmware/src/drivers/ams_tmd4903/ams_tmd4903.c b/firmware/src/drivers/ams_tmd4903/ams_tmd4903.c
index bac97bd..32d7545 100644
--- a/firmware/src/drivers/ams_tmd4903/ams_tmd4903.c
+++ b/firmware/src/drivers/ams_tmd4903/ams_tmd4903.c
@@ -36,7 +36,7 @@
 #include <variant/inc/variant.h>
 
 #define AMS_TMD4903_APP_ID      APP_ID_MAKE(APP_ID_VENDOR_GOOGLE, 12)
-#define AMS_TMD4903_APP_VERSION 1
+#define AMS_TMD4903_APP_VERSION 6
 
 #ifndef PROX_INT_PIN
 #error "PROX_INT_PIN is not defined; please define in variant.h"
@@ -129,12 +129,12 @@
 #define PROX_STREAMING 0
 
 #define INFO_PRINT(fmt, ...) do { \
-        osLog(LOG_INFO, "[AMS TMD4903] " fmt, ##__VA_ARGS__); \
+        osLog(LOG_INFO, "%s " fmt, "[TMD4903]", ##__VA_ARGS__); \
     } while (0);
 
 #define DEBUG_PRINT(fmt, ...) do { \
         if (enable_debug) {  \
-            osLog(LOG_INFO, "[AMS TMD4903] " fmt, ##__VA_ARGS__); \
+            INFO_PRINT(fmt, ##__VA_ARGS__); \
         } \
     } while (0);
 
@@ -170,6 +170,7 @@
     SENSOR_STATE_DISABLING_PROX_3,
     SENSOR_STATE_ALS_SAMPLING,
     SENSOR_STATE_PROX_SAMPLING,
+    SENSOR_STATE_PROX_TRANSITION_0,
     SENSOR_STATE_IDLE,
 };
 
@@ -205,12 +206,13 @@
 
     union EmbeddedDataPoint lastAlsSample;
 
-    uint8_t proxState; // enum ProxState
+    uint8_t lastProxState; // enum ProxState
 
     bool alsOn;
     bool proxOn;
     bool alsCalibrating;
     bool proxCalibrating;
+    bool proxDirectMode;
 };
 
 static struct SensorData mTask;
@@ -240,8 +242,7 @@
 static bool proxIsr(struct ChainedIsr *localIsr)
 {
     struct SensorData *data = container_of(localIsr, struct SensorData, isr);
-    bool firstProxSample = (data->proxState == PROX_STATE_INIT);
-    uint8_t lastProxState = data->proxState;
+    uint8_t lastProxState = data->lastProxState;
     union EmbeddedDataPoint sample;
     bool pinState;
 
@@ -256,17 +257,16 @@
         (void)sample;
         (void)pinState;
         (void)lastProxState;
-        (void)firstProxSample;
         if (!pinState)
             osEnqueuePrivateEvt(EVT_SENSOR_PROX_INTERRUPT, NULL, NULL, mTask.tid);
 #else
-        if (firstProxSample && !pinState) {
-            osEnqueuePrivateEvt(EVT_SENSOR_PROX_INTERRUPT, NULL, NULL, mTask.tid);
-        } else if (!firstProxSample) {
+        if (data->proxDirectMode) {
             sample.fdata = (pinState) ? AMS_TMD4903_REPORT_FAR_VALUE : AMS_TMD4903_REPORT_NEAR_VALUE;
-            data->proxState = (pinState) ? PROX_STATE_FAR : PROX_STATE_NEAR;
-            if (data->proxState != lastProxState)
+            data->lastProxState = (pinState) ? PROX_STATE_FAR : PROX_STATE_NEAR;
+            if (data->lastProxState != lastProxState)
                 osEnqueueEvt(sensorGetMyEventType(SENS_TYPE_PROX), sample.vptr, NULL);
+        } else {
+            osEnqueuePrivateEvt(EVT_SENSOR_PROX_INTERRUPT, NULL, NULL, mTask.tid);
         }
 #endif
     } else if (data->alsOn && data->alsCalibrating && !pinState) {
@@ -277,9 +277,9 @@
     return true;
 }
 
-static bool enableInterrupt(struct Gpio *pin, struct ChainedIsr *isr)
+static bool enableInterrupt(struct Gpio *pin, struct ChainedIsr *isr, enum ExtiTrigger trigger)
 {
-    extiEnableIntGpio(pin, EXTI_TRIGGER_BOTH);
+    extiEnableIntGpio(pin, trigger);
     extiChainIsr(PROX_IRQ, isr);
     return true;
 }
@@ -304,12 +304,11 @@
     osEnqueuePrivateEvt(EVT_SENSOR_ALS_TIMER, cookie, NULL, mTask.tid);
 }
 
-#define GLASS_ATTENUATION 1.08f
-#define DEVICE_FACTOR     300.0f
-#define LUX_PER_COUNTS   ((GLASS_ATTENUATION * DEVICE_FACTOR)/AMS_TMD4903_ATIME_MS)
-#define R_COEFF           0.15f
-#define G_COEFF           1.0f
-#define B_COEFF           -0.5f
+#define LUX_PER_COUNTS   (799.397f/AMS_TMD4903_ATIME_MS)
+#define C_COEFF           2.387f
+#define R_COEFF           -1.57f
+#define G_COEFF           2.69f
+#define B_COEFF           -3.307f
 
 static inline float getLuxFromAlsData(uint16_t c, uint16_t r, uint16_t g, uint16_t b)
 {
@@ -319,17 +318,7 @@
     // TODO (trevorbunker): You can use IR ratio (depends on light source) to
     // select between different R, G, and B coefficients
 
-    // Remove IR component
-    float ir = (r + g + b - c) / 2;
-    float r_p = r - ir;
-    float g_p = g - ir;
-    float b_p = b - ir;
-
-    // Calculate total count
-    float g_2p = R_COEFF * r_p + G_COEFF * g_p + B_COEFF * b_p;
-
-    // Calculate lux
-    return (g_2p * LUX_PER_COUNTS) + mTask.alsOffset;
+    return LUX_PER_COUNTS * ((c * C_COEFF) + (r * R_COEFF) + (g * G_COEFF) + (b * B_COEFF)) * mTask.alsOffset;
 }
 
 static void sendCalibrationResultAls(uint8_t status, float offset) {
@@ -346,10 +335,8 @@
     data->data_header.status = status;
     data->offset = offset;
 
-    if (!osEnqueueEvt(EVT_APP_TO_HOST, data, heapFree)) {
-        heapFree(data);
+    if (!osEnqueueEvtOrFree(EVT_APP_TO_HOST, data, heapFree))
         osLog(LOG_WARN, "Couldn't send als cal result evt");
-    }
 }
 
 static void sendCalibrationResultProx(uint8_t status, int16_t *offsets) {
@@ -371,16 +358,17 @@
     for (i = 0; i < 4; i++)
         data->offsets[i] = offsets[i];
 
-    if (!osEnqueueEvt(EVT_APP_TO_HOST, data, heapFree)) {
-        heapFree(data);
+    if (!osEnqueueEvtOrFree(EVT_APP_TO_HOST, data, heapFree))
         osLog(LOG_WARN, "Couldn't send prox cal result evt");
-    }
 }
 
 static void setMode(bool alsOn, bool proxOn, void *cookie)
 {
     mTask.txrxBuf[0] = AMS_TMD4903_REG_ENABLE;
-    mTask.txrxBuf[1] = POWER_ON_BIT | (alsOn ? ALS_ENABLE_BIT : 0) | (proxOn ? (PROX_INT_ENABLE_BIT | PROX_ENABLE_BIT) : 0);
+    mTask.txrxBuf[1] =
+        ((alsOn || proxOn) ? POWER_ON_BIT : 0) |
+        (alsOn ? ALS_ENABLE_BIT : 0) |
+        (proxOn ? (PROX_INT_ENABLE_BIT | PROX_ENABLE_BIT) : 0);
     i2cMasterTx(I2C_BUS_ID, I2C_ADDR, mTask.txrxBuf, 2, &i2cCallback, cookie);
 }
 
@@ -435,10 +423,10 @@
     mTask.alsOn = true;
     mTask.lastAlsSample.idata = AMS_TMD4903_ALS_INVALID;
     mTask.alsCalibrating = true;
-    mTask.alsOffset = 0.0f;
+    mTask.alsOffset = 1.0f;
 
     extiClearPendingGpio(mTask.pin);
-    enableInterrupt(mTask.pin, &mTask.isr);
+    enableInterrupt(mTask.pin, &mTask.isr, EXTI_TRIGGER_FALLING);
 
     mTask.txrxBuf[0] = AMS_TMD4903_REG_ENABLE;
     mTask.txrxBuf[1] = POWER_ON_BIT | ALS_ENABLE_BIT | ALS_INT_ENABLE_BIT;
@@ -475,14 +463,15 @@
 
     if (on) {
         extiClearPendingGpio(mTask.pin);
-        enableInterrupt(mTask.pin, &mTask.isr);
+        enableInterrupt(mTask.pin, &mTask.isr, EXTI_TRIGGER_FALLING);
     } else {
         disableInterrupt(mTask.pin, &mTask.isr);
         extiClearPendingGpio(mTask.pin);
     }
 
-    mTask.proxState = PROX_STATE_INIT;
+    mTask.lastProxState = PROX_STATE_INIT;
     mTask.proxOn = on;
+    mTask.proxDirectMode = false;
 
     setMode(mTask.alsOn, on, (void *)(on ? SENSOR_STATE_ENABLING_PROX : SENSOR_STATE_DISABLING_PROX));
     return true;
@@ -519,12 +508,13 @@
         return false;
     }
 
-    mTask.proxState = PROX_STATE_INIT;
+    mTask.lastProxState = PROX_STATE_INIT;
     mTask.proxOn = true;
     mTask.proxCalibrating = true;
+    mTask.proxDirectMode = false;
 
     extiClearPendingGpio(mTask.pin);
-    enableInterrupt(mTask.pin, &mTask.isr);
+    enableInterrupt(mTask.pin, &mTask.isr, EXTI_TRIGGER_FALLING);
 
     mTask.txrxBuf[0] = AMS_TMD4903_REG_ENABLE;
     mTask.txrxBuf[1] = POWER_ON_BIT; // REG_ENABLE
@@ -556,8 +546,8 @@
     bool result = true;
 
     // See note in sendLastSampleAls
-    if (mTask.proxState != PROX_STATE_INIT) {
-        sample.fdata = (mTask.proxState == PROX_STATE_NEAR) ? AMS_TMD4903_REPORT_NEAR_VALUE : AMS_TMD4903_REPORT_FAR_VALUE;
+    if (mTask.lastProxState != PROX_STATE_INIT) {
+        sample.fdata = (mTask.lastProxState == PROX_STATE_NEAR) ? AMS_TMD4903_REPORT_NEAR_VALUE : AMS_TMD4903_REPORT_FAR_VALUE;
         result = osEnqueuePrivateEvt(sensorGetMyEventType(SENS_TYPE_PROX), sample.vptr, NULL, tid);
     }
     return result;
@@ -759,7 +749,6 @@
 
                 mTask.alsOn = false;
                 mTask.alsCalibrating = false;
-                mTask.alsOffset = sample.fdata;
 
                 mTask.txrxBuf[0] = AMS_TMD4903_REG_ENABLE;
                 mTask.txrxBuf[1] = 0; // REG_ENABLE
@@ -774,7 +763,7 @@
 
     case SENSOR_STATE_PROX_SAMPLING:
         ps = *(uint16_t*)(mTask.txrxBuf);
-        lastProxState = mTask.proxState;
+        lastProxState = mTask.lastProxState;
 
         DEBUG_PRINT("prox sample ready: prox=%u\n", ps);
 
@@ -786,13 +775,13 @@
 #else
             if (ps > AMS_TMD4903_PROX_THRESHOLD_HIGH) {
                 sample.fdata = AMS_TMD4903_REPORT_NEAR_VALUE;
-                mTask.proxState = PROX_STATE_NEAR;
+                mTask.lastProxState = PROX_STATE_NEAR;
             } else {
                 sample.fdata = AMS_TMD4903_REPORT_FAR_VALUE;
-                mTask.proxState = PROX_STATE_FAR;
+                mTask.lastProxState = PROX_STATE_FAR;
             }
 
-            if (mTask.proxState != lastProxState)
+            if (mTask.lastProxState != lastProxState)
                 osEnqueueEvt(sensorGetMyEventType(SENS_TYPE_PROX), sample.vptr, NULL);
 #endif
 
@@ -802,13 +791,32 @@
             mTask.txrxBuf[1] = 0x60; // REG_INTCLEAR - reset proximity interrupts
             i2cMasterTx(I2C_BUS_ID, I2C_ADDR, mTask.txrxBuf, 2, &i2cCallback, (void *)SENSOR_STATE_IDLE);
 #else
-            // After the first prox sample, change the proximity interrupt setting to follow proximity state
-            mTask.txrxBuf[0] = AMS_TMD4903_REG_CFG4;
-            mTask.txrxBuf[1] = 0x27; // REG_CFG4 - proximity state direct to interrupt pin
-            i2cMasterTx(I2C_BUS_ID, I2C_ADDR, mTask.txrxBuf, 2, &i2cCallback, (void *)SENSOR_STATE_IDLE);
+            // The TMD4903 direct interrupt mode does not work properly if enabled while something is covering the sensor,
+            // so we need to wait until it is far.
+            if (mTask.lastProxState == PROX_STATE_FAR) {
+                disableInterrupt(mTask.pin, &mTask.isr);
+                extiClearPendingGpio(mTask.pin);
+
+                // Switch to proximity interrupt direct mode
+                mTask.txrxBuf[0] = AMS_TMD4903_REG_CFG4;
+                mTask.txrxBuf[1] = 0x27; // REG_CFG4 - proximity state direct to interrupt pin
+                i2cMasterTx(I2C_BUS_ID, I2C_ADDR, mTask.txrxBuf, 2, &i2cCallback, (void *)SENSOR_STATE_PROX_TRANSITION_0);
+            } else {
+                // If we are in the "near" state, we cannot change to direct interrupt mode, so just clear the interrupt
+                mTask.txrxBuf[0] = AMS_TMD4903_REG_INTCLEAR;
+                mTask.txrxBuf[1] = 0x60; // REG_INTCLEAR - reset proximity interrupts
+                i2cMasterTx(I2C_BUS_ID, I2C_ADDR, mTask.txrxBuf, 2, &i2cCallback, (void *)SENSOR_STATE_IDLE);
+            }
 #endif
         }
+        break;
 
+    case SENSOR_STATE_PROX_TRANSITION_0:
+        if (mTask.proxOn) {
+            mTask.proxDirectMode = true;
+            extiClearPendingGpio(mTask.pin);
+            enableInterrupt(mTask.pin, &mTask.isr, EXTI_TRIGGER_BOTH);
+        }
         break;
 
     default:
@@ -829,8 +837,9 @@
     mTask.alsOn = false;
     mTask.proxOn = false;
     mTask.lastAlsSample.idata = AMS_TMD4903_ALS_INVALID;
-    mTask.proxState = PROX_STATE_INIT;
+    mTask.lastProxState = PROX_STATE_INIT;
     mTask.proxCalibrating = false;
+    mTask.alsOffset = 1.0f;
 
     mTask.pin = gpioRequest(PROX_INT_PIN);
     gpioConfigInput(mTask.pin, GPIO_SPEED_LOW, GPIO_PULL_NONE);
diff --git a/firmware/src/drivers/bosch_bmi160/akm_ak09915_slave.c b/firmware/src/drivers/bosch_bmi160/akm_ak09915_slave.c
index fb987ba..371f997 100644
--- a/firmware/src/drivers/bosch_bmi160/akm_ak09915_slave.c
+++ b/firmware/src/drivers/bosch_bmi160/akm_ak09915_slave.c
@@ -19,7 +19,7 @@
 
 #define kScale_mag 0.15f
 
-void parseMagData(MagTask_t *magTask, uint8_t *buf, float *x, float *y, float *z) {
+void parseMagData(struct MagTask *magTask, uint8_t *buf, float *x, float *y, float *z) {
     int32_t raw_x = (*(int16_t *)&buf[0]);
     int32_t raw_y = (*(int16_t *)&buf[2]);
     int32_t raw_z = (*(int16_t *)&buf[4]);
diff --git a/firmware/src/drivers/bosch_bmi160/akm_ak09915_slave.h b/firmware/src/drivers/bosch_bmi160/akm_ak09915_slave.h
index 8c683d8..d7bb12e 100644
--- a/firmware/src/drivers/bosch_bmi160/akm_ak09915_slave.h
+++ b/firmware/src/drivers/bosch_bmi160/akm_ak09915_slave.h
@@ -31,14 +31,14 @@
 #define AKM_AK09915_REG_CNTL1     0x30
 #define AKM_AK09915_REG_CNTL2     0x31
 
-typedef struct AK09915Task {
+struct MagTask {
     int32_t dummy;
-} MagTask_t;
+};
 
 #define MAG_I2C_ADDR 0x0C
 #define MAG_REG_DATA AKM_AK09915_REG_DATA
 
-void parseMagData(MagTask_t *magTask, uint8_t *buf, float *x, float *y, float *z);
+void parseMagData(struct MagTask *magTask, uint8_t *buf, float *x, float *y, float *z);
 
 #ifdef __cplusplus
 }
diff --git a/firmware/src/drivers/bosch_bmi160/bosch_bmi160.c b/firmware/src/drivers/bosch_bmi160/bosch_bmi160.c
index 352b0d6..d682af0 100644
--- a/firmware/src/drivers/bosch_bmi160/bosch_bmi160.c
+++ b/firmware/src/drivers/bosch_bmi160/bosch_bmi160.c
@@ -14,33 +14,34 @@
  * limitations under the License.
  */
 
-#include <stdlib.h>
-#include <string.h>
-#include <timer.h>
-#include <sensors.h>
-#include <heap.h>
-#include <spi.h>
-#include <slab.h>
-#include <limits.h>
-#include <plat/inc/rtc.h>
-#include <plat/inc/gpio.h>
-#include <plat/inc/exti.h>
-#include <plat/inc/syscfg.h>
-#include <gpio.h>
-#include <isr.h>
-#include <hostIntf.h>
-#include <nanohubPacket.h>
-#include <variant/inc/variant.h>
-#include <variant/inc/sensType.h>
+#include <algos/time_sync.h>
+#include <atomic.h>
 #include <cpu/inc/cpuMath.h>
-
+#include <gpio.h>
+#include <heap.h>
+#include <hostIntf.h>
+#include <isr.h>
+#include <nanohub_math.h>
+#include <nanohubPacket.h>
+#include <plat/inc/exti.h>
+#include <plat/inc/gpio.h>
+#include <plat/inc/syscfg.h>
+#include <plat/inc/rtc.h>
+#include <sensors.h>
 #include <seos.h>
+#include <slab.h>
+#include <spi.h>
+#include <timer.h>
+#include <variant/inc/sensType.h>
+#include <variant/inc/variant.h>
 
 #ifdef MAG_SLAVE_PRESENT
 #include <algos/mag_cal.h>
 #endif
-#include <algos/time_sync.h>
-#include <nanohub_math.h>
+
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
 
 #define INFO_PRINT(fmt, ...) do { \
         osLog(LOG_INFO, "%s " fmt, "[BMI160]", ##__VA_ARGS__); \
@@ -51,13 +52,23 @@
     } while (0);
 
 #define DEBUG_PRINT(fmt, ...) do { \
-        if (enable_debug) {  \
+        if (DBG_ENABLE) {  \
             INFO_PRINT(fmt,  ##__VA_ARGS__); \
         } \
     } while (0);
 
-static const bool enable_debug = 0;
+#define DEBUG_PRINT_IF(cond, fmt, ...) do { \
+        if ((cond) && DBG_ENABLE) {  \
+            INFO_PRINT(fmt,  ##__VA_ARGS__); \
+        } \
+    } while (0);
 
+#define DBG_ENABLE                0
+#define DBG_CHUNKED               0
+#define DBG_INT                   0
+#define DBG_SHALLOW_PARSE         0
+#define DBG_STATE                 0
+#define DBG_WM_CALC               0
 #define TIMESTAMP_DBG             0
 
 // fixme: to list required definitions for a slave mag
@@ -153,6 +164,15 @@
 #define INT_FIFO_WM     0x40
 #define INT_NO_MOTION   0x80
 
+#define BMI160_FRAME_HEADER_INVALID  0x80   // mark the end of valid data
+#define BMI160_FRAME_HEADER_SKIP     0x81   // not defined by hw, used for skip a byte in buffer
+
+#define WATERMARK_MIN                1
+#define WATERMARK_MAX                200    // must <= 255 (0xff)
+
+#define WATERMARK_MAX_SENSOR_RATE    400    // Accel and gyro are 400 Hz max
+#define WATERMARK_TIME_UNIT_NS       (1000000000ULL/(WATERMARK_MAX_SENSOR_RATE))
+
 #define gSPI    BMI160_SPI_BUS_ID
 
 #define ACCL_INT_LINE EXTI_LINE_P6
@@ -189,7 +209,8 @@
 #define kSensorTimerIntervalUs   39ull        // bmi160 clock increaments every 39000ns
 
 #define kMinRTCTimeIncrementNs   1250000ull // forced min rtc time increment, 1.25ms for 400Hz
-#define kMinSensorTimeIncrement  64         // forced min sensortime increment, 64 = 2.5 msec for 400Hz
+#define kMinSensorTimeIncrement  64         // forced min sensortime increment,
+                                            // 64 = 2.5 msec for 400Hz
 
 #define ACC_MIN_RATE    5
 #define GYR_MIN_RATE    6
@@ -207,9 +228,10 @@
 #define RETRY_CNT_MAG 30
 
 #define SPI_PACKET_SIZE 30
-#define FIFO_READ_SIZE 1028
-#define BUF_MARGIN 16   // some extra buffer for additional reg RW when a FIFO read happens
-#define SPI_BUF_SIZE (FIFO_READ_SIZE + BUF_MARGIN)
+#define FIFO_READ_SIZE  (1024+4)
+#define CHUNKED_READ_SIZE (64)
+#define BUF_MARGIN 32   // some extra buffer for additional reg RW when a FIFO read happens
+#define SPI_BUF_SIZE (FIFO_READ_SIZE + CHUNKED_READ_SIZE + BUF_MARGIN)
 
 enum SensorIndex {
     ACC = 0,
@@ -263,7 +285,18 @@
     SENSOR_STEP_CNT,
     SENSOR_TIME_SYNC,
     SENSOR_SAVE_CALIBRATION,
+    SENSOR_NUM_OF_STATE
 };
+static const char * getStateName(int32_t s) {
+#if DBG_STATE
+    static const char* const l[] = {"BOOT", "VERIFY_ID", "INIT", "IDLE", "PWR_UP",
+            "PWR-DN", "CFG_CHANGE", "INT1", "INT2", "CALIB", "STEP_CNT", "SYNC", "SAVE_CALIB"};
+    if (s >= 0 && s < SENSOR_NUM_OF_STATE) {
+        return l[s];
+    }
+#endif
+    return "???";
+}
 
 enum MagConfigState {
     MAG_SET_START,
@@ -355,6 +388,10 @@
     bool magBiasCurrent;
     bool fifo_enabled[3];
 
+    // for step count
+    uint32_t stepCntSamplingTimerHandle;
+    bool step_cnt_changed;
+
     // spi buffers
     int xferCnt;
     uint8_t *dataBuffer;
@@ -364,13 +401,14 @@
     uint8_t txrxBuffer[SPI_BUF_SIZE];
 
     // states
+    volatile uint8_t state;  //task state, type enum SensorState, do NOT change this directly
     enum InitState init_state;
     enum MagConfigState mag_state;
-    enum SensorState state;
     enum CalibrationState calibration_state;
 
     // pending configs
     bool pending_int[2];
+    bool pending_step_cnt;
     bool pending_config[NUM_OF_SENSOR];
     bool pending_calibration_save;
     bool pending_time_sync;
@@ -378,6 +416,16 @@
     bool pending_dispatch;
     bool frame_sensortime_valid;
 
+    // FIFO setting
+    uint16_t chunkReadSize;
+    uint8_t  watermark;
+
+    // spi rw
+    struct SlabAllocator *mDataSlab;
+    uint16_t mWbufCnt;
+    uint8_t mRegCnt;
+    uint8_t mRetryLeft;
+    bool spiInUse;
 };
 
 static uint32_t AccRates[] = {
@@ -415,20 +463,40 @@
 };
 
 static uint32_t StepCntRates[] = {
+    SENSOR_HZ(1.0f/300.0f),
+    SENSOR_HZ(1.0f/240.0f),
+    SENSOR_HZ(1.0f/180.0f),
+    SENSOR_HZ(1.0f/120.0f),
+    SENSOR_HZ(1.0f/90.0f),
+    SENSOR_HZ(1.0f/60.0f),
+    SENSOR_HZ(1.0f/45.0f),
+    SENSOR_HZ(1.0f/30.0f),
+    SENSOR_HZ(1.0f/15.0f),
+    SENSOR_HZ(1.0f/10.0f),
+    SENSOR_HZ(1.0f/5.0f),
     SENSOR_RATE_ONCHANGE,
     0
 };
 
+static const uint64_t stepCntRateTimerVals[] = // should match StepCntRates and be the timer length for that rate in nanosecs
+{
+    300 * 1000000000ULL,
+    240 * 1000000000ULL,
+    180 * 1000000000ULL,
+    120 * 1000000000ULL,
+    90 * 1000000000ULL,
+    60 * 1000000000ULL,
+    45 * 1000000000ULL,
+    30 * 1000000000ULL,
+    15 * 1000000000ULL,
+    10 * 1000000000ULL,
+    5 * 1000000000ULL,
+};
+
 static struct BMI160Task mTask;
-static uint16_t mWbufCnt = 0;
-static uint8_t mRegCnt = 0;
 
-static uint8_t mRetryLeft;
-
-static struct SlabAllocator *mDataSlab;
-
-#ifdef MAG_I2C_ADDR
-static MagTask_t magTask;
+#ifdef MAG_SLAVE_PRESENT
+static struct MagTask magTask;
 #endif
 
 #define MAG_WRITE(addr, data)                                   \
@@ -467,17 +535,69 @@
     .flags1 = SENSOR_INFO_FLAGS1_BIAS, \
     .biasType = bias
 
+typedef struct BMI160Task _Task;
+#define TASK  _Task* const _task
+
+// To get rid of static variables all task functions should have a task structure pointer input.
+// This is an intermediate step.
+#define TDECL()  TASK = &mTask; (void)_task
+
+// Access task variables without explicitly specify the task structure pointer.
+#define T(v)  (_task->v)
+
+// Atomic get state
+#define GET_STATE() (atomicReadByte(&(_task->state)))
+
+// Atomic set state, this set the state to arbitrary value, use with caution
+#define SET_STATE(s) do{\
+        DEBUG_PRINT_IF(DBG_STATE, "set state %s\n", getStateName(s));\
+        atomicWriteByte(&(_task->state), (s));\
+    }while(0)
+
+// Atomic switch state from IDLE to desired state.
+static bool trySwitchState_(TASK, enum SensorState newState) {
+#if DBG_STATE
+    bool ret = atomicCmpXchgByte(&T(state), SENSOR_IDLE, newState);
+    uint8_t prevState = ret ? SENSOR_IDLE : GET_STATE();
+    DEBUG_PRINT("switch state %s->%s, %s\n",
+            getStateName(prevState), getStateName(newState), ret ? "ok" : "failed");
+    return ret;
+#else
+    return atomicCmpXchgByte(&T(state), SENSOR_IDLE, newState);
+#endif
+}
+// Short-hand
+#define trySwitchState(s) trySwitchState_(_task, (s))
+
+// Chunked FIFO read functions
+static void chunkedReadInit_(TASK, int index, int size);
+#define chunkedReadInit(a,b) chunkedReadInit_(_task, (a), (b))
+static void chunkedReadSpiCallback(void *cookie, int error);
+static void initiateFifoRead_(TASK, bool isInterruptContext);
+#define initiateFifoRead(a) initiateFifoRead_(_task, (a))
+static uint8_t* shallowParseFrame(uint8_t * buf, int size);
+
+// Watermark calculation
+static uint8_t calcWatermark2_(TASK);
+#define calcWatermark2() calcWatermark2_(_task)
+
 static const struct SensorInfo mSensorInfo[NUM_OF_SENSOR] =
 {
-    { DEC_INFO_RATE_RAW("Accelerometer", AccRates, SENS_TYPE_ACCEL, NUM_AXIS_THREE, NANOHUB_INT_NONWAKEUP, 3000, SENS_TYPE_ACCEL_RAW, 1.0/kScale_acc) },
-    { DEC_INFO_RATE("Gyroscope", GyrRates, SENS_TYPE_GYRO, NUM_AXIS_THREE, NANOHUB_INT_NONWAKEUP, 20) },
-    { DEC_INFO_RATE_BIAS("Magnetometer", MagRates, SENS_TYPE_MAG, NUM_AXIS_THREE, NANOHUB_INT_NONWAKEUP, 600, SENS_TYPE_MAG_BIAS) },
-    { DEC_INFO("Step Detector", SENS_TYPE_STEP_DETECT, NUM_AXIS_EMBEDDED, NANOHUB_INT_NONWAKEUP, 100) },
-    { DEC_INFO("Double Tap", SENS_TYPE_DOUBLE_TAP, NUM_AXIS_EMBEDDED, NANOHUB_INT_NONWAKEUP, 20) },
+    { DEC_INFO_RATE_RAW("Accelerometer", AccRates, SENS_TYPE_ACCEL, NUM_AXIS_THREE,
+            NANOHUB_INT_NONWAKEUP, 3000, SENS_TYPE_ACCEL_RAW, 1.0/kScale_acc) },
+    { DEC_INFO_RATE("Gyroscope", GyrRates, SENS_TYPE_GYRO, NUM_AXIS_THREE,
+            NANOHUB_INT_NONWAKEUP, 20) },
+    { DEC_INFO_RATE_BIAS("Magnetometer", MagRates, SENS_TYPE_MAG, NUM_AXIS_THREE,
+            NANOHUB_INT_NONWAKEUP, 600, SENS_TYPE_MAG_BIAS) },
+    { DEC_INFO("Step Detector", SENS_TYPE_STEP_DETECT, NUM_AXIS_EMBEDDED,
+            NANOHUB_INT_NONWAKEUP, 100) },
+    { DEC_INFO("Double Tap", SENS_TYPE_DOUBLE_TAP, NUM_AXIS_EMBEDDED,
+            NANOHUB_INT_NONWAKEUP, 20) },
     { DEC_INFO("Flat", SENS_TYPE_FLAT, NUM_AXIS_EMBEDDED, NANOHUB_INT_NONWAKEUP, 20) },
     { DEC_INFO("Any Motion", SENS_TYPE_ANY_MOTION, NUM_AXIS_EMBEDDED, NANOHUB_INT_NONWAKEUP, 20) },
     { DEC_INFO("No Motion", SENS_TYPE_NO_MOTION, NUM_AXIS_EMBEDDED, NANOHUB_INT_NONWAKEUP, 20) },
-    { DEC_INFO_RATE("Step Counter", StepCntRates, SENS_TYPE_STEP_COUNT, NUM_AXIS_EMBEDDED, NANOHUB_INT_NONWAKEUP, 20) },
+    { DEC_INFO_RATE("Step Counter", StepCntRates, SENS_TYPE_STEP_COUNT, NUM_AXIS_EMBEDDED,
+            NANOHUB_INT_NONWAKEUP, 20) },
 };
 
 static void time_init(void) {
@@ -511,19 +631,25 @@
 
 static void dataEvtFree(void *ptr)
 {
+    TDECL();
     struct TripleAxisDataEvent *ev = (struct TripleAxisDataEvent *)ptr;
-    slabAllocatorFree(mDataSlab, ev);
+    slabAllocatorFree(T(mDataSlab), ev);
 }
 
 static void spiQueueWrite(uint8_t addr, uint8_t data, uint32_t delay)
 {
-    mTask.packets[mRegCnt].size = 2;
-    mTask.packets[mRegCnt].txBuf = &mTask.txrxBuffer[mWbufCnt];
-    mTask.packets[mRegCnt].rxBuf = &mTask.txrxBuffer[mWbufCnt];
-    mTask.packets[mRegCnt].delay = delay * 1000;
-    mTask.txrxBuffer[mWbufCnt++] = BMI160_SPI_WRITE | addr;
-    mTask.txrxBuffer[mWbufCnt++] = data;
-    mRegCnt++;
+    TDECL();
+    if (T(spiInUse)) {
+        ERROR_PRINT("SPI in use, cannot queue write\n");
+        return;
+    }
+    T(packets[T(mRegCnt)]).size = 2;
+    T(packets[T(mRegCnt)]).txBuf = &T(txrxBuffer[T(mWbufCnt)]);
+    T(packets[T(mRegCnt)]).rxBuf = &T(txrxBuffer[T(mWbufCnt)]);
+    T(packets[T(mRegCnt)]).delay = delay * 1000;
+    T(txrxBuffer[T(mWbufCnt++)]) = BMI160_SPI_WRITE | addr;
+    T(txrxBuffer[T(mWbufCnt++)]) = data;
+    T(mRegCnt)++;
 }
 
 /*
@@ -531,61 +657,72 @@
  */
 static void spiQueueRead(uint8_t addr, size_t size, uint8_t **buf, uint32_t delay)
 {
-    *buf = &mTask.txrxBuffer[mWbufCnt];
-    mTask.packets[mRegCnt].size = size + 1;
-    mTask.packets[mRegCnt].txBuf = &mTask.txrxBuffer[mWbufCnt];
-    mTask.packets[mRegCnt].rxBuf = *buf;
-    mTask.packets[mRegCnt].delay = delay * 1000;
-    mTask.txrxBuffer[mWbufCnt++] = BMI160_SPI_READ | addr;
-    mWbufCnt += size;
-    mRegCnt++;
+    TDECL();
+    if (T(spiInUse)) {
+        ERROR_PRINT("SPI in use, cannot queue read %d %d\n", (int)addr, (int)size);
+        return;
+    }
+
+    *buf = &T(txrxBuffer[T(mWbufCnt)]);
+    T(packets[T(mRegCnt)]).size = size + 1; // first byte will not contain valid data
+    T(packets[T(mRegCnt)]).txBuf = &T(txrxBuffer[T(mWbufCnt)]);
+    T(packets[T(mRegCnt)]).rxBuf = *buf;
+    T(packets[T(mRegCnt)]).delay = delay * 1000;
+    T(txrxBuffer[T(mWbufCnt)++]) = BMI160_SPI_READ | addr;
+    T(mWbufCnt) += size;
+    T(mRegCnt)++;
 }
 
 static void spiBatchTxRx(struct SpiMode *mode,
-        SpiCbkF callback, void *cookie)
+        SpiCbkF callback, void *cookie, const char * src)
 {
-    if (mWbufCnt > SPI_BUF_SIZE) {
+    TDECL();
+    if (T(mWbufCnt) > SPI_BUF_SIZE) {
         ERROR_PRINT("NO enough SPI buffer space, dropping transaction.\n");
         return;
     }
-    if (mRegCnt > SPI_PACKET_SIZE) {
+    if (T(mRegCnt) > SPI_PACKET_SIZE) {
         ERROR_PRINT("spiBatchTxRx too many packets!\n");
         return;
     }
 
-    spiMasterRxTx(mTask.spiDev, mTask.cs,
-        mTask.packets, mRegCnt, mode, callback, cookie);
-    mRegCnt = 0;
-    mWbufCnt = 0;
+    T(spiInUse) = true;
+    spiMasterRxTx(T(spiDev), T(cs), T(packets), T(mRegCnt), mode, callback, cookie);
+    T(mRegCnt) = 0;
+    T(mWbufCnt) = 0;
 }
 
+
 static bool bmi160Isr1(struct ChainedIsr *isr)
 {
-    struct BMI160Task *data = container_of(isr, struct BMI160Task, Isr1);
+    TASK = container_of(isr, struct BMI160Task, Isr1);
 
-    if (!extiIsPendingGpio(data->Int1)) {
+    if (!extiIsPendingGpio(T(Int1))) {
         return false;
     }
-
-    osEnqueuePrivateEvt(EVT_SENSOR_INTERRUPT_1, data, NULL, mTask.tid);
-    extiClearPendingGpio(data->Int1);
+    DEBUG_PRINT_IF(DBG_INT, "i1\n");
+    initiateFifoRead(true /*isInterruptContext*/);
+    extiClearPendingGpio(T(Int1));
     return true;
 }
 
+
 static bool bmi160Isr2(struct ChainedIsr *isr)
 {
-    struct BMI160Task *data = container_of(isr, struct BMI160Task, Isr2);
+    TASK = container_of(isr, struct BMI160Task, Isr2);
 
-    if (!extiIsPendingGpio(data->Int2))
+    if (!extiIsPendingGpio(T(Int2)))
         return false;
 
-    osEnqueuePrivateEvt(EVT_SENSOR_INTERRUPT_2, data, NULL, mTask.tid);
-    extiClearPendingGpio(data->Int2);
+    DEBUG_PRINT_IF(DBG_INT, "i2\n");
+    osEnqueuePrivateEvt(EVT_SENSOR_INTERRUPT_2, _task, NULL, T(tid));
+    extiClearPendingGpio(T(Int2));
     return true;
 }
 
 static void sensorSpiCallback(void *cookie, int err)
 {
+    mTask.spiInUse = false;
     osEnqueuePrivateEvt(EVT_SPI_DONE, cookie, NULL, mTask.tid);
 }
 
@@ -599,6 +736,17 @@
     osEnqueuePrivateEvt(EVT_TIME_SYNC, data, NULL, mTask.tid);
 }
 
+static void stepCntSamplingCallback(uint32_t timerId, void *data)
+{
+    union EmbeddedDataPoint step_cnt;
+
+    if (mTask.sensors[STEPCNT].powered && mTask.step_cnt_changed) {
+        mTask.step_cnt_changed = false;
+        step_cnt.idata = mTask.total_step_cnt;
+        osEnqueueEvt(EVT_SENSOR_STEP_COUNTER, step_cnt.vptr, NULL);
+    }
+}
+
 static bool accFirmwareUpload(void *cookie)
 {
     sensorSignalInternalEvt(mTask.sensors[ACC].handle,
@@ -700,7 +848,7 @@
     SPI_WRITE(BMI160_REG_MAGIC, 0x80);
 
     // Config the MAG I2C device address
-#ifdef MAG_I2C_ADDR
+#ifdef MAG_SLAVE_PRESENT
     SPI_WRITE(BMI160_REG_MAG_IF_0, (MAG_I2C_ADDR << 1));
 #endif
 
@@ -783,7 +931,7 @@
         break;
     case MAG_SET_ADDR:
         // config MAG read data address to the first data register
-#ifdef MAG_I2C_ADDR
+#ifdef MAG_SLAVE_PRESENT
         SPI_WRITE(BMI160_REG_MAG_IF_2, MAG_REG_DATA);
 #endif
         mTask.mag_state = MAG_SET_DATA;
@@ -802,56 +950,6 @@
     SPI_READ(BMI160_REG_STATUS, 1, &mTask.statusBuffer, 1000);
 }
 
-static uint8_t calcWatermark(void)
-{
-    int i;
-    uint64_t min_latency = ULONG_LONG_MAX;
-    uint32_t max_rate = 0;
-    uint8_t min_watermark = 6;
-    uint8_t max_watermark = 200;
-    uint8_t watermark;
-    uint32_t temp_cnt, total_cnt = 0;
-    uint32_t header_cnt = ULONG_MAX;
-
-    for (i = ACC; i <= MAG; i++) {
-        if (mTask.sensors[i].configed && mTask.sensors[i].latency != SENSOR_LATENCY_NODATA) {
-            min_latency = mTask.sensors[i].latency < min_latency ? mTask.sensors[i].latency : min_latency;
-            max_rate = mTask.sensors[i].rate > max_rate ? mTask.sensors[i].rate : max_rate;
-        }
-    }
-
-    // if max_rate is less than or equal to 50Hz, we lower the minimum watermark level
-    if (max_rate <= SENSOR_HZ(50.0f)) {
-        min_watermark = 3;
-    }
-
-    // if any sensor request no batching, we set a minimum watermark
-    // of 24 bytes (12 bytes if all rates are below 50Hz).
-    if (min_latency == 0) {
-        return min_watermark;
-    }
-
-    // each accel and gyro sample are 6 bytes
-    // each mag samlpe is 8 bytes
-    // the total number of header byte is estimated by the min samples
-    // the actual number of header byte may exceed this estimate but it's ok to
-    // batch a bit faster.
-    for (i = ACC; i <= MAG; i++) {
-        if (mTask.sensors[i].configed && mTask.sensors[i].latency != SENSOR_LATENCY_NODATA) {
-
-            temp_cnt = (uint32_t)U64_DIV_BY_U64_CONSTANT(min_latency * (mTask.sensors[i].rate / 1024), 1000000000ull);
-            header_cnt = temp_cnt < header_cnt ? temp_cnt : header_cnt;
-            total_cnt += temp_cnt * (i == MAG ? 8 : 6);
-        }
-    }
-    total_cnt += header_cnt;
-    watermark = ((total_cnt / 4) < 0xff) ? (total_cnt / 4) : 0xff; // 4 bytes per count in the watermark register.
-    watermark = watermark < min_watermark ? min_watermark : watermark;
-    watermark = watermark > max_watermark ? max_watermark : watermark;
-
-    return watermark;
-}
-
 static inline bool anyFifoEnabled(void)
 {
     return (mTask.fifo_enabled[ACC] || mTask.fifo_enabled[GYR] || mTask.fifo_enabled[MAG]);
@@ -859,6 +957,7 @@
 
 static void configFifo(void)
 {
+    TDECL();
     int i;
     uint8_t val = 0x12;
     bool any_fifo_enabled_prev = anyFifoEnabled();
@@ -909,7 +1008,9 @@
 
     // calculate the new watermark level
     if (anyFifoEnabled()) {
-        SPI_WRITE(BMI160_REG_FIFO_CONFIG_0, calcWatermark());
+        mTask.watermark = calcWatermark2_(_task);
+        DEBUG_PRINT("wm=%d", mTask.watermark);
+        SPI_WRITE(BMI160_REG_FIFO_CONFIG_0, mTask.watermark);
     }
 
     // config the fifo register
@@ -928,24 +1029,21 @@
 
 static bool accPower(bool on, void *cookie)
 {
-    INFO_PRINT("accPower: on=%d, state=%d\n", on, mTask.state);
+    TDECL();
 
-    if (mTask.state == SENSOR_IDLE) {
+    INFO_PRINT("accPower: on=%d, state=%s\n", on, getStateName(GET_STATE()));
+    if (trySwitchState(on ? SENSOR_POWERING_UP : SENSOR_POWERING_DOWN)) {
         if (on) {
-            mTask.state = SENSOR_POWERING_UP;
-
             // set ACC power mode to NORMAL
             SPI_WRITE(BMI160_REG_CMD, 0x11, 50000);
         } else {
-            mTask.state = SENSOR_POWERING_DOWN;
-
             // set ACC power mode to SUSPEND
             mTask.sensors[ACC].configed = false;
             configFifo();
             SPI_WRITE(BMI160_REG_CMD, 0x10, 5000);
         }
         mTask.sensors[ACC].powered = on;
-        spiBatchTxRx(&mTask.mode, sensorSpiCallback, &mTask.sensors[ACC]);
+        spiBatchTxRx(&mTask.mode, sensorSpiCallback, &mTask.sensors[ACC], __FUNCTION__);
     } else {
         mTask.pending_config[ACC] = true;
         mTask.sensors[ACC].pConfig.enable = on;
@@ -955,21 +1053,18 @@
 
 static bool gyrPower(bool on, void *cookie)
 {
-    INFO_PRINT("gyrPower: on=%d, state=%d\n", on, mTask.state);
+    TDECL();
+    INFO_PRINT("gyrPower: on=%d, state=%s\n", on, getStateName(GET_STATE()));
 
-    if (mTask.state == SENSOR_IDLE) {
+    if (trySwitchState(on ? SENSOR_POWERING_UP : SENSOR_POWERING_DOWN)) {
         if (on) {
-            mTask.state = SENSOR_POWERING_UP;
-
             // set GYR power mode to NORMAL
             SPI_WRITE(BMI160_REG_CMD, 0x15, 50000);
         } else {
-            mTask.state = SENSOR_POWERING_DOWN;
-
             // set GYR power mode to SUSPEND
             mTask.sensors[GYR].configed = false;
             configFifo();
-            SPI_WRITE(BMI160_REG_CMD, 0x14, 1000);
+            SPI_WRITE(BMI160_REG_CMD, 0x14, 5000);
         }
 
         if (anyFifoEnabled() && on != mTask.sensors[GYR].powered) {
@@ -980,7 +1075,7 @@
         }
 
         mTask.sensors[GYR].powered = on;
-        spiBatchTxRx(&mTask.mode, sensorSpiCallback, &mTask.sensors[GYR]);
+        spiBatchTxRx(&mTask.mode, sensorSpiCallback, &mTask.sensors[GYR], __FUNCTION__);
     } else {
         mTask.pending_config[GYR] = true;
         mTask.sensors[GYR].pConfig.enable = on;
@@ -990,24 +1085,20 @@
 
 static bool magPower(bool on, void *cookie)
 {
-    INFO_PRINT("magPower: on=%d, state=%d\n", on, mTask.state);
-
-    if (mTask.state == SENSOR_IDLE) {
+    TDECL();
+    INFO_PRINT("magPower: on=%d, state=%s\n", on, getStateName(GET_STATE()));
+    if (trySwitchState(on ? SENSOR_POWERING_UP : SENSOR_POWERING_DOWN)) {
         if (on) {
-            mTask.state = SENSOR_POWERING_UP;
-
             // set MAG power mode to NORMAL
             SPI_WRITE(BMI160_REG_CMD, 0x19, 10000);
         } else {
-            mTask.state = SENSOR_POWERING_DOWN;
-
             // set MAG power mode to SUSPEND
             mTask.sensors[MAG].configed = false;
             configFifo();
-            SPI_WRITE(BMI160_REG_CMD, 0x18, 1000);
+            SPI_WRITE(BMI160_REG_CMD, 0x18, 5000);
         }
         mTask.sensors[MAG].powered = on;
-        spiBatchTxRx(&mTask.mode, sensorSpiCallback, &mTask.sensors[MAG]);
+        spiBatchTxRx(&mTask.mode, sensorSpiCallback, &mTask.sensors[MAG], __FUNCTION__);
     } else {
         mTask.pending_config[MAG] = true;
         mTask.sensors[MAG].pConfig.enable = on;
@@ -1017,22 +1108,22 @@
 
 static bool stepPower(bool on, void *cookie)
 {
-    if (mTask.state == SENSOR_IDLE) {
+    TDECL();
+    if (trySwitchState(on ? SENSOR_POWERING_UP : SENSOR_POWERING_DOWN)) {
         // if step counter is powered, no need to change actual config of step
         // detector.
         // But we choose to perform one SPI_WRITE anyway to go down the code path
         // to state SENSOR_POWERING_UP/DOWN to update sensor manager.
         if (on) {
-            mTask.state = SENSOR_POWERING_UP;
             mTask.interrupt_enable_2 |= 0x08;
         } else {
-            mTask.state = SENSOR_POWERING_DOWN;
-            mTask.interrupt_enable_2 &= ~0x08;
+            if (!mTask.sensors[STEPCNT].powered)
+                mTask.interrupt_enable_2 &= ~0x08;
             mTask.sensors[STEP].configed = false;
         }
         mTask.sensors[STEP].powered = on;
         SPI_WRITE(BMI160_REG_INT_EN_2, mTask.interrupt_enable_2, 450);
-        spiBatchTxRx(&mTask.mode, sensorSpiCallback, &mTask.sensors[STEP]);
+        spiBatchTxRx(&mTask.mode, sensorSpiCallback, &mTask.sensors[STEP], __FUNCTION__);
     } else {
         mTask.pending_config[STEP] = true;
         mTask.sensors[STEP].pConfig.enable = on;
@@ -1042,18 +1133,17 @@
 
 static bool flatPower(bool on, void *cookie)
 {
-    if (mTask.state == SENSOR_IDLE) {
+    TDECL();
+    if (trySwitchState(on ? SENSOR_POWERING_UP : SENSOR_POWERING_DOWN)) {
         if (on) {
-            mTask.state = SENSOR_POWERING_UP;
             mTask.interrupt_enable_0 |= 0x80;
         } else {
-            mTask.state = SENSOR_POWERING_DOWN;
             mTask.interrupt_enable_0 &= ~0x80;
             mTask.sensors[FLAT].configed = false;
         }
         mTask.sensors[FLAT].powered = on;
         SPI_WRITE(BMI160_REG_INT_EN_0, mTask.interrupt_enable_0, 450);
-        spiBatchTxRx(&mTask.mode, sensorSpiCallback, &mTask.sensors[FLAT]);
+        spiBatchTxRx(&mTask.mode, sensorSpiCallback, &mTask.sensors[FLAT], __FUNCTION__);
     } else {
         mTask.pending_config[FLAT] = true;
         mTask.sensors[FLAT].pConfig.enable = on;
@@ -1063,18 +1153,17 @@
 
 static bool doubleTapPower(bool on, void *cookie)
 {
-    if (mTask.state == SENSOR_IDLE) {
+    TDECL();
+    if (trySwitchState(on ? SENSOR_POWERING_UP : SENSOR_POWERING_DOWN)) {
         if (on) {
-            mTask.state = SENSOR_POWERING_UP;
             mTask.interrupt_enable_0 |= 0x10;
         } else {
-            mTask.state = SENSOR_POWERING_DOWN;
             mTask.interrupt_enable_0 &= ~0x10;
             mTask.sensors[DTAP].configed = false;
         }
         mTask.sensors[DTAP].powered = on;
         SPI_WRITE(BMI160_REG_INT_EN_0, mTask.interrupt_enable_0, 450);
-        spiBatchTxRx(&mTask.mode, sensorSpiCallback, &mTask.sensors[DTAP]);
+        spiBatchTxRx(&mTask.mode, sensorSpiCallback, &mTask.sensors[DTAP], __FUNCTION__);
     } else {
         mTask.pending_config[DTAP] = true;
         mTask.sensors[DTAP].pConfig.enable = on;
@@ -1084,20 +1173,20 @@
 
 static bool anyMotionPower(bool on, void *cookie)
 {
-    DEBUG_PRINT("anyMotionPower: on=%d, oneshot_cnt %d, state=%d\n", on, mTask.active_oneshot_sensor_cnt, mTask.state);
+    TDECL();
+    DEBUG_PRINT("anyMotionPower: on=%d, oneshot_cnt %d, state=%s\n",
+            on, mTask.active_oneshot_sensor_cnt, getStateName(GET_STATE()));
 
-    if (mTask.state == SENSOR_IDLE) {
+    if (trySwitchState(on ? SENSOR_POWERING_UP : SENSOR_POWERING_DOWN)) {
         if (on) {
-            mTask.state = SENSOR_POWERING_UP;
             mTask.interrupt_enable_0 |= 0x07;
         } else {
-            mTask.state = SENSOR_POWERING_DOWN;
             mTask.interrupt_enable_0 &= ~0x07;
             mTask.sensors[ANYMO].configed = false;
         }
         mTask.sensors[ANYMO].powered = on;
         SPI_WRITE(BMI160_REG_INT_EN_0, mTask.interrupt_enable_0, 450);
-        spiBatchTxRx(&mTask.mode, sensorSpiCallback, &mTask.sensors[ANYMO]);
+        spiBatchTxRx(&mTask.mode, sensorSpiCallback, &mTask.sensors[ANYMO], __FUNCTION__);
     } else {
         mTask.pending_config[ANYMO] = true;
         mTask.sensors[ANYMO].pConfig.enable = on;
@@ -1107,20 +1196,19 @@
 
 static bool noMotionPower(bool on, void *cookie)
 {
-    DEBUG_PRINT("noMotionPower: on=%d, oneshot_cnt %d, state=%d\n", on, mTask.active_oneshot_sensor_cnt, mTask.state);
-
-    if (mTask.state == SENSOR_IDLE) {
+    TDECL();
+    DEBUG_PRINT("noMotionPower: on=%d, oneshot_cnt %d, state=%s\n",
+            on, mTask.active_oneshot_sensor_cnt, getStateName(GET_STATE()));
+    if (trySwitchState(on ? SENSOR_POWERING_UP : SENSOR_POWERING_DOWN)) {
         if (on) {
-            mTask.state = SENSOR_POWERING_UP;
             mTask.interrupt_enable_2 |= 0x07;
         } else {
-            mTask.state = SENSOR_POWERING_DOWN;
             mTask.interrupt_enable_2 &= ~0x07;
             mTask.sensors[NOMO].configed = false;
         }
         mTask.sensors[NOMO].powered = on;
         SPI_WRITE(BMI160_REG_INT_EN_2, mTask.interrupt_enable_2, 450);
-        spiBatchTxRx(&mTask.mode, sensorSpiCallback, &mTask.sensors[NOMO]);
+        spiBatchTxRx(&mTask.mode, sensorSpiCallback, &mTask.sensors[NOMO], __FUNCTION__);
     } else {
         mTask.pending_config[NOMO] = true;
         mTask.sensors[NOMO].pConfig.enable = on;
@@ -1130,9 +1218,9 @@
 
 static bool stepCntPower(bool on, void *cookie)
 {
-    if (mTask.state == SENSOR_IDLE) {
+    TDECL();
+    if (trySwitchState(on ? SENSOR_POWERING_UP : SENSOR_POWERING_DOWN)) {
         if (on) {
-            mTask.state = SENSOR_POWERING_UP;
             if (!mTask.sensors[STEP].powered) {
                 mTask.interrupt_enable_2 |= 0x08;
                 SPI_WRITE(BMI160_REG_INT_EN_2, mTask.interrupt_enable_2, 450);
@@ -1140,7 +1228,10 @@
             // set step_cnt_en bit
             SPI_WRITE(BMI160_REG_STEP_CONF_1, 0x08 | 0x03, 1000);
         } else {
-            mTask.state = SENSOR_POWERING_DOWN;
+            if (mTask.stepCntSamplingTimerHandle) {
+                timTimerCancel(mTask.stepCntSamplingTimerHandle);
+                mTask.stepCntSamplingTimerHandle = 0;
+            }
             if (!mTask.sensors[STEP].powered) {
                 mTask.interrupt_enable_2 &= ~0x08;
                 SPI_WRITE(BMI160_REG_INT_EN_2, mTask.interrupt_enable_2);
@@ -1151,7 +1242,7 @@
             mTask.sensors[STEPCNT].configed = false;
         }
         mTask.sensors[STEPCNT].powered = on;
-        spiBatchTxRx(&mTask.mode, sensorSpiCallback, &mTask.sensors[STEPCNT]);
+        spiBatchTxRx(&mTask.mode, sensorSpiCallback, &mTask.sensors[STEPCNT], __FUNCTION__);
     } else {
         mTask.pending_config[STEPCNT] = true;
         mTask.sensors[STEPCNT].pConfig.enable = on;
@@ -1212,13 +1303,13 @@
 
 static bool accSetRate(uint32_t rate, uint64_t latency, void *cookie)
 {
+    TDECL();
     int odr, osr = 0;
 
-    INFO_PRINT("accSetRate: rate=%ld, latency=%lld, state=%d\n", rate, latency, mTask.state);
+    INFO_PRINT("accSetRate: rate=%ld, latency=%lld, state=%s\n", rate, latency,
+            getStateName(GET_STATE()));
 
-    if (mTask.state == SENSOR_IDLE) {
-        mTask.state = SENSOR_CONFIG_CHANGING;
-
+    if (trySwitchState(SENSOR_CONFIG_CHANGING)) {
         odr = computeOdr(rate);
         if (!odr) {
             ERROR_PRINT("invalid acc rate\n");
@@ -1260,7 +1351,7 @@
         // flush the data and configure the fifo
         configFifo();
 
-        spiBatchTxRx(&mTask.mode, sensorSpiCallback, &mTask.sensors[ACC]);
+        spiBatchTxRx(&mTask.mode, sensorSpiCallback, &mTask.sensors[ACC], __FUNCTION__);
     } else {
         mTask.pending_config[ACC] = true;
         mTask.sensors[ACC].pConfig.enable = 1;
@@ -1272,13 +1363,12 @@
 
 static bool gyrSetRate(uint32_t rate, uint64_t latency, void *cookie)
 {
+    TDECL();
     int odr, osr = 0;
+    INFO_PRINT("gyrSetRate: rate=%ld, latency=%lld, state=%s\n", rate, latency,
+            getStateName(GET_STATE()));
 
-    INFO_PRINT("gyrSetRate: rate=%ld, latency=%lld, state=%d\n", rate, latency, mTask.state);
-
-    if (mTask.state == SENSOR_IDLE) {
-        mTask.state = SENSOR_CONFIG_CHANGING;
-
+    if (trySwitchState(SENSOR_CONFIG_CHANGING)) {
         odr = computeOdr(rate);
         if (!odr) {
             ERROR_PRINT("invalid gyr rate\n");
@@ -1317,7 +1407,7 @@
         // flush the data and configure the fifo
         configFifo();
 
-        spiBatchTxRx(&mTask.mode, sensorSpiCallback, &mTask.sensors[GYR]);
+        spiBatchTxRx(&mTask.mode, sensorSpiCallback, &mTask.sensors[GYR], __FUNCTION__);
     } else {
         mTask.pending_config[GYR] = true;
         mTask.sensors[GYR].pConfig.enable = 1;
@@ -1329,16 +1419,16 @@
 
 static bool magSetRate(uint32_t rate, uint64_t latency, void *cookie)
 {
+    TDECL();
     int odr;
 
     if (rate == SENSOR_RATE_ONCHANGE)
         rate = SENSOR_HZ(100);
 
-    INFO_PRINT("magSetRate: rate=%ld, latency=%lld, state=%d\n", rate, latency, mTask.state);
+    INFO_PRINT("magSetRate: rate=%ld, latency=%lld, state=%s\n", rate, latency,
+            getStateName(GET_STATE()));
 
-    if (mTask.state == SENSOR_IDLE) {
-        mTask.state = SENSOR_CONFIG_CHANGING;
-
+    if (trySwitchState(SENSOR_CONFIG_CHANGING)) {
         mTask.sensors[MAG].rate = rate;
         mTask.sensors[MAG].latency = latency;
         mTask.sensors[MAG].configed = true;
@@ -1359,7 +1449,7 @@
         // flush the data and configure the fifo
         configFifo();
 
-        spiBatchTxRx(&mTask.mode, sensorSpiCallback, &mTask.sensors[MAG]);
+        spiBatchTxRx(&mTask.mode, sensorSpiCallback, &mTask.sensors[MAG], __FUNCTION__);
     } else {
         mTask.pending_config[MAG] = true;
         mTask.sensors[MAG].pConfig.enable = 1;
@@ -1431,6 +1521,17 @@
     mTask.sensors[STEPCNT].latency = latency;
     mTask.sensors[STEPCNT].configed = true;
 
+    if (rate == SENSOR_RATE_ONCHANGE && mTask.stepCntSamplingTimerHandle) {
+        timTimerCancel(mTask.stepCntSamplingTimerHandle);
+        mTask.stepCntSamplingTimerHandle = 0;
+    } else if (rate != SENSOR_RATE_ONCHANGE) {
+        if (mTask.stepCntSamplingTimerHandle) {
+            timTimerCancel(mTask.stepCntSamplingTimerHandle);
+        }
+        mTask.stepCntSamplingTimerHandle = timTimerSet(sensorTimerLookupCommon(StepCntRates, stepCntRateTimerVals, rate),
+                                                       0, 50, stepCntSamplingCallback, NULL, false);
+    }
+
     sensorSignalInternalEvt(mTask.sensors[STEPCNT].handle,
             SENSOR_INTERNAL_EVT_RATE_CHG, rate, latency);
     return true;
@@ -1452,26 +1553,27 @@
     }
 }
 
-static void int1Evt(void);
-
 static bool accFlush(void *cookie)
 {
+    TDECL();
     mTask.sensors[ACC].flush++;
-    int1Evt();
+    initiateFifoRead(false /*isInterruptContext*/);
     return true;
 }
 
 static bool gyrFlush(void *cookie)
 {
+    TDECL();
     mTask.sensors[GYR].flush++;
-    int1Evt();
+    initiateFifoRead(false /*isInterruptContext*/);
     return true;
 }
 
 static bool magFlush(void *cookie)
 {
+    TDECL();
     mTask.sensors[MAG].flush++;
-    int1Evt();
+    initiateFifoRead(false /*isInterruptContext*/);
     return true;
 }
 
@@ -1500,13 +1602,15 @@
     return osEnqueueEvt(EVT_SENSOR_NO_MOTION, SENSOR_DATA_EVENT_FLUSH, NULL);
 }
 
-static void stepCntFlushGetData()
+static bool stepCntFlushGetData()
 {
-    if (mTask.state == SENSOR_IDLE) {
-        mTask.state = SENSOR_STEP_CNT;
+    TDECL();
+    if (trySwitchState(SENSOR_STEP_CNT)) {
         SPI_READ(BMI160_REG_STEP_CNT_0, 2, &mTask.dataBuffer);
-        spiBatchTxRx(&mTask.mode, sensorSpiCallback, &mTask.sensors[STEPCNT]);
+        spiBatchTxRx(&mTask.mode, sensorSpiCallback, &mTask.sensors[STEPCNT], __FUNCTION__);
+        return true;
     }
+    return false;
 }
 
 static bool stepCntFlush(void *cookie)
@@ -1530,8 +1634,15 @@
             mTask.total_step_cnt += (cur_step_cnt - mTask.last_step_cnt);
         }
         mTask.last_step_cnt = cur_step_cnt;
-        step_cnt.idata = mTask.total_step_cnt;
-        osEnqueueEvt(EVT_SENSOR_STEP_COUNTER, step_cnt.vptr, NULL);
+
+        // Send the event if the current rate is ONCHANGE or we need to flush;
+        // otherwise, wait until step count sampling timer expires
+        if (mTask.sensors[STEPCNT].rate == SENSOR_RATE_ONCHANGE || mTask.sensors[STEPCNT].flush) {
+            step_cnt.idata = mTask.total_step_cnt;
+            osEnqueueEvt(EVT_SENSOR_STEP_COUNTER, step_cnt.vptr, NULL);
+        } else {
+            mTask.step_cnt_changed = true;
+        }
     }
 
     while (mTask.sensors[STEPCNT].flush) {
@@ -1596,13 +1707,16 @@
 static void flushAllData(void)
 {
     int i;
-    for (i = ACC; i <= MAG; i++)
-        flushData(&mTask.sensors[i], EVENT_TYPE_BIT_DISCARDABLE | sensorGetMyEventType(mSensorInfo[i].sensorType));
+    for (i = ACC; i <= MAG; i++) {
+        flushData(&mTask.sensors[i],
+                EVENT_TYPE_BIT_DISCARDABLE | sensorGetMyEventType(mSensorInfo[i].sensorType));
+    }
 }
 
 static bool allocateDataEvt(struct BMI160Sensor *mSensor, uint64_t rtc_time)
 {
-    mSensor->data_evt = slabAllocatorAlloc(mDataSlab);
+    TDECL();
+    mSensor->data_evt = slabAllocatorAlloc(T(mDataSlab));
     if (mSensor->data_evt == NULL) {
         // slab allocation failed
         ERROR_PRINT("slabAllocatorAlloc() failed\n");
@@ -1644,22 +1758,18 @@
     }
 
     if (mSensor->idx == MAG) {
-#ifdef MAG_I2C_ADDR
+#ifdef MAG_SLAVE_PRESENT
         parseMagData(&magTask, &buf[0], &x, &y, &z);
-#endif
-
         BMM150_TO_ANDROID_COORDINATE(x, y, z);
 
-#ifdef MAG_SLAVE_PRESENT
         float xi, yi, zi;
-        magCalRemoveSoftiron(&mTask.moc,
-                x, y, z,
-                &xi, &yi, &zi);
+        magCalRemoveSoftiron(&mTask.moc, x, y, z, &xi, &yi, &zi);
 
-        newMagBias |= magCalUpdate(&mTask.moc,
-                sensorTime * kSensorTimerIntervalUs, xi, yi, zi);
+        newMagBias |= magCalUpdate(&mTask.moc, sensorTime * kSensorTimerIntervalUs, xi, yi, zi);
 
         magCalRemoveBias(&mTask.moc, xi, yi, zi, &x, &y, &z);
+#else
+        return;
 #endif
     } else {
         raw_x = (buf[0] | buf[1] << 8);
@@ -1686,7 +1796,8 @@
     if (mSensor->idx == MAG && (newMagBias || !mTask.magBiasPosted)) {
         if (mSensor->data_evt->samples[0].firstSample.numSamples > 0) {
             // flush existing samples so the bias appears after them
-            flushData(mSensor, EVENT_TYPE_BIT_DISCARDABLE | sensorGetMyEventType(mSensorInfo[MAG].sensorType));
+            flushData(mSensor,
+                    EVENT_TYPE_BIT_DISCARDABLE | sensorGetMyEventType(mSensorInfo[MAG].sensorType));
             if (!allocateDataEvt(mSensor, rtc_time))
                 return;
         }
@@ -1694,7 +1805,8 @@
             mTask.magBiasCurrent = true;
         mSensor->data_evt->samples[0].firstSample.biasCurrent = mTask.magBiasCurrent;
         mSensor->data_evt->samples[0].firstSample.biasPresent = 1;
-        mSensor->data_evt->samples[0].firstSample.biasSample = mSensor->data_evt->samples[0].firstSample.numSamples;
+        mSensor->data_evt->samples[0].firstSample.biasSample =
+                mSensor->data_evt->samples[0].firstSample.numSamples;
         sample = &mSensor->data_evt->samples[mSensor->data_evt->samples[0].firstSample.numSamples++];
 #ifdef MAG_SLAVE_PRESENT
         magCalGetBias(&mTask.moc, &sample->x, &sample->y, &sample->z);
@@ -1723,6 +1835,8 @@
 
     //DEBUG_PRINT("bmi160: x: %d, y: %d, z: %d\n", (int)(1000*x), (int)(1000*y), (int)(1000*z));
 
+    //TODO: This was added to prevent to much data of the same type accumulate in internal buffer.
+    //      It might no longer be necessary and can be removed.
     if (mSensor->data_evt->samples[0].firstSample.numSamples == MAX_NUM_COMMS_EVENT_SAMPLES) {
         flushAllData();
     }
@@ -1764,9 +1878,15 @@
     }
 
     while (size > 0) {
-        if (buf[i] == 0x80) {
-            // header 0x80 means no more data
+        if (buf[i] == BMI160_FRAME_HEADER_INVALID) {
+            // reaching invalid header means no more data
             break;
+        } else if (buf[i] == BMI160_FRAME_HEADER_SKIP) {
+            // manually injected skip header
+            DEBUG_PRINT_IF(DBG_CHUNKED, "skip nop header");
+            i++;
+            size--;
+            continue;
         }
 
         fh_mode = buf[i] >> 6;
@@ -1791,17 +1911,19 @@
             } else if (fh_param == 1) {
                 // sensortime frame
                 if (size >= 3) {
-                    // The active sensor with the highest odr/lowest delta is the one that determines
-                    // the sensor time increments.
+                    // The active sensor with the highest odr/lowest delta is the one that
+                    // determines the sensor time increments.
                     for (j = ACC; j <= MAG; j++) {
-                        if (mTask.sensors[j].configed && mTask.sensors[j].latency != SENSOR_LATENCY_NODATA) {
-                            min_delta = min_delta < mTask.time_delta[j] ? min_delta : mTask.time_delta[j];
+                        if (mTask.sensors[j].configed &&
+                                mTask.sensors[j].latency != SENSOR_LATENCY_NODATA) {
+                            min_delta = min_delta < mTask.time_delta[j] ? min_delta :
+                                    mTask.time_delta[j];
                         }
                     }
                     sensor_time24 = buf[i + 2] << 16 | buf[i + 1] << 8 | buf[i];
 
-                    // clear lower bits that measure time from taking the sample to reading the FIFO,
-                    // something we're not interested in.
+                    // clear lower bits that measure time from taking the sample to reading the
+                    // FIFO, something we're not interested in.
                     sensor_time24 &= ~(min_delta - 1);
 
                     full_sensor_time = parseSensortime(sensor_time24);
@@ -1841,12 +1963,11 @@
                             mTask.time_delta[j] = saved_time_delta[j];
                         }
 
-#if TIMESTAMP_DBG
-                        DEBUG_PRINT("sensortime invalid: full, frame, task = %llu, %llu, %llu\n",
+                        DEBUG_PRINT_IF(TIMESTAMP_DBG,
+                                "sensortime invalid: full, frame, task = %llu, %llu, %llu\n",
                                 full_sensor_time,
                                 frame_sensor_time,
                                 mTask.frame_sensortime);
-#endif
 
                         // Parse again with known valid timing.
                         // This time the sensor events will be committed into event buffer.
@@ -1859,10 +1980,11 @@
                     for (j = ACC; j <= MAG; j++) {
                         mTask.prev_frame_time[j] = observed[j] ? full_sensor_time : (ULONG_LONG_MAX - 1);
 
-                        // sensor can be disabled in the middle of the FIFO,
-                        // but wait till the FIFO end to invalidate prev_frame_time since it's still needed for parsing.
+                        // sensor can be disabled in the middle of the FIFO, but wait till the FIFO
+                        // end to invalidate prev_frame_time since it's still needed for parsing.
                         // Also invalidate pending delta just to be safe.
-                        if (!mTask.sensors[j].configed || mTask.sensors[j].latency == SENSOR_LATENCY_NODATA) {
+                        if (!mTask.sensors[j].configed ||
+                                mTask.sensors[j].latency == SENSOR_LATENCY_NODATA) {
                             mTask.prev_frame_time[j] = ULONG_LONG_MAX;
                             mTask.pending_delta[j] = false;
                         }
@@ -1883,7 +2005,8 @@
                             mTask.pending_delta[j] = false;
                             mTask.time_delta[j] = mTask.next_delta[j];
 #if TIMESTAMP_DBG
-                            DEBUG_PRINT("%s new delta %u\n", mSensorInfo[j].sensorName, (unsigned int)mTask.time_delta[j]);
+                            DEBUG_PRINT("%s new delta %u\n", mSensorInfo[j].sensorName,
+                                    (unsigned int)mTask.time_delta[j]);
 #endif
                         }
                     }
@@ -1905,8 +2028,8 @@
             // 3) The underestimated frame time of a newly enabled sensor will be corrected
             // as soon as it shows up in the same frame with another sensor.
             // 4) (prev_frame_time == ULONG_LONG_MAX) means the sensor wasn't enabled.
-            // 5) (prev_frame_time == ULONG_LONG_MAX -1) means the sensor didn't appear in the
-            // last data frame of the previous fifo read.  So it won't be used as a frame time reference.
+            // 5) (prev_frame_time == ULONG_LONG_MAX -1) means the sensor didn't appear in the last
+            // data frame of the previous fifo read.  So it won't be used as a frame time reference.
 
             tmp_frame_time = 0;
             for (j = ACC; j <= MAG; j++) {
@@ -1924,7 +2047,8 @@
             if (fh_param & 4) { // have mag data
                 if (size >= 8) {
                     if (frame_sensor_time_valid) {
-                        parseRawData(&mTask.sensors[MAG], &buf[i], 0, tmp_frame_time); // scale not used
+                        // scale not used
+                        parseRawData(&mTask.sensors[MAG], &buf[i], 0, tmp_frame_time);
 #if TIMESTAMP_DBG
                         if (mTask.prev_frame_time[MAG] == ULONG_LONG_MAX) {
                             DEBUG_PRINT("mag enabled: frame %d time 0x%08x\n",
@@ -2005,7 +2129,7 @@
         }
     }
 
-    // flush data events.
+    //flush data events.
     flushAllData();
 }
 
@@ -2018,6 +2142,7 @@
  */
 static void int2Handling(void)
 {
+    TDECL();
     union EmbeddedDataPoint trigger_axies;
     uint8_t int_status_0 = mTask.statusBuffer[1];
     uint8_t int_status_1 = mTask.statusBuffer[2];
@@ -2027,9 +2152,7 @@
             osEnqueueEvt(EVT_SENSOR_STEP, NULL, NULL);
         }
         if (mTask.sensors[STEPCNT].powered) {
-            mTask.state = SENSOR_STEP_CNT;
-            SPI_READ(BMI160_REG_STEP_CNT_0, 2, &mTask.dataBuffer);
-            spiBatchTxRx(&mTask.mode, sensorSpiCallback, &mTask.sensors[STEPCNT]);
+            T(pending_step_cnt) = true;
         }
     }
     if ((int_status_0 & INT_ANY_MOTION) && mTask.sensors[ANYMO].powered) {
@@ -2061,32 +2184,18 @@
 
 static void int2Evt(void)
 {
-    if (mTask.state == SENSOR_IDLE) {
-        mTask.state = SENSOR_INT_2_HANDLING;
-
+    TDECL();
+    if (trySwitchState(SENSOR_INT_2_HANDLING)) {
         // Read the interrupt reg value to determine what interrupts
         SPI_READ(BMI160_REG_INT_STATUS_0, 4, &mTask.statusBuffer);
-        spiBatchTxRx(&mTask.mode, sensorSpiCallback, &mTask);
-    } else if (mTask.state != SENSOR_INT_2_HANDLING) {
-        // If we are not handling Int 2 right now, we need to put it to pending.
+        spiBatchTxRx(&mTask.mode, sensorSpiCallback, &mTask, __FUNCTION__);
+    } else {
+        // even if we are still in SENSOR_INT_2_HANDLING, the SPI may already finished and we need
+        // to issue another SPI read to get the latest status
         mTask.pending_int[1] = true;
     }
 }
 
-static void int1Evt(void)
-{
-    if (mTask.state == SENSOR_IDLE) {
-        // read out fifo.
-        mTask.state = SENSOR_INT_1_HANDLING;
-        mTask.xferCnt = FIFO_READ_SIZE;
-        SPI_READ(BMI160_REG_FIFO_DATA, mTask.xferCnt, &mTask.dataBuffer);
-        spiBatchTxRx(&mTask.mode, sensorSpiCallback, &mTask);
-    } else if (mTask.state != SENSOR_INT_1_HANDLING) {
-        // If we are not handling Int 1 right now, we need to put it to pending.
-        mTask.pending_int[0] = true;
-    }
-}
-
 // bits[6:7] in OFFSET[6] to enable/disable gyro/accel offset.
 // bits[0:5] in OFFSET[6] stores the most significant 2 bits of gyro offset at
 // its x, y, z axies.
@@ -2107,25 +2216,32 @@
     return mode;
 }
 
-static void saveCalibration()
+static bool saveCalibration()
 {
-    mTask.state = SENSOR_SAVE_CALIBRATION;
-    if (mTask.sensors[ACC].offset_enable) {
-        SPI_WRITE(BMI160_REG_OFFSET_0, mTask.sensors[ACC].offset[0] & 0xFF, 450);
-        SPI_WRITE(BMI160_REG_OFFSET_0 + 1, mTask.sensors[ACC].offset[1] & 0xFF, 450);
-        SPI_WRITE(BMI160_REG_OFFSET_0 + 2, mTask.sensors[ACC].offset[2] & 0xFF, 450);
+    TDECL();
+    if (trySwitchState(SENSOR_SAVE_CALIBRATION)) {
+        if (mTask.sensors[ACC].offset_enable) {
+            SPI_WRITE(BMI160_REG_OFFSET_0, mTask.sensors[ACC].offset[0] & 0xFF, 450);
+            SPI_WRITE(BMI160_REG_OFFSET_0 + 1, mTask.sensors[ACC].offset[1] & 0xFF, 450);
+            SPI_WRITE(BMI160_REG_OFFSET_0 + 2, mTask.sensors[ACC].offset[2] & 0xFF, 450);
+        }
+        if (mTask.sensors[GYR].offset_enable) {
+            SPI_WRITE(BMI160_REG_OFFSET_3, mTask.sensors[GYR].offset[0] & 0xFF, 450);
+            SPI_WRITE(BMI160_REG_OFFSET_3 + 1, mTask.sensors[GYR].offset[1] & 0xFF, 450);
+            SPI_WRITE(BMI160_REG_OFFSET_3 + 2, mTask.sensors[GYR].offset[2] & 0xFF, 450);
+        }
+        SPI_WRITE(BMI160_REG_OFFSET_6, offset6Mode(), 450);
+        SPI_READ(BMI160_REG_OFFSET_0, 7, &mTask.dataBuffer);
+        spiBatchTxRx(&mTask.mode, sensorSpiCallback, NULL, __FUNCTION__);
+        return true;
+    } else {
+        DEBUG_PRINT("%s, state != IDLE", __FUNCTION__);
+        return false;
     }
-    if (mTask.sensors[GYR].offset_enable) {
-        SPI_WRITE(BMI160_REG_OFFSET_3, mTask.sensors[GYR].offset[0] & 0xFF, 450);
-        SPI_WRITE(BMI160_REG_OFFSET_3 + 1, mTask.sensors[GYR].offset[1] & 0xFF, 450);
-        SPI_WRITE(BMI160_REG_OFFSET_3 + 2, mTask.sensors[GYR].offset[2] & 0xFF, 450);
-    }
-    SPI_WRITE(BMI160_REG_OFFSET_6, offset6Mode(), 450);
-    SPI_READ(BMI160_REG_OFFSET_0, 7, &mTask.dataBuffer);
-    spiBatchTxRx(&mTask.mode, sensorSpiCallback, NULL);
 }
 
-static void sendCalibrationResult(uint8_t status, uint8_t sensorType, int32_t xBias, int32_t yBias, int32_t zBias) {
+static void sendCalibrationResult(uint8_t status, uint8_t sensorType,
+        int32_t xBias, int32_t yBias, int32_t zBias) {
     struct CalibrationData *data = heapAlloc(sizeof(struct CalibrationData));
     if (!data) {
         osLog(LOG_WARN, "Couldn't alloc cal result pkt");
@@ -2148,15 +2264,16 @@
 
 static void accCalibrationHandling(void)
 {
+    TDECL();
     switch (mTask.calibration_state) {
     case CALIBRATION_START:
-        mRetryLeft = RETRY_CNT_CALIBRATION;
+        T(mRetryLeft) = RETRY_CNT_CALIBRATION;
 
         // turn ACC to NORMAL mode
         SPI_WRITE(BMI160_REG_CMD, 0x11, 50000);
 
         mTask.calibration_state = CALIBRATION_FOC;
-        spiBatchTxRx(&mTask.mode, sensorSpiCallback, &mTask.sensors[ACC]);
+        spiBatchTxRx(&mTask.mode, sensorSpiCallback, &mTask.sensors[ACC], __FUNCTION__);
         break;
     case CALIBRATION_FOC:
 
@@ -2174,7 +2291,7 @@
         SPI_READ(BMI160_REG_STATUS, 1, &mTask.statusBuffer, 50000);
 
         mTask.calibration_state = CALIBRATION_WAIT_FOC_DONE;
-        spiBatchTxRx(&mTask.mode, sensorSpiCallback, &mTask.sensors[ACC]);
+        spiBatchTxRx(&mTask.mode, sensorSpiCallback, &mTask.sensors[ACC], __FUNCTION__);
         break;
     case CALIBRATION_WAIT_FOC_DONE:
         // if the STATUS REG has bit 3 set, it means calbration is done.
@@ -2193,13 +2310,13 @@
             // calibration hasn't finished yet, go back to wait for 50ms.
             SPI_READ(BMI160_REG_STATUS, 1, &mTask.statusBuffer, 50000);
             mTask.calibration_state = CALIBRATION_WAIT_FOC_DONE;
-            mRetryLeft--;
+            T(mRetryLeft)--;
         }
-        spiBatchTxRx(&mTask.mode, sensorSpiCallback, &mTask.sensors[ACC]);
+        spiBatchTxRx(&mTask.mode, sensorSpiCallback, &mTask.sensors[ACC], __FUNCTION__);
 
         // if calbration hasn't finished after 10 polling on the STATUS reg,
         // declare timeout.
-        if (mRetryLeft == 0) {
+        if (T(mRetryLeft) == 0) {
             mTask.calibration_state = CALIBRATION_TIMEOUT;
         }
         break;
@@ -2221,7 +2338,9 @@
                 (unsigned int)mTask.sensors[ACC].offset[1],
                 (unsigned int)mTask.sensors[ACC].offset[2]);
 
-        sendCalibrationResult(SENSOR_APP_EVT_STATUS_SUCCESS, SENS_TYPE_ACCEL, mTask.sensors[ACC].offset[0], mTask.sensors[ACC].offset[1], mTask.sensors[ACC].offset[2]);
+        sendCalibrationResult(SENSOR_APP_EVT_STATUS_SUCCESS, SENS_TYPE_ACCEL,
+                mTask.sensors[ACC].offset[0], mTask.sensors[ACC].offset[1],
+                mTask.sensors[ACC].offset[2]);
 
         // Enable offset compensation for accel
         uint8_t mode = offset6Mode();
@@ -2231,7 +2350,7 @@
         SPI_WRITE(BMI160_REG_CMD, 0x10, 5000);
 
         mTask.calibration_state = CALIBRATION_DONE;
-        spiBatchTxRx(&mTask.mode, sensorSpiCallback, &mTask.sensors[ACC]);
+        spiBatchTxRx(&mTask.mode, sensorSpiCallback, &mTask.sensors[ACC], __FUNCTION__);
         break;
     default:
         ERROR_PRINT("Invalid calibration state\n");
@@ -2241,16 +2360,16 @@
 
 static bool accCalibration(void *cookie)
 {
-    if ((mTask.state != SENSOR_IDLE) || mTask.sensors[ACC].powered) {
+    TDECL();
+    if (!mTask.sensors[ACC].powered && trySwitchState(SENSOR_CALIBRATING)) {
+        mTask.calibration_state = CALIBRATION_START;
+        accCalibrationHandling();
+        return true;
+    } else {
         ERROR_PRINT("cannot calibrate accel because sensor is busy\n");
         sendCalibrationResult(SENSOR_APP_EVT_STATUS_BUSY, SENS_TYPE_ACCEL, 0, 0, 0);
         return false;
-    } else {
-        mTask.state = SENSOR_CALIBRATING;
-        mTask.calibration_state = CALIBRATION_START;
-        accCalibrationHandling();
     }
-    return true;
 }
 
 static bool accCfgData(void *data, void *cookie)
@@ -2262,27 +2381,28 @@
     mTask.sensors[ACC].offset[2] = values[2];
     mTask.sensors[ACC].offset_enable = true;
 
-    INFO_PRINT("accCfgData: data=%02lx, %02lx, %02lx\n", values[0] & 0xFF, values[1] & 0xFF, values[2] & 0xFF);
+    INFO_PRINT("accCfgData: data=%02lx, %02lx, %02lx\n",
+            values[0] & 0xFF, values[1] & 0xFF, values[2] & 0xFF);
 
-    if (mTask.state == SENSOR_IDLE)
-        saveCalibration();
-    else
+    if (!saveCalibration()) {
         mTask.pending_calibration_save = true;
+    }
 
     return true;
 }
 
 static void gyrCalibrationHandling(void)
 {
+    TDECL();
     switch (mTask.calibration_state) {
     case CALIBRATION_START:
-        mRetryLeft = RETRY_CNT_CALIBRATION;
+        T(mRetryLeft) = RETRY_CNT_CALIBRATION;
 
         // turn GYR to NORMAL mode
         SPI_WRITE(BMI160_REG_CMD, 0x15, 50000);
 
         mTask.calibration_state = CALIBRATION_FOC;
-        spiBatchTxRx(&mTask.mode, sensorSpiCallback, &mTask.sensors[GYR]);
+        spiBatchTxRx(&mTask.mode, sensorSpiCallback, &mTask.sensors[GYR], __FUNCTION__);
         break;
     case CALIBRATION_FOC:
 
@@ -2299,7 +2419,7 @@
         SPI_READ(BMI160_REG_STATUS, 1, &mTask.statusBuffer, 50000);
 
         mTask.calibration_state = CALIBRATION_WAIT_FOC_DONE;
-        spiBatchTxRx(&mTask.mode, sensorSpiCallback, &mTask.sensors[GYR]);
+        spiBatchTxRx(&mTask.mode, sensorSpiCallback, &mTask.sensors[GYR], __FUNCTION__);
         break;
     case CALIBRATION_WAIT_FOC_DONE:
 
@@ -2319,13 +2439,13 @@
             // calibration hasn't finished yet, go back to wait for 50ms.
             SPI_READ(BMI160_REG_STATUS, 1, &mTask.statusBuffer, 50000);
             mTask.calibration_state = CALIBRATION_WAIT_FOC_DONE;
-            mRetryLeft--;
+            T(mRetryLeft)--;
         }
-        spiBatchTxRx(&mTask.mode, sensorSpiCallback, &mTask.sensors[GYR]);
+        spiBatchTxRx(&mTask.mode, sensorSpiCallback, &mTask.sensors[GYR], __FUNCTION__);
 
         // if calbration hasn't finished after 10 polling on the STATUS reg,
         // declare timeout.
-        if (mRetryLeft == 0) {
+        if (T(mRetryLeft) == 0) {
             mTask.calibration_state = CALIBRATION_TIMEOUT;
         }
         break;
@@ -2347,7 +2467,9 @@
                 (unsigned int)mTask.sensors[GYR].offset[1],
                 (unsigned int)mTask.sensors[GYR].offset[2]);
 
-        sendCalibrationResult(SENSOR_APP_EVT_STATUS_SUCCESS, SENS_TYPE_GYRO, mTask.sensors[GYR].offset[0], mTask.sensors[GYR].offset[1], mTask.sensors[GYR].offset[2]);
+        sendCalibrationResult(SENSOR_APP_EVT_STATUS_SUCCESS, SENS_TYPE_GYRO,
+                mTask.sensors[GYR].offset[0], mTask.sensors[GYR].offset[1],
+                mTask.sensors[GYR].offset[2]);
 
         // Enable offset compensation for gyro
         uint8_t mode = offset6Mode();
@@ -2357,7 +2479,7 @@
         SPI_WRITE(BMI160_REG_CMD, 0x14, 1000);
 
         mTask.calibration_state = CALIBRATION_DONE;
-        spiBatchTxRx(&mTask.mode, sensorSpiCallback, &mTask.sensors[GYR]);
+        spiBatchTxRx(&mTask.mode, sensorSpiCallback, &mTask.sensors[GYR], __FUNCTION__);
         break;
     default:
         ERROR_PRINT("Invalid calibration state\n");
@@ -2367,16 +2489,16 @@
 
 static bool gyrCalibration(void *cookie)
 {
-    if ((mTask.state != SENSOR_IDLE) || mTask.sensors[GYR].powered) {
+    TDECL();
+    if (!mTask.sensors[GYR].powered && trySwitchState(SENSOR_CALIBRATING)) {
+        mTask.calibration_state = CALIBRATION_START;
+        gyrCalibrationHandling();
+        return true;
+    } else {
         ERROR_PRINT("cannot calibrate gyro because sensor is busy\n");
         sendCalibrationResult(SENSOR_APP_EVT_STATUS_BUSY, SENS_TYPE_GYRO, 0, 0, 0);
         return false;
-    } else {
-        mTask.state = SENSOR_CALIBRATING;
-        mTask.calibration_state = CALIBRATION_START;
-        gyrCalibrationHandling();
     }
-    return true;
 }
 
 static bool gyrCfgData(void *data, void *cookie)
@@ -2388,12 +2510,12 @@
     mTask.sensors[GYR].offset[2] = values[2];
     mTask.sensors[GYR].offset_enable = true;
 
-    INFO_PRINT("gyrCfgData: data=%02lx, %02lx, %02lx\n", values[0] & 0xFF, values[1] & 0xFF, values[2] & 0xFF);
+    INFO_PRINT("gyrCfgData: data=%02lx, %02lx, %02lx\n",
+            values[0] & 0xFF, values[1] & 0xFF, values[2] & 0xFF);
 
-    if (mTask.state == SENSOR_IDLE)
-        saveCalibration();
-    else
+    if (!saveCalibration()) {
         mTask.pending_calibration_save = true;
+    }
 
     return true;
 }
@@ -2402,7 +2524,8 @@
 {
     float *values = data;
 
-    INFO_PRINT("magCfgData: %ld, %ld, %ld\n", (int32_t)(values[0] * 1000), (int32_t)(values[1] * 1000), (int32_t)(values[2] * 1000));
+    INFO_PRINT("magCfgData: %ld, %ld, %ld\n",
+            (int32_t)(values[0] * 1000), (int32_t)(values[1] * 1000), (int32_t)(values[2] * 1000));
 
 #ifdef MAG_SLAVE_PRESENT
     mTask.moc.x_bias = values[0];
@@ -2436,15 +2559,18 @@
 
 static const struct SensorOps mSensorOps[NUM_OF_SENSOR] =
 {
-    { DEC_OPS_CAL_CFG(accPower, accFirmwareUpload, accSetRate, accFlush, accCalibration, accCfgData) },
-    { DEC_OPS_CAL_CFG(gyrPower, gyrFirmwareUpload, gyrSetRate, gyrFlush, gyrCalibration, gyrCfgData) },
+    { DEC_OPS_CAL_CFG(accPower, accFirmwareUpload, accSetRate, accFlush, accCalibration,
+            accCfgData) },
+    { DEC_OPS_CAL_CFG(gyrPower, gyrFirmwareUpload, gyrSetRate, gyrFlush, gyrCalibration,
+            gyrCfgData) },
     { DEC_OPS_CFG(magPower, magFirmwareUpload, magSetRate, magFlush, magCfgData) },
     { DEC_OPS(stepPower, stepFirmwareUpload, stepSetRate, stepFlush) },
     { DEC_OPS(doubleTapPower, doubleTapFirmwareUpload, doubleTapSetRate, doubleTapFlush) },
     { DEC_OPS(flatPower, flatFirmwareUpload, flatSetRate, flatFlush) },
     { DEC_OPS(anyMotionPower, anyMotionFirmwareUpload, anyMotionSetRate, anyMotionFlush) },
     { DEC_OPS(noMotionPower, noMotionFirmwareUpload, noMotionSetRate, noMotionFlush) },
-    { DEC_OPS_SEND(stepCntPower, stepCntFirmwareUpload, stepCntSetRate, stepCntFlush, stepCntSendLastData) },
+    { DEC_OPS_SEND(stepCntPower, stepCntFirmwareUpload, stepCntSetRate, stepCntFlush,
+            stepCntSendLastData) },
 };
 
 static void configEvent(struct BMI160Sensor *mSensor, struct ConfigStat *ConfigData)
@@ -2463,6 +2589,7 @@
 
 static void timeSyncEvt(uint32_t evtGeneration, bool evtDataValid)
 {
+    TDECL();
     // not processing pending events
     if (evtDataValid) {
         // stale event
@@ -2472,22 +2599,22 @@
         mTask.active_poll_generation = mTask.poll_generation;
     }
 
-    if (mTask.state != SENSOR_IDLE) {
-        mTask.pending_time_sync = true;
-    } else {
-        mTask.state = SENSOR_TIME_SYNC;
+    if (trySwitchState(SENSOR_TIME_SYNC)) {
         SPI_READ(BMI160_REG_SENSORTIME_0, 3, &mTask.sensorTimeBuffer);
         SPI_READ(BMI160_REG_TEMPERATURE_0, 2, &mTask.temperatureBuffer);
-        spiBatchTxRx(&mTask.mode, sensorSpiCallback, &mTask);
+        spiBatchTxRx(&mTask.mode, sensorSpiCallback, &mTask, __FUNCTION__);
+    } else {
+        mTask.pending_time_sync = true;
     }
 }
 
 static void processPendingEvt(void)
 {
+    TDECL();
     enum SensorIndex i;
     if (mTask.pending_int[0]) {
         mTask.pending_int[0] = false;
-        int1Evt();
+        initiateFifoRead(false /*isInterruptContext*/);
         return;
     }
     if (mTask.pending_int[1]) {
@@ -2507,19 +2634,19 @@
             return;
         }
     }
-    if (mTask.sensors[STEPCNT].flush > 0) {
-        stepCntFlushGetData();
+    if (mTask.sensors[STEPCNT].flush > 0 || T(pending_step_cnt)) {
+        T(pending_step_cnt) = T(pending_step_cnt) && !stepCntFlushGetData();
         return;
     }
     if (mTask.pending_calibration_save) {
-        mTask.pending_calibration_save = false;
-        saveCalibration();
+        mTask.pending_calibration_save = !saveCalibration();
         return;
     }
 }
 
 static void sensorInit(void)
 {
+    TDECL();
     switch (mTask.init_state) {
     case RESET_BMI160:
         DEBUG_PRINT("Performing soft reset\n");
@@ -2529,7 +2656,7 @@
         SPI_READ(BMI160_REG_MAGIC, 1, &mTask.dataBuffer, 100);
 
         mTask.init_state = INIT_BMI160;
-        spiBatchTxRx(&mTask.mode, sensorSpiCallback, &mTask);
+        spiBatchTxRx(&mTask.mode, sensorSpiCallback, &mTask, "sensorInit RESET" );
         break;
 
     case INIT_BMI160:
@@ -2581,37 +2708,37 @@
         // Reset fifo
         SPI_WRITE(BMI160_REG_CMD, 0xB0, 10000);
 
-#ifdef MAG_I2C_ADDR
+#ifdef MAG_SLAVE_PRESENT
         mTask.init_state = INIT_MAG;
         mTask.mag_state = MAG_SET_START;
 #else
         // no mag connected to secondary interface
         mTask.init_state = INIT_ON_CHANGE_SENSORS;
 #endif
-        spiBatchTxRx(&mTask.mode, sensorSpiCallback, &mTask);
+        spiBatchTxRx(&mTask.mode, sensorSpiCallback, &mTask, "sensorInit INIT");
         break;
 
     case INIT_MAG:
         // Don't check statusBuffer if we are just starting mag config
         if (mTask.mag_state == MAG_SET_START) {
-            mRetryLeft = RETRY_CNT_MAG;
+            T(mRetryLeft) = RETRY_CNT_MAG;
             magConfig();
         } else if (mTask.mag_state < MAG_SET_DATA && mTask.statusBuffer[1] & 0x04) {
             // fixme: poll_until to reduce states
             // fixme: check should be done before SPI_READ in MAG_READ
             SPI_READ(BMI160_REG_STATUS, 1, &mTask.statusBuffer, 1000);
-            if (--mRetryLeft == 0) {
+            if (--T(mRetryLeft) == 0) {
                 ERROR_PRINT("INIT_MAG failed\n");
                 // fixme: duplicate suspend mag here
                 mTask.mag_state = MAG_INIT_FAILED;
                 mTask.init_state = INIT_ON_CHANGE_SENSORS;
             }
         } else {
-            mRetryLeft = RETRY_CNT_MAG;
+            T(mRetryLeft) = RETRY_CNT_MAG;
             magConfig();
         }
 
-        spiBatchTxRx(&mTask.mode, sensorSpiCallback, &mTask);
+        spiBatchTxRx(&mTask.mode, sensorSpiCallback, &mTask, "sensorInit INIT_MAG");
         break;
 
     case INIT_ON_CHANGE_SENSORS:
@@ -2640,7 +2767,7 @@
         SPI_WRITE(BMI160_REG_INT_FLAT_1, 0x14, 450);
 
         mTask.init_state = INIT_DONE;
-        spiBatchTxRx(&mTask.mode, sensorSpiCallback, &mTask);
+        spiBatchTxRx(&mTask.mode, sensorSpiCallback, &mTask, "sensorInit INIT_ONC");
         break;
 
     default:
@@ -2650,34 +2777,36 @@
 
 static void handleSpiDoneEvt(const void* evtData)
 {
+    TDECL();
     struct BMI160Sensor *mSensor;
     uint64_t SensorTime;
     int16_t temperature16;
     int i;
+    bool returnIdle = false;
 
-    switch (mTask.state) {
+    switch (GET_STATE()) {
     case SENSOR_BOOT:
-        mRetryLeft = RETRY_CNT_ID;
-        mTask.state = SENSOR_VERIFY_ID;
+        T(mRetryLeft) = RETRY_CNT_ID;
+        SET_STATE(SENSOR_VERIFY_ID);
         // dummy reads after boot, wait 100us
         SPI_READ(BMI160_REG_MAGIC, 1, &mTask.statusBuffer, 100);
         // read the device ID for bmi160
         SPI_READ(BMI160_REG_ID, 1, &mTask.dataBuffer);
-        spiBatchTxRx(&mTask.mode, sensorSpiCallback, &mTask);
+        spiBatchTxRx(&mTask.mode, sensorSpiCallback, &mTask, "spiDone SENSOR_BOOT");
         break;
     case SENSOR_VERIFY_ID:
         if (mTask.dataBuffer[1] != BMI160_ID) {
-            mRetryLeft --;
+            T(mRetryLeft) --;
             ERROR_PRINT("failed id match: %02x\n", mTask.dataBuffer[1]);
-            if (mRetryLeft == 0)
+            if (T(mRetryLeft) == 0)
                 break;
             // For some reason the first ID read will fail to get the
             // correct value. need to retry a few times.
-            mTask.state = SENSOR_BOOT;
+            SET_STATE(SENSOR_BOOT);
             timTimerSet(100000000, 100, 100, sensorTimerCallback, NULL, true);
             break;
         } else {
-            mTask.state = SENSOR_INITIALIZING;
+            SET_STATE(SENSOR_INITIALIZING);
             mTask.init_state = RESET_BMI160;
             sensorInit();
             break;
@@ -2687,9 +2816,8 @@
             DEBUG_PRINT("Done initialzing, system IDLE\n");
             for (i=0; i<NUM_OF_SENSOR; i++)
                 sensorRegisterInitComplete(mTask.sensors[i].handle);
-            mTask.state = SENSOR_IDLE;
             // In case other tasks have already requested us before we finish booting up.
-            processPendingEvt();
+            returnIdle = true;
         } else {
             sensorInit();
         }
@@ -2703,8 +2831,7 @@
             //DEBUG_PRINT("oneshot on\n");
         }
         sensorSignalInternalEvt(mSensor->handle, SENSOR_INTERNAL_EVT_POWER_STATE_CHG, 1, 0);
-        mTask.state = SENSOR_IDLE;
-        processPendingEvt();
+        returnIdle = true;
         break;
     case SENSOR_POWERING_DOWN:
         mSensor = (struct BMI160Sensor *)evtData;
@@ -2715,55 +2842,44 @@
             //DEBUG_PRINT("oneshot off\n");
         }
         sensorSignalInternalEvt(mSensor->handle, SENSOR_INTERNAL_EVT_POWER_STATE_CHG, 0, 0);
-        mTask.state = SENSOR_IDLE;
 
         if (mTask.pending_dispatch) {
             mTask.pending_dispatch = false;
             dispatchData();
         }
-
-        mTask.state = SENSOR_IDLE;
-        processPendingEvt();
+        returnIdle = true;
         break;
     case SENSOR_INT_1_HANDLING:
         dispatchData();
         sendFlushEvt();
-        mTask.state = SENSOR_IDLE;
-        processPendingEvt();
+        returnIdle = true;
         break;
     case SENSOR_INT_2_HANDLING:
         int2Handling();
-        // If it is not step cnt, we are done.
-        if (mTask.state == SENSOR_INT_2_HANDLING) {
-            mTask.state = SENSOR_IDLE;
-            processPendingEvt();
-        }
+        returnIdle = true;
         break;
     case SENSOR_CONFIG_CHANGING:
         mSensor = (struct BMI160Sensor *)evtData;
         sensorSignalInternalEvt(mSensor->handle,
                 SENSOR_INTERNAL_EVT_RATE_CHG, mSensor->rate, mSensor->latency);
-        mTask.state = SENSOR_IDLE;
 
         if (mTask.pending_dispatch) {
             mTask.pending_dispatch = false;
             dispatchData();
         }
 
-        mTask.state = SENSOR_IDLE;
-        processPendingEvt();
+        returnIdle = true;
         break;
     case SENSOR_CALIBRATING:
         mSensor = (struct BMI160Sensor *)evtData;
         if (mTask.calibration_state == CALIBRATION_DONE) {
             DEBUG_PRINT("DONE calibration\n");
-            mTask.state = SENSOR_IDLE;
-            processPendingEvt();
+            returnIdle = true;
         } else if (mTask.calibration_state == CALIBRATION_TIMEOUT) {
             DEBUG_PRINT("Calibration TIMED OUT\n");
-            sendCalibrationResult(SENSOR_APP_EVT_STATUS_ERROR, (mSensor->idx == ACC) ? SENS_TYPE_ACCEL : SENS_TYPE_GYRO, 0, 0, 0);
-            mTask.state = SENSOR_IDLE;
-            processPendingEvt();
+            sendCalibrationResult(SENSOR_APP_EVT_STATUS_ERROR,
+                    (mSensor->idx == ACC) ? SENS_TYPE_ACCEL : SENS_TYPE_GYRO, 0, 0, 0);
+            returnIdle = true;
         } else if (mSensor->idx == ACC) {
             accCalibrationHandling();
         } else if (mSensor->idx == GYR) {
@@ -2772,11 +2888,11 @@
         break;
     case SENSOR_STEP_CNT:
         sendStepCnt();
-        mTask.state = SENSOR_IDLE;
-        processPendingEvt();
+        returnIdle = true;
         break;
     case SENSOR_TIME_SYNC:
-        SensorTime = parseSensortime(mTask.sensorTimeBuffer[1] | (mTask.sensorTimeBuffer[2] << 8) | (mTask.sensorTimeBuffer[3] << 16));
+        SensorTime = parseSensortime(mTask.sensorTimeBuffer[1] |
+                (mTask.sensorTimeBuffer[2] << 8) | (mTask.sensorTimeBuffer[3] << 16));
         map_sensortime_to_rtc_time(SensorTime, rtcGetTime());
 
         temperature16 = (mTask.temperatureBuffer[1] | (mTask.temperatureBuffer[2] << 8));
@@ -2789,31 +2905,38 @@
 
         if (mTask.active_poll_generation == mTask.poll_generation) {
             // attach the generation number to event
-            timTimerSet(kTimeSyncPeriodNs, 100, 100, timeSyncCallback, (void *)mTask.poll_generation, true);
+            timTimerSet(kTimeSyncPeriodNs, 100, 100, timeSyncCallback,
+                    (void *)mTask.poll_generation, true);
         }
 
-        mTask.state = SENSOR_IDLE;
-        processPendingEvt();
+        returnIdle = true;
         break;
     case SENSOR_SAVE_CALIBRATION:
-        DEBUG_PRINT("SENSOR_SAVE_CALIBRATION: %02x %02x %02x %02x %02x %02x %02x\n", mTask.dataBuffer[1], mTask.dataBuffer[2], mTask.dataBuffer[3], mTask.dataBuffer[4], mTask.dataBuffer[5], mTask.dataBuffer[6], mTask.dataBuffer[7]);
-        mTask.state = SENSOR_IDLE;
-        processPendingEvt();
+        DEBUG_PRINT("SENSOR_SAVE_CALIBRATION: %02x %02x %02x %02x %02x %02x %02x\n",
+                mTask.dataBuffer[1], mTask.dataBuffer[2], mTask.dataBuffer[3], mTask.dataBuffer[4],
+                mTask.dataBuffer[5], mTask.dataBuffer[6], mTask.dataBuffer[7]);
+        returnIdle = true;
         break;
     default:
         break;
     }
+
+    if (returnIdle) {
+        SET_STATE(SENSOR_IDLE);
+        processPendingEvt();
+    }
 }
 
 static void handleEvent(uint32_t evtType, const void* evtData)
 {
+    TDECL();
     uint64_t currTime;
     uint8_t *packet;
     float newMagBias;
 
     switch (evtType) {
     case EVT_APP_START:
-        mTask.state = SENSOR_BOOT;
+        SET_STATE(SENSOR_BOOT);
         osEventUnsubscribe(mTask.tid, EVT_APP_START);
 
         // wait 100ms for sensor to boot
@@ -2840,7 +2963,7 @@
         break;
 
     case EVT_SENSOR_INTERRUPT_1:
-        int1Evt();
+        initiateFifoRead(false /*isInterruptContext*/);
         break;
     case EVT_SENSOR_INTERRUPT_2:
         int2Evt();
@@ -2869,6 +2992,7 @@
 
 static bool startTask(uint32_t task_id)
 {
+    TDECL();
     DEBUG_PRINT("        IMU:  %ld\n", task_id);
 
     enum SensorIndex i;
@@ -2876,33 +3000,37 @@
 
     time_init();
 
-    mTask.tid = task_id;
+    T(tid) = task_id;
 
-    mTask.Int1 = gpioRequest(BMI160_INT1_PIN);
-    mTask.Isr1.func = bmi160Isr1;
-    mTask.Int2 = gpioRequest(BMI160_INT2_PIN);
-    mTask.Isr2.func = bmi160Isr2;
-    mTask.pending_int[0] = false;
-    mTask.pending_int[1] = false;
-    mTask.pending_dispatch = false;
-    mTask.frame_sensortime_valid = false;
-    mTask.poll_generation = 0;
-    mTask.tempCelsius = kTempInvalid;
-    mTask.tempTime = 0;
+    T(Int1) = gpioRequest(BMI160_INT1_PIN);
+    T(Isr1).func = bmi160Isr1;
+    T(Int2) = gpioRequest(BMI160_INT2_PIN);
+    T(Isr2).func = bmi160Isr2;
+    T(pending_int[0]) = false;
+    T(pending_int[1]) = false;
+    T(pending_step_cnt) = false;
+    T(pending_dispatch) = false;
+    T(frame_sensortime_valid) = false;
+    T(poll_generation) = 0;
+    T(tempCelsius) = kTempInvalid;
+    T(tempTime) = 0;
 
-    mTask.mode.speed = BMI160_SPI_SPEED_HZ;
-    mTask.mode.bitsPerWord = 8;
-    mTask.mode.cpol = SPI_CPOL_IDLE_HI;
-    mTask.mode.cpha = SPI_CPHA_TRAILING_EDGE;
-    mTask.mode.nssChange = true;
-    mTask.mode.format = SPI_FORMAT_MSB_FIRST;
-    mTask.cs = GPIO_PB(12);
-    spiMasterRequest(BMI160_SPI_BUS_ID, &mTask.spiDev);
+    T(mode).speed = BMI160_SPI_SPEED_HZ;
+    T(mode).bitsPerWord = 8;
+    T(mode).cpol = SPI_CPOL_IDLE_HI;
+    T(mode).cpha = SPI_CPHA_TRAILING_EDGE;
+    T(mode).nssChange = true;
+    T(mode).format = SPI_FORMAT_MSB_FIRST;
+    T(cs) = GPIO_PB(12);
+
+    T(watermark) = 0;
+
+    spiMasterRequest(BMI160_SPI_BUS_ID, &T(spiDev));
 
     for (i = ACC; i < NUM_OF_SENSOR; i++) {
-        initSensorStruct(&mTask.sensors[i], i);
-        mTask.sensors[i].handle = sensorRegister(&mSensorInfo[i], &mSensorOps[i], NULL, false);
-        mTask.pending_config[i] = false;
+        initSensorStruct(&T(sensors[i]), i);
+        T(sensors[i]).handle = sensorRegister(&mSensorInfo[i], &mSensorOps[i], NULL, false);
+        T(pending_config[i]) = false;
     }
 
     osEventSubscribe(mTask.tid, EVT_APP_START);
@@ -2922,33 +3050,37 @@
     // the fifo size is 1K.
     // 20 slabs because some slabs may only hold 1-2 samples.
     // XXX: this consumes too much memeory, need to optimize
-    mDataSlab = slabAllocatorNew(slabSize, 4, 20);
-    if (!mDataSlab) {
+    T(mDataSlab) = slabAllocatorNew(slabSize, 4, 20);
+    if (!T(mDataSlab)) {
         INFO_PRINT("slabAllocatorNew() failed\n");
         return false;
     }
+    T(mWbufCnt) = 0;
+    T(mRegCnt) = 0;
+    T(spiInUse) = false;
 
-    mTask.interrupt_enable_0 = 0x00;
-    mTask.interrupt_enable_2 = 0x00;
+    T(interrupt_enable_0) = 0x00;
+    T(interrupt_enable_2) = 0x00;
 
     // initialize the last bmi160 time to be ULONG_MAX, so that we know it's
     // not valid yet.
-    mTask.last_sensortime = 0;
-    mTask.frame_sensortime = ULONG_LONG_MAX;
+    T(last_sensortime) = 0;
+    T(frame_sensortime) = ULONG_LONG_MAX;
 
     // it's ok to leave interrupt open all the time.
-    enableInterrupt(mTask.Int1, &mTask.Isr1);
-    enableInterrupt(mTask.Int2, &mTask.Isr2);
+    enableInterrupt(T(Int1), &T(Isr1));
+    enableInterrupt(T(Int2), &T(Isr2));
 
     return true;
 }
 
 static void endTask(void)
 {
+    TDECL();
 #ifdef MAG_SLAVE_PRESENT
     destroy_mag_cal(&mTask.moc);
 #endif
-    slabAllocatorDestroy(mDataSlab);
+    slabAllocatorDestroy(T(mDataSlab));
     spiMasterRelease(mTask.spiDev);
 
     // disable and release interrupt.
@@ -2958,4 +3090,274 @@
     gpioRelease(mTask.Int2);
 }
 
+/**
+ * Parse BMI160 FIFO frame without side effect.
+ *
+ * The major purpose of this function is to determine if FIFO content is received completely (start
+ * to see invalid headers). If not, return the pointer to the beginning last incomplete frame so
+ * additional read can use this pointer as start of read buffer.
+ *
+ * @param buf  buffer location
+ * @param size size of data to be parsed
+ *
+ * @return NULL if the FIFO is received completely; or pointer to the beginning of last incomplete
+ * frame for additional read.
+ */
+static uint8_t* shallowParseFrame(uint8_t * buf, int size) {
+    int i = 0;
+    int iLastFrame = 0; // last valid frame header index
+
+    DEBUG_PRINT_IF(DBG_SHALLOW_PARSE, "spf start %p: %x %x %x\n", buf, buf[0], buf[1], buf[2]);
+    while (size > 0) {
+        int fh_mode, fh_param;
+        iLastFrame = i;
+
+        if (buf[i] == BMI160_FRAME_HEADER_INVALID) {
+            // no more data
+            DEBUG_PRINT_IF(DBG_SHALLOW_PARSE, "spf:at%d=0x80\n", iLastFrame);
+            return NULL;
+        } else if (buf[i] == BMI160_FRAME_HEADER_SKIP) {
+            // artifically added nop frame header, skip
+            DEBUG_PRINT_IF(DBG_SHALLOW_PARSE, "at %d, skip header\n", i);
+            i++;
+            size--;
+            continue;
+        }
+
+        //++frame_num;
+
+        fh_mode = buf[i] >> 6;
+        fh_param = (buf[i] >> 2) & 0xf;
+
+        i++;
+        size--;
+
+        if (fh_mode == 1) {
+            // control frame.
+            if (fh_param == 0) {
+                // skip frame, we skip it (1 byte)
+                i++;
+                size--;
+                DEBUG_PRINT_IF(DBG_SHALLOW_PARSE, "at %d, a skip frame\n", iLastFrame);
+            } else if (fh_param == 1) {
+                // sensortime frame  (3 bytes)
+                i += 3;
+                size -= 3;
+                DEBUG_PRINT_IF(DBG_SHALLOW_PARSE, "at %d, a sensor_time frame\n", iLastFrame);
+            } else if (fh_param == 2) {
+                // fifo_input config frame (1byte)
+                i++;
+                size--;
+                DEBUG_PRINT_IF(DBG_SHALLOW_PARSE, "at %d, a fifo cfg frame\n", iLastFrame);
+            } else {
+                size = 0; // drop this batch
+                DEBUG_PRINT_IF(DBG_SHALLOW_PARSE, "Invalid fh_param in control frame!!\n");
+                // mark invalid
+                buf[iLastFrame] = BMI160_FRAME_HEADER_INVALID;
+                return NULL;
+            }
+        } else if (fh_mode == 2) {
+            // regular frame, dispatch data to each sensor's own fifo
+            if (fh_param & 4) { // have mag data
+                i += 8;
+                size -= 8;
+            }
+            if (fh_param & 2) { // have gyro data
+                i += 6;
+                size -= 6;
+            }
+            if (fh_param & 1) { // have accel data
+                i += 6;
+                size -= 6;
+            }
+            DEBUG_PRINT_IF(DBG_SHALLOW_PARSE, "at %d, a reg frame acc %d, gyro %d, mag %d\n",
+                       iLastFrame, fh_param &1 ? 1:0, fh_param&2?1:0, fh_param&4?1:0);
+        } else {
+            size = 0; // drop this batch
+            DEBUG_PRINT_IF(DBG_SHALLOW_PARSE, "spf: Invalid fh_mode %d!!\n", fh_mode);
+            //mark invalid
+            buf[iLastFrame] = BMI160_FRAME_HEADER_INVALID;
+            return NULL;
+        }
+    }
+
+    // there is a partial frame, return where to write next chunck of data
+    DEBUG_PRINT_IF(DBG_SHALLOW_PARSE, "partial frame ends %p\n", buf + iLastFrame);
+    return buf + iLastFrame;
+}
+
+/**
+ * Intialize the first read of chunked SPI read sequence.
+ *
+ * @param index starting index of the txrxBuffer in which the data will be write into.
+ */
+static void chunkedReadInit_(TASK, int index, int size) {
+
+    if (GET_STATE() != SENSOR_INT_1_HANDLING) {
+        ERROR_PRINT("chunkedReadInit in wrong mode");
+        return;
+    }
+
+    T(mWbufCnt) = index;
+    if (T(mWbufCnt) > FIFO_READ_SIZE) {
+        // drop data to prevent bigger issue
+        T(mWbufCnt) = 0;
+    }
+    T(chunkReadSize) = size > CHUNKED_READ_SIZE ? size : CHUNKED_READ_SIZE;
+
+    DEBUG_PRINT_IF(DBG_CHUNKED, "crd %d>>%d\n", T(chunkReadSize), index);
+    SPI_READ(BMI160_REG_FIFO_DATA, T(chunkReadSize), &T(dataBuffer));
+    spiBatchTxRx(&T(mode), chunkedReadSpiCallback, _task, __FUNCTION__);
+}
+
+/**
+ * Chunked SPI read callback.
+ *
+ * Handles the chunked read logic: issue additional read if necessary, or calls sensorSpiCallback()
+ * if the entire FIFO is read.
+ *
+ * @param cookie extra data
+ * @param err    error
+ *
+ * @see sensorSpiCallback()
+ */
+static void chunkedReadSpiCallback(void *cookie, int err) {
+    TASK = (_Task*) cookie;
+
+    T(spiInUse) = false;
+    DEBUG_PRINT_IF(err !=0 || GET_STATE() != SENSOR_INT_1_HANDLING,
+            "crcb,e:%d,s:%d", err, (int)GET_STATE());
+    bool int1 = gpioGet(T(Int1));
+    if (err != 0) {
+        DEBUG_PRINT_IF(DBG_CHUNKED, "crd retry");
+        // read full fifo length to be safe
+        chunkedReadInit(0, FIFO_READ_SIZE);
+        return;
+    }
+
+    *T(dataBuffer) = BMI160_FRAME_HEADER_SKIP; // fill the 0x00/0xff hole at the first byte
+    uint8_t* end = shallowParseFrame(T(dataBuffer), T(chunkReadSize));
+
+    if (end == NULL) {
+        // if interrupt is still set after read for some reason, set the pending interrupt
+        // to handle it immediately after data is handled.
+        T(pending_int[0]) = T(pending_int[0]) || int1;
+
+        // recover the buffer and valid data size to make it looks like a single read so that
+        // real frame parse works properly
+        T(dataBuffer) = T(txrxBuffer);
+        T(xferCnt) = FIFO_READ_SIZE;
+        sensorSpiCallback(cookie, err);
+    } else {
+        DEBUG_PRINT_IF(DBG_CHUNKED, "crd cont");
+        chunkedReadInit(end - T(txrxBuffer), CHUNKED_READ_SIZE);
+    }
+}
+
+/**
+ * Initiate read of sensor fifo.
+ *
+ * If task is in idle state, init chunked FIFO read; otherwise, submit an interrupt message or mark
+ * the read pending depending if it is called in interrupt context.
+ *
+ * @param isInterruptContext true if called from interrupt context; false otherwise.
+ *
+ */
+static void initiateFifoRead_(TASK, bool isInterruptContext) {
+    if (trySwitchState(SENSOR_INT_1_HANDLING)) {
+        // estimate first read size to be watermark + 1 more sample + some extra
+        int firstReadSize = T(watermark) * 4 + 32; // 1+6+6+8+1+3 + extra = 25 + extra = 32
+        if (firstReadSize < CHUNKED_READ_SIZE) {
+            firstReadSize = CHUNKED_READ_SIZE;
+        }
+        chunkedReadInit(0, firstReadSize);
+    } else {
+        if (isInterruptContext) {
+            // called from interrupt context, queue event
+            osEnqueuePrivateEvt(EVT_SENSOR_INTERRUPT_1, _task, NULL, T(tid));
+        } else {
+            // non-interrupt context, set pending flag, so next time it will be picked up after
+            // switching back to idle.
+            // Note: even if we are still in SENSOR_INT_1_HANDLING, the SPI may already finished and
+            // we need to issue another SPI read to get the latest status.
+            T(pending_int[0]) = true;
+        }
+    }
+}
+
+/**
+ * Calculate fifo size using normalized input.
+ *
+ * @param iPeriod normalized period vector
+ * @param iLatency normalized latency vector
+ * @param factor vector that contains size factor for each sensor
+ * @param n size of the vectors
+ *
+ * @return max size of FIFO to guarantee latency requirements of all sensors or SIZE_MAX if no
+ * sensor is active.
+ */
+static size_t calcFifoSize(const int* iPeriod, const int* iLatency, const int* factor, int n) {
+    int i;
+
+    int minLatency = INT_MAX;
+    for (i = 0; i < n; i++) {
+        if (iLatency[i] > 0) {
+            minLatency = iLatency[i] < minLatency ? iLatency[i] : minLatency;
+        }
+    }
+    DEBUG_PRINT_IF(DBG_WM_CALC, "cfifo: min latency %d unit", minLatency);
+
+    bool anyActive = false;
+    size_t s = 0;
+    size_t head = 0;
+    for (i = 0; i < n; i++) {
+        if (iPeriod[i] > 0) {
+            anyActive = true;
+            size_t t =  minLatency / iPeriod[i];
+            head = t > head ? t : head;
+            s += t * factor[i];
+            DEBUG_PRINT_IF(DBG_WM_CALC, "cfifo: %d, s+= %d*%d, head = %d", i, t, factor[i], head);
+        }
+    }
+
+    return anyActive ? head + s : SIZE_MAX;
+}
+
+/**
+ * Calculate the watermark setting from sensor registration information
+ *
+ * It is assumed  that all sensor period share a common denominator (true for BMI160) and the
+ * latency of sensor will be lower bounded by its sampling period.
+ *
+ * @return watermark register setting
+ */
+static uint8_t calcWatermark2_(TASK) {
+    int period[] = {-1, -1, -1};
+    int latency[] = {-1, -1, -1};
+    const int factor[] = {6, 6, 8};
+    int i;
+
+    for (i = ACC; i <= MAG; ++i) {
+        if (T(sensors[i]).configed) {
+            period[i - ACC] = SENSOR_HZ((float)WATERMARK_MAX_SENSOR_RATE) / T(sensors[i]).rate;
+            latency[i - ACC] = U64_DIV_BY_U64_CONSTANT(
+                    T(sensors[i]).latency + WATERMARK_TIME_UNIT_NS/2, WATERMARK_TIME_UNIT_NS);
+            DEBUG_PRINT_IF(DBG_WM_CALC, "cwm2: f %dHz, l %dus => T %d unit, L %d unit",
+                    (int) T(sensors[i]).rate/1024,
+                    (int) U64_DIV_BY_U64_CONSTANT(T(sensors[i]).latency, 1000),
+                    period[i-ACC], latency[i-ACC]);
+        }
+    }
+
+
+    size_t watermark = calcFifoSize(period, latency, factor, MAG - ACC + 1) / 4;
+    DEBUG_PRINT_IF(DBG_WM_CALC, "cwm2: wm = %d", watermark);
+    watermark = watermark < WATERMARK_MIN ? WATERMARK_MIN : watermark;
+    watermark = watermark > WATERMARK_MAX ? WATERMARK_MAX : watermark;
+
+    return watermark;
+}
+
 INTERNAL_APP_INIT(BMI160_APP_ID, 1, startTask, endTask, handleEvent);
+
+
diff --git a/firmware/src/drivers/bosch_bmi160/bosch_bmm150_slave.c b/firmware/src/drivers/bosch_bmi160/bosch_bmm150_slave.c
index c4971df..4a223fb 100644
--- a/firmware/src/drivers/bosch_bmi160/bosch_bmm150_slave.c
+++ b/firmware/src/drivers/bosch_bmi160/bosch_bmm150_slave.c
@@ -19,7 +19,7 @@
 
 #define kScale_mag 0.0625f         // 1.0f / 16.0f;
 
-void bmm150SaveDigData(MagTask_t *magTask, uint8_t *data, size_t offset)
+void bmm150SaveDigData(struct MagTask *magTask, uint8_t *data, size_t offset)
 {
     // magnetometer temperature calibration data is read in 3 bursts of 8 byte
     // length each.
@@ -45,8 +45,7 @@
     }
 }
 
-static int32_t bmm150TempCompensateX(
-       MagTask_t *magTask, int16_t mag_x, uint16_t rhall)
+static int32_t bmm150TempCompensateX(struct MagTask *magTask, int16_t mag_x, uint16_t rhall)
 {
     int32_t inter_retval = 0;
 
@@ -82,7 +81,7 @@
     return inter_retval;
 }
 
-static int32_t bmm150TempCompensateY(MagTask_t *magTask, int16_t mag_y, uint16_t rhall)
+static int32_t bmm150TempCompensateY(struct MagTask *magTask, int16_t mag_y, uint16_t rhall)
 {
     int32_t inter_retval = 0;
 
@@ -118,7 +117,7 @@
     return inter_retval;
 }
 
-static int32_t bmm150TempCompensateZ(MagTask_t *magTask, int16_t mag_z, uint16_t rhall)
+static int32_t bmm150TempCompensateZ(struct MagTask *magTask, int16_t mag_z, uint16_t rhall)
 {
     int32_t retval = 0;
     if (mag_z != BMM150_MAG_HALL_OVERFLOW_ADCVAL) {
@@ -136,7 +135,7 @@
     return retval;
 }
 
-void parseMagData(MagTask_t *magTask, uint8_t *buf, float *x, float *y, float *z) {
+void parseMagData(struct MagTask *magTask, uint8_t *buf, float *x, float *y, float *z) {
     int32_t mag_x = (*(int16_t *)&buf[0]) >> 3;
     int32_t mag_y = (*(int16_t *)&buf[2]) >> 3;
     int32_t mag_z = (*(int16_t *)&buf[4]) >> 1;
diff --git a/firmware/src/drivers/bosch_bmi160/bosch_bmm150_slave.h b/firmware/src/drivers/bosch_bmi160/bosch_bmm150_slave.h
index 6947668..59e53fb 100644
--- a/firmware/src/drivers/bosch_bmi160/bosch_bmm150_slave.h
+++ b/firmware/src/drivers/bosch_bmi160/bosch_bmm150_slave.h
@@ -53,7 +53,7 @@
 #define BMM150_CALIB_HEX_LACKS              0x100000
 #define BMM150_MAG_OVERFLOW_OUTPUT_S32      ((int32_t)(-2147483647-1))
 
-typedef struct BMM150Task {
+struct MagTask {
     uint16_t dig_z1;
     int16_t dig_z2, dig_z3, dig_z4;
     uint16_t dig_xyz1;
@@ -61,13 +61,13 @@
     int8_t dig_x1, dig_y1, dig_x2, dig_y2;
     uint8_t dig_xy1;
     int8_t dig_xy2;
-} MagTask_t;
+};
 
 #define MAG_I2C_ADDR 0x10
 #define MAG_REG_DATA BMM150_REG_DATA
 
-void bmm150SaveDigData(MagTask_t *magTask, uint8_t *data, size_t offset);
-void parseMagData(MagTask_t *magTask, uint8_t *buf, float *x, float *y, float *z);
+void bmm150SaveDigData(struct MagTask *magTask, uint8_t *data, size_t offset);
+void parseMagData(struct MagTask *magTask, uint8_t *buf, float *x, float *y, float *z);
 
 #ifdef __cplusplus
 }
diff --git a/firmware/src/drivers/hall/hall.c b/firmware/src/drivers/hall/hall.c
index 208103f..030aa8a 100644
--- a/firmware/src/drivers/hall/hall.c
+++ b/firmware/src/drivers/hall/hall.c
@@ -32,8 +32,11 @@
 #include <plat/inc/syscfg.h>
 #include <variant/inc/variant.h>
 
+#define APP_VERSION 2
+
 #define HALL_REPORT_OPENED_VALUE  0
 #define HALL_REPORT_CLOSED_VALUE  1
+#define HALL_DEBOUNCE_TIMER_DELAY 10000000ULL // 10 milliseconds
 
 #ifndef HALL_PIN
 #error "HALL_PIN is not defined; please define in variant.h"
@@ -43,6 +46,7 @@
 #error "HALL_IRQ is not defined; please define in variant.h"
 #endif
 
+
 static struct SensorTask
 {
     struct Gpio *pin;
@@ -50,26 +54,49 @@
 
     uint32_t id;
     uint32_t sensorHandle;
+    uint32_t debounceTimerHandle;
+
+    int32_t prevReportedValue;
 
     bool on;
 } mTask;
 
+static void debounceTimerCallback(uint32_t timerId, void *cookie)
+{
+    union EmbeddedDataPoint sample;
+    bool prevPinState = (bool)cookie;
+    bool pinState = gpioGet(mTask.pin);
+
+    if (mTask.on) {
+        if (pinState == prevPinState) {
+            sample.idata = pinState ? HALL_REPORT_OPENED_VALUE :
+                HALL_REPORT_CLOSED_VALUE;
+
+            if (sample.idata != mTask.prevReportedValue) {
+                mTask.prevReportedValue = sample.idata;
+                osEnqueueEvt(sensorGetMyEventType(SENS_TYPE_HALL), sample.vptr, NULL);
+            }
+        }
+    }
+}
+
 static bool hallIsr(struct ChainedIsr *localIsr)
 {
     struct SensorTask *data = container_of(localIsr, struct SensorTask, isr);
+    bool pinState = gpioGet(data->pin);
 
     if (!extiIsPendingGpio(data->pin)) {
         return false;
     }
 
     if (data->on) {
-        union EmbeddedDataPoint sample;
-        bool pinState = gpioGet(data->pin);
-        sample.idata = pinState ? HALL_REPORT_OPENED_VALUE :
-            HALL_REPORT_CLOSED_VALUE;
-        osEnqueueEvt(sensorGetMyEventType(SENS_TYPE_HALL), sample.vptr, NULL);
+        if (mTask.debounceTimerHandle)
+            timTimerCancel(mTask.debounceTimerHandle);
+
+        mTask.debounceTimerHandle = timTimerSet(HALL_DEBOUNCE_TIMER_DELAY, 0, 50, debounceTimerCallback, (void*)pinState, true /* oneShot */);
     }
 
+
     extiClearPendingGpio(data->pin);
     return true;
 }
@@ -90,9 +117,16 @@
     return true;
 }
 
+static const uint32_t supportedRates[] =
+{
+    SENSOR_RATE_ONCHANGE,
+    0
+};
+
 static const struct SensorInfo mSensorInfo =
 {
     .sensorName = "Hall",
+    .supportedRates = supportedRates,
     .sensorType = SENS_TYPE_HALL,
     .numAxis = NUM_AXIS_EMBEDDED,
     .interrupt = NANOHUB_INT_WAKEUP,
@@ -110,6 +144,13 @@
     }
 
     mTask.on = on;
+    mTask.prevReportedValue = -1;
+
+    if (mTask.debounceTimerHandle) {
+        timTimerCancel(mTask.debounceTimerHandle);
+        mTask.debounceTimerHandle = 0;
+    }
+
     return sensorSignalInternalEvt(mTask.sensorHandle, SENSOR_INTERNAL_EVT_POWER_STATE_CHG, on, 0);
 }
 
@@ -137,12 +178,26 @@
     return osEnqueueEvt(sensorGetMyEventType(SENS_TYPE_HALL), SENSOR_DATA_EVENT_FLUSH, NULL);
 }
 
+static bool hallSendLastSample(void *cookie, uint32_t tid)
+{
+    union EmbeddedDataPoint sample;
+    bool result = true;
+
+    if (mTask.prevReportedValue != -1) {
+        sample.idata = mTask.prevReportedValue;
+        result = osEnqueuePrivateEvt(sensorGetMyEventType(SENS_TYPE_HALL), sample.vptr, NULL, tid);
+    }
+
+    return result;
+}
+
 static const struct SensorOps mSensorOps =
 {
     .sensorPower = hallPower,
     .sensorFirmwareUpload = hallFirmwareUpload,
     .sensorSetRate = hallSetRate,
     .sensorFlush = hallFlush,
+    .sensorSendOneDirectEvt = hallSendLastSample
 };
 
 static void handleEvent(uint32_t evtType, const void* evtData)
@@ -155,6 +210,7 @@
 
     mTask.id = taskId;
     mTask.sensorHandle = sensorRegister(&mSensorInfo, &mSensorOps, NULL, true);
+    mTask.prevReportedValue = -1;
     mTask.pin = gpioRequest(HALL_PIN);
     mTask.isr.func = hallIsr;
 
@@ -170,4 +226,4 @@
     sensorUnregister(mTask.sensorHandle);
 }
 
-INTERNAL_APP_INIT(APP_ID_MAKE(APP_ID_VENDOR_GOOGLE, 6), 0, startTask, endTask, handleEvent);
+INTERNAL_APP_INIT(APP_ID_MAKE(APP_ID_VENDOR_GOOGLE, 6), APP_VERSION, startTask, endTask, handleEvent);
diff --git a/firmware/src/drivers/hall_twopole/hall_twopole.c b/firmware/src/drivers/hall_twopole/hall_twopole.c
new file mode 100644
index 0000000..4d69d54
--- /dev/null
+++ b/firmware/src/drivers/hall_twopole/hall_twopole.c
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <float.h>
+
+#include <eventnums.h>
+#include <gpio.h>
+#include <heap.h>
+#include <hostIntf.h>
+#include <isr.h>
+#include <nanohubPacket.h>
+#include <sensors.h>
+#include <seos.h>
+#include <timer.h>
+#include <plat/inc/gpio.h>
+#include <plat/inc/exti.h>
+#include <plat/inc/syscfg.h>
+#include <variant/inc/variant.h>
+
+#define APP_ID      APP_ID_MAKE(APP_ID_VENDOR_GOOGLE, 11)
+#define APP_VERSION 2
+
+#define HALL_REPORT_OPENED_VALUE  0
+#define HALL_REPORT_CLOSED_VALUE  1
+#define HALL_DEBOUNCE_TIMER_DELAY 10000000ULL // 10 milliseconds
+
+#ifndef HALL_S_PIN
+#error "HALL_S_PIN is not defined; please define in variant.h"
+#endif
+#ifndef HALL_S_IRQ
+#error "HALL_S_IRQ is not defined; please define in variant.h"
+#endif
+#ifndef HALL_N_PIN
+#error "HALL_N_PIN is not defined; please define in variant.h"
+#endif
+#ifndef HALL_N_IRQ
+#error "HALL_N_IRQ is not defined; please define in variant.h"
+#endif
+
+#define MAKE_TYPE(sPin,nPin) (sPin ? HALL_REPORT_OPENED_VALUE : HALL_REPORT_CLOSED_VALUE) + \
+    ((nPin ? HALL_REPORT_OPENED_VALUE : HALL_REPORT_CLOSED_VALUE) << 1)
+
+static struct SensorTask
+{
+    struct Gpio *sPin;
+    struct Gpio *nPin;
+    struct ChainedIsr sIsr;
+    struct ChainedIsr nIsr;
+
+    uint32_t id;
+    uint32_t sensorHandle;
+    uint32_t debounceTimerHandle;
+
+    int32_t prevReportedState;
+
+    bool on;
+} mTask;
+
+static void hallReportState(int32_t pinState)
+{
+    union EmbeddedDataPoint sample;
+    if (pinState != mTask.prevReportedState) {
+        mTask.prevReportedState = pinState;
+        sample.idata = pinState;
+        osEnqueueEvt(sensorGetMyEventType(SENS_TYPE_HALL), sample.vptr, NULL);
+    }
+}
+
+static void debounceTimerCallback(uint32_t timerId, void *cookie)
+{
+    int32_t prevPinState = (int32_t)cookie;
+    int32_t currPinState = MAKE_TYPE(gpioGet(mTask.sPin), gpioGet(mTask.nPin));
+
+    if (mTask.on && (currPinState == prevPinState)) {
+        hallReportState(currPinState);
+    }
+}
+
+static void startDebounceTimer(struct SensorTask *data)
+{
+    int32_t currPinState = MAKE_TYPE(gpioGet(data->sPin), gpioGet(data->nPin));
+    if (data->debounceTimerHandle)
+        timTimerCancel(data->debounceTimerHandle);
+
+    data->debounceTimerHandle = timTimerSet(HALL_DEBOUNCE_TIMER_DELAY, 0, 50, debounceTimerCallback, (void*)currPinState, true /* oneShot */);
+}
+
+static bool hallSouthIsr(struct ChainedIsr *localIsr)
+{
+    struct SensorTask *data = container_of(localIsr, struct SensorTask, sIsr);
+    if (data->on)
+        startDebounceTimer(data);
+    extiClearPendingGpio(data->sPin);
+    return true;
+}
+
+static bool hallNorthIsr(struct ChainedIsr *localIsr)
+{
+    struct SensorTask *data = container_of(localIsr, struct SensorTask, nIsr);
+    if (data->on)
+        startDebounceTimer(data);
+    extiClearPendingGpio(data->nPin);
+    return true;
+}
+
+static bool enableInterrupt(struct Gpio *pin, struct ChainedIsr *isr, IRQn_Type irqn)
+{
+    gpioConfigInput(pin, GPIO_SPEED_LOW, GPIO_PULL_NONE);
+    syscfgSetExtiPort(pin);
+    extiEnableIntGpio(pin, EXTI_TRIGGER_BOTH);
+    extiChainIsr(irqn, isr);
+    return true;
+}
+
+static bool disableInterrupt(struct Gpio *pin, struct ChainedIsr *isr, IRQn_Type irqn)
+{
+    extiUnchainIsr(irqn, isr);
+    extiDisableIntGpio(pin);
+    return true;
+}
+
+static const uint32_t supportedRates[] =
+{
+    SENSOR_RATE_ONCHANGE,
+    0
+};
+
+static const struct SensorInfo mSensorInfo =
+{
+    .sensorName = "Hall",
+    .supportedRates = supportedRates,
+    .sensorType = SENS_TYPE_HALL,
+    .numAxis = NUM_AXIS_EMBEDDED,
+    .interrupt = NANOHUB_INT_WAKEUP,
+    .minSamples = 20
+};
+
+static bool hallPower(bool on, void *cookie)
+{
+    if (on) {
+        extiClearPendingGpio(mTask.sPin);
+        extiClearPendingGpio(mTask.nPin);
+        enableInterrupt(mTask.sPin, &mTask.sIsr, HALL_S_IRQ);
+        enableInterrupt(mTask.nPin, &mTask.nIsr, HALL_N_IRQ);
+    } else {
+        disableInterrupt(mTask.sPin, &mTask.sIsr, HALL_S_IRQ);
+        disableInterrupt(mTask.nPin, &mTask.nIsr, HALL_N_IRQ);
+        extiClearPendingGpio(mTask.sPin);
+        extiClearPendingGpio(mTask.nPin);
+    }
+
+    mTask.on = on;
+    mTask.prevReportedState = -1;
+
+    if (mTask.debounceTimerHandle) {
+        timTimerCancel(mTask.debounceTimerHandle);
+        mTask.debounceTimerHandle = 0;
+    }
+
+    return sensorSignalInternalEvt(mTask.sensorHandle, SENSOR_INTERNAL_EVT_POWER_STATE_CHG, on, 0);
+}
+
+static bool hallFirmwareUpload(void *cookie)
+{
+    return sensorSignalInternalEvt(mTask.sensorHandle, SENSOR_INTERNAL_EVT_FW_STATE_CHG, 1, 0);
+}
+
+static bool hallSetRate(uint32_t rate, uint64_t latency, void *cookie)
+{
+    // report initial state of hall interrupt pin
+    if (mTask.on)
+        hallReportState(MAKE_TYPE(gpioGet(mTask.sPin), gpioGet(mTask.nPin)));
+
+    return sensorSignalInternalEvt(mTask.sensorHandle, SENSOR_INTERNAL_EVT_RATE_CHG, rate, latency);
+}
+
+static bool hallFlush(void *cookie)
+{
+    return osEnqueueEvt(sensorGetMyEventType(SENS_TYPE_HALL), SENSOR_DATA_EVENT_FLUSH, NULL);
+}
+
+static bool hallSendLastSample(void *cookie, uint32_t tid)
+{
+    union EmbeddedDataPoint sample;
+    bool result = true;
+
+    if (mTask.prevReportedState != -1) {
+        sample.idata = mTask.prevReportedState;
+        result = osEnqueuePrivateEvt(sensorGetMyEventType(SENS_TYPE_HALL), sample.vptr, NULL, tid);
+    }
+
+    return result;
+}
+
+static const struct SensorOps mSensorOps =
+{
+    .sensorPower = hallPower,
+    .sensorFirmwareUpload = hallFirmwareUpload,
+    .sensorSetRate = hallSetRate,
+    .sensorFlush = hallFlush,
+    .sensorSendOneDirectEvt = hallSendLastSample
+};
+
+static void handleEvent(uint32_t evtType, const void* evtData)
+{
+}
+
+static bool startTask(uint32_t taskId)
+{
+    osLog(LOG_INFO, "HALL: task starting\n");
+
+    mTask.id = taskId;
+    mTask.sensorHandle = sensorRegister(&mSensorInfo, &mSensorOps, NULL, true);
+    mTask.prevReportedState = -1;
+    mTask.sPin = gpioRequest(HALL_S_PIN);
+    mTask.nPin = gpioRequest(HALL_N_PIN);
+    mTask.sIsr.func = hallSouthIsr;
+    mTask.nIsr.func = hallNorthIsr;
+
+    return true;
+}
+
+static void endTask(void)
+{
+    disableInterrupt(mTask.sPin, &mTask.sIsr, HALL_S_IRQ);
+    disableInterrupt(mTask.nPin, &mTask.nIsr, HALL_N_IRQ);
+    extiUnchainIsr(HALL_S_IRQ, &mTask.sIsr);
+    extiUnchainIsr(HALL_N_IRQ, &mTask.nIsr);
+    extiClearPendingGpio(mTask.sPin);
+    extiClearPendingGpio(mTask.nPin);
+    gpioRelease(mTask.sPin);
+    gpioRelease(mTask.nPin);
+    sensorUnregister(mTask.sensorHandle);
+    memset(&mTask, 0, sizeof(struct SensorTask));
+}
+
+INTERNAL_APP_INIT(APP_ID, APP_VERSION, startTask, endTask, handleEvent);
diff --git a/firmware/src/drivers/rohm_rpr0521/rohm_rpr0521.c b/firmware/src/drivers/rohm_rpr0521/rohm_rpr0521.c
index 97821a8..c318313 100644
--- a/firmware/src/drivers/rohm_rpr0521/rohm_rpr0521.c
+++ b/firmware/src/drivers/rohm_rpr0521/rohm_rpr0521.c
@@ -534,10 +534,10 @@
     case SENSOR_STATE_INIT_OFFSETS:
         /* PS Threshold register */
         mTask.txrxBuf[0] = ROHM_RPR0521_REG_PS_TH_LSB;
-        mTask.txrxBuf[1] = (ROHM_RPR0521_THRESHOLD_ASSERT_NEAR && 0xFF);
-        mTask.txrxBuf[2] = (ROHM_RPR0521_THRESHOLD_ASSERT_NEAR && 0xFF00) >> 8;
-        mTask.txrxBuf[3] = (ROHM_RPR0521_THRESHOLD_DEASSERT_NEAR && 0xFF);
-        mTask.txrxBuf[4] = (ROHM_RPR0521_THRESHOLD_DEASSERT_NEAR && 0xFF00) >> 8;
+        mTask.txrxBuf[1] = (ROHM_RPR0521_THRESHOLD_ASSERT_NEAR & 0xFF);
+        mTask.txrxBuf[2] = (ROHM_RPR0521_THRESHOLD_ASSERT_NEAR & 0xFF00) >> 8;
+        mTask.txrxBuf[3] = (ROHM_RPR0521_THRESHOLD_DEASSERT_NEAR & 0xFF);
+        mTask.txrxBuf[4] = (ROHM_RPR0521_THRESHOLD_DEASSERT_NEAR & 0xFF00) >> 8;
         i2cMasterTx(I2C_BUS_ID, I2C_ADDR, mTask.txrxBuf, 5, &i2cCallback, (void *)SENSOR_STATE_INIT_THRESHOLDS);
         break;
 
@@ -703,5 +703,5 @@
     }
 }
 
-INTERNAL_APP_INIT(APP_ID_MAKE(APP_ID_VENDOR_GOOGLE, 10), 0, init_app, end_app, handle_event);
+INTERNAL_APP_INIT(APP_ID_MAKE(APP_ID_VENDOR_GOOGLE, 10), 1, init_app, end_app, handle_event);
 
diff --git a/firmware/src/heap.c b/firmware/src/heap.c
index ab3a183..b172c70 100644
--- a/firmware/src/heap.c
+++ b/firmware/src/heap.c
@@ -19,14 +19,25 @@
 #include <stdint.h>
 #include <stdio.h>
 #include <heap.h>
+#include <seos.h>
 
+#define TIDX_HEAP_EXTRA 2 // must be >= 0; best if > 0, don't make it > 7, since it unnecessarily limits max heap size we can manage
 
+#define TIDX_HEAP_BITS (TASK_IDX_BITS + TIDX_HEAP_EXTRA)
+
+#define TIDX_MASK ((1 << TIDX_HEAP_BITS) - 1)
+#define MAX_HEAP_ORDER (31 - TIDX_HEAP_BITS)
+
+#if MAX_HEAP_ORDER < 16
+# error Too little HEAP is available
+#endif
 
 struct HeapNode {
 
     struct HeapNode* prev;
-    uint32_t size:31;
+    uint32_t size: MAX_HEAP_ORDER;
     uint32_t used: 1;
+    uint32_t tidx: TIDX_HEAP_BITS; // TASK_IDX_BITS to uniquely identify task; + extra bits of redundant counter add extra protection
     uint8_t  data[];
 };
 
@@ -133,6 +144,7 @@
         node = (struct HeapNode*)(best->data + sz);
 
         node->used = 0;
+        node->tidx = 0;
         node->size = best->size - sz - sizeof(struct HeapNode);
         node->prev = best;
 
@@ -145,6 +157,7 @@
     }
 
     best->used = 1;
+    best->tidx = osGetCurrentTid();
     ret = best->data;
 
 out:
@@ -157,10 +170,17 @@
     struct HeapNode *node, *t;
     bool haveLock;
 
+    if (ptr == NULL) {
+        // NULL is a valid reply from heapAlloc, and thus it is not an error for
+        // us to receive it here.  We just ignore it.
+        return;
+    }
+
     haveLock = trylockTryTake(&gHeapLock);
 
     node = ((struct HeapNode*)ptr) - 1;
     node->used = 0;
+    node->tidx = 0;
 
     if (haveLock) {
 
@@ -182,5 +202,31 @@
         gNeedFreeMerge = true;
 }
 
+int heapFreeAll(uint32_t tid)
+{
+    struct HeapNode *node;
+    bool haveLock;
+    int count = 0;
 
+    if (!tid)
+        return -1;
 
+    // this can only fail if called from interrupt
+    haveLock = trylockTryTake(&gHeapLock);
+    if (!haveLock)
+        return -1;
+
+    node = gHeapHead;
+    tid &= TIDX_MASK;
+    do {
+        if (node->tidx == tid) {
+            node->used = 0;
+            node->tidx = 0;
+            count++;
+        }
+    } while ((node = heapPrvGetNext(node)) != NULL);
+    gNeedFreeMerge = true;
+    trylockRelease(&gHeapLock);
+
+    return count;
+}
diff --git a/firmware/src/hostIntf.c b/firmware/src/hostIntf.c
index 43144b9..052ba9f 100644
--- a/firmware/src/hostIntf.c
+++ b/firmware/src/hostIntf.c
@@ -20,12 +20,15 @@
 #include <string.h>
 #include <alloca.h>
 
-#include <plat/inc/pwr.h>
 #include <variant/inc/variant.h>
+#include <eventnums.h>
+
+#include <plat/inc/pwr.h>
+
+#include <nanohub/crc.h>
 
 #include <platform.h>
 #include <cpu.h>
-#include <crc.h>
 #include <hostIntf.h>
 #include <hostIntf_priv.h>
 #include <nanohubCommand.h>
@@ -196,7 +199,7 @@
 {
     struct hostIntfIntErrMsg *msg = (struct hostIntfIntErrMsg *)cookie;
     osLog(msg->level, "%s failed with: %d\n", msg->func, msg->reason);
-    atomicAdd(&mIntErrMsgCnt, -1UL);
+    atomicAdd32bits(&mIntErrMsgCnt, -1UL);
 }
 
 static void hostIntfDeferErrLog(enum LogLevel level, enum hostIntfIntErrReason reason, const char *func)
@@ -209,7 +212,7 @@
     mIntErrMsg[mIntErrMsgIdx].reason = reason;
     mIntErrMsg[mIntErrMsgIdx].func = func;
     if (osDefer(hostIntfPrintErrMsg, &mIntErrMsg[mIntErrMsgIdx], false)) {
-        atomicAdd(&mIntErrMsgCnt, 1UL);
+        atomicAdd32bits(&mIntErrMsgCnt, 1UL);
         mIntErrMsgIdx = (mIntErrMsgIdx + 1) % HOSTINTF_MAX_ERR_MSG;
     }
 }
@@ -1050,7 +1053,7 @@
             osEventSubscribe(mHostIntfTid, EVT_NO_SENSOR_CONFIG_EVENT);
             osEventSubscribe(mHostIntfTid, EVT_APP_TO_HOST);
 #ifdef DEBUG_LOG_EVT
-            osEventSubscribe(mHostIntfTid, DEBUG_LOG_EVT);
+            osEventSubscribe(mHostIntfTid, EVT_DEBUG_LOG);
             platEarlyLogFlush();
 #endif
             reason = pwrResetReason();
@@ -1082,7 +1085,7 @@
             halCmd->handler((void *)&halMsg[2], halMsg[0] - 1);
     }
 #ifdef DEBUG_LOG_EVT
-    else if (evtType == DEBUG_LOG_EVT) {
+    else if (evtType == EVT_DEBUG_LOG) {
         data = (struct HostIntfDataBuffer *)evtData;
         if (data->sensType == SENS_TYPE_INVALID && data->dataType == HOSTINTF_DATA_TYPE_LOG) {
             simpleQueueEnqueue(mOutputQ, evtData, sizeof(uint32_t) + data->length, true);
diff --git a/firmware/src/nanohubCommand.c b/firmware/src/nanohubCommand.c
index d037d2d..5e5f33b 100644
--- a/firmware/src/nanohubCommand.c
+++ b/firmware/src/nanohubCommand.c
@@ -14,13 +14,22 @@
  * limitations under the License.
  */
 
-#include <plat/inc/taggedPtr.h>
-#include <plat/inc/rtc.h>
 #include <inttypes.h>
 #include <string.h>
 #include <stdint.h>
 #include <sys/endian.h>
 
+#include <variant/inc/variant.h>
+#include <eventnums.h>
+
+#include <plat/inc/taggedPtr.h>
+#include <plat/inc/rtc.h>
+#include <plat/inc/bl.h>
+#include <plat/inc/plat.h>
+
+#include <nanohub/crc.h>
+#include <nanohub/rsa.h>
+
 #include <atomicBitset.h>
 #include <atomic.h>
 #include <hostIntf.h>
@@ -35,13 +44,8 @@
 #include <slab.h>
 #include <sensType.h>
 #include <timer.h>
-#include <crc.h>
-#include <rsa.h>
 #include <appSec.h>
 #include <cpu.h>
-#include <plat/inc/bl.h>
-#include <plat/inc/plat.h>
-#include <variant/inc/variant.h>
 
 #define NANOHUB_COMMAND(_reason, _fastHandler, _handler, _minReqType, _maxReqType) \
         { .reason = _reason, .fastHandler = _fastHandler, .handler = _handler, \
@@ -53,20 +57,29 @@
 #define SYNC_DATAPOINTS 16
 #define SYNC_RESET      10000000000ULL /* 10 seconds, ~100us drift */
 
+// maximum number of bytes to feed into appSecRxData at once
+// The bigger the number, the more time we block other event processing
+// appSecRxData only feeds 16 bytes at a time into writeCbk, so large
+// numbers don't buy us that much
+#define MAX_APP_SEC_RX_DATA_LEN 64
+
+#define REQUIRE_SIGNED_IMAGE    true
+
 struct DownloadState
 {
     struct AppSecState *appSecState;
-    uint32_t size;
-    uint32_t srcOffset;
-    uint32_t dstOffset;
-    uint8_t *start;
-    uint32_t crc;
-    uint32_t srcCrc;
+    uint32_t size;      // document size, as reported by client
+    uint32_t srcOffset; // bytes received from client
+    uint32_t dstOffset; // bytes sent to flash
+    struct AppHdr *start;     // start of flash segment, where to write
+    uint32_t crc;       // document CRC-32, as reported by client
+    uint32_t srcCrc;    // current state of CRC-32 we generate from input
     uint8_t  data[NANOHUB_PACKET_PAYLOAD_MAX];
     uint8_t  len;
+    uint8_t  lenLeft;
     uint8_t  chunkReply;
-    uint8_t  type;
     bool     erase;
+    bool     eraseScheduled;
 };
 
 struct TimeSync
@@ -147,18 +160,12 @@
 
 static AppSecErr writeCbk(const void *data, uint32_t len)
 {
-    AppSecErr ret;
+    AppSecErr ret = APP_SEC_BAD;
 
-    mpuAllowRamExecution(true);
-    mpuAllowRomWrite(true);
-    if (!BL.blProgramShared(mDownloadState->start + mDownloadState->dstOffset, (uint8_t *)data, len, BL_FLASH_KEY1, BL_FLASH_KEY2)) {
-        ret = APP_SEC_BAD;
-    } else {
+    if (osWriteShared((uint8_t*)(mDownloadState->start) + mDownloadState->dstOffset, data, len)) {
         ret = APP_SEC_NO_ERROR;
         mDownloadState->dstOffset += len;
     }
-    mpuAllowRomWrite(false);
-    mpuAllowRamExecution(false);
 
     return ret;
 }
@@ -180,7 +187,7 @@
     return APP_SEC_NO_ERROR;
 }
 
-static AppSecErr aesKeyAccessCbk(uint64_t keyIdx, void *keyBuf)
+static AppSecErr osSecretKeyLookup(uint64_t keyId, void *keyBuf)
 {
     struct SeosEedataEncrKeyData kd;
     void *state = NULL;
@@ -191,8 +198,9 @@
         if (!eeDataGetAllVersions(EE_DATA_NAME_ENCR_KEY, &kd, &sz, &state))
             break;
 
-        if (sz == sizeof(struct SeosEedataEncrKeyData) && kd.keyID == keyIdx) {
-            memcpy(keyBuf, kd.key, sizeof(kd.key));
+        if (sz == sizeof(struct SeosEedataEncrKeyData) && kd.keyID == keyId) {
+            if (keyBuf)
+                memcpy(keyBuf, kd.key, sizeof(kd.key));
             return APP_SEC_NO_ERROR;
         }
     }
@@ -200,6 +208,43 @@
     return APP_SEC_KEY_NOT_FOUND;
 }
 
+static AppSecErr osSecretKeyDelete(uint64_t keyId)
+{
+    struct SeosEedataEncrKeyData kd;
+    void *state = NULL;
+    bool good = true;
+    int count = 0;
+
+    while(1) {
+        uint32_t sz = sizeof(struct SeosEedataEncrKeyData);
+        void *addr = eeDataGetAllVersions(EE_DATA_NAME_ENCR_KEY, &kd, &sz, &state);
+
+        if (!addr)
+            break;
+
+        if (sz == sizeof(kd) && kd.keyID == keyId) {
+            good = eeDataEraseOldVersion(EE_DATA_NAME_ENCR_KEY, addr) && good;
+            count++;
+        }
+    }
+
+    return count == 0 ? APP_SEC_KEY_NOT_FOUND : good ? APP_SEC_NO_ERROR : APP_SEC_BAD;
+}
+
+static AppSecErr osSecretKeyAdd(uint64_t keyId, void *keyBuf)
+{
+    struct SeosEedataEncrKeyData kd;
+
+    // do not add key if it already exists
+    if (osSecretKeyLookup(keyId, NULL) != APP_SEC_KEY_NOT_FOUND)
+        return APP_SEC_BAD;
+
+    memcpy(&kd.key, keyBuf, 32);
+    kd.keyID = keyId;
+
+    return eeDataSet(EE_DATA_NAME_ENCR_KEY, &kd, sizeof(kd)) ? APP_SEC_NO_ERROR : APP_SEC_BAD;
+}
+
 static void freeDownloadState()
 {
     if (mDownloadState->appSecState)
@@ -208,280 +253,359 @@
     mDownloadState = NULL;
 }
 
-static void resetDownloadState()
+static void resetDownloadState(bool initial)
 {
+    bool doCreate = true;
+
     mAppSecStatus = APP_SEC_NO_ERROR;
     if (mDownloadState->appSecState)
         appSecDeinit(mDownloadState->appSecState);
-    mDownloadState->appSecState = appSecInit(writeCbk, pubKeyFindCbk, aesKeyAccessCbk, true);
+    mDownloadState->appSecState = appSecInit(writeCbk, pubKeyFindCbk, osSecretKeyLookup, REQUIRE_SIGNED_IMAGE);
     mDownloadState->srcOffset = 0;
     mDownloadState->srcCrc = ~0;
-    mDownloadState->dstOffset = 4; // skip over header
+    if (!initial) {
+        // if no data was written, we can reuse the same segment
+        if (mDownloadState->dstOffset)
+            osAppSegmentClose(mDownloadState->start, mDownloadState->dstOffset, SEG_ST_ERASED);
+        else
+            doCreate = false;
+    }
+    if (doCreate)
+        mDownloadState->start = osAppSegmentCreate(mDownloadState->size);
+    if (!mDownloadState->start)
+        mDownloadState->erase = true;
+    mDownloadState->dstOffset = 0;
+}
+
+static bool doStartFirmwareUpload(struct NanohubStartFirmwareUploadRequest *req)
+{
+    if (!mDownloadState) {
+        mDownloadState = heapAlloc(sizeof(struct DownloadState));
+
+        if (!mDownloadState)
+            return false;
+        else
+            memset(mDownloadState, 0x00, sizeof(struct DownloadState));
+    }
+
+    mDownloadState->size = le32toh(req->size);
+    mDownloadState->crc = le32toh(req->crc);
+    mDownloadState->chunkReply = NANOHUB_FIRMWARE_CHUNK_REPLY_ACCEPTED;
+    resetDownloadState(true);
+
+    return true;
 }
 
 static uint32_t startFirmwareUpload(void *rx, uint8_t rx_len, void *tx, uint64_t timestamp)
 {
     struct NanohubStartFirmwareUploadRequest *req = rx;
     struct NanohubStartFirmwareUploadResponse *resp = tx;
-    uint8_t *shared, *shared_start, *shared_end;
-    int len, total_len;
-    uint32_t sharedSz;
 
-    shared_start = platGetSharedAreaInfo(&sharedSz);
-    shared_end = shared_start + sharedSz;
+    resp->accepted = doStartFirmwareUpload(req);
+
+    return sizeof(*resp);
+}
+
+static void deferredUpdateOs(void *cookie)
+{
+    const struct AppHdr *app = cookie;
+    struct OsUpdateHdr *os = (struct OsUpdateHdr *)(&(app->hdr) + 1);
+    uint32_t uploadStatus = OS_UPDT_HDR_CHECK_FAILED;
+    uint8_t marker = OS_UPDT_MARKER_DOWNLOADED;
+    struct Segment *seg = osGetSegment(app);
+    uint32_t segSize = osSegmentGetSize(seg);
+
+    osLog(LOG_INFO, "%s: checking OS image @ %p\n", __func__, os);
+    // some sanity checks before asking BL to do image lookup
+    hostIntfSetBusy(true);
+    if (segSize >= (sizeof(*app) + sizeof(*os)) && segSize > os->size) {
+        if (osWriteShared(&os->marker, &marker, sizeof(os->marker)))
+            uploadStatus = BL.blVerifyOsUpdate();
+        else
+            osLog(LOG_ERROR, "%s: could not set marker on OS image\n", __func__);
+    }
+    hostIntfSetBusy(false);
+    osLog(LOG_INFO, "%s: status=%" PRIu32 "\n", __func__, uploadStatus);
+}
+
+static AppSecErr updateKey(const struct AppHdr *app)
+{
+    AppSecErr ret;
+    struct KeyInfo *ki = (struct KeyInfo *)(&(app->hdr) + 1);
+    uint8_t *data = (uint8_t *)(ki + 1);
+    uint64_t keyId = KEY_ID_MAKE(APP_ID_GET_VENDOR(app->hdr.appId), ki->id);
+    const char *op;
+
+    if ((app->hdr.fwFlags & FL_KEY_HDR_DELETE) != 0) {
+        // removing existing key
+        ret = osSecretKeyDelete(keyId);
+        op = "Removing";
+    } else {
+        // adding new key
+        ret = osSecretKeyAdd(keyId, data);
+        op = "Adding";
+    }
+    osLog(LOG_INFO, "%s: %s key: id=%016" PRIX64 "; ret=%" PRIu32 "\n",
+          __func__, op, keyId, ret);
+
+    return ret;
+}
+
+static uint32_t appSecErrToNanohubReply(AppSecErr status)
+{
+    uint32_t reply;
+
+    switch (status) {
+    case APP_SEC_NO_ERROR:
+        reply = NANOHUB_FIRMWARE_UPLOAD_SUCCESS;
+        break;
+    case APP_SEC_KEY_NOT_FOUND:
+        reply = NANOHUB_FIRMWARE_UPLOAD_APP_SEC_KEY_NOT_FOUND;
+        break;
+    case APP_SEC_HEADER_ERROR:
+        reply = NANOHUB_FIRMWARE_UPLOAD_APP_SEC_HEADER_ERROR;
+        break;
+    case APP_SEC_TOO_MUCH_DATA:
+        reply = NANOHUB_FIRMWARE_UPLOAD_APP_SEC_TOO_MUCH_DATA;
+        break;
+    case APP_SEC_TOO_LITTLE_DATA:
+        reply = NANOHUB_FIRMWARE_UPLOAD_APP_SEC_TOO_LITTLE_DATA;
+        break;
+    case APP_SEC_SIG_VERIFY_FAIL:
+        reply = NANOHUB_FIRMWARE_UPLOAD_APP_SEC_SIG_VERIFY_FAIL;
+        break;
+    case APP_SEC_SIG_DECODE_FAIL:
+        reply = NANOHUB_FIRMWARE_UPLOAD_APP_SEC_SIG_DECODE_FAIL;
+        break;
+    case APP_SEC_SIG_ROOT_UNKNOWN:
+        reply = NANOHUB_FIRMWARE_UPLOAD_APP_SEC_SIG_ROOT_UNKNOWN;
+        break;
+    case APP_SEC_MEMORY_ERROR:
+        reply = NANOHUB_FIRMWARE_UPLOAD_APP_SEC_MEMORY_ERROR;
+        break;
+    case APP_SEC_INVALID_DATA:
+        reply = NANOHUB_FIRMWARE_UPLOAD_APP_SEC_INVALID_DATA;
+        break;
+    case APP_SEC_VERIFY_FAILED:
+        reply = NANOHUB_FIRMWARE_UPLOAD_APP_SEC_VERIFY_FAILED;
+        break;
+    default:
+        reply = NANOHUB_FIRMWARE_UPLOAD_APP_SEC_BAD;
+        break;
+    }
+    return reply;
+}
+
+static uint32_t firmwareFinish(bool valid)
+{
+    struct AppHdr *app;
+    struct Segment *storageSeg;
+    uint32_t segState;
+    uint32_t ret = NANOHUB_FIRMWARE_UPLOAD_SUCCESS;
 
     if (!mDownloadState) {
-        mDownloadState = heapAlloc(sizeof(struct DownloadState));
+        ret = appSecErrToNanohubReply(mAppSecStatus);
+        osLog(LOG_INFO, "%s: no DL status; decoding secure status: %" PRIu32 "\n", __func__, ret);
+        return ret;
+    }
 
-        if (!mDownloadState) {
-            resp->accepted = false;
-            return sizeof(*resp);
+    app = mDownloadState->start;
+    storageSeg = osGetSegment(app);
+
+    if (mAppSecStatus == APP_SEC_NO_ERROR && valid) {
+        osLog(LOG_INFO, "%s: Secure verification passed\n", __func__);
+        if (storageSeg->state != SEG_ST_RESERVED ||
+                mDownloadState->size < sizeof(struct FwCommonHdr) ||
+                app->hdr.magic != APP_HDR_MAGIC ||
+                app->hdr.fwVer != APP_HDR_VER_CUR) {
+            segState = SEG_ST_ERASED;
+            osLog(LOG_INFO, "%s: Header verification failed\n", __func__);
         } else {
-            memset(mDownloadState, 0x00, sizeof(struct DownloadState));
+            segState = SEG_ST_VALID;
+        }
+    } else {
+        segState = SEG_ST_ERASED;
+        osLog(LOG_INFO, "%s: Secure verification failed: valid=%d; status=%" PRIu32 "\n", __func__, valid, mAppSecStatus);
+    }
+
+    if (!osAppSegmentClose(app, mDownloadState->dstOffset, segState)) {
+        osLog(LOG_INFO, "%s: Failed to close segment\n", __func__);
+        valid = false;
+    } else {
+        segState = osAppSegmentGetState(app);
+        valid = (segState == SEG_ST_VALID);
+    }
+    osLog(LOG_INFO, "Loaded %s image type %" PRIu8 ": %" PRIu32
+                    " bytes @ %p; state=%02" PRIX32 "\n",
+                    valid ? "valid" : "invalid",
+                    app->hdr.payInfoType, mDownloadState->size,
+                    mDownloadState->start, segState);
+
+    freeDownloadState(); // no more access to mDownloadState
+
+    if (!valid)
+        ret = NANOHUB_FIRMWARE_UPLOAD_APP_SEC_BAD;
+
+    // take extra care about some special payload types
+    if (ret == NANOHUB_FIRMWARE_UPLOAD_SUCCESS) {
+        switch(app->hdr.payInfoType) {
+        case LAYOUT_OS:
+            osLog(LOG_INFO, "Performing OS update\n");
+            // we want to give this message a chance to reach host before we start erasing stuff
+            osDefer(deferredUpdateOs, (void*)app, false);
+            break;
+        case LAYOUT_KEY:
+            ret = appSecErrToNanohubReply(updateKey(app));
+            break;
         }
     }
 
-    mDownloadState->type = req->type;
-    mDownloadState->size = le32toh(req->size);
-    mDownloadState->crc = le32toh(req->crc);
-    mDownloadState->chunkReply = NANOHUB_FIRMWARE_CHUNK_REPLY_ACCEPTED;
-
-    for (shared = shared_start;
-         shared < shared_end && shared[0] != 0xFF;
-         shared += total_len) {
-        len = (shared[1] << 16) | (shared[2] << 8) | shared[3];
-        total_len = sizeof(uint32_t) + ((len + 3) & ~3) + sizeof(uint32_t);
+    if (ret != NANOHUB_FIRMWARE_UPLOAD_SUCCESS || (app->hdr.fwFlags & FL_APP_HDR_VOLATILE)) {
+        if ((app->hdr.fwFlags & FL_APP_HDR_SECURE))
+            osAppWipeData((struct AppHdr*)app);
+        osAppSegmentSetState(app, SEG_ST_ERASED);
     }
 
-    if (shared + sizeof(uint32_t) + ((mDownloadState->size + 3) & ~3) + sizeof(uint32_t) < shared_end) {
-        mDownloadState->start = shared;
-        mDownloadState->erase = false;
-    } else {
-        mDownloadState->start = shared_start;
-        mDownloadState->erase = true;
-    }
-    resetDownloadState();
+    // if any error happened after we downloaded and verified image, we say it is unknown fault
+    // we don't have download status, so e have to save returned value in secure status field, because
+    // host may request the same status multiple times
+    if (ret != NANOHUB_FIRMWARE_UPLOAD_SUCCESS)
+        mAppSecStatus = APP_SEC_BAD;
 
-    resp->accepted = true;
-    return sizeof(*resp);
+    return ret;
 }
 
 static void firmwareErase(void *cookie)
 {
-    osLog(LOG_INFO, "hostIntfFirmwareErase: Firmware Erase\n");
     if (mDownloadState->erase == true) {
-        mpuAllowRamExecution(true);
-        mpuAllowRomWrite(true);
-        (void)BL.blEraseShared(BL_FLASH_KEY1, BL_FLASH_KEY2);
-        mpuAllowRomWrite(false);
-        mpuAllowRamExecution(false);
+        osLog(LOG_INFO, "%s: erasing shared area\n", __func__);
+        osEraseShared();
+        mDownloadState->start = osAppSegmentCreate(mDownloadState->size);
+        if (!mDownloadState->start)
+            firmwareFinish(false);
         mDownloadState->erase = false;
         hostIntfSetInterrupt(NANOHUB_INT_CMD_WAIT);
     }
-}
-
-static AppSecErr giveAppSecTimeIfNeeded(struct AppSecState *state, AppSecErr prevRet)
-{
-    /* XXX: this will need to go away for real asynchronicity */
-
-    while (prevRet == APP_SEC_NEED_MORE_TIME)
-        prevRet = appSecDoSomeProcessing(state);
-
-    return prevRet;
-}
-
-static uint8_t firmwareFinish(bool valid)
-{
-    uint8_t buffer[7];
-    int padlen;
-    uint32_t crc;
-    uint16_t marker;
-    static const char magic[] = APP_HDR_MAGIC;
-    const struct AppHdr *app;
-
-    mAppSecStatus = appSecRxDataOver(mDownloadState->appSecState);
-    mAppSecStatus = giveAppSecTimeIfNeeded(mDownloadState->appSecState, mAppSecStatus);
-
-    if (mAppSecStatus == APP_SEC_NO_ERROR && valid && mDownloadState->type == BL_FLASH_APP_ID) {
-        app = (const struct AppHdr *)&mDownloadState->start[4];
-
-        if (app->marker != APP_HDR_MARKER_UPLOADING ||
-                mDownloadState->size < sizeof(uint32_t) + sizeof(struct AppHdr) ||
-                memcmp(magic, app->magic, sizeof(magic)-1) ||
-                app->fmtVer != APP_HDR_VER_CUR) {
-            marker = APP_HDR_MARKER_DELETED;
-        } else {
-            marker = APP_HDR_MARKER_VALID;
-        }
-
-        osLog(LOG_INFO, "Loaded %s app: %ld bytes @ %p\n", marker == APP_HDR_MARKER_VALID ? "valid" : "invalid", mDownloadState->size, mDownloadState->start);
-
-        mpuAllowRamExecution(true);
-        mpuAllowRomWrite(true);
-        if (!BL.blProgramShared((uint8_t *)&app->marker, (uint8_t *)&marker, sizeof(marker), BL_FLASH_KEY1, BL_FLASH_KEY2)) {
-            mpuAllowRomWrite(false);
-            mpuAllowRamExecution(false);
-            return NANOHUB_FIRMWARE_CHUNK_REPLY_RESEND;
-        }
-    } else {
-        mpuAllowRamExecution(true);
-        mpuAllowRomWrite(true);
-    }
-
-    if (mAppSecStatus == APP_SEC_NO_ERROR && valid)
-        buffer[0] = ((mDownloadState->type & 0xF) << 4) | (mDownloadState->type & 0xF);
-    else
-        buffer[0] = 0x00;
-
-    buffer[1] = (mDownloadState->dstOffset - 4) >> 16;
-    buffer[2] = (mDownloadState->dstOffset - 4) >> 8;
-    buffer[3] = (mDownloadState->dstOffset - 4);
-
-    if (!BL.blProgramShared(mDownloadState->start, buffer, 4, BL_FLASH_KEY1, BL_FLASH_KEY2)) {
-        mpuAllowRomWrite(false);
-        mpuAllowRamExecution(false);
-        return NANOHUB_FIRMWARE_CHUNK_REPLY_RESEND;
-    }
-
-    crc = ~crc32(mDownloadState->start, mDownloadState->dstOffset, ~0);
-    padlen =  (4 - (mDownloadState->dstOffset & 3)) & 3;
-    memset(buffer, 0x00, padlen);
-    memcpy(&buffer[padlen], &crc, sizeof(uint32_t));
-    mDownloadState->size = mDownloadState->dstOffset + padlen + sizeof(uint32_t);
-
-    if (!BL.blProgramShared(mDownloadState->start + mDownloadState->dstOffset, buffer, padlen + sizeof(uint32_t), BL_FLASH_KEY1, BL_FLASH_KEY2)) {
-        mpuAllowRomWrite(false);
-        mpuAllowRamExecution(false);
-        return NANOHUB_FIRMWARE_CHUNK_REPLY_RESEND;
-    }
-
-    mpuAllowRomWrite(false);
-    mpuAllowRamExecution(false);
-    freeDownloadState();
-
-    return NANOHUB_FIRMWARE_CHUNK_REPLY_ACCEPTED;
+    mDownloadState->eraseScheduled = false;
 }
 
 static void firmwareWrite(void *cookie)
 {
+    bool valid;
+    bool finished = false;
+    struct NanohubHalContUploadTx *resp = cookie;
+    // only check crc when cookie is NULL (write came from kernel, not HAL)
+    bool checkCrc = !cookie;
+
+    if (mAppSecStatus == APP_SEC_NEED_MORE_TIME) {
+        mAppSecStatus = appSecDoSomeProcessing(mDownloadState->appSecState);
+    } else if (mDownloadState->lenLeft) {
+        const uint8_t *data = mDownloadState->data + mDownloadState->len - mDownloadState->lenLeft;
+        uint32_t len = mDownloadState->lenLeft, lenLeft, lenRem = 0;
+
+        if (len > MAX_APP_SEC_RX_DATA_LEN) {
+            lenRem = len - MAX_APP_SEC_RX_DATA_LEN;
+            len = MAX_APP_SEC_RX_DATA_LEN;
+        }
+
+        mAppSecStatus = appSecRxData(mDownloadState->appSecState, data, len, &lenLeft);
+        mDownloadState->lenLeft = lenLeft + lenRem;
+    }
+
+    valid = (mAppSecStatus == APP_SEC_NO_ERROR);
+    if (mAppSecStatus == APP_SEC_NEED_MORE_TIME || mDownloadState->lenLeft) {
+        osDefer(firmwareWrite, cookie, false);
+        return;
+    } else if (valid) {
+        if (mDownloadState->srcOffset == mDownloadState->size) {
+            finished = true;
+            valid = !checkCrc || mDownloadState->crc == ~mDownloadState->srcCrc;
+        } else if (mDownloadState->srcOffset > mDownloadState->size) {
+            valid = false;
+        }
+    }
+    if (!valid)
+        finished = true;
+    if (finished) {
+        if (firmwareFinish(valid) != NANOHUB_FIRMWARE_UPLOAD_SUCCESS)
+            valid = false;
+    }
+    if (resp) {
+        resp->success = valid;
+        osEnqueueEvtOrFree(EVT_APP_TO_HOST, resp, heapFree);
+    }
+}
+
+static uint32_t doFirmwareChunk(uint8_t *data, uint32_t offset, uint32_t len, void *cookie)
+{
     uint32_t reply;
 
-    if (mDownloadState->type == BL_FLASH_APP_ID) {
-        /* XXX: this will need to change for real asynchronicity */
-        const uint8_t *data = mDownloadState->data;
-        uint32_t len = mDownloadState->len, lenLeft;
-        mAppSecStatus = APP_SEC_NO_ERROR;
-
-        while (len) {
-            mAppSecStatus = appSecRxData(mDownloadState->appSecState, data, len, &lenLeft);
-            data += len - lenLeft;
-            len = lenLeft;
-
-            mAppSecStatus = giveAppSecTimeIfNeeded(mDownloadState->appSecState, mAppSecStatus);
-        }
-    }
-    else
-        mAppSecStatus = writeCbk(mDownloadState->data, mDownloadState->len);
-
-    if (mAppSecStatus == APP_SEC_NO_ERROR) {
-        if (mDownloadState->srcOffset == mDownloadState->size && mDownloadState->crc == ~mDownloadState->srcCrc) {
-            reply = firmwareFinish(true);
-            if (mDownloadState)
-                mDownloadState->chunkReply = reply;
-        } else {
-            mDownloadState->chunkReply = NANOHUB_FIRMWARE_CHUNK_REPLY_ACCEPTED;
-        }
+    if (!mDownloadState) {
+        reply = NANOHUB_FIRMWARE_CHUNK_REPLY_CANCEL_NO_RETRY;
+    } else if (mAppSecStatus == APP_SEC_NEED_MORE_TIME || mDownloadState->lenLeft) {
+        reply = NANOHUB_FIRMWARE_CHUNK_REPLY_RESEND;
+    } else if (mDownloadState->chunkReply != NANOHUB_FIRMWARE_CHUNK_REPLY_ACCEPTED) {
+        reply = mDownloadState->chunkReply;
+        firmwareFinish(false);
     } else {
-        freeDownloadState();
+        if (mDownloadState->erase == true) {
+            reply = NANOHUB_FIRMWARE_CHUNK_REPLY_WAIT;
+            if (!mDownloadState->eraseScheduled)
+                mDownloadState->eraseScheduled = osDefer(firmwareErase, NULL, false);
+        } else if (!mDownloadState->start) {
+            // this means we can't allocate enough space even after we did erase
+            reply = NANOHUB_FIRMWARE_CHUNK_REPLY_CANCEL_NO_RETRY;
+            firmwareFinish(false);
+        } else if (offset != mDownloadState->srcOffset) {
+            reply = NANOHUB_FIRMWARE_CHUNK_REPLY_RESTART;
+            resetDownloadState(false);
+        } else {
+            if (!cookie)
+                mDownloadState->srcCrc = crc32(data, len, mDownloadState->srcCrc);
+            mDownloadState->srcOffset += len;
+            memcpy(mDownloadState->data, data, len);
+            mDownloadState->lenLeft = mDownloadState->len = len;
+            reply = NANOHUB_FIRMWARE_CHUNK_REPLY_ACCEPTED;
+            osDefer(firmwareWrite, cookie, false);
+        }
     }
-    hostIntfSetBusy(false);
+
+    return reply;
 }
 
 static uint32_t firmwareChunk(void *rx, uint8_t rx_len, void *tx, uint64_t timestamp)
 {
-    uint32_t offset;
-    uint8_t len;
     struct NanohubFirmwareChunkRequest *req = rx;
     struct NanohubFirmwareChunkResponse *resp = tx;
+    uint32_t offset = le32toh(req->offset);
+    uint8_t len = rx_len - sizeof(req->offset);
 
-    offset = le32toh(req->offset);
-    len = rx_len - sizeof(req->offset);
-
-    if (!mDownloadState) {
-        resp->chunkReply = NANOHUB_FIRMWARE_CHUNK_REPLY_CANCEL_NO_RETRY;
-    } else if (mDownloadState->chunkReply != NANOHUB_FIRMWARE_CHUNK_REPLY_ACCEPTED) {
-        resp->chunkReply = mDownloadState->chunkReply;
-        freeDownloadState();
-    } else if (mDownloadState->erase == true) {
-        resp->chunkReply = NANOHUB_FIRMWARE_CHUNK_REPLY_WAIT;
-        osDefer(firmwareErase, NULL, false);
-    } else if (offset != mDownloadState->srcOffset) {
-        resp->chunkReply = NANOHUB_FIRMWARE_CHUNK_REPLY_RESTART;
-        resetDownloadState();
-    } else {
-        mDownloadState->srcCrc = crc32(req->data, len, mDownloadState->srcCrc);
-        mDownloadState->srcOffset += len;
-        if ((mDownloadState->srcOffset == mDownloadState->size && mDownloadState->crc != ~mDownloadState->srcCrc) || (mDownloadState->srcOffset > mDownloadState->size)) {
-            firmwareFinish(false);
-            resp->chunkReply = NANOHUB_FIRMWARE_CHUNK_REPLY_CANCEL;
-        } else {
-            memcpy(mDownloadState->data, req->data, len);
-            mDownloadState->len = len;
-            resp->chunkReply = NANOHUB_FIRMWARE_CHUNK_REPLY_ACCEPTED;
-            hostIntfSetBusy(true);
-            osDefer(firmwareWrite, NULL, false);
-        }
-    }
+    resp->chunkReply = doFirmwareChunk(req->data, offset, len, NULL);
 
     return sizeof(*resp);
 }
 
+static uint32_t doFinishFirmwareUpload()
+{
+    uint32_t reply;
+
+    if (!mDownloadState) {
+        reply = appSecErrToNanohubReply(mAppSecStatus);
+    } else if (mDownloadState->srcOffset == mDownloadState->size) {
+        reply = NANOHUB_FIRMWARE_UPLOAD_PROCESSING;
+    } else {
+        reply = firmwareFinish(false);
+    }
+
+    return reply;
+}
+
 static uint32_t finishFirmwareUpload(void *rx, uint8_t rx_len, void *tx, uint64_t timestamp)
 {
     struct NanohubFinishFirmwareUploadResponse *resp = tx;
-
-    if (!mDownloadState) {
-        switch (mAppSecStatus) {
-        case APP_SEC_NO_ERROR:
-            resp->uploadReply = NANOHUB_FIRMWARE_UPLOAD_SUCCESS;
-            break;
-        case APP_SEC_KEY_NOT_FOUND:
-            resp->uploadReply = NANOHUB_FIRMWARE_UPLOAD_APP_SEC_KEY_NOT_FOUND;
-            break;
-        case APP_SEC_HEADER_ERROR:
-            resp->uploadReply = NANOHUB_FIRMWARE_UPLOAD_APP_SEC_HEADER_ERROR;
-            break;
-        case APP_SEC_TOO_MUCH_DATA:
-            resp->uploadReply = NANOHUB_FIRMWARE_UPLOAD_APP_SEC_TOO_MUCH_DATA;
-            break;
-        case APP_SEC_TOO_LITTLE_DATA:
-            resp->uploadReply = NANOHUB_FIRMWARE_UPLOAD_APP_SEC_TOO_LITTLE_DATA;
-            break;
-        case APP_SEC_SIG_VERIFY_FAIL:
-            resp->uploadReply = NANOHUB_FIRMWARE_UPLOAD_APP_SEC_SIG_VERIFY_FAIL;
-            break;
-        case APP_SEC_SIG_DECODE_FAIL:
-            resp->uploadReply = NANOHUB_FIRMWARE_UPLOAD_APP_SEC_SIG_DECODE_FAIL;
-            break;
-        case APP_SEC_SIG_ROOT_UNKNOWN:
-            resp->uploadReply = NANOHUB_FIRMWARE_UPLOAD_APP_SEC_SIG_ROOT_UNKNOWN;
-            break;
-        case APP_SEC_MEMORY_ERROR:
-            resp->uploadReply = NANOHUB_FIRMWARE_UPLOAD_APP_SEC_MEMORY_ERROR;
-            break;
-        case APP_SEC_INVALID_DATA:
-            resp->uploadReply = NANOHUB_FIRMWARE_UPLOAD_APP_SEC_INVALID_DATA;
-            break;
-        default:
-            resp->uploadReply = NANOHUB_FIRMWARE_UPLOAD_APP_SEC_BAD;
-            break;
-        }
-    } else if (mDownloadState->srcOffset == mDownloadState->size) {
-        resp->uploadReply = NANOHUB_FIRMWARE_UPLOAD_PROCESSING;
-    } else {
-        resp->uploadReply = NANOHUB_FIRMWARE_UPLOAD_WAITING_FOR_DATA;
-    }
-
+    resp->uploadReply = doFinishFirmwareUpload();
+    if (resp->uploadReply != NANOHUB_FIRMWARE_UPLOAD_PROCESSING)
+        osLog(LOG_INFO, "%s: reply=%" PRIu8 "\n", __func__, resp->uploadReply);
     return sizeof(*resp);
 }
 
@@ -583,7 +707,7 @@
                 break;
 #ifdef DEBUG_LOG_EVT
             case HOSTINTF_DATA_TYPE_LOG:
-                packet->evtType = htole32(DEBUG_LOG_EVT);
+                packet->evtType = htole32(HOST_EVT_DEBUG_LOG);
                 break;
 #endif
             default:
@@ -872,16 +996,41 @@
     return NULL;
 }
 
+static void halSendMgmtResponse(uint32_t cmd, uint32_t status)
+{
+    struct NanohubHalMgmtTx *resp;
+
+    resp = heapAlloc(sizeof(*resp));
+    if (resp) {
+        resp->hdr = (struct NanohubHalHdr) {
+            .appId = APP_ID_MAKE(APP_ID_VENDOR_GOOGLE, 0),
+            .len = sizeof(*resp) - sizeof(resp->hdr) + sizeof(resp->hdr.msg),
+            .msg = cmd,
+        };
+        resp->status = htole32(status);
+        osEnqueueEvtOrFree(EVT_APP_TO_HOST, resp, heapFree);
+    }
+}
+
 static void halExtAppsOn(void *rx, uint8_t rx_len)
 {
+    struct NanohubHalMgmtRx *req = rx;
+
+    halSendMgmtResponse(NANOHUB_HAL_EXT_APPS_ON, osExtAppStartApps(le64toh(req->appId)));
 }
 
 static void halExtAppsOff(void *rx, uint8_t rx_len)
 {
+    struct NanohubHalMgmtRx *req = rx;
+
+    halSendMgmtResponse(NANOHUB_HAL_EXT_APPS_OFF, osExtAppStopApps(le64toh(req->appId)));
 }
 
 static void halExtAppDelete(void *rx, uint8_t rx_len)
 {
+    struct NanohubHalMgmtRx *req = rx;
+
+    halSendMgmtResponse(NANOHUB_HAL_EXT_APP_DELETE, osExtAppEraseApps(le64toh(req->appId)));
 }
 
 static void halQueryMemInfo(void *rx, uint8_t rx_len)
@@ -944,10 +1093,10 @@
 static void halStartUpload(void *rx, uint8_t rx_len)
 {
     struct NanohubHalStartUploadRx *req = rx;
+    struct NanohubStartFirmwareUploadRequest hwReq = {
+        .size= req->length
+    };
     struct NanohubHalStartUploadTx *resp;
-    uint8_t *shared, *shared_start, *shared_end;
-    int len, total_len;
-    uint32_t sharedSz;
 
     if (!(resp = heapAlloc(sizeof(*resp))))
         return;
@@ -955,53 +1104,18 @@
     resp->hdr.appId = APP_ID_MAKE(APP_ID_VENDOR_GOOGLE, 0);
     resp->hdr.len = sizeof(*resp) - sizeof(struct NanohubHalHdr) + 1;
     resp->hdr.msg = NANOHUB_HAL_START_UPLOAD;
+    resp->success = doStartFirmwareUpload(&hwReq);
 
-    shared_start = platGetSharedAreaInfo(&sharedSz);
-    shared_end = shared_start + sharedSz;
-
-    if (!mDownloadState) {
-        mDownloadState = heapAlloc(sizeof(struct DownloadState));
-
-        if (!mDownloadState) {
-            resp->success = false;
-            osEnqueueEvtOrFree(EVT_APP_TO_HOST, resp, heapFree);
-            return;
-        } else {
-            memset(mDownloadState, 0x00, sizeof(struct DownloadState));
-        }
-    }
-
-    mDownloadState->type = req->isOs ? BL_FLASH_KERNEL_ID : BL_FLASH_APP_ID;
-    mDownloadState->size = le32toh(req->length);
-    mDownloadState->crc = le32toh(0x00000000);
-    mDownloadState->chunkReply = NANOHUB_FIRMWARE_CHUNK_REPLY_ACCEPTED;
-
-    for (shared = shared_start;
-         shared < shared_end && shared[0] != 0xFF;
-         shared += total_len) {
-        len = (shared[1] << 16) | (shared[2] << 8) | shared[3];
-        total_len = sizeof(uint32_t) + ((len + 3) & ~3) + sizeof(uint32_t);
-    }
-
-    if (shared + sizeof(uint32_t) + ((mDownloadState->size + 3) & ~3) + sizeof(uint32_t) < shared_end) {
-        mDownloadState->start = shared;
-        mDownloadState->erase = false;
-    } else {
-        mDownloadState->start = shared_start;
-        mDownloadState->erase = true;
-    }
-    resetDownloadState();
-
-    resp->success = true;
     osEnqueueEvtOrFree(EVT_APP_TO_HOST, resp, heapFree);
 }
 
 static void halContUpload(void *rx, uint8_t rx_len)
 {
+    uint32_t offset;
+    uint32_t reply;
+    uint8_t len;
     struct NanohubHalContUploadRx *req = rx;
     struct NanohubHalContUploadTx *resp;
-    uint32_t offset;
-    uint8_t len;
 
     if (!(resp = heapAlloc(sizeof(*resp))))
         return;
@@ -1010,38 +1124,26 @@
     resp->hdr.len = sizeof(*resp) - sizeof(struct NanohubHalHdr) + 1;
     resp->hdr.msg = NANOHUB_HAL_CONT_UPLOAD;
 
-    offset = le32toh(req->offset);
-    len = rx_len - sizeof(req->offset);
-
     if (!mDownloadState) {
-        resp->success = NANOHUB_FIRMWARE_CHUNK_REPLY_CANCEL_NO_RETRY;
-    } else if (mDownloadState->erase == true) {
-        resp->success = NANOHUB_FIRMWARE_CHUNK_REPLY_WAIT;
-        firmwareErase(NULL);
-    } else if (offset != mDownloadState->srcOffset) {
-        resp->success = NANOHUB_FIRMWARE_CHUNK_REPLY_RESTART;
-        resetDownloadState();
-    } else if (mDownloadState->srcOffset + len <= mDownloadState->size) {
-        mDownloadState->srcOffset += len;
-        memcpy(mDownloadState->data, req->data, len);
-        mDownloadState->len = len;
-        hostIntfSetBusy(true);
-        firmwareWrite(NULL);
-        if (mDownloadState)
-            resp->success = mDownloadState->chunkReply;
-        else
-            resp->success = NANOHUB_FIRMWARE_CHUNK_REPLY_ACCEPTED;
+        reply = NANOHUB_FIRMWARE_CHUNK_REPLY_CANCEL_NO_RETRY;
     } else {
-        resp->success = NANOHUB_FIRMWARE_CHUNK_REPLY_CANCEL_NO_RETRY;
+        offset = le32toh(req->offset);
+        len = rx_len - sizeof(req->offset);
+        reply = doFirmwareChunk(req->data, offset, len, resp);
     }
-    resp->success = !resp->success;
+    if (reply != NANOHUB_FIRMWARE_CHUNK_REPLY_ACCEPTED) {
+        osLog(LOG_ERROR, "%s: reply=%" PRIu32 "\n", __func__, reply);
 
-    osEnqueueEvtOrFree(EVT_APP_TO_HOST, resp, heapFree);
+        resp->success = false;
+
+        osEnqueueEvtOrFree(EVT_APP_TO_HOST, resp, heapFree);
+    }
 }
 
 static void halFinishUpload(void *rx, uint8_t rx_len)
 {
     struct NanohubHalFinishUploadTx *resp;
+    uint32_t reply;
 
     if (!(resp = heapAlloc(sizeof(*resp))))
         return;
@@ -1050,48 +1152,11 @@
     resp->hdr.len = sizeof(*resp) - sizeof(struct NanohubHalHdr) + 1;
     resp->hdr.msg = NANOHUB_HAL_FINISH_UPLOAD;
 
-    if (!mDownloadState) {
-        switch (mAppSecStatus) {
-        case APP_SEC_NO_ERROR:
-            resp->success = NANOHUB_FIRMWARE_UPLOAD_SUCCESS;
-            break;
-        case APP_SEC_KEY_NOT_FOUND:
-            resp->success = NANOHUB_FIRMWARE_UPLOAD_APP_SEC_KEY_NOT_FOUND;
-            break;
-        case APP_SEC_HEADER_ERROR:
-            resp->success = NANOHUB_FIRMWARE_UPLOAD_APP_SEC_HEADER_ERROR;
-            break;
-        case APP_SEC_TOO_MUCH_DATA:
-            resp->success = NANOHUB_FIRMWARE_UPLOAD_APP_SEC_TOO_MUCH_DATA;
-            break;
-        case APP_SEC_TOO_LITTLE_DATA:
-            resp->success = NANOHUB_FIRMWARE_UPLOAD_APP_SEC_TOO_LITTLE_DATA;
-            break;
-        case APP_SEC_SIG_VERIFY_FAIL:
-            resp->success = NANOHUB_FIRMWARE_UPLOAD_APP_SEC_SIG_VERIFY_FAIL;
-            break;
-        case APP_SEC_SIG_DECODE_FAIL:
-            resp->success = NANOHUB_FIRMWARE_UPLOAD_APP_SEC_SIG_DECODE_FAIL;
-            break;
-        case APP_SEC_SIG_ROOT_UNKNOWN:
-            resp->success = NANOHUB_FIRMWARE_UPLOAD_APP_SEC_SIG_ROOT_UNKNOWN;
-            break;
-        case APP_SEC_MEMORY_ERROR:
-            resp->success = NANOHUB_FIRMWARE_UPLOAD_APP_SEC_MEMORY_ERROR;
-            break;
-        case APP_SEC_INVALID_DATA:
-            resp->success = NANOHUB_FIRMWARE_UPLOAD_APP_SEC_INVALID_DATA;
-            break;
-        default:
-            resp->success = NANOHUB_FIRMWARE_UPLOAD_APP_SEC_BAD;
-            break;
-        }
-    } else if (mDownloadState->srcOffset == mDownloadState->size) {
-        resp->success = NANOHUB_FIRMWARE_UPLOAD_PROCESSING;
-    } else {
-        resp->success = NANOHUB_FIRMWARE_UPLOAD_WAITING_FOR_DATA;
-    }
-    resp->success = !resp->success;
+    reply = doFinishFirmwareUpload();
+
+    osLog(LOG_INFO, "%s: reply=%" PRIu32 "\n", __func__, reply);
+
+    resp->success = (reply == NANOHUB_FIRMWARE_UPLOAD_SUCCESS);
 
     osEnqueueEvtOrFree(EVT_APP_TO_HOST, resp, heapFree);
 }
diff --git a/firmware/src/osApi.c b/firmware/src/osApi.c
index 4715576..0a03384 100644
--- a/firmware/src/osApi.c
+++ b/firmware/src/osApi.c
@@ -32,37 +32,37 @@
 
 static void osExpApiEvtqSubscribe(uintptr_t *retValP, va_list args)
 {
-    uint32_t tid = va_arg(args, uint32_t);
+    (void)va_arg(args, uint32_t); // tid
     uint32_t evtType = va_arg(args, uint32_t);
 
-    *retValP = osEventSubscribe(tid, evtType);
+    *retValP = osEventSubscribe(0, evtType);
 }
 
 static void osExpApiEvtqUnsubscribe(uintptr_t *retValP, va_list args)
 {
-    uint32_t tid = va_arg(args, uint32_t);
+    (void)va_arg(args, uint32_t); // tid
     uint32_t evtType = va_arg(args, uint32_t);
 
-    *retValP = osEventUnsubscribe(tid, evtType);
+    *retValP = osEventUnsubscribe(0, evtType);
 }
 
 static void osExpApiEvtqEnqueue(uintptr_t *retValP, va_list args)
 {
     uint32_t evtType = va_arg(args, uint32_t);
     void *evtData = va_arg(args, void*);
-    uint32_t tid = va_arg(args, uint32_t);
+    (void)va_arg(args, uint32_t); // tid
 
-    *retValP = osEnqueueEvtAsApp(evtType, evtData, tid);
+    *retValP = osEnqueueEvtAsApp(evtType, evtData, 0);
 }
 
 static void osExpApiEvtqEnqueuePrivate(uintptr_t *retValP, va_list args)
 {
     uint32_t evtType = va_arg(args, uint32_t);
     void *evtData = va_arg(args, void*);
-    uint32_t freeTid = va_arg(args, uint32_t);
+    (void)va_arg(args, uint32_t); // tid
     uint32_t toTid = va_arg(args, uint32_t);
 
-    *retValP = osEnqueuePrivateEvtAsApp(evtType, evtData, freeTid, toTid);
+    *retValP = osEnqueuePrivateEvtAsApp(evtType, evtData, 0, toTid);
 }
 
 static void osExpApiEvtqRetainEvt(uintptr_t *retValP, va_list args)
@@ -106,11 +106,11 @@
 static void osExpApiSensorReg(uintptr_t *retValP, va_list args)
 {
     const struct SensorInfo *si = va_arg(args, const struct SensorInfo*);
-    uint32_t tid = va_arg(args, uint32_t);
+    (void)va_arg(args, uint32_t); // tid
     void *cookie = va_arg(args, void *);
     bool initComplete = va_arg(args, int);
 
-    *retValP = (uintptr_t)sensorRegisterAsApp(si, tid, cookie, initComplete);
+    *retValP = (uintptr_t)sensorRegisterAsApp(si, 0, cookie, initComplete);
 }
 
 static void osExpApiSensorUnreg(uintptr_t *retValP, va_list args)
@@ -138,42 +138,42 @@
 
 static void osExpApiSensorReq(uintptr_t *retValP, va_list args)
 {
-    uint32_t clientId = va_arg(args, uint32_t);
+    (void)va_arg(args, uint32_t); // clientId == tid
     uint32_t sensorHandle = va_arg(args, uint32_t);
     uint32_t rate = va_arg(args, uint32_t);
     uint32_t latency_lo = va_arg(args, uint32_t);
     uint32_t latency_hi = va_arg(args, uint32_t);
     uint64_t latency = (((uint64_t)latency_hi) << 32) + latency_lo;
 
-    *retValP = sensorRequest(clientId, sensorHandle, rate, latency);
+    *retValP = sensorRequest(0, sensorHandle, rate, latency);
 }
 
 static void osExpApiSensorRateChg(uintptr_t *retValP, va_list args)
 {
-    uint32_t clientId = va_arg(args, uint32_t);
+    (void)va_arg(args, uint32_t); // clientId == tid
     uint32_t sensorHandle = va_arg(args, uint32_t);
     uint32_t newRate = va_arg(args, uint32_t);
     uint32_t newLatency_lo = va_arg(args, uint32_t);
     uint32_t newLatency_hi = va_arg(args, uint32_t);
     uint64_t newLatency = (((uint64_t)newLatency_hi) << 32) + newLatency_lo;
 
-    *retValP = sensorRequestRateChange(clientId, sensorHandle, newRate, newLatency);
+    *retValP = sensorRequestRateChange(0, sensorHandle, newRate, newLatency);
 }
 
 static void osExpApiSensorRel(uintptr_t *retValP, va_list args)
 {
-    uint32_t clientId = va_arg(args, uint32_t);
+    (void)va_arg(args, uint32_t); // clientId == tid
     uint32_t sensorHandle = va_arg(args, uint32_t);
 
-    *retValP = sensorRelease(clientId, sensorHandle);
+    *retValP = sensorRelease(0, sensorHandle);
 }
 
 static void osExpApiSensorTrigger(uintptr_t *retValP, va_list args)
 {
-    uint32_t clientId = va_arg(args, uint32_t);
+    (void)va_arg(args, uint32_t); // clientId == tid
     uint32_t sensorHandle = va_arg(args, uint32_t);
 
-    *retValP = sensorTriggerOndemand(clientId, sensorHandle);
+    *retValP = sensorTriggerOndemand(0, sensorHandle);
 }
 
 static void osExpApiSensorGetRate(uintptr_t *retValP, va_list args)
@@ -195,12 +195,12 @@
     uint32_t length_hi = va_arg(args, uint32_t);
     uint32_t jitterPpm = va_arg(args, uint32_t);
     uint32_t driftPpm = va_arg(args, uint32_t);
-    uint32_t tid = va_arg(args, uint32_t);
+    (void)va_arg(args, uint32_t); // tid
     void *cookie = va_arg(args, void *);
     bool oneshot = va_arg(args, int);
     uint64_t length = (((uint64_t)length_hi) << 32) + length_lo;
 
-    *retValP = timTimerSetAsApp(length, jitterPpm, driftPpm, tid, cookie, oneshot);
+    *retValP = timTimerSetAsApp(length, jitterPpm, driftPpm, 0, cookie, oneshot);
 }
 
 static void osExpApiTimCancelTimer(uintptr_t *retValP, va_list args)
@@ -255,12 +255,12 @@
     slabAllocatorFree(allocator, mem);
 }
 
-static union OsApiSlabItem* osExpApiI2cCbkInfoAlloc(uint32_t tid, void *cookie)
+static union OsApiSlabItem* osExpApiI2cCbkInfoAlloc(void *cookie)
 {
     union OsApiSlabItem *thing = slabAllocatorAlloc(mSlabAllocator);
 
     if (thing) {
-        thing->i2cAppCbkInfo.toTid = tid;
+        thing->i2cAppCbkInfo.toTid = osGetCurrentTid();
         thing->i2cAppCbkInfo.cookie = cookie;
     }
 
@@ -289,6 +289,7 @@
     if (!osEnqueuePrivateEvt(EVT_APP_I2C_CBK, &thing->i2cAppCbkEvt, osExpApiI2cInternalEvtFreeF, tid)) {
         osLog(LOG_WARN, "Failed to send I2C evt to app. This might end badly for the app...");
         osExpApiI2cInternalEvtFreeF(thing);
+        // TODO: terminate app here: memory pressure is severe
     }
 }
 
@@ -375,9 +376,9 @@
     size_t txSize = va_arg(args, size_t);
     void *rxBuf = va_arg(args, void*);
     size_t rxSize = va_arg(args, size_t);
-    uint32_t tid = va_arg(args, uint32_t);
+    (void)va_arg(args, uint32_t); // tid
     void *cookie = va_arg(args, void *);
-    union OsApiSlabItem *cbkInfo = osExpApiI2cCbkInfoAlloc(tid, cookie);
+    union OsApiSlabItem *cbkInfo = osExpApiI2cCbkInfoAlloc(cookie);
 
     if (!cbkInfo)
         *retValP =  -ENOMEM;
@@ -408,9 +409,9 @@
     uint32_t busId = va_arg(args, uint32_t);
     void *rxBuf = va_arg(args, void*);
     size_t rxSize = va_arg(args, size_t);
-    uint32_t tid = va_arg(args, uint32_t);
+    (void)va_arg(args, uint32_t); // tid
     void *cookie = va_arg(args, void *);
-    union OsApiSlabItem *cbkInfo = osExpApiI2cCbkInfoAlloc(tid, cookie);
+    union OsApiSlabItem *cbkInfo = osExpApiI2cCbkInfoAlloc(cookie);
 
     if (!cbkInfo)
         *retValP =  -ENOMEM;
@@ -425,9 +426,9 @@
 {
     uint32_t busId = va_arg(args, uint32_t);
     uint8_t byte = va_arg(args, int);
-    uint32_t tid = va_arg(args, uint32_t);
+    (void)va_arg(args, uint32_t); // tid
     void *cookie = va_arg(args, void *);
-    union OsApiSlabItem *cbkInfo = osExpApiI2cCbkInfoAlloc(tid, cookie);
+    union OsApiSlabItem *cbkInfo = osExpApiI2cCbkInfoAlloc(cookie);
 
     if (!cbkInfo)
         *retValP =  -ENOMEM;
@@ -443,9 +444,9 @@
     uint32_t busId = va_arg(args, uint32_t);
     const void *txBuf = va_arg(args, const void*);
     size_t txSize = va_arg(args, size_t);
-    uint32_t tid = va_arg(args, uint32_t);
+    (void)va_arg(args, uint32_t); // tid
     void *cookie = va_arg(args, void *);
-    union OsApiSlabItem *cbkInfo = osExpApiI2cCbkInfoAlloc(tid, cookie);
+    union OsApiSlabItem *cbkInfo = osExpApiI2cCbkInfoAlloc(cookie);
 
     if (!cbkInfo)
         *retValP =  -ENOMEM;
diff --git a/firmware/src/platform/linux/platform.c b/firmware/src/platform/linux/platform.c
index 1fde689..8a45558 100644
--- a/firmware/src/platform/linux/platform.c
+++ b/firmware/src/platform/linux/platform.c
@@ -60,6 +60,11 @@
     return 0;
 }
 
+uint32_t platFreeResources(uint32_t tid)
+{
+    return 0;
+}
+
 int main(int argc, char** argv)
 {
     osMain();
diff --git a/firmware/src/platform/stm32f4xx/bl.c b/firmware/src/platform/stm32f4xx/bl.c
index 39bd18c..2786c36 100644
--- a/firmware/src/platform/stm32f4xx/bl.c
+++ b/firmware/src/platform/stm32f4xx/bl.c
@@ -16,18 +16,23 @@
 
 
 #include <variant/inc/variant.h>
+
 #include <plat/inc/cmsis.h>
 #include <plat/inc/gpio.h>
 #include <plat/inc/pwr.h>
 #include <plat/inc/bl.h>
+
+#include <nanohub/sha2.h>
+#include <nanohub/aes.h>
+#include <nanohub/rsa.h>
+#include <nanohub/nanohub.h>
+
 #include <printf.h>
 #include <string.h>
 #include <alloca.h>
 #include <gpio.h>
-#include <sha2.h>
-#include <aes.h>
-#include <rsa.h>
 
+static uint32_t blVerifyOsImage(const uint8_t *addr, struct OsUpdateHdr **start, uint32_t *size);
 
 struct StmCrc
 {
@@ -425,8 +430,7 @@
 {
     struct StmFlash *flash = (struct StmFlash *)FLASH_BASE;
     const uint32_t sector_cnt = sizeof(mBlFlashTable) / sizeof(struct blFlashTable);
-    uint32_t acr_cache, cr_cache, offset, i, j = 0, int_state = 0, erase_cnt = 0;
-    uint8_t erase_mask[sector_cnt];
+    uint32_t acr_cache, cr_cache, offset, i, j = 0, int_state = 0;
     uint8_t *ptr;
 
     if (((length == 0)) ||
@@ -437,15 +441,6 @@
         return false;
     }
 
-    // disable interrupts
-    // otherwise an interrupt during flash write/erase will stall the processor
-    // until the write/erase completes
-    int_state = blDisableInts();
-
-    // figure out which (if any) blocks we have to erase
-    for (i = 0; i < sector_cnt; i++)
-        erase_mask[i] = 0;
-
     // compute which flash block we are starting from
     for (i = 0; i < sector_cnt; i++) {
         if (dst >= mBlFlashTable[i].address &&
@@ -455,7 +450,7 @@
     }
 
     // now loop through all the flash blocks and see if we have to do any
-    // 0 -> 1 transitions of a bit. If so, we must erase that block
+    // 0 -> 1 transitions of a bit. If so, return false
     // 1 -> 0 transitions of a bit do not require an erase
     offset = (uint32_t)(dst - mBlFlashTable[i].address);
     ptr = mBlFlashTable[i].address;
@@ -467,16 +462,18 @@
         }
 
         if ((ptr[offset] & src[j]) != src[j]) {
-            erase_mask[i] = 1;
-            erase_cnt++;
-            j += mBlFlashTable[i].length - offset;
-            offset = mBlFlashTable[i].length;
+            return false;
         } else {
             j++;
             offset++;
         }
     }
 
+    // disable interrupts
+    // otherwise an interrupt during flash write will stall the processor
+    // until the write completes
+    int_state = blDisableInts();
+
     // wait for flash to not be busy (should never be set at this point)
     while (flash->SR & FLASH_SR_BSY);
 
@@ -503,9 +500,6 @@
     flash->ACR &= ~(FLASH_ACR_DCEN | FLASH_ACR_ICEN);
     flash->ACR |= (FLASH_ACR_DCRST | FLASH_ACR_ICRST);
 
-    if (erase_cnt)
-        blEraseSectors(sector_cnt, erase_mask);
-
     blWriteBytes(dst, src, length);
 
     flash->ACR = acr_cache;
@@ -516,7 +510,7 @@
     return !memcmp(dst, src, length);
 }
 
-static bool blExtApiProgramTypedArea(uint8_t *dst, const uint8_t *src, uint32_t length, uint32_t key1, uint32_t key2, uint32_t type)
+static bool blProgramTypedArea(uint8_t *dst, const uint8_t *src, uint32_t length, uint32_t type, uint32_t key1, uint32_t key2)
 {
     const uint32_t sector_cnt = sizeof(mBlFlashTable) / sizeof(struct blFlashTable);
     uint32_t i;
@@ -537,15 +531,15 @@
 
 static bool blExtApiProgramSharedArea(uint8_t *dst, const uint8_t *src, uint32_t length, uint32_t key1, uint32_t key2)
 {
-    return blExtApiProgramTypedArea(dst, src, length, key1, key2, BL_FLASH_SHARED);
+    return blProgramTypedArea(dst, src, length, BL_FLASH_SHARED, key1, key2);
 }
 
 static bool blExtApiProgramEe(uint8_t *dst, const uint8_t *src, uint32_t length, uint32_t key1, uint32_t key2)
 {
-    return blExtApiProgramTypedArea(dst, src, length, key1, key2, BL_FLASH_EEDATA);
+    return blProgramTypedArea(dst, src, length, BL_FLASH_EEDATA, key1, key2);
 }
 
-static bool blExtApiEraseSharedArea(uint32_t key1, uint32_t key2)
+static bool blEraseTypedArea(uint32_t type, uint32_t key1, uint32_t key2)
 {
     struct StmFlash *flash = (struct StmFlash *)FLASH_BASE;
     const uint32_t sector_cnt = sizeof(mBlFlashTable) / sizeof(struct blFlashTable);
@@ -553,7 +547,7 @@
     uint8_t erase_mask[sector_cnt];
 
     for (i = 0; i < sector_cnt; i++) {
-        if (mBlFlashTable[i].type == BL_FLASH_SHARED) {
+        if (mBlFlashTable[i].type == type) {
             erase_mask[i] = 1;
             erase_cnt++;
         } else {
@@ -604,6 +598,30 @@
     return true; //we assume erase worked
 }
 
+static bool blExtApiEraseSharedArea(uint32_t key1, uint32_t key2)
+{
+    return blEraseTypedArea(BL_FLASH_SHARED, key1, key2);
+}
+
+static uint32_t blVerifyOsUpdate(struct OsUpdateHdr **start, uint32_t *size)
+{
+    uint32_t ret;
+    int i;
+
+    for (i = 0; i < BL_SCAN_OFFSET; i += 4) {
+        ret = blVerifyOsImage(__shared_start + i, start, size);
+        if (ret != OS_UPDT_HDR_CHECK_FAILED)
+            break;
+    }
+
+    return ret;
+}
+
+static uint32_t blExtApiVerifyOsUpdate(void)
+{
+    return blVerifyOsUpdate(NULL, NULL);
+}
+
 static void blSupirousIntHandler(void)
 {
     //BAD!
@@ -681,105 +699,80 @@
     .blAesCbcEncr = &aesCbcEncr,
     .blAesCbcDecr = &aesCbcDecr,
     .blSigPaddingVerify = &blExtApiSigPaddingVerify,
+    .blVerifyOsUpdate = &blExtApiVerifyOsUpdate,
 };
 
-static void blApplyVerifiedUpdate(void) //only called if an update has been found to exist and be valid, signed, etc!
+static void blApplyVerifiedUpdate(const struct OsUpdateHdr *os) //only called if an update has been found to exist and be valid, signed, etc!
 {
-    const struct OsUpdateHdr *updt = (const struct OsUpdateHdr*)__shared_start;
-
     //copy shared to code, and if successful, erase shared area
-    if (blProgramFlash(__code_start, (const uint8_t*)(updt + 1), updt->size, BL_FLASH_KEY1, BL_FLASH_KEY2))
-        (void)blExtApiEraseSharedArea(BL_FLASH_KEY1, BL_FLASH_KEY2);
+    if (blEraseTypedArea(BL_FLASH_KERNEL, BL_FLASH_KEY1, BL_FLASH_KEY2))
+        if (blProgramTypedArea(__code_start, (const uint8_t*)(os + 1), os->size, BL_FLASH_KERNEL, BL_FLASH_KEY1, BL_FLASH_KEY2))
+            (void)blExtApiEraseSharedArea(BL_FLASH_KEY1, BL_FLASH_KEY2);
 }
 
-static void blUpdateMark(uint32_t from, uint32_t to)
+static void blWriteMark(struct OsUpdateHdr *hdr, uint32_t mark)
 {
-    struct OsUpdateHdr *hdr = (struct OsUpdateHdr*)__shared_start;
-    uint8_t dstVal = to;
+    uint8_t dstVal = mark;
 
-    if (hdr->marker != from)
+    (void)blExtApiProgramSharedArea(&hdr->marker, &dstVal, sizeof(hdr->marker), BL_FLASH_KEY1, BL_FLASH_KEY2);
+}
+
+static void blUpdateMark(uint32_t old, uint32_t new)
+{
+    struct OsUpdateHdr *hdr = (struct OsUpdateHdr *)__shared_start;
+
+    if (hdr->marker != old)
         return;
 
-    (void)blProgramFlash(&hdr->marker, &dstVal, 1, BL_FLASH_KEY1, BL_FLASH_KEY2);
+    blWriteMark(hdr, new);
 }
 
-static bool blUpdateVerify(void)
+static uint32_t blVerifyOsImage(const uint8_t *addr, struct OsUpdateHdr **start, uint32_t *size)
 {
     const uint32_t *rsaKey, *osSigHash, *osSigPubkey, *ourHash, *rsaResult, *expectedHash = NULL;
-    const struct OsUpdateHdr *hdr = (const struct OsUpdateHdr*)__shared_start;
-    uint32_t i, j, numRsaKeys = 0, rsaStateVar1, rsaStateVar2, rsaStep = 0;
+    struct OsUpdateHdr *hdr = (struct OsUpdateHdr*)addr;
+    struct OsUpdateHdr cpy;
+    uint32_t i, numRsaKeys = 0, rsaStateVar1, rsaStateVar2, rsaStep = 0;
     const uint8_t *updateBinaryData;
     bool isValid = false;
     struct Sha2state sha;
     struct RsaState rsa;
+    uint32_t ret = OS_UPDT_HDR_CHECK_FAILED;
+    const uint32_t overhead = sizeof(*hdr) + 2 * RSA_WORDS;
 
-    //some basic sanity checking
-    for (i = 0; i < sizeof(hdr->magic); i++) {
-        if (hdr->magic[i] != mOsUpdateMagic[i])
-            break;
-    }
+    // header does not fit or is not aligned
+    if (addr < __shared_start || addr > (__shared_end - overhead) || ((uintptr_t)addr & 3))
+        return OS_UPDT_HDR_CHECK_FAILED;
 
-    if (i != sizeof(hdr->magic)) {
-        //magic value is wrong -> DO NOTHING (shared area might contain something that is not an update but is useful & valid)!
-        return false;
-    }
+    // image does not fit
+    if (hdr->size > (__shared_end - addr - overhead))
+        return OS_UPDT_HDR_CHECK_FAILED;
 
-    if (hdr->marker == OS_UPDT_MARKER_INVALID) {
-        //it's already been checked and found invalid
-        return false;
-    }
+    // OS magic does not match
+    if (memcmp(hdr->magic, mOsUpdateMagic, sizeof(hdr->magic)) != 0)
+        return OS_UPDT_HDR_CHECK_FAILED;
 
-    if (hdr->marker == OS_UPDT_MARKER_VERIFIED) {
-        //it's been verified already
-        return true;
-    }
+    // we don't allow shortcuts on success path, but we want to fail quickly
+    if (hdr->marker == OS_UPDT_MARKER_INVALID)
+        return OS_UPDT_HDR_MARKER_INVALID;
 
-    if (hdr->marker != OS_UPDT_MARKER_DOWNLOADED) {
-        //it's not a fully downloaded update or is not an update
-        goto fail;
-    }
-
-    if (hdr->size & 3) {
-        //updates are always multiples of 4 bytes in size
-        goto fail;
-    }
-
-    if (hdr->size > __shared_end - __shared_start) {
-        //update would not fit in shared area if it were real
-        goto fail;
-    }
-
-    if (hdr->size > __code_end - __code_start) {
-        //udpate would not fit in code area
-        goto fail;
-    }
-
-    if (__shared_end - __shared_start - hdr->size < sizeof(struct OsUpdateHdr) + 2 * RSA_WORDS) {
-        //udpate + header + sig would not fit in shared area if it were real
-        goto fail;
-    }
+    // download did not finish
+    if (hdr->marker == OS_UPDT_MARKER_INPROGRESS)
+        return OS_UPDT_HDR_MARKER_INVALID;
 
     //get pointers
     updateBinaryData = (const uint8_t*)(hdr + 1);
     osSigHash = (const uint32_t*)(updateBinaryData + hdr->size);
     osSigPubkey = osSigHash + RSA_WORDS;
 
-    //hash the update
-    sha2init(&sha);
-    sha2processBytes(&sha, hdr, sizeof(const struct OsUpdateHdr) + hdr->size);
-    ourHash = sha2finish(&sha);
-
     //make sure the pub key is known
     for (i = 0, rsaKey = blExtApiGetRsaKeyInfo(&numRsaKeys); i < numRsaKeys; i++, rsaKey += RSA_WORDS) {
-        for (j = 0; j < RSA_WORDS; j++) {
-            if (rsaKey[j] != osSigPubkey[j])
-                break;
-        }
-        if (j == RSA_WORDS) //it matches
+        if (memcmp(rsaKey, osSigPubkey, RSA_BYTES) == 0)
             break;
     }
 
     if (i == numRsaKeys) {
+        ret = OS_UPDT_UNKNOWN_PUBKEY;
         //signed with an unknown key -> fail
         goto fail;
     }
@@ -791,6 +784,7 @@
 
     if (!rsaResult) {
         //decode fails -> invalid sig
+        ret = OS_UPDT_INVALID_SIGNATURE;
         goto fail;
     }
 
@@ -799,27 +793,43 @@
 
     if (!expectedHash) {
         //padding check fails -> invalid sig
+        ret = OS_UPDT_INVALID_SIGNATURE_HASH;
         goto fail;
     }
 
-    //verify hash match
-    for (i = 0; i < SHA2_HASH_WORDS; i++) {
-        if (expectedHash[i] != ourHash[i])
-            break;
-    }
+    //hash the update
+    sha2init(&sha);
 
-    if (i != SHA2_HASH_WORDS) {
+    memcpy(&cpy, hdr, sizeof(cpy));
+    cpy.marker = OS_UPDT_MARKER_INPROGRESS;
+    sha2processBytes(&sha, &cpy, sizeof(cpy));
+    sha2processBytes(&sha, (uint8_t*)(hdr + 1), hdr->size);
+    ourHash = sha2finish(&sha);
+
+    //verify hash match
+    if (memcmp(expectedHash, ourHash, SHA2_HASH_SIZE) != 0) {
         //hash does not match -> data tampered with
+        ret = OS_UPDT_INVALID_SIGNATURE_HASH; // same error; do not disclose nature of hash problem
         goto fail;
     }
 
     //it is valid
     isValid = true;
+    ret = OS_UPDT_SUCCESS;
+    if (start)
+        *start = hdr;
+    if (size)
+        *size = hdr->size;
 
 fail:
     //mark it appropriately
-    blUpdateMark(OS_UPDT_MARKER_DOWNLOADED, isValid ? OS_UPDT_MARKER_VERIFIED : OS_UPDT_MARKER_INVALID);
-    return isValid;
+    blWriteMark(hdr, isValid ? OS_UPDT_MARKER_VERIFIED : OS_UPDT_MARKER_INVALID);
+    return ret;
+}
+
+static inline bool blUpdateVerify()
+{
+    return blVerifyOsImage(__shared_start, NULL, NULL) == OS_UPDT_SUCCESS;
 }
 
 static void blSpiLoaderDrainRxFifo(struct StmSpi *spi)
@@ -858,7 +868,7 @@
     return blSpiLoaderTxRxByte(spi, 0) == BL_ACK;
 }
 
-static void blSpiLoader(void)
+static void blSpiLoader(bool force)
 {
     const uint32_t intInPin = SH_INT_WAKEUP - GPIO_PA(0);
     struct StmGpio *gpioa = (struct StmGpio*)GPIOA_BASE;
@@ -866,6 +876,8 @@
     struct StmRcc *rcc = (struct StmRcc*)RCC_BASE;
     uint32_t oldApb2State, oldAhb1State, nRetries;
     bool seenErase = false;
+    uint32_t nextAddr = 0;
+    uint32_t expectedSize = 0;
 
     if (SH_INT_WAKEUP < GPIO_PA(0) || SH_INT_WAKEUP > GPIO_PA(15)) {
 
@@ -894,7 +906,7 @@
     gpioa->MODER = (gpioa->MODER & 0xffff00ff & ~(0x03 << (intInPin * 2))) | 0x0000aa00;
 
     //if int pin is not low, do not bother any further
-    if (!(gpioa->IDR & (1 << intInPin))) {
+    if (!(gpioa->IDR & (1 << intInPin)) || force) {
 
         //config SPI
         spi->CR1 = 0x00000040; //spi is on, configured same as bootloader would
@@ -998,8 +1010,14 @@
                     }
 
                     //reject addresses outside of our fake area or on invalid checksum
-                    if (blSpiLoaderTxRxByte(spi, 0) != checksum || addr < BL_SHARED_AREA_FAKE_ADDR || addr - BL_SHARED_AREA_FAKE_ADDR > __shared_end - __shared_start)
-                       break;
+                    if (blSpiLoaderTxRxByte(spi, 0) != checksum ||
+                        addr < BL_SHARED_AREA_FAKE_ADDR ||
+                        addr - BL_SHARED_AREA_FAKE_ADDR > __shared_end - __shared_start)
+                        break;
+
+                    addr -= BL_SHARED_AREA_FAKE_ADDR;
+                    if (addr != nextAddr)
+                        break;
 
                     //ack the address
                     (void)blSpiLoaderSendAck(spi, true);
@@ -1016,12 +1034,12 @@
                     }
 
                     //reject writes that takes out outside fo shared area or invalid checksums
-                    if (blSpiLoaderTxRxByte(spi, 0) != checksum || addr + len - BL_SHARED_AREA_FAKE_ADDR > __shared_end - __shared_start)
+                    if (blSpiLoaderTxRxByte(spi, 0) != checksum || addr + len > __shared_end - __shared_start)
                        break;
 
-                    //a write anywhere where the OS header will be must start at 0
-                    if (addr && addr < sizeof(struct OsUpdateHdr))
-                        break;
+                    // OBSOLETE: superseded by sequential contiguous write requirement
+                    //if (addr && addr < sizeof(struct OsUpdateHdr))
+                    //    break;
 
                     //a write starting at zero must be big enough to contain a full OS update header
                     if (!addr) {
@@ -1037,11 +1055,15 @@
                         //verify magic check passed & marker is properly set to inprogress
                         if (i != sizeof(hdr->magic) || hdr->marker != OS_UPDT_MARKER_INPROGRESS)
                             break;
+                        expectedSize = sizeof(*hdr) + hdr->size + 2 * RSA_BYTES;
                     }
+                    if (addr + len > expectedSize)
+                        break;
 
                     //do it
-                    ack = blProgramFlash(__shared_start + addr - BL_SHARED_AREA_FAKE_ADDR, data, len, BL_FLASH_KEY1, BL_FLASH_KEY2);
+                    ack = blExtApiProgramSharedArea(__shared_start + addr, data, len, BL_FLASH_KEY1, BL_FLASH_KEY2);
                     blSpiLoaderDrainRxFifo(spi);
+                    nextAddr += len;
                     break;
 
                 case BL_CMD_ERASE:
@@ -1062,8 +1084,11 @@
 
                     //do it
                     ack = blExtApiEraseSharedArea(BL_FLASH_KEY1, BL_FLASH_KEY2);
-                    if (ack)
+                    if (ack) {
                         seenErase = true;
+                        nextAddr = 0;
+                        expectedSize = 0;
+                    }
                     blSpiLoaderDrainRxFifo(spi);
                     break;
 
@@ -1076,7 +1101,6 @@
                     break;
 
                 case BL_CMD_UPDATE_FINISHED:
-
                     blUpdateMark(OS_UPDT_MARKER_INPROGRESS, OS_UPDT_MARKER_DOWNLOADED);
                     ack = blUpdateVerify();
                     break;
@@ -1099,6 +1123,7 @@
 {
     extern char __bss_end[], __bss_start[], __data_end[], __data_start[], __data_data[];
     uint32_t appBase = ((uint32_t)&__code_start) & ~1;
+    bool forceLoad = false;
 
     //make sure we're the vector table and no ints happen (BL does not use them)
     blDisableInts();
@@ -1112,11 +1137,19 @@
     blLog("NanohubOS bootloader up @ %p\n", &__blEntry);
 
     //enter SPI loader if requested
-    blSpiLoader();
+    do {
+        uint32_t res;
+        struct OsUpdateHdr *os;
 
-    //try to run main app
-    if (blUpdateVerify())
-        blApplyVerifiedUpdate();
+        blSpiLoader(forceLoad);
+        res = blVerifyOsUpdate(&os, NULL);
+        if (res == OS_UPDT_SUCCESS)
+            blApplyVerifiedUpdate(os);
+        else if (res != OS_UPDT_HDR_CHECK_FAILED)
+            blExtApiEraseSharedArea(BL_FLASH_KEY1, BL_FLASH_KEY2);
+
+        forceLoad = true;
+    } while (*(volatile uint32_t*)appBase == 0xFFFFFFFF);
 
     //call main app with ints off
     blDisableInts();
@@ -1132,17 +1165,3 @@
     //we should never return here
     while(1);
 }
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/firmware/src/platform/stm32f4xx/crc.c b/firmware/src/platform/stm32f4xx/crc.c
index 8538cc5..771703d 100644
--- a/firmware/src/platform/stm32f4xx/crc.c
+++ b/firmware/src/platform/stm32f4xx/crc.c
@@ -16,7 +16,7 @@
 
 #include <string.h>
 
-#include <crc.h>
+#include <nanohub/crc.h>
 #include <seos.h>
 
 #include <plat/inc/pwr.h>
diff --git a/firmware/src/platform/stm32f4xx/dma.c b/firmware/src/platform/stm32f4xx/dma.c
index e295f83..226bb98 100644
--- a/firmware/src/platform/stm32f4xx/dma.c
+++ b/firmware/src/platform/stm32f4xx/dma.c
@@ -86,6 +86,8 @@
 struct StmDmaStreamState {
     DmaCallbackF callback;
     void *cookie;
+    uint16_t tid;
+    uint16_t reserved;
 };
 
 struct StmDmaDev {
@@ -194,9 +196,11 @@
     struct StmDmaStreamRegs *regs = dmaGetStreamRegs(busId, stream);
 
     dmaLogDebug("teif");
-
     dmaStop(busId, stream);
+
+    uint16_t oldTid = osSetCurrentTid(state->tid);
     state->callback(state->cookie, regs->NDTR, EIO);
+    osSetCurrentTid(oldTid);
 }
 
 static void dmaIsrTcif(uint8_t busId, uint8_t stream)
@@ -205,9 +209,11 @@
     struct StmDmaStreamRegs *regs = dmaGetStreamRegs(busId, stream);
 
     dmaLogDebug("tcif");
-
     dmaStop(busId, stream);
+
+    uint16_t oldTid = osSetCurrentTid(state->tid);
     state->callback(state->cookie, regs->NDTR, 0);
+    osSetCurrentTid(oldTid);
 }
 
 static void dmaIsr(uint8_t busId, uint8_t stream)
@@ -238,6 +244,7 @@
     struct StmDmaStreamState *state = dmaGetStreamState(busId, stream);
     state->callback = callback;
     state->cookie = cookie;
+    state->tid = osGetCurrentTid();
 
     pwrUnitClock(PERIPH_BUS_AHB1, STM_DMA_CLOCK_UNIT[busId], true);
 
@@ -276,7 +283,9 @@
 void dmaStop(uint8_t busId, uint8_t stream)
 {
     struct StmDmaStreamRegs *regs = dmaGetStreamRegs(busId, stream);
+    struct StmDmaStreamState *state = dmaGetStreamState(busId, stream);
 
+    state->tid = 0;
     dmaClearIsr(busId, stream, STM_DMA_ISR_TEIFx);
     dmaClearIsr(busId, stream, STM_DMA_ISR_TCIFx);
     NVIC_DisableIRQ(STM_DMA_IRQ[busId][stream]);
@@ -284,9 +293,27 @@
     regs->CR &= ~STM_DMA_CR_EN;
     while (regs->CR & STM_DMA_CR_EN)
         ;
+
 }
 
 const enum IRQn dmaIrq(uint8_t busId, uint8_t stream)
 {
     return STM_DMA_IRQ[busId][stream];
 }
+
+int dmaStopAll(uint32_t tid)
+{
+    int busId, stream, count = 0;
+
+    for (busId = 0; busId < STM_DMA_NUM_DEVS; ++busId) {
+        for (stream = 0; stream < STM_DMA_NUM_STREAMS; ++stream) {
+            struct StmDmaStreamState *state = dmaGetStreamState(busId, stream);
+            if (state->tid == tid) {
+                dmaStop(busId, stream);
+                count++;
+            }
+        }
+    }
+
+    return count;
+}
diff --git a/firmware/src/platform/stm32f4xx/eeData.c b/firmware/src/platform/stm32f4xx/eeData.c
index bcd8893..906b9f3 100644
--- a/firmware/src/platform/stm32f4xx/eeData.c
+++ b/firmware/src/platform/stm32f4xx/eeData.c
@@ -20,7 +20,6 @@
 #include <stdint.h>
 #include <eeData.h>
 
-
 extern uint32_t __eedata_start[], __eedata_end[];
 
 //STM32F4xx eedata stores data in 4-byte aligned chunks
@@ -65,7 +64,7 @@
     return name && name < EE_DATA_NAME_MAX;
 }
 
-static bool eeDataGetEx(uint32_t name, uint32_t *offsetP, bool first, void *buf, uint32_t *szP)
+static void *eeDataGetEx(uint32_t name, uint32_t *offsetP, bool first, void *buf, uint32_t *szP)
 {
     uint32_t sz = 0;
     void *data;
@@ -76,7 +75,7 @@
     //find the data item
     data = eeFind(name, offsetP, first, &sz);
     if (!data)
-        return false;
+        return NULL;
 
     if (buf && szP) {    //get the data
         if (sz > *szP)
@@ -87,25 +86,22 @@
     else if (szP)        //get size
         *szP = sz;
 
-    return true;
+    return (uint32_t*)data - 1;
 }
 
 bool eeDataGet(uint32_t name, void *buf, uint32_t *szP)
 {
     uint32_t offset = 0;
 
-    return eeDataGetEx(name, &offset, false, buf, szP);
+    return eeDataGetEx(name, &offset, false, buf, szP) != NULL;
 }
 
-bool eeDataGetAllVersions(uint32_t name, void *buf, uint32_t *szP, void **stateP)
+void *eeDataGetAllVersions(uint32_t name, void *buf, uint32_t *szP, void **stateP)
 {
     uint32_t offset = *(uint32_t*)stateP;
-    bool ret;
-
-    ret = eeDataGetEx(name, &offset, true, buf, szP);
+    void *addr = eeDataGetEx(name, &offset, true, buf, szP);
     *(uint32_t*)stateP = offset;
-
-    return ret;
+    return addr;
 }
 
 static bool eeWrite(void *dst, const void *src, uint32_t len)
@@ -141,13 +137,17 @@
     return ret;
 }
 
-bool eeDataEraseOldVersion(uint32_t name, void *state)
+bool eeDataEraseOldVersion(uint32_t name, void *vaddr)
 {
-    uint32_t v = *(uint32_t*)state;
+    uint32_t *addr = (uint32_t*)vaddr;
+    uint32_t v;
 
-    if (!eeIsValidName(name))
+    // sanity check
+    if (!eeIsValidName(name) || addr < __eedata_start || addr >= (__eedata_end - 1))
         return false;
 
+    v = *addr;
+
     //verify name
     if ((v & EE_DATA_NAME_MAX) != name)
         return false;
@@ -156,15 +156,5 @@
     v &=~ EE_DATA_NAME_MAX;
 
     //store result
-    return eeWrite(state, &v, sizeof(v));
+    return eeWrite(addr, &v, sizeof(v));
 }
-
-
-
-
-
-
-
-
-
-
diff --git a/firmware/src/platform/stm32f4xx/exti.c b/firmware/src/platform/stm32f4xx/exti.c
index 596f663..30e91db 100644
--- a/firmware/src/platform/stm32f4xx/exti.c
+++ b/firmware/src/platform/stm32f4xx/exti.c
@@ -154,3 +154,14 @@
     unchainIsr(&exti->base, isr);
     return 0;
 }
+
+int extiUnchainAll(uint32_t tid)
+{
+    int i, count = 0;
+    struct ExtiInterrupt *exti = gInterrupts;
+
+    for (i = 0; i < ARRAY_SIZE(gInterrupts); ++i, ++exti)
+        count += unchainIsrAll(&exti->base, tid);
+
+    return count;
+}
diff --git a/firmware/src/platform/stm32f4xx/i2c.c b/firmware/src/platform/stm32f4xx/i2c.c
index feb433c..dd911c1 100644
--- a/firmware/src/platform/stm32f4xx/i2c.c
+++ b/firmware/src/platform/stm32f4xx/i2c.c
@@ -159,6 +159,7 @@
 
     // StmI2cSpiMasterState
     uint8_t masterState;
+    uint16_t tid;
 };
 
 struct StmI2cCfg {
@@ -350,13 +351,27 @@
     stmI2cIrqDisable(pdev, I2C_CR2_ITBUFEN | I2C_CR2_ITERREN);
 }
 
+static inline void stmI2cInvokeRxCallback(struct I2cStmState *state, size_t tx, size_t rx, int err)
+{
+    uint16_t oldTid = osSetCurrentTid(state->tid);
+    state->rx.callback(state->rx.cookie, tx, rx, err);
+    osSetCurrentTid(oldTid);
+}
+
+static inline void stmI2cInvokeTxCallback(struct I2cStmState *state, size_t tx, size_t rx, int err)
+{
+    uint16_t oldTid = osSetCurrentTid(state->tid);
+    state->tx.callback(state->tx.cookie, tx, rx, err);
+    osSetCurrentTid(oldTid);
+}
+
 static inline void stmI2cSlaveRxDone(struct StmI2cDev *pdev)
 {
     struct I2cStmState *state = &pdev->state;
     size_t rxOffst = state->rx.offset;
 
     state->rx.offset = 0;
-    state->rx.callback(state->rx.cookie, 0, rxOffst, 0);
+    stmI2cInvokeRxCallback(state, 0, rxOffst, 0);
 }
 
 static inline void stmI2cSlaveTxDone(struct StmI2cDev *pdev)
@@ -365,7 +380,7 @@
     size_t txOffst = state->tx.offset;
 
     stmI2cSlaveIdle(pdev);
-    state->tx.callback(state->tx.cookie, txOffst, 0, 0);
+    stmI2cInvokeTxCallback(state, txOffst, 0, 0);
 }
 
 static void stmI2cSlaveTxNextByte(struct StmI2cDev *pdev)
@@ -382,7 +397,7 @@
     } else {
         state->slaveState = STM_I2C_SLAVE_TX_ARMED;
         stmI2cIrqDisable(pdev, I2C_CR2_ITBUFEN);
-        state->tx.callback(state->tx.cookie, state->tx.offset, 0, 0);
+        stmI2cInvokeTxCallback(state, state->tx.offset, 0, 0);
     }
 }
 
@@ -483,10 +498,10 @@
 
     state->tx.offset = 0;
     state->rx.offset = 0;
-    state->tx.callback(state->tx.cookie, txOffst, rxOffst, err);
+    stmI2cInvokeTxCallback(state, txOffst, rxOffst, 0);
 
     do {
-        id = atomicAdd(&pdev->next, 1);
+        id = atomicAdd32bits(&pdev->next, 1);
     } while (!id);
 
     for (i=0; i<I2C_MAX_QUEUE_DEPTH; i++) {
@@ -857,7 +872,7 @@
         xfer->cookie = cookie;
 
         do {
-            id = atomicAdd(&pdev->last, 1);
+            id = atomicAdd32bits(&pdev->last, 1);
         } while (!id);
 
         // after this point the transfer can be picked up by the transfer
@@ -883,6 +898,7 @@
                 state->rx.size = xfer->rxSize;
                 state->rx.callback = NULL;
                 state->rx.cookie = NULL;
+                state->tid = osGetCurrentTid();
                 if (pdev->board->sleepDev >= 0)
                     platRequestDevInSleepMode(pdev->board->sleepDev, 12);
                 stmI2cPutXfer(xfer);
@@ -961,6 +977,7 @@
         state->rx.callback = callback;
         state->rx.cookie = cookie;
         state->slaveState = STM_I2C_SLAVE_RX_ARMED;
+        state->tid = osGetCurrentTid();
 
         pwrUnitClock(PERIPH_BUS_APB1, cfg->clock, true);
         pwrUnitReset(PERIPH_BUS_APB1, cfg->clock, true);
diff --git a/firmware/src/platform/stm32f4xx/platform.c b/firmware/src/platform/stm32f4xx/platform.c
index d1046d3..7f213a0 100644
--- a/firmware/src/platform/stm32f4xx/platform.c
+++ b/firmware/src/platform/stm32f4xx/platform.c
@@ -23,6 +23,7 @@
 #include <plat/inc/plat.h>
 #include <plat/inc/exti.h>
 #include <plat/inc/syscfg.h>
+#include <plat/inc/dma.h>
 #include <stdbool.h>
 #include <stdio.h>
 #include <string.h>
@@ -168,7 +169,7 @@
 
     while (i < mEarlyLogBufferOffset) {
         userData = (struct HostIntfDataBuffer *)(mEarlyLogBuffer + i);
-        osEnqueueEvt(EVENT_TYPE_BIT_DISCARDABLE | DEBUG_LOG_EVT, userData, platEarlyLogFree);
+        osEnqueueEvt(EVENT_TYPE_BIT_DISCARDABLE | EVT_DEBUG_LOG, userData, platEarlyLogFree);
         i += HOSTINTF_HEADER_SIZE + userData->length;
     }
 #endif
@@ -178,7 +179,7 @@
 {
 #if defined(DEBUG_LOG_EVT)
     if (userData && mLateBoot) {
-        if (!osEnqueueEvt(EVENT_TYPE_BIT_DISCARDABLE | DEBUG_LOG_EVT, userData, heapFree))
+        if (!osEnqueueEvt(EVENT_TYPE_BIT_DISCARDABLE | EVT_DEBUG_LOG, userData, heapFree))
             heapFree(userData);
     }
 #endif
@@ -679,3 +680,10 @@
     return rtcGetBackupStorage();
 }
 
+uint32_t platFreeResources(uint32_t tid)
+{
+    uint32_t dmaCount = dmaStopAll(tid);
+    uint32_t irqCount = extiUnchainAll(tid);
+
+    return (dmaCount << 8) | irqCount;
+}
diff --git a/firmware/src/sensors.c b/firmware/src/sensors.c
index 4468e54..b6e606c 100644
--- a/firmware/src/sensors.c
+++ b/firmware/src/sensors.c
@@ -35,6 +35,11 @@
 #define SENSOR_RATE_IMPOSSIBLE    0xFFFFFFF3UL /* used in rate calc to indicate impossible combinations */
 #define SENSOR_LATENCY_INVALID    0xFFFFFFFFFFFFFFFFULL
 
+#define HANDLE_TO_TID(handle) (((handle) >> (32 - TASK_TID_BITS)) & TASK_TID_MASK)
+#define EXT_APP_TID(s) HANDLE_TO_TID(s->handle)
+#define LOCAL_APP_OPS(s) ((const struct SensorOps*)taggedPtrToPtr(s->callInfo))
+#define IS_LOCAL_APP(s) (taggedPtrIsPtr(s->callInfo))
+
 struct Sensor {
     const struct SensorInfo *si;
     uint32_t handle;         /* here 0 means invalid */
@@ -77,8 +82,12 @@
 struct SingleAxisDataEvent singleAxisFlush = { .referenceTime = 0 };
 struct TripleAxisDataEvent tripleAxisFlush = { .referenceTime = 0 };
 
-
-
+static inline uint32_t newSensorHandle()
+{
+    // FIXME: only let lower 8 bits of counter to the id; should use all 16 bits, but this
+    // somehow confuses upper layers; pending investigation
+    return (osGetCurrentTid() << 16) | (atomicAdd32bits(&mNextSensorHandle, 1) & 0xFF);
+}
 
 bool sensorsInit(void)
 {
@@ -118,10 +127,13 @@
     if (idx < 0)
         return 0;
 
-    /* grab a handle */
+    /* grab a handle:
+     * this is safe since nobody else could have "JUST" taken this handle,
+     * we'll need to circle around 16 bits before that happens, and have the same TID
+     */
     do {
-        handle = atomicAdd(&mNextSensorHandle, 1);
-    } while (!handle || sensorFindByHandle(handle)); /* this is safe since nobody else could have "JUST" taken this handle, we'll need to circle around 32bits before that happens */
+        handle = newSensorHandle();
+    } while (!handle || sensorFindByHandle(handle));
 
     /* fill the struct in and mark it valid (by setting handle) */
     s = mSensors + idx;
@@ -129,6 +141,7 @@
     s->currentRate = SENSOR_RATE_OFF;
     s->currentLatency = SENSOR_LATENCY_INVALID;
     s->callInfo = callInfo;
+    // TODO: is internal app, callinfo is OPS struct; shall we validate it here?
     s->callData = callData;
     s->initComplete = initComplete ? 1 : 0;
     mem_reorder_barrier();
@@ -153,9 +166,10 @@
     return sensorRegisterEx(si, taggedPtrMakeFromPtr(ops), callData, initComplete);
 }
 
-uint32_t sensorRegisterAsApp(const struct SensorInfo *si, uint32_t tid, void *callData, bool initComplete)
+uint32_t sensorRegisterAsApp(const struct SensorInfo *si, uint32_t unusedTid, void *callData, bool initComplete)
 {
-    return sensorRegisterEx(si, taggedPtrMakeFromUint(tid), callData, initComplete);
+    (void)unusedTid;
+    return sensorRegisterEx(si, taggedPtrMakeFromUint(0), callData, initComplete);
 }
 
 bool sensorRegisterInitComplete(uint32_t handle)
@@ -193,11 +207,21 @@
     slabAllocatorFree(mInternalEvents, event);
 }
 
+#define INVOKE_AS_OWNER_AND_RETURN(func, ...)                       \
+{                                                                   \
+    if (!func)                                                      \
+        return false;                                               \
+    uint16_t oldTid = osSetCurrentTid(HANDLE_TO_TID(s->handle));    \
+    bool done = func(__VA_ARGS__);                                  \
+    osSetCurrentTid(oldTid);                                        \
+    return done;                                                    \
+}
+
 static bool sensorCallFuncPower(struct Sensor* s, bool on)
 {
-    if (taggedPtrIsPtr(s->callInfo))
-        return ((const struct SensorOps*)taggedPtrToPtr(s->callInfo))->sensorPower(on, s->callData);
-    else {
+    if (IS_LOCAL_APP(s)) {
+        INVOKE_AS_OWNER_AND_RETURN(LOCAL_APP_OPS(s)->sensorPower, on, s->callData);
+    } else {
         struct SensorsInternalEvent *evt = (struct SensorsInternalEvent*)slabAllocatorAlloc(mInternalEvents);
 
         if (!evt)
@@ -205,7 +229,9 @@
 
         evt->externalPowerEvt.on = on;
         evt->externalPowerEvt.callData = s->callData;
-        if (osEnqueuePrivateEvt(EVT_APP_SENSOR_POWER, &evt->externalPowerEvt, sensorCallFuncPowerEvtFreeF, taggedPtrToUint(s->callInfo)))
+
+        if (osEnqueuePrivateEvt(EVT_APP_SENSOR_POWER, &evt->externalPowerEvt,
+            sensorCallFuncPowerEvtFreeF, EXT_APP_TID(s)))
             return true;
 
         slabAllocatorFree(mInternalEvents, evt);
@@ -213,12 +239,18 @@
     }
 }
 
+// the most common callback goes as a helper function
+static bool sensorCallAsOwner(struct Sensor* s, bool (*callback)(void*))
+{
+    INVOKE_AS_OWNER_AND_RETURN(callback, s->callData);
+}
+
 static bool sensorCallFuncFwUpld(struct Sensor* s)
 {
-    if (taggedPtrIsPtr(s->callInfo))
-        return ((const struct SensorOps*)taggedPtrToPtr(s->callInfo))->sensorFirmwareUpload(s->callData);
+    if (IS_LOCAL_APP(s))
+        return sensorCallAsOwner(s, LOCAL_APP_OPS(s)->sensorFirmwareUpload);
     else
-        return osEnqueuePrivateEvt(EVT_APP_SENSOR_FW_UPLD, s->callData, NULL, taggedPtrToUint(s->callInfo));
+        return osEnqueuePrivateEvt(EVT_APP_SENSOR_FW_UPLD, s->callData, NULL, EXT_APP_TID(s));
 }
 
 static void sensorCallFuncExternalEvtFreeF(void* event)
@@ -228,9 +260,9 @@
 
 static bool sensorCallFuncSetRate(struct Sensor* s, uint32_t rate, uint64_t latency)
 {
-    if (taggedPtrIsPtr(s->callInfo))
-        return ((const struct SensorOps*)taggedPtrToPtr(s->callInfo))->sensorSetRate(rate, latency, s->callData);
-    else {
+    if (IS_LOCAL_APP(s)) {
+        INVOKE_AS_OWNER_AND_RETURN(LOCAL_APP_OPS(s)->sensorSetRate, rate, latency, s->callData);
+    } else {
         struct SensorsInternalEvent *evt = (struct SensorsInternalEvent*)slabAllocatorAlloc(mInternalEvents);
 
         if (!evt)
@@ -239,7 +271,8 @@
         evt->externalSetRateEvt.latency = latency;
         evt->externalSetRateEvt.rate = rate;
         evt->externalSetRateEvt.callData = s->callData;
-        if (osEnqueuePrivateEvt(EVT_APP_SENSOR_SET_RATE, &evt->externalSetRateEvt, sensorCallFuncExternalEvtFreeF, taggedPtrToUint(s->callInfo)))
+        if (osEnqueuePrivateEvt(EVT_APP_SENSOR_SET_RATE, &evt->externalSetRateEvt,
+            sensorCallFuncExternalEvtFreeF, EXT_APP_TID(s)))
             return true;
 
         slabAllocatorFree(mInternalEvents, evt);
@@ -249,25 +282,25 @@
 
 static bool sensorCallFuncCalibrate(struct Sensor* s)
 {
-    if (taggedPtrIsPtr(s->callInfo) && ((const struct SensorOps*)taggedPtrToPtr(s->callInfo))->sensorCalibrate)
-        return ((const struct SensorOps*)taggedPtrToPtr(s->callInfo))->sensorCalibrate(s->callData);
+    if (IS_LOCAL_APP(s))
+        return sensorCallAsOwner(s, LOCAL_APP_OPS(s)->sensorCalibrate);
     else
-        return osEnqueuePrivateEvt(EVT_APP_SENSOR_CALIBRATE, s->callData, NULL, taggedPtrToUint(s->callInfo));
+        return osEnqueuePrivateEvt(EVT_APP_SENSOR_CALIBRATE, s->callData, NULL, EXT_APP_TID(s));
 }
 
 static bool sensorCallFuncFlush(struct Sensor* s)
 {
-    if (taggedPtrIsPtr(s->callInfo))
-        return ((const struct SensorOps*)taggedPtrToPtr(s->callInfo))->sensorFlush(s->callData);
+    if (IS_LOCAL_APP(s))
+        return sensorCallAsOwner(s, LOCAL_APP_OPS(s)->sensorFlush);
     else
-        return osEnqueuePrivateEvt(EVT_APP_SENSOR_FLUSH, s->callData, NULL, taggedPtrToUint(s->callInfo));
+        return osEnqueuePrivateEvt(EVT_APP_SENSOR_FLUSH, s->callData, NULL, EXT_APP_TID(s));
 }
 
 static bool sensorCallFuncCfgData(struct Sensor* s, void* cfgData)
 {
-    if (taggedPtrIsPtr(s->callInfo) && ((const struct SensorOps*)taggedPtrToPtr(s->callInfo))->sensorCfgData)
-        return ((const struct SensorOps*)taggedPtrToPtr(s->callInfo))->sensorCfgData(cfgData, s->callData);
-    else {
+    if (IS_LOCAL_APP(s)) {
+        INVOKE_AS_OWNER_AND_RETURN(LOCAL_APP_OPS(s)->sensorCfgData, cfgData, s->callData);
+    } else {
         struct SensorsInternalEvent *evt = (struct SensorsInternalEvent*)slabAllocatorAlloc(mInternalEvents);
 
         if (!evt)
@@ -275,7 +308,8 @@
 
         evt->externalCfgDataEvt.data = cfgData;
         evt->externalCfgDataEvt.callData = s->callData;
-        if (osEnqueuePrivateEvt(EVT_APP_SENSOR_CFG_DATA, &evt->externalCfgDataEvt, sensorCallFuncExternalEvtFreeF, taggedPtrToUint(s->callInfo)))
+        if (osEnqueuePrivateEvt(EVT_APP_SENSOR_CFG_DATA, &evt->externalCfgDataEvt,
+            sensorCallFuncExternalEvtFreeF, EXT_APP_TID(s)))
             return true;
 
         slabAllocatorFree(mInternalEvents, evt);
@@ -285,9 +319,9 @@
 
 static bool sensorCallFuncMarshall(struct Sensor* s, uint32_t evtType, void *evtData, TaggedPtr *evtFreeingInfoP)
 {
-    if (taggedPtrIsPtr(s->callInfo) && ((const struct SensorOps*)taggedPtrToPtr(s->callInfo))->sensorCfgData)
-        return ((const struct SensorOps*)taggedPtrToPtr(s->callInfo))->sensorMarshallData(evtType, evtData, evtFreeingInfoP, s->callData);
-    else {
+    if (IS_LOCAL_APP(s)) {
+        INVOKE_AS_OWNER_AND_RETURN(LOCAL_APP_OPS(s)->sensorMarshallData, evtType, evtData, evtFreeingInfoP, s->callData);
+    } else {
         struct SensorsInternalEvent *evt = (struct SensorsInternalEvent*)slabAllocatorAlloc(mInternalEvents);
 
         if (!evt)
@@ -297,7 +331,8 @@
         evt->externalMarshallEvt.origEvtData = evtData;
         evt->externalMarshallEvt.evtFreeingInfo = *evtFreeingInfoP;
         evt->externalMarshallEvt.callData = s->callData;
-        if (osEnqueuePrivateEvt(EVT_APP_SENSOR_MARSHALL, &evt->externalMarshallEvt, sensorCallFuncExternalEvtFreeF, taggedPtrToUint(s->callInfo)))
+        if (osEnqueuePrivateEvt(EVT_APP_SENSOR_MARSHALL, &evt->externalMarshallEvt,
+            sensorCallFuncExternalEvtFreeF, EXT_APP_TID(s)))
             return true;
 
         slabAllocatorFree(mInternalEvents, evt);
@@ -307,17 +342,17 @@
 
 static bool sensorCallFuncTrigger(struct Sensor* s)
 {
-    if (taggedPtrIsPtr(s->callInfo))
-        return ((const struct SensorOps*)taggedPtrToPtr(s->callInfo))->sensorTriggerOndemand(s->callData);
+    if (IS_LOCAL_APP(s))
+        return sensorCallAsOwner(s, LOCAL_APP_OPS(s)->sensorTriggerOndemand);
     else
-        return osEnqueuePrivateEvt(EVT_APP_SENSOR_TRIGGER, s->callData, NULL, taggedPtrToUint(s->callInfo));
+        return osEnqueuePrivateEvt(EVT_APP_SENSOR_TRIGGER, s->callData, NULL, EXT_APP_TID(s));
 }
 
 static bool sensorCallFuncSendOneDirectEvt(struct Sensor* s, uint32_t tid)
 {
-    if (taggedPtrIsPtr(s->callInfo))
-        return ((const struct SensorOps*)taggedPtrToPtr(s->callInfo))->sensorSendOneDirectEvt(s->callData, tid);
-    else {
+    if (IS_LOCAL_APP(s)) {
+        INVOKE_AS_OWNER_AND_RETURN(LOCAL_APP_OPS(s)->sensorSendOneDirectEvt, s->callData, tid);
+    } else {
         struct SensorsInternalEvent *evt = (struct SensorsInternalEvent*)slabAllocatorAlloc(mInternalEvents);
 
         if (!evt)
@@ -325,7 +360,8 @@
 
         evt->externalSendDirectEvt.tid = tid;
         evt->externalSendDirectEvt.callData = s->callData;
-        if (osEnqueuePrivateEvt(EVT_APP_SENSOR_SEND_ONE_DIR_EVT, &evt->externalSendDirectEvt, sensorCallFuncExternalEvtFreeF, taggedPtrToUint(s->callInfo)))
+        if (osEnqueuePrivateEvt(EVT_APP_SENSOR_SEND_ONE_DIR_EVT, &evt->externalSendDirectEvt,
+            sensorCallFuncExternalEvtFreeF, EXT_APP_TID(s)))
             return true;
 
         slabAllocatorFree(mInternalEvents, evt);
@@ -610,6 +646,8 @@
         if (req && req->handle == sensorHandle && req->clientTid == clientTid) {
             req->rate = SENSOR_RATE_OFF;
             req->latency = SENSOR_LATENCY_INVALID;
+            req->clientTid = 0;
+            req->handle = 0;
             mem_reorder_barrier();
             slabAllocatorFree(mCliSensMatrix, req);
             return true;
@@ -619,15 +657,20 @@
     return false;
 }
 
-bool sensorRequest(uint32_t clientTid, uint32_t sensorHandle, uint32_t rate, uint64_t latency)
+bool sensorRequest(uint32_t unusedTid, uint32_t sensorHandle, uint32_t rate, uint64_t latency)
 {
     struct Sensor* s = sensorFindByHandle(sensorHandle);
     uint32_t newSensorRate;
     uint64_t samplingPeriod;
+    uint32_t clientTid;
+
+    (void)unusedTid;
 
     if (!s)
         return false;
 
+    clientTid = osGetCurrentTid();
+
     /* verify the rate is possible */
     newSensorRate = sensorCalcHwRate(s, rate, 0);
     if (newSensorRate == SENSOR_RATE_IMPOSSIBLE)
@@ -651,16 +694,19 @@
     return true;
 }
 
-bool sensorRequestRateChange(uint32_t clientTid, uint32_t sensorHandle, uint32_t newRate, uint64_t newLatency)
+bool sensorRequestRateChange(uint32_t unusedTid, uint32_t sensorHandle, uint32_t newRate, uint64_t newLatency)
 {
     struct Sensor* s = sensorFindByHandle(sensorHandle);
     uint32_t oldRate, newSensorRate;
     uint64_t oldLatency, samplingPeriod;
+    uint32_t clientTid;
+
+    (void)unusedTid;
 
     if (!s)
         return false;
 
-
+    clientTid = osGetCurrentTid();
     /* get current rate */
     if (!sensorGetCurRequestorRate(sensorHandle, clientTid, &oldRate, &oldLatency))
         return false;
@@ -683,14 +729,17 @@
     return true;
 }
 
-bool sensorRelease(uint32_t clientTid, uint32_t sensorHandle)
+bool sensorRelease(uint32_t unusedTid, uint32_t sensorHandle)
 {
     struct Sensor* s = sensorFindByHandle(sensorHandle);
+
+    (void) unusedTid;
+
     if (!s)
         return false;
 
     /* record the request */
-    if (!sensorDeleteRequestor(sensorHandle, clientTid))
+    if (!sensorDeleteRequestor(sensorHandle, osGetCurrentTid()))
         return false;
 
     /* update actual sensor if needed */
@@ -698,14 +747,19 @@
     return true;
 }
 
-bool sensorTriggerOndemand(uint32_t clientTid, uint32_t sensorHandle)
+bool sensorTriggerOndemand(uint32_t unusedTid, uint32_t sensorHandle)
 {
     struct Sensor* s = sensorFindByHandle(sensorHandle);
     uint32_t i;
+    uint32_t clientTid;
+
+    (void)unusedTid;
 
     if (!s || !s->hasOndemand)
         return false;
 
+    clientTid = osGetCurrentTid();
+
     for (i = 0; i < MAX_CLI_SENS_MATRIX_SZ; i++) {
         struct SensorsClientRequest *req = slabAllocatorGetNth(mCliSensMatrix, i);
 
@@ -777,3 +831,16 @@
 
     return sensorCallFuncMarshall(s, evtType, evtData, evtFreeingInfoP);
 }
+
+int sensorUnregisterAll(uint32_t tid)
+{
+    int i, count = 0;
+
+    for (i = 0; i < MAX_REGISTERED_SENSORS; i++)
+        if (HANDLE_TO_TID(mSensors[i].handle) == tid) {
+            sensorUnregister(mSensors[i].handle);
+            count++;
+        }
+
+    return count;
+}
diff --git a/firmware/src/seos.c b/firmware/src/seos.c
index 9d4ce50..cc24576 100644
--- a/firmware/src/seos.c
+++ b/firmware/src/seos.c
@@ -34,35 +34,74 @@
 #include <heap.h>
 #include <slab.h>
 #include <cpu.h>
-#include <crc.h>
 #include <util.h>
+#include <mpu.h>
+#include <nanohubPacket.h>
+#include <atomic.h>
 
+#include <nanohub/nanohub.h>
+#include <nanohub/crc.h>
+
+#define NO_NODE (TaskIndex)(-1)
+#define for_each_task(listHead, task) for (task = osTaskByIdx((listHead)->next); task; task = osTaskByIdx(task->list.next))
+#define MAKE_NEW_TID(task) task->tid = ((task->tid + TASK_TID_INCREMENT) & TASK_TID_COUNTER_MASK) | \
+                                       (osTaskIndex(task) & TASK_TID_IDX_MASK);
+#define TID_TO_TASK_IDX(tid) (tid & TASK_TID_IDX_MASK)
+
+#define FL_TASK_STOPPED 1
+
+#define EVT_SUBSCRIBE_TO_EVT         0x00000000
+#define EVT_UNSUBSCRIBE_TO_EVT       0x00000001
+#define EVT_DEFERRED_CALLBACK        0x00000002
+#define EVT_PRIVATE_EVT              0x00000003
+
+#define EVENT_WITH_ORIGIN(evt, origin)       (((evt) & EVT_MASK) | ((origin) << (32 - TASK_TID_BITS)))
+#define EVENT_GET_ORIGIN(evt) ((evt) >> (32 - TASK_TID_BITS))
+#define EVENT_GET_EVENT(evt) ((evt) & (EVT_MASK & ~EVENT_TYPE_BIT_DISCARDABLE))
 
 /*
  * Since locking is difficult to do right for adding/removing listeners and such
  * since it can happen in interrupt context and not, and one such operation can
  * interrupt another, and we do have a working event queue, we enqueue all the
  * requests and then deal with them in the main code only when the event bubbles
- * up to the front of the quque. This allows us to not need locks around the
+ * up to the front of the queue. This allows us to not need locks around the
  * data structures.
  */
 
+SET_PACKED_STRUCT_MODE_ON
+struct TaskList {
+    TaskIndex prev;
+    TaskIndex next;
+} ATTRIBUTE_PACKED;
+SET_PACKED_STRUCT_MODE_OFF
+
 struct Task {
-    /* pointers may become invalid. Tids do not. Zero tid -> not a valid task */
-    uint32_t tid;
-
-    uint16_t subbedEvtCount;
-    uint16_t subbedEvtListSz;
-    uint32_t *subbedEvents; /* NULL for invalid tasks */
-
     /* App entry points */
-    const struct AppHdr *appHdr;
+    const struct AppHdr *app;
 
     /* per-platform app info */
     struct PlatAppInfo platInfo;
 
     /* for some basic number of subbed events, the array is stored directly here. after that, a heap chunk is used */
     uint32_t subbedEventsInt[MAX_EMBEDDED_EVT_SUBS];
+    uint32_t *subbedEvents; /* NULL for invalid tasks */
+
+    struct TaskList list;
+
+    /* task pointer will not change throughout task lifetime,
+     * however same task pointer may be reused for a new task; to eliminate the ambiguity,
+     * TID is maintained for each task such that new tasks will be guaranteed to receive different TID */
+    uint16_t tid;
+
+    uint8_t  subbedEvtCount;
+    uint8_t  subbedEvtListSz;
+    uint8_t  flags;
+    uint8_t  ioCount;
+
+};
+
+struct TaskPool {
+    struct Task data[MAX_TASKS];
 };
 
 union InternalThing {
@@ -83,27 +122,253 @@
     union OsApiSlabItem osApiItem;
 };
 
-#define EVT_SUBSCRIBE_TO_EVT         0x00000000
-#define EVT_UNSUBSCRIBE_TO_EVT       0x00000001
-#define EVT_DEFERRED_CALLBACK        0x00000002
-#define EVT_PRIVATE_EVT              0x00000003
-
-
+static struct TaskPool mTaskPool;
 static struct EvtQueue *mEvtsInternal;
 static struct SlabAllocator* mMiscInternalThingsSlab;
-static struct Task mTasks[MAX_TASKS];
-static uint32_t mNextTidInfo = FIRST_VALID_TID;
+static struct TaskList mFreeTasks;
+static struct TaskList mTasks;
+static struct Task *mCurrentTask;
+static struct Task *mSystemTask;
 static TaggedPtr *mCurEvtEventFreeingInfo = NULL; //used as flag for retaining. NULL when none or already retained
 
-static struct Task* osTaskFindByTid(uint32_t tid)
+static inline void list_init(struct TaskList *l)
 {
-    uint32_t i;
+    l->prev = l->next = NO_NODE;
+}
 
-    for(i = 0; i < MAX_TASKS; i++)
-        if (mTasks[i].tid && mTasks[i].tid == tid)
-            return mTasks + i;
+static inline struct Task *osGetCurrentTask()
+{
+    return mCurrentTask;
+}
 
-    return NULL;
+static struct Task *osSetCurrentTask(struct Task *task)
+{
+    struct Task *old = mCurrentTask;
+    while (true) {
+        old = mCurrentTask;
+        if (atomicCmpXchg32bits((uint32_t*)&mCurrentTask, (uint32_t)old, (uint32_t)task))
+            break;
+    }
+    return old;
+}
+
+// beyond this point, noone shall access mCurrentTask directly
+
+static inline bool osTaskTestFlags(struct Task *task, uint32_t mask)
+{
+    return (atomicReadByte(&task->flags) & mask) != 0;
+}
+
+static inline uint32_t osTaskClrSetFlags(struct Task *task, uint32_t clrMask, uint32_t setMask)
+{
+    while (true) {
+        uint8_t flags = atomicReadByte(&task->flags);
+        uint8_t newFlags = (flags & ~clrMask) | setMask;
+        if (atomicCmpXchgByte(&task->flags, flags, newFlags))
+            return newFlags;
+    }
+}
+
+static inline uint32_t osTaskAddIoCount(struct Task *task, int32_t delta)
+{
+    uint8_t count = atomicAddByte(&task->ioCount, delta);
+
+    count += delta; // old value is returned, so we add it again
+
+    return count;
+}
+
+static inline uint32_t osTaskGetIoCount(struct Task *task)
+{
+    return atomicReadByte(&task->ioCount);
+}
+
+static inline uint8_t osTaskIndex(struct Task *task)
+{
+    // we don't need signed diff here: this way we simplify boundary check
+    size_t idx = task - &mTaskPool.data[0];
+    return idx >= MAX_TASKS || &mTaskPool.data[idx] != task ? NO_NODE : idx;
+}
+
+static inline struct Task *osTaskByIdx(size_t idx)
+{
+    return idx >= MAX_TASKS ? NULL : &mTaskPool.data[idx];
+}
+
+uint32_t osGetCurrentTid()
+{
+    return osGetCurrentTask()->tid;
+}
+
+uint32_t osSetCurrentTid(uint32_t tid)
+{
+    struct Task *task = osTaskByIdx(TID_TO_TASK_IDX(tid));
+
+    if (task && task->tid == tid) {
+        struct Task *preempted = osSetCurrentTask(task);
+        return preempted->tid;
+    }
+
+    return osGetCurrentTid();
+}
+
+static inline struct Task *osTaskListPeekHead(struct TaskList *listHead)
+{
+    TaskIndex idx = listHead->next;
+    return idx == NO_NODE ? NULL : &mTaskPool.data[idx];
+}
+
+#ifdef DEBUG
+static void dumpListItems(const char *p, struct TaskList *listHead)
+{
+    int i = 0;
+    struct Task *task;
+
+    osLog(LOG_ERROR, "List: %s (%p) [%u;%u]\n",
+          p,
+          listHead,
+          listHead ? listHead->prev : NO_NODE,
+          listHead ? listHead->next : NO_NODE
+    );
+    if (!listHead)
+        return;
+
+    for_each_task(listHead, task) {
+        osLog(LOG_ERROR, "  item %d: task=%p TID=%04X [%u;%u;%u]\n",
+              i,
+              task,
+              task->tid,
+              task->list.prev,
+              osTaskIndex(task),
+              task->list.next
+        );
+        ++i;
+    }
+}
+
+static void dumpTaskList(const char *f, struct Task *task, struct TaskList *listHead)
+{
+    osLog(LOG_ERROR, "%s: pool: %p; task=%p [%u;%u;%u]; listHead=%p [%u;%u]\n",
+          f,
+          &mTaskPool,
+          task,
+          task ? task->list.prev : NO_NODE,
+          osTaskIndex(task),
+          task ? task->list.next : NO_NODE,
+          listHead,
+          listHead ? listHead->prev : NO_NODE,
+          listHead ? listHead->next : NO_NODE
+    );
+    dumpListItems("Tasks", &mTasks);
+    dumpListItems("Free Tasks", &mFreeTasks);
+}
+#else
+#define dumpTaskList(a,b,c)
+#endif
+
+static inline void osTaskListRemoveTask(struct TaskList *listHead, struct Task *task)
+{
+    if (task && listHead) {
+        struct TaskList *cur = &task->list;
+        TaskIndex left_idx = cur->prev;
+        TaskIndex right_idx = cur->next;
+        struct TaskList *left =  left_idx == NO_NODE ? listHead : &mTaskPool.data[left_idx].list;
+        struct TaskList *right = right_idx == NO_NODE ? listHead : &mTaskPool.data[right_idx].list;
+        cur->prev = cur->next = NO_NODE;
+        left->next = right_idx;
+        right->prev = left_idx;
+    } else {
+        dumpTaskList(__func__, task, listHead);
+    }
+}
+
+static inline void osTaskListAddTail(struct TaskList *listHead, struct Task *task)
+{
+    if (task && listHead) {
+        struct TaskList *cur = &task->list;
+        TaskIndex last_idx = listHead->prev;
+        TaskIndex new_idx = osTaskIndex(task);
+        struct TaskList *last = last_idx == NO_NODE ? listHead : &mTaskPool.data[last_idx].list;
+        cur->prev = last_idx;
+        cur->next = NO_NODE;
+        last->next = new_idx;
+        listHead->prev = new_idx;
+    } else {
+        dumpTaskList(__func__, task, listHead);
+    }
+}
+
+static struct Task *osAllocTask()
+{
+    struct Task *task = osTaskListPeekHead(&mFreeTasks);
+
+    if (task) {
+        osTaskListRemoveTask(&mFreeTasks, task);
+        uint16_t tid = task->tid;
+        memset(task, 0, sizeof(*task));
+        task->tid = tid;
+    }
+
+    return task;
+}
+
+static void osFreeTask(struct Task *task)
+{
+    if (task) {
+        task->flags = 0;
+        task->ioCount = 0;
+        osTaskListAddTail(&mFreeTasks, task);
+    }
+}
+
+static void osRemoveTask(struct Task *task)
+{
+    osTaskListRemoveTask(&mTasks, task);
+}
+
+static void osAddTask(struct Task *task)
+{
+    osTaskListAddTail(&mTasks, task);
+}
+
+static inline struct Task* osTaskFindByTid(uint32_t tid)
+{
+    TaskIndex idx = TID_TO_TASK_IDX(tid);
+
+    return idx < MAX_TASKS ? &mTaskPool.data[idx] : NULL;
+}
+
+static inline bool osTaskInit(struct Task *task)
+{
+    struct Task *preempted = osSetCurrentTask(task);
+    bool done = cpuAppInit(task->app, &task->platInfo, task->tid);
+    osSetCurrentTask(preempted);
+    return done;
+}
+
+static inline void osTaskEnd(struct Task *task)
+{
+    struct Task *preempted = osSetCurrentTask(task);
+    uint16_t tid = task->tid;
+
+    cpuAppEnd(task->app, &task->platInfo);
+
+    // task was supposed to release it's resources,
+    // but we do our cleanup anyway
+    osSetCurrentTask(mSystemTask);
+    platFreeResources(tid); // HW resources cleanup (IRQ, DMA etc)
+    sensorUnregisterAll(tid);
+    timTimerCancelAll(tid);
+    heapFreeAll(tid);
+    // NOTE: we don't need to unsubscribe from events
+    osSetCurrentTask(preempted);
+}
+
+static inline void osTaskHandle(struct Task *task, uint32_t evtType, const void* evtData)
+{
+    struct Task *preempted = osSetCurrentTask(task);
+    cpuAppHandle(task->app, &task->platInfo, evtType, evtData);
+    osSetCurrentTask(preempted);
 }
 
 static void handleEventFreeing(uint32_t evtType, void *evtData, uintptr_t evtFreeData) // watch out, this is synchronous
@@ -121,7 +386,7 @@
         if (!task)
             osLog(LOG_ERROR, "EINCEPTION: Failed to find app to call app to free event sent to app(s).\n");
         else
-            cpuAppHandle(task->appHdr, &task->platInfo, EVT_APP_FREE_EVT_DATA, &fd);
+            osTaskHandle(task, EVT_APP_FREE_EVT_DATA, &fd);
     }
 }
 
@@ -148,135 +413,518 @@
 
 static struct Task* osTaskFindByAppID(uint64_t appID)
 {
-    uint32_t i;
+    struct Task *task;
 
-    for (i = 0; i < MAX_TASKS; i++)
-        if (mTasks[i].appHdr && mTasks[i].appHdr->appId == appID)
-            return mTasks + i;
+    for_each_task(&mTasks, task) {
+        if (task->app && task->app->hdr.appId == appID)
+            return task;
+    }
 
     return NULL;
 }
 
-static uint32_t osGetFreeTid(void)
+void osSegmentIteratorInit(struct SegmentIterator *it)
 {
-    do {
-        if (mNextTidInfo == LAST_VALID_TID)
-            mNextTidInfo = FIRST_VALID_TID;
-        else
-            mNextTidInfo++;
-    } while (osTaskFindByTid(mNextTidInfo));
+    uint32_t sz;
+    uint8_t *start = platGetSharedAreaInfo(&sz);
 
-    return mNextTidInfo;
+    it->shared    = (const struct Segment *)(start);
+    it->sharedEnd = (const struct Segment *)(start + sz);
+    it->seg       = NULL;
+}
+
+bool osAppSegmentSetState(const struct AppHdr *app, uint32_t segState)
+{
+    bool done;
+    struct Segment *seg = osGetSegment(app);
+    uint8_t state = segState;
+
+    if (!seg)
+        return false;
+
+    mpuAllowRamExecution(true);
+    mpuAllowRomWrite(true);
+    done = BL.blProgramShared(&seg->state, &state, sizeof(state), BL_FLASH_KEY1, BL_FLASH_KEY2);
+    mpuAllowRomWrite(false);
+    mpuAllowRamExecution(false);
+
+    return done;
+}
+
+bool osSegmentSetSize(struct Segment *seg, uint32_t size)
+{
+    bool ret = true;
+
+    if (!seg)
+        return false;
+
+    if (size > SEG_SIZE_MAX) {
+        seg->state = SEG_ST_ERASED;
+        size = SEG_SIZE_MAX;
+        ret = false;
+    }
+    seg->size[0] = size;
+    seg->size[1] = size >> 8;
+    seg->size[2] = size >> 16;
+
+    return ret;
+}
+
+struct Segment *osSegmentGetEnd()
+{
+    uint32_t size;
+    uint8_t *start = platGetSharedAreaInfo(&size);
+    return (struct Segment *)(start + size);
+}
+
+struct Segment *osGetSegment(const struct AppHdr *app)
+{
+    uint32_t size;
+    uint8_t *start = platGetSharedAreaInfo(&size);
+
+    return (struct Segment *)((uint8_t*)app &&
+                              (uint8_t*)app >= start &&
+                              (uint8_t*)app < (start + size) ?
+                              (uint8_t*)app - sizeof(struct Segment) : NULL);
+}
+
+bool osEraseShared()
+{
+    mpuAllowRamExecution(true);
+    mpuAllowRomWrite(true);
+    (void)BL.blEraseShared(BL_FLASH_KEY1, BL_FLASH_KEY2);
+    mpuAllowRomWrite(false);
+    mpuAllowRamExecution(false);
+    return true;
+}
+
+bool osWriteShared(void *dest, const void *src, uint32_t len)
+{
+    bool ret;
+
+    mpuAllowRamExecution(true);
+    mpuAllowRomWrite(true);
+    ret = BL.blProgramShared(dest, src, len, BL_FLASH_KEY1, BL_FLASH_KEY2);
+    mpuAllowRomWrite(false);
+    mpuAllowRamExecution(false);
+
+    if (!ret)
+        osLog(LOG_ERROR, "osWriteShared: blProgramShared return false\n");
+
+    return ret;
+}
+
+struct AppHdr *osAppSegmentCreate(uint32_t size)
+{
+    struct SegmentIterator it;
+    const struct Segment *storageSeg = NULL;
+    struct AppHdr *app;
+
+    osSegmentIteratorInit(&it);
+    while (osSegmentIteratorNext(&it)) {
+        if (osSegmentGetState(it.seg) == SEG_ST_EMPTY) {
+            storageSeg = it.seg;
+            break;
+        }
+    }
+    if (!storageSeg || osSegmentSizeGetNext(storageSeg, size) > it.sharedEnd)
+        return NULL;
+
+    app = osSegmentGetData(storageSeg);
+    osAppSegmentSetState(app, SEG_ST_RESERVED);
+
+    return app;
+}
+
+bool osAppSegmentClose(struct AppHdr *app, uint32_t segDataSize, uint32_t segState)
+{
+    struct Segment seg;
+
+    // this is enough for holding padding to uint32_t and the footer
+    uint8_t footer[sizeof(uint32_t) + FOOTER_SIZE];
+    int footerLen;
+    bool ret;
+    uint32_t totalSize;
+    uint8_t *start = platGetSharedAreaInfo(&totalSize);
+    uint8_t *end = start + totalSize;
+    int32_t fullSize = segDataSize + sizeof(seg); // without footer or padding
+    struct Segment *storageSeg = osGetSegment(app);
+
+    // sanity check
+    if (segDataSize >= SEG_SIZE_MAX)
+        return false;
+
+    // physical limits check
+    if (osSegmentSizeAlignedWithFooter(segDataSize) + sizeof(struct Segment) > totalSize)
+        return false;
+
+    // available space check: we could truncate size, instead of disallowing it,
+    // but we know that we performed validation on the size before, in *Create call,
+    // and it was fine, so this must be a programming error, and so we fail.
+    // on a side note: size may grow or shrink compared to original estimate.
+    // typically it shrinks, since we skip some header info and padding, as well
+    // as signature blocks, but it is possible that at some point we may produce
+    // more data for some reason. At that time the logic here may need to change
+    if (osSegmentSizeGetNext(storageSeg, segDataSize) > (struct Segment*)end)
+        return false;
+
+    seg.state = segState;
+    osSegmentSetSize(&seg, segDataSize);
+
+    ret = osWriteShared((uint8_t*)storageSeg, (uint8_t*)&seg, sizeof(seg));
+
+    footerLen = (-fullSize) & 3;
+    memset(footer, 0x00, footerLen);
+
+#ifdef SEGMENT_CRC_SUPPORT
+    struct SegmentFooter segFooter {
+        .crc = ~crc32(storageSeg, fullSize, ~0),
+    };
+    memcpy(&footer[footerLen], &segFooter, sizeof(segFooter));
+    footerLen += sizeof(segFooter);
+#endif
+
+    if (ret && footerLen)
+        ret = osWriteShared((uint8_t*)storageSeg + fullSize, footer, footerLen);
+
+    return ret;
+}
+
+bool osAppWipeData(struct AppHdr *app)
+{
+    struct Segment *seg = osGetSegment(app);
+    int32_t size = osSegmentGetSize(seg);
+    uint8_t *p = (uint8_t*)app;
+    uint32_t state = osSegmentGetState(seg);
+    uint8_t buf[256];
+    bool done = true;
+
+    if (!seg || size == SEG_SIZE_INVALID || state == SEG_ST_EMPTY) {
+        osLog(LOG_ERROR, "%s: can't erase segment: app=%p; seg=%p"
+                         "; size=%" PRIu32
+                         "; state=%" PRIu32
+                         "\n",
+                         __func__, app, seg, size, state);
+        return false;
+    }
+
+    size = osSegmentSizeAlignedWithFooter(size);
+
+    memset(buf, 0, sizeof(buf));
+    while (size > 0) {
+        uint32_t flashSz = size > sizeof(buf) ? sizeof(buf) : size;
+        // keep trying to zero-out stuff even in case of intermittent failures.
+        // flash write may occasionally fail on some byte, but it is not good enough
+        // reason to not rewrite other bytes
+        bool res = osWriteShared(p, buf, flashSz);
+        done = done && res;
+        size -= flashSz;
+        p += flashSz;
+    }
+
+    return done;
+}
+
+static inline bool osAppIsValid(const struct AppHdr *app)
+{
+    return app->hdr.magic == APP_HDR_MAGIC &&
+           app->hdr.fwVer == APP_HDR_VER_CUR &&
+           (app->hdr.fwFlags & FL_APP_HDR_APPLICATION) != 0 &&
+           app->hdr.payInfoType == LAYOUT_APP;
+}
+
+static bool osExtAppIsValid(const struct AppHdr *app, uint32_t len)
+{
+    //TODO: when CRC support is ready, add CRC check here
+    return  osAppIsValid(app) &&
+            len >= sizeof(*app) &&
+            osAppSegmentGetState(app) == SEG_ST_VALID &&
+            !(app->hdr.fwFlags & FL_APP_HDR_INTERNAL);
+}
+
+static bool osIntAppIsValid(const struct AppHdr *app)
+{
+    return  osAppIsValid(app) &&
+            osAppSegmentGetState(app) == SEG_STATE_INVALID &&
+            (app->hdr.fwFlags & FL_APP_HDR_INTERNAL) != 0;
+}
+
+static inline bool osExtAppErase(const struct AppHdr *app)
+{
+    return osAppSegmentSetState(app, SEG_ST_ERASED);
+}
+
+static struct Task *osLoadApp(const struct AppHdr *app) {
+    struct Task *task;
+
+    task = osAllocTask();
+    if (!task) {
+        osLog(LOG_WARN, "External app id %016" PRIX64 " @ %p cannot be used as too many apps already exist.\n", app->hdr.appId, app);
+        return NULL;
+    }
+    task->app = app;
+    bool done = (app->hdr.fwFlags & FL_APP_HDR_INTERNAL) ?
+                cpuInternalAppLoad(task->app, &task->platInfo) :
+                cpuAppLoad(task->app, &task->platInfo);
+
+    if (!done) {
+        osLog(LOG_WARN, "App @ %p ID %016" PRIX64 " failed to load\n", app, app->hdr.appId);
+        osFreeTask(task);
+        task = NULL;
+    }
+
+    return task;
+}
+
+static void osUnloadApp(struct Task *task)
+{
+    // this is called on task that has stopped running, or had never run
+    cpuAppUnload(task->app, &task->platInfo);
+    osFreeTask(task);
+}
+
+static bool osStartApp(const struct AppHdr *app)
+{
+    bool done = false;
+    struct Task *task;
+
+    if ((task = osLoadApp(app)) != NULL) {
+        task->subbedEvtListSz = MAX_EMBEDDED_EVT_SUBS;
+        task->subbedEvents = task->subbedEventsInt;
+        MAKE_NEW_TID(task);
+
+        done = osTaskInit(task);
+
+        if (!done) {
+            osLog(LOG_WARN, "App @ %p ID %016" PRIX64 "failed to init\n", task->app, task->app->hdr.appId);
+            osUnloadApp(task);
+        } else {
+            osAddTask(task);
+        }
+    }
+
+    return done;
+}
+
+static bool osStopTask(struct Task *task)
+{
+    if (!task)
+        return false;
+
+    osTaskClrSetFlags(task, 0, FL_TASK_STOPPED);
+    osRemoveTask(task);
+
+    if (osTaskGetIoCount(task)) {
+        osTaskHandle(task, EVT_APP_STOP, NULL);
+        osEnqueueEvtOrFree(EVT_APP_END, task, NULL);
+    } else {
+        osTaskEnd(task);
+        osUnloadApp(task);
+    }
+
+    return true;
+}
+
+static bool osExtAppFind(struct SegmentIterator *it, uint64_t appId)
+{
+    uint64_t vendor = APP_ID_GET_VENDOR(appId);
+    uint64_t seqId = APP_ID_GET_SEQ_ID(appId);
+    uint64_t curAppId;
+    const struct AppHdr *app;
+    const struct Segment *seg;
+
+    while (osSegmentIteratorNext(it)) {
+        seg = it->seg;
+        if (seg->state == SEG_ST_EMPTY)
+            break;
+        if (seg->state != SEG_ST_VALID)
+            continue;
+        app = osSegmentGetData(seg);
+        curAppId = app->hdr.appId;
+
+        if ((vendor == APP_VENDOR_ANY || vendor == APP_ID_GET_VENDOR(curAppId)) &&
+            (seqId == APP_SEQ_ID_ANY || seqId == APP_ID_GET_SEQ_ID(curAppId)))
+            return true;
+    }
+
+    return false;
+}
+
+static uint32_t osExtAppStopEraseApps(uint64_t appId, bool doErase)
+{
+    const struct AppHdr *app;
+    int32_t len;
+    struct Task *task;
+    struct SegmentIterator it;
+    uint32_t stopCount = 0;
+    uint32_t eraseCount = 0;
+    uint32_t appCount = 0;
+    uint32_t taskCount = 0;
+    struct MgmtStatus stat = { .value = 0 };
+
+    osSegmentIteratorInit(&it);
+    while (osExtAppFind(&it, appId)) {
+        app = osSegmentGetData(it.seg);
+        len = osSegmentGetSize(it.seg);
+        if (!osExtAppIsValid(app, len))
+            continue;
+        appCount++;
+        task = osTaskFindByAppID(app->hdr.appId);
+        if (task)
+            taskCount++;
+        if (task && task->app == app) {
+            if (osStopTask(task))
+                stopCount++;
+            else
+                continue;
+            if (doErase && osExtAppErase(app))
+                eraseCount++;
+        }
+    }
+    SET_COUNTER(stat.app,   appCount);
+    SET_COUNTER(stat.task,  taskCount);
+    SET_COUNTER(stat.op,    stopCount);
+    SET_COUNTER(stat.erase, eraseCount);
+
+    return stat.value;
+}
+
+uint32_t osExtAppStopApps(uint64_t appId)
+{
+    return osExtAppStopEraseApps(appId, false);
+}
+
+uint32_t osExtAppEraseApps(uint64_t appId)
+{
+    return osExtAppStopEraseApps(appId, true);
+}
+
+static void osScanExternal()
+{
+    struct SegmentIterator it;
+    osSegmentIteratorInit(&it);
+    while (osSegmentIteratorNext(&it)) {
+        switch (osSegmentGetState(it.seg)) {
+        case SEG_ST_EMPTY:
+            // everything looks good
+            osLog(LOG_INFO, "External area is good\n");
+            return;
+        case SEG_ST_ERASED:
+        case SEG_ST_VALID:
+            // this is valid stuff, ignore
+            break;
+        case SEG_ST_RESERVED:
+        default:
+            // something is wrong: erase everything
+            osLog(LOG_ERROR, "External area is damaged. Erasing\n");
+            osEraseShared();
+            return;
+        }
+    }
+}
+
+uint32_t osExtAppStartApps(uint64_t appId)
+{
+    const struct AppHdr *app;
+    int32_t len;
+    struct SegmentIterator it;
+    struct SegmentIterator checkIt;
+    uint32_t startCount = 0;
+    uint32_t eraseCount = 0;
+    uint32_t appCount = 0;
+    uint32_t taskCount = 0;
+    struct MgmtStatus stat = { .value = 0 };
+
+    osScanExternal();
+
+    osSegmentIteratorInit(&it);
+    while (osExtAppFind(&it, appId)) {
+        app = osSegmentGetData(it.seg);
+        len = osSegmentGetSize(it.seg);
+
+        // skip erased or malformed apps
+        if (!osExtAppIsValid(app, len))
+            continue;
+
+        appCount++;
+        checkIt = it;
+        // find the most recent copy
+        while (osExtAppFind(&checkIt, app->hdr.appId)) {
+            if (osExtAppErase(app)) // erase the old one, so we skip it next time
+                eraseCount++;
+            app = osSegmentGetData(checkIt.seg);
+        }
+
+        if (osTaskFindByAppID(app->hdr.appId)) {
+            // this either the most recent external app with the same ID,
+            // or internal app with the same id; in both cases we do nothing
+            taskCount++;
+            continue;
+        }
+
+        if (osStartApp(app))
+            startCount++;
+    }
+    SET_COUNTER(stat.app,   appCount);
+    SET_COUNTER(stat.task,  taskCount);
+    SET_COUNTER(stat.op,    startCount);
+    SET_COUNTER(stat.erase, eraseCount);
+
+    return stat.value;
 }
 
 static void osStartTasks(void)
 {
-    static const char magic[] = APP_HDR_MAGIC;
     const struct AppHdr *app;
-    uint32_t i, nTasks = 0, nApps, sharedSz;
+    uint32_t i, nApps;
     struct Task* task;
-    const uint8_t *shared, *sharedEnd;
-    int len, total_len;
-    uint8_t id1, id2;
+    uint32_t status = 0;
+    uint32_t taskCnt = 0;
+
+    osLog(LOG_DEBUG, "Initializing task pool...\n");
+    list_init(&mTasks);
+    list_init(&mFreeTasks);
+    for (i = 0; i < MAX_TASKS; ++i) {
+        task = &mTaskPool.data[i];
+        list_init(&task->list);
+        osFreeTask(task);
+    }
+
+    mSystemTask = osAllocTask(); // this is a dummy task; holder of TID 0; all system code will run with TID 0
+    osSetCurrentTask(mSystemTask);
+    osLog(LOG_DEBUG, "System task is: %p\n", mSystemTask);
 
     /* first enum all internal apps, making sure to check for dupes */
-    osLog(LOG_DEBUG, "Reading internal app list...\n");
-    for (i = 0, app = platGetInternalAppList(&nApps); i < nApps && nTasks < MAX_TASKS  && app->fmtVer == APP_HDR_VER_CUR; i++, app++) {
-
-        if (app->marker != APP_HDR_MARKER_INTERNAL) {
-            osLog(LOG_WARN, "Weird marker on internal app: [%p]=0x%04X\n", app, app->marker);
+    osLog(LOG_DEBUG, "Starting internal apps...\n");
+    for (i = 0, app = platGetInternalAppList(&nApps); i < nApps; i++, app++) {
+        if (!osIntAppIsValid(app)) {
+            osLog(LOG_WARN, "Invalid internal app @ %p ID %016" PRIX64
+                            "header version: %" PRIu16
+                            "\n",
+                            app, app->hdr.appId, app->hdr.fwVer);
             continue;
         }
-        if ((task = osTaskFindByAppID(app->appId))) {
-            osLog(LOG_WARN, "Internal app id %016" PRIX64 "@ %p attempting to update internal app @ %p. Ignored.\n", app->appId, app, task->appHdr);
+
+        if (!(app->hdr.fwFlags & FL_APP_HDR_INTERNAL)) {
+            osLog(LOG_WARN, "Internal app is not marked: [%p]: flags: 0x%04" PRIX16
+                            "; ID: %016" PRIX64
+                            "; ignored\n",
+                            app, app->hdr.fwFlags, app->hdr.appId);
             continue;
         }
-        mTasks[nTasks++].appHdr = app;
-    }
-
-    /* then enum all external apps, making sure to find the latest (by position in flash) and checking for conflicts with internal apps */
-    osLog(LOG_DEBUG, "Reading external app list...\n");
-    for (shared = platGetSharedAreaInfo(&sharedSz), sharedEnd = shared + sharedSz; shared < sharedEnd && shared[0] != 0xFF; shared += total_len) {
-        id1 = shared[0] & 0x0F;
-        id2 = (shared[0] >> 4) & 0x0F;
-        len = (shared[1] << 16) | (shared[2] << 8) | shared[3];
-        total_len = sizeof(uint32_t) + ((len + 3) & ~3) + sizeof(uint32_t);
-
-        if (shared + total_len > sharedEnd)
-            break;
-
-        //skip over erased sections
-        if (id1 != id2 || id1 != BL_FLASH_APP_ID)
+        if ((task = osTaskFindByAppID(app->hdr.appId))) {
+            osLog(LOG_WARN, "Internal app ID %016" PRIX64
+                            "@ %p attempting to update internal app @ %p; app @%p ignored.\n",
+                            app->hdr.appId, app, task->app, app);
             continue;
-
-        if (1 /*crc32(shared, total_len, ~0) == CRC_RESIDUE*/) {
-            app = (const struct AppHdr *)&shared[4];
-            if (len >= sizeof(struct AppHdr) && !memcmp(magic, app->magic, sizeof(magic) - 1) && app->fmtVer == APP_HDR_VER_CUR) {
-
-                if (app->marker != APP_HDR_MARKER_VALID)  //this may need more logic to handle partially-uploaded things
-                    osLog(LOG_WARN, "Weird marker on external app: [%p]=0x%04X\n", app, app->marker);
-                else if ((task = osTaskFindByAppID(app->appId))) {
-                    if (task->appHdr->marker == APP_HDR_MARKER_INTERNAL)
-                        osLog(LOG_WARN, "External app id %016" PRIX64 " @ %p attempting to update internal app @ %p. This is not allowed.\n", app->appId, app, task->appHdr);
-                    else {
-                        osLog(LOG_DEBUG, "External app id %016" PRIX64 " @ %p updating app @ %p\n", app->appId, app, task->appHdr);
-                        task->appHdr = app;
-                    }
-                }
-                else if (nTasks == MAX_TASKS)
-                    osLog(LOG_WARN, "External app id %016" PRIX64 " @ %p cannot be used as too many apps already exist.\n", app->appId, app);
-                else
-                    mTasks[nTasks++].appHdr = app;
-            }
         }
+        if (osStartApp(app))
+            taskCnt++;
     }
 
-    osLog(LOG_DEBUG, "Enumerated %" PRIu32 " apps\n", nTasks);
-
-    /* Now that we have pointers to all the latest app headers, let's try loading then. */
-    /* Note that if a new version fails to init we will NOT try the old (no reason to assume this is safe) */
-    osLog(LOG_DEBUG, "Loading apps...\n");
-    for (i = 0; i < nTasks;) {
-
-        if (mTasks[i].appHdr->marker == APP_HDR_MARKER_INTERNAL) {
-            if (cpuInternalAppLoad(mTasks[i].appHdr, &mTasks[i].platInfo)) {
-                i++;
-                continue;
-            }
-        }
-        else {
-            if (cpuAppLoad(mTasks[i].appHdr, &mTasks[i].platInfo)) {
-                i++;
-                continue;
-            }
-        }
-
-        //if we're here, an app failed to load - remove it from the list
-        osLog(LOG_WARN, "App @ %p failed to load\n", mTasks[i].appHdr);
-        memcpy(mTasks + i, mTasks + --nTasks, sizeof(struct Task));
-    }
-
-    osLog(LOG_DEBUG, "Loaded %" PRIu32 " apps\n", nTasks);
-
-    /* now finish initing structs, assign tids, call init funcs */
-    osLog(LOG_DEBUG, "Starting apps...\n");
-    for (i = 0; i < nTasks;) {
-
-        mTasks[i].subbedEvtListSz = MAX_EMBEDDED_EVT_SUBS;
-        mTasks[i].subbedEvents = mTasks[i].subbedEventsInt;
-        mTasks[i].tid = osGetFreeTid();
-
-        if (cpuAppInit(mTasks[i].appHdr, &mTasks[i].platInfo, mTasks[i].tid))
-            i++;
-        else {
-            //if we're here, an app failed to init - unload & remove it from the list
-            osLog(LOG_WARN, "App @ %p failed to init\n", mTasks[i].appHdr);
-            cpuAppUnload(mTasks[i].appHdr, &mTasks[i].platInfo);
-            memcpy(mTasks + i, mTasks + --nTasks, sizeof(struct Task));
-        }
-    }
-
-    osLog(LOG_DEBUG, "Started %" PRIu32 " apps\n", nTasks);
+    osLog(LOG_DEBUG, "Starting external apps...\n");
+    status = osExtAppStartApps(APP_ID_ANY);
+    osLog(LOG_DEBUG, "Started %" PRIu32 " internal apps; EXT status: %08" PRIX32 "\n", taskCnt, status);
 }
 
 static void osInternalEvtHandle(uint32_t evtType, void *evtData)
@@ -318,6 +966,12 @@
         }
         break;
 
+    case EVT_APP_END:
+        task = evtData;
+        osTaskEnd(task);
+        osUnloadApp(task);
+        break;
+
     case EVT_DEFERRED_CALLBACK:
         da->deferred.callback(da->deferred.cookie);
         break;
@@ -329,7 +983,7 @@
             TaggedPtr *tmp = mCurEvtEventFreeingInfo;
             mCurEvtEventFreeingInfo = NULL;
 
-            cpuAppHandle(task->appHdr, &task->platInfo, da->privateEvt.evtType, da->privateEvt.evtData);
+            osTaskHandle(task, da->privateEvt.evtType, da->privateEvt.evtData);
 
             mCurEvtEventFreeingInfo = tmp;
         }
@@ -381,28 +1035,33 @@
 void osMainDequeueLoop(void)
 {
     TaggedPtr evtFreeingInfo;
-    uint32_t evtType, i, j;
+    uint32_t evtType, j;
     void *evtData;
+    struct Task *task;
+    uint16_t tid;
 
     /* get an event */
     if (!evtQueueDequeue(mEvtsInternal, &evtType, &evtData, &evtFreeingInfo, true))
         return;
 
+    evtType = EVENT_GET_EVENT(evtType);
+    tid = EVENT_GET_ORIGIN(evtType);
+    task = osTaskFindByTid(tid);
+    if (task)
+        osTaskAddIoCount(task, -1);
+
     /* by default we free them when we're done with them */
     mCurEvtEventFreeingInfo = &evtFreeingInfo;
 
-    if (evtType < EVT_NO_FIRST_USER_EVENT) { /* no need for discardable check. all internal events arent discardable */
+    if (evtType < EVT_NO_FIRST_USER_EVENT) {
         /* handle deferred actions and other reserved events here */
         osInternalEvtHandle(evtType, evtData);
-    }
-    else {
+    } else {
         /* send this event to all tasks who want it */
-        for (i = 0; i < MAX_TASKS; i++) {
-            if (!mTasks[i].subbedEvents) /* only check real tasks */
-                continue;
-            for (j = 0; j < mTasks[i].subbedEvtCount; j++) {
-                if (mTasks[i].subbedEvents[j] == (evtType & ~EVENT_TYPE_BIT_DISCARDABLE)) {
-                    cpuAppHandle(mTasks[i].appHdr, &mTasks[i].platInfo, evtType & ~EVENT_TYPE_BIT_DISCARDABLE, evtData);
+        for_each_task(&mTasks, task) {
+            for (j = 0; j < task->subbedEvtCount; j++) {
+                if (task->subbedEvents[j] == evtType) {
+                    osTaskHandle(task, evtType, evtData);
                     break;
                 }
             }
@@ -446,17 +1105,38 @@
 
 bool osEventSubscribe(uint32_t tid, uint32_t evtType)
 {
-    return osEventSubscribeUnsubscribe(tid, evtType, true);
+    (void)tid;
+    return osEventSubscribeUnsubscribe(osGetCurrentTid(), evtType, true);
 }
 
 bool osEventUnsubscribe(uint32_t tid, uint32_t evtType)
 {
-    return osEventSubscribeUnsubscribe(tid, evtType, false);
+    (void)tid;
+    return osEventSubscribeUnsubscribe(osGetCurrentTid(), evtType, false);
+}
+
+static bool osEnqueueEvtCommon(uint32_t evtType, void *evtData, TaggedPtr evtFreeInfo)
+{
+    struct Task *task = osGetCurrentTask();
+
+    if (osTaskTestFlags(task, FL_TASK_STOPPED)) {
+        handleEventFreeing(evtType, evtData, evtFreeInfo);
+        return true;
+    }
+
+    evtType = EVENT_WITH_ORIGIN(evtType, osGetCurrentTid());
+    osTaskAddIoCount(task, 1);
+
+    if (evtQueueEnqueue(mEvtsInternal, evtType, evtData, evtFreeInfo, false))
+        return true;
+
+    osTaskAddIoCount(task, -1);
+    return false;
 }
 
 bool osEnqueueEvt(uint32_t evtType, void *evtData, EventFreeF evtFreeF)
 {
-    return evtQueueEnqueue(mEvtsInternal, evtType, evtData, taggedPtrMakeFromPtr(evtFreeF), false);
+    return osEnqueueEvtCommon(evtType, evtData, taggedPtrMakeFromPtr(evtFreeF));
 }
 
 bool osEnqueueEvtOrFree(uint32_t evtType, void *evtData, EventFreeF evtFreeF)
@@ -471,7 +1151,12 @@
 
 bool osEnqueueEvtAsApp(uint32_t evtType, void *evtData, uint32_t fromAppTid)
 {
-    return evtQueueEnqueue(mEvtsInternal, evtType, evtData, taggedPtrMakeFromUint(fromAppTid), false);
+    // compatibility with existing external apps
+    if (evtType & EVENT_TYPE_BIT_DISCARDABLE_COMPAT)
+        evtType |= EVENT_TYPE_BIT_DISCARDABLE;
+
+    (void)fromAppTid;
+    return osEnqueueEvtCommon(evtType, evtData, taggedPtrMakeFromUint(osGetCurrentTid()));
 }
 
 bool osDefer(OsDeferCbkF callback, void *cookie, bool urgent)
@@ -511,16 +1196,17 @@
 
 bool osEnqueuePrivateEvtAsApp(uint32_t evtType, void *evtData, uint32_t fromAppTid, uint32_t toTid)
 {
-    return osEnqueuePrivateEvtEx(evtType, evtData, taggedPtrMakeFromUint(fromAppTid), toTid);
+    (void)fromAppTid;
+    return osEnqueuePrivateEvtEx(evtType, evtData, taggedPtrMakeFromUint(osGetCurrentTid()), toTid);
 }
 
 bool osTidById(uint64_t appId, uint32_t *tid)
 {
-    uint32_t i;
+    struct Task *task;
 
-    for (i = 0; i < MAX_TASKS; i++) {
-        if (mTasks[i].appHdr && mTasks[i].appHdr->appId == appId) {
-            *tid = mTasks[i].tid;
+    for_each_task(&mTasks, task) {
+        if (task->app && task->app->hdr.appId == appId) {
+            *tid = task->tid;
             return true;
         }
     }
@@ -530,15 +1216,18 @@
 
 bool osAppInfoById(uint64_t appId, uint32_t *appIdx, uint32_t *appVer, uint32_t *appSize)
 {
-    uint32_t i;
+    uint32_t i = 0;
+    struct Task *task;
 
-    for (i = 0; i < MAX_TASKS; i++) {
-        if (mTasks[i].appHdr && mTasks[i].appHdr->appId == appId) {
+    for_each_task(&mTasks, task) {
+        const struct AppHdr *app = task->app;
+        if (app && app->hdr.appId == appId) {
             *appIdx = i;
-            *appVer = mTasks[i].appHdr->appVer;
-            *appSize = mTasks[i].appHdr->rel_end;
+            *appVer = app->hdr.appVer;
+            *appSize = app->sect.rel_end;
             return true;
         }
+        i++;
     }
 
     return false;
@@ -546,11 +1235,19 @@
 
 bool osAppInfoByIndex(uint32_t appIdx, uint64_t *appId, uint32_t *appVer, uint32_t *appSize)
 {
-    if (appIdx < MAX_TASKS && mTasks[appIdx].appHdr) {
-        *appId = mTasks[appIdx].appHdr->appId;
-        *appVer = mTasks[appIdx].appHdr->appVer;
-        *appSize = mTasks[appIdx].appHdr->rel_end;
-        return true;
+    struct Task *task;
+    int i = 0;
+
+    for_each_task(&mTasks, task) {
+        if (i != appIdx) {
+            ++i;
+        } else {
+            const struct AppHdr *app = task->app;
+            *appId = app->hdr.appId;
+            *appVer = app->hdr.appVer;
+            *appSize = app->sect.rel_end;
+            return true;
+        }
     }
 
     return false;
@@ -622,10 +1319,3 @@
 };
 
 #endif
-
-
-PREPOPULATED_ENCR_KEY(google_encr_key, ENCR_KEY_GOOGLE_PREPOPULATED, 0xf1, 0x51, 0x9b, 0x2e, 0x26, 0x6c, 0xeb, 0xe7, 0xd6, 0xd6, 0x0d, 0x17, 0x11, 0x94, 0x99, 0x19, 0x1c, 0xfb, 0x71, 0x56, 0x53, 0xf7, 0xe0, 0x7d, 0x90, 0x07, 0x53, 0x68, 0x10, 0x95, 0x1b, 0x70);
-
-
-
-
diff --git a/firmware/src/timer.c b/firmware/src/timer.c
index a84b101..c623f02 100644
--- a/firmware/src/timer.c
+++ b/firmware/src/timer.c
@@ -32,7 +32,8 @@
 struct Timer {
     uint64_t      expires; /* time of next expiration */
     uint64_t      period;  /* 0 for oneshot */
-    uint32_t      id;      /* 0 for disabled */
+    uint16_t      id;      /* 0 for disabled */
+    uint16_t      tid;     /* we need TID always, for system management */
     uint32_t      jitterPpm;
     uint32_t      driftPpm;
     TaggedPtr     callInfo;
@@ -67,22 +68,22 @@
     slabAllocatorFree(mInternalEvents, event);
 }
 
-static void timCallFunc(TaggedPtr callInfo, uint32_t id, void *callData)
+static void timCallFunc(struct Timer *tim)
 {
     struct TimerEvent *evt;
+    TaggedPtr callInfo = tim->callInfo;
 
     if (taggedPtrIsPtr(callInfo)) {
-        ((TimTimerCbkF)taggedPtrToPtr(callInfo))(id, callData);
+        osSetCurrentTid(tim->tid);
+        ((TimTimerCbkF)taggedPtrToPtr(callInfo))(tim->id, tim->callData);
     } else {
-        if (!(evt = slabAllocatorAlloc(mInternalEvents)))
-            return;
-
-        evt->timerId = id;
-        evt->data = callData;
-        if (osEnqueuePrivateEvt(EVT_APP_TIMER, evt, timerCallFuncFreeF, taggedPtrToUint(callInfo)))
-            return;
-
-        slabAllocatorFree(mInternalEvents, evt);
+        osSetCurrentTid(OS_SYSTEM_TID);
+        if ((evt = slabAllocatorAlloc(mInternalEvents)) != 0) {
+            evt->timerId = tim->id;
+            evt->data = tim->callData;
+            if (!osEnqueuePrivateEvt(EVT_APP_TIMER, evt, timerCallFuncFreeF, tim->tid))
+                slabAllocatorFree(mInternalEvents, evt);
+        }
     }
 }
 
@@ -91,43 +92,40 @@
     uint32_t maxDrift = 0, maxJitter = 0, maxErrTotal = 0;
     bool somethingDone, totalSomethingDone = false;
     uint64_t nextTimer;
-    TaggedPtr callInfo;
-    uint32_t i, id;
-    void *callData;
+    uint32_t i;
+    struct Timer *tim;
 
     // protect from concurrent execution [timIntHandler() and timTimerSetEx()]
     uint64_t intSta = cpuIntsOff();
+    uint16_t oldTid = osGetCurrentTid();
 
     do {
         somethingDone = false;
         nextTimer = 0;
 
-        for (i = 0; i < MAX_TIMERS; i++) {
-            if (!mTimers[i].id)
+        for (i = 0, tim = &mTimers[0]; i < MAX_TIMERS; i++, tim++) {
+            if (!tim->id)
                 continue;
 
-            if (mTimers[i].expires <= timGetTime()) {
+            if (tim->expires <= timGetTime()) {
                 somethingDone = true;
-                callInfo = mTimers[i].callInfo;
-                callData = mTimers[i].callData;
-                id = mTimers[i].id;
-                if (mTimers[i].period)
-                    mTimers[i].expires += mTimers[i].period;
+                if (tim->period)
+                    tim->expires += tim->period;
                 else {
-                    mTimers[i].id = 0;
+                    tim->id = 0;
                     atomicBitsetClearBit(mTimersValid, i);
                 }
-                timCallFunc(callInfo, id, callData);
+                timCallFunc(tim);
             }
             else {
-                if (mTimers[i].jitterPpm > maxJitter)
-                    maxJitter = mTimers[i].jitterPpm;
-                if (mTimers[i].driftPpm > maxDrift)
-                    maxDrift = mTimers[i].driftPpm;
-                if (mTimers[i].driftPpm + mTimers[i].jitterPpm > maxErrTotal)
-                    maxErrTotal = mTimers[i].driftPpm + mTimers[i].jitterPpm;
-                if (!nextTimer || nextTimer > mTimers[i].expires)
-                    nextTimer = mTimers[i].expires;
+                if (tim->jitterPpm > maxJitter)
+                    maxJitter = tim->jitterPpm;
+                if (tim->driftPpm > maxDrift)
+                    maxDrift = tim->driftPpm;
+                if (tim->driftPpm + tim->jitterPpm > maxErrTotal)
+                    maxErrTotal = tim->driftPpm + tim->jitterPpm;
+                if (!nextTimer || nextTimer > tim->expires)
+                    nextTimer = tim->expires;
             }
         }
 
@@ -139,6 +137,7 @@
     if (!nextTimer)
         platSleepClockRequest(0, 0, 0, 0);
 
+    osSetCurrentTid(oldTid);
     cpuIntsRestore(intSta);
 
     return totalSomethingDone;
@@ -149,14 +148,14 @@
     uint64_t curTime = timGetTime();
     int32_t idx = atomicBitsetFindClearAndSet(mTimersValid);
     struct Timer *t;
-    uint32_t timId;
+    uint16_t timId;
 
     if (idx < 0) /* no free timers */
         return 0;
 
     /* generate next timer ID */
     do {
-        timId = atomicAdd(&mNextTimerId, 1);
+        timId = atomicAdd32bits(&mNextTimerId, 1);
     } while (!timId || timFindTimerById(timId));
 
     /* grab our struct & fill it in */
@@ -170,6 +169,7 @@
 
     /* as soon as we write timer Id, it becomes valid and might fire */
     t->id = timId;
+    t->tid = osGetCurrentTid();
 
     /* fire as needed & recalc alarms*/
     timFireAsNeededAndUpdateAlarms();
@@ -185,7 +185,7 @@
 
 uint32_t timTimerSetAsApp(uint64_t length, uint32_t jitterPpm, uint32_t driftPpm, uint32_t tid, void* data, bool oneShot)
 {
-    return timTimerSetEx(length, jitterPpm, driftPpm, taggedPtrMakeFromUint(tid), data, oneShot);
+    return timTimerSetEx(length, jitterPpm, driftPpm, taggedPtrMakeFromUint(0), data, oneShot);
 }
 
 bool timTimerCancel(uint32_t timerId)
@@ -207,6 +207,26 @@
     return false;
 }
 
+int timTimerCancelAll(uint32_t tid)
+{
+    uint64_t intState;
+    struct Timer *tim;
+    int i, count;
+
+    tim = &mTimers[0];
+    intState = cpuIntsOff();
+    for (i = 0, count = 0; i < MAX_TIMERS; ++i, ++tim) {
+        if (tim->tid != tid)
+            continue;
+        count++;
+        tim->id = 0; /* this disables it */
+        /* this frees struct */
+        atomicBitsetClearBit(mTimersValid, tim - mTimers);
+    }
+    cpuIntsRestore(intState);
+    return count;
+}
+
 bool timIntHandler(void)
 {
     return timFireAsNeededAndUpdateAlarms();
diff --git a/firmware/inc/aes.h b/lib/include/nanohub/aes.h
similarity index 93%
rename from firmware/inc/aes.h
rename to lib/include/nanohub/aes.h
index 30a0678..37e6038 100644
--- a/firmware/inc/aes.h
+++ b/lib/include/nanohub/aes.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef _AES_H_
-#define _AES_H_
+#ifndef _NANOHUB_AES_H_
+#define _NANOHUB_AES_H_
 
 #include <stdint.h>
 
@@ -29,7 +29,7 @@
 
 #define AES_KEY_WORDS     8
 #define AES_BLOCK_WORDS   4
-
+#define AES_BLOCK_SIZE    16 // in bytes
 
 //basic AES block ops
 void aesInitForEncr(struct AesContext *ctx, const uint32_t *k);
@@ -51,5 +51,5 @@
 
 
 
-#endif
+#endif // _NANOHUB_AES_H_
 
diff --git a/firmware/inc/cpu/cortexm4f/appRelocFormat.h b/lib/include/nanohub/appRelocFormat.h
similarity index 100%
rename from firmware/inc/cpu/cortexm4f/appRelocFormat.h
rename to lib/include/nanohub/appRelocFormat.h
diff --git a/firmware/inc/crc.h b/lib/include/nanohub/crc.h
similarity index 92%
rename from firmware/inc/crc.h
rename to lib/include/nanohub/crc.h
index afad91f..96b7e4c 100644
--- a/firmware/inc/crc.h
+++ b/lib/include/nanohub/crc.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef __CRC_H
-#define __CRC_H
+#ifndef _NANOHUB_CRC_H_
+#define _NANOHUB_CRC_H_
 
 #include <stddef.h>
 #include <stdint.h>
@@ -38,4 +38,4 @@
  */
 uint32_t crc32(const void *buf, size_t size, uint32_t crc);
 
-#endif /* __CRC_H */
+#endif /* _NANOHUB_CRC_H_ */
diff --git a/lib/include/nanohub/nanoapp.h b/lib/include/nanohub/nanoapp.h
new file mode 100644
index 0000000..da6f341
--- /dev/null
+++ b/lib/include/nanohub/nanoapp.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _NANOHUB_NANOAPP_H_
+#define _NANOHUB_NANOAPP_H_
+
+#include <stdint.h>
+#include <stdbool.h>
+
+void *reallocOrDie(void *buf, size_t bufSz);
+void assertMem(size_t used, size_t total);
+bool readFile(void *dst, uint32_t len, const char *fileName);
+void *loadFile(const char *fileName, uint32_t *size);
+void printHash(FILE *out, const char *pfx, const uint32_t *hash, size_t size);
+void printHashRev(FILE *out, const char *pfx, const uint32_t *hash, size_t size);
+
+#endif /* _NANOHUB_NANOAPP_H_ */
diff --git a/lib/include/nanohub/nanohub.h b/lib/include/nanohub/nanohub.h
new file mode 100644
index 0000000..53c5189
--- /dev/null
+++ b/lib/include/nanohub/nanohub.h
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _NANOHUB_NANOHUB_H_
+#define _NANOHUB_NANOHUB_H_
+
+#include <inttypes.h>
+#include <nanohub/aes.h>
+
+/* this file is collection of nanohub-related definitions shared between multiple parties,
+ * including but not limited to: HAL, Kernel, utilities, nanohub FW
+ * it provides minimum details on nanohub implementation, necessary to reliably identify it, and
+ * generate/parse compatible images
+ */
+
+#define NANOAPP_SIGNED_FLAG    0x1  // contents is signed with one or more signature block(s)
+#define NANOAPP_ENCRYPTED_FLAG 0x2  // contents is encrypted with exactly one encryption key
+
+#define NANOAPP_AOSP_MAGIC (((uint32_t)'N' <<  0) | ((uint32_t)'A' <<  8) | ((uint32_t)'N' << 16) | ((uint32_t)'O' << 24))
+#define NANOAPP_FW_MAGIC (((uint32_t)'N' <<  0) | ((uint32_t)'B' <<  8) | ((uint32_t)'I' << 16) | ((uint32_t)'N' << 24))
+#define GOOGLE_LAYOUT_MAGIC (((uint32_t)'G' <<  0) | ((uint32_t)'o' <<  8) | ((uint32_t)'o' << 16) | ((uint32_t)'g' << 24))
+
+// The binary format below is in little endian format
+struct nano_app_binary_t {
+    uint32_t header_version;       // 0x1 for this version
+    uint32_t magic;                // "NANO"
+    uint64_t app_id;               // App Id contains vendor id
+    uint32_t app_version;          // Version of the app
+    uint32_t flags;                // Signed, encrypted
+    uint64_t hw_hub_type;          // which hub type is this compiled for
+    uint32_t reserved[2];          // Should be all zeroes
+    uint8_t  custom_binary[0];     // start of custom binary data
+};
+
+// we translate AOSP header into FW header: this header is in LE format
+// please maintain natural alignment for every field (matters to Intel; otherwise is has to be declared as packed)
+struct FwCommonHdr {
+    uint32_t magic;         // external & internal: NANOAPP_FW_MAGIC
+    uint16_t fwVer;         // external & internal: set to 1; header version
+    uint16_t fwFlags;       // external & internal: class : EXTERNAL/INTERNAL, EXEC/NOEXEC, APP/KERNEL/EEDATA/...
+    uint64_t appId;         // external: copy from AOSP header; internal: defined locally
+    uint32_t appVer;        // external: copy from AOSP header; internal: defined locally
+    uint8_t  payInfoType;   // external: copy ImageLayout::payload; internal: LAYOUT_APP
+    uint8_t  payInfoSize;   // sizeof(PayloadInfo) for this payload type
+    uint8_t  rfu[2];        // filled with 0xFF
+};
+
+struct SectInfo {
+    uint32_t data_start;
+    uint32_t data_end;
+    uint32_t data_data;
+
+    uint32_t bss_start;
+    uint32_t bss_end;
+
+    uint32_t got_start;
+    uint32_t got_end;
+    uint32_t rel_start;
+    uint32_t rel_end;
+};
+
+// this is platform-invariant version of struct TaskFuncs (from seos.h)
+struct AppVectors {
+    uint32_t init;
+    uint32_t end;
+    uint32_t handle;
+};
+
+#define FLASH_RELOC_OFFSET offsetof(struct AppHdr, sect)        // used by appSupport.c at run time
+#define BINARY_RELOC_OFFSET offsetof(struct BinHdr, sect)       // used by postprocess at build time
+
+struct BinCommonHdr {
+    uint32_t magic;
+    uint32_t appVer;
+};
+
+// binary nanoapp image (.bin) produced by objcopy starts with this binary header (LE)
+struct BinHdr {
+    struct BinCommonHdr hdr;
+    struct SectInfo     sect;
+    struct AppVectors   vec;
+};
+
+// FW nanoapp image starts with this binary header (LE) in flash
+struct AppHdr {
+    struct FwCommonHdr hdr;
+    struct SectInfo    sect;
+    struct AppVectors  vec;
+};
+
+struct AppSecSignHdr {
+    uint32_t appDataLen;
+};
+
+struct AppSecEncrHdr {
+    uint64_t keyID;
+    uint32_t dataLen;
+    uint32_t IV[AES_BLOCK_WORDS];
+};
+
+#define LAYOUT_APP  1
+#define LAYOUT_KEY  2
+#define LAYOUT_OS   3
+#define LAYOUT_DATA 4
+
+struct ImageLayout {
+    uint32_t magic;     // Layout ID: (GOOGLE_LAYOUT_MAGIC for this implementation)
+    uint8_t  version;   // layout version
+    uint8_t  payload;   // type of payload: APP, SECRET KEY, OS IMAGE, USER DATA, ...
+    uint16_t flags;     // layout flags: extra options for certain payload types; payload-specific
+};
+
+// .napp image starts with this binary header (LE)
+// it is optionally followed by AppSecSignHdr and/or AppSecEncrHdr
+// all of the above are included in signing hash, but never encrypted
+// encryption (if enabled) starts immediately after those
+struct ImageHeader {
+    struct nano_app_binary_t aosp;
+    struct ImageLayout   layout;
+};
+
+#define CKK_RSA 0x00
+#define CKK_AES 0x1F
+
+#define CKO_PUBLIC_KEY  0x02
+#define CKO_PRIVATE_KEY 0x03
+#define CKO_SECRET_KEY  0x04
+
+// flags
+#define FL_KI_ENFORCE_ID 0x0001  // if set, size, key_type, obj_type must be valid
+
+// payload header format: LAYOUT_KEY
+struct KeyInfo {
+    union {
+        struct {
+            uint16_t id;        // arbitrary number, != 0, equivalent of PKCS#11 name
+            uint16_t flags;     // key flags (additional PKCS#11 attrs, unused for now; must be 0)
+            uint16_t size;      // key size in bits
+            uint8_t  key_type;  // 8 LSB of PKCS-11 CKK_<KEY TYPE>
+            uint8_t  obj_type;  // 8 LSB of PKCS-11 CKO_<OBJ TYPE>
+        };
+        uint64_t data;          // complete 64-bit key-id, unique within this APP namespace (complete id is <APP_ID | KEY_INFO> 128 bits)
+    };
+};
+
+#define AES_KEY_ID(_id) (((struct KeyInfo){ .key_type = CKK_AES, .obj_type = CKO_SECRET_KEY, .size = 256, .id = (_id) }).data)
+
+// payload header format: LAYOUT_APP
+struct AppInfo {
+    struct SectInfo   sect;
+    struct AppVectors vec;
+};
+
+#define OS_UPDT_MARKER_INPROGRESS     0xFF
+#define OS_UPDT_MARKER_DOWNLOADED     0xFE
+#define OS_UPDT_MARKER_VERIFIED       0xF0
+#define OS_UPDT_MARKER_INVALID        0x00
+#define OS_UPDT_MAGIC                 "Nanohub OS" //11 bytes incl terminator
+
+// payload header format: LAYOUT_OS
+struct OsUpdateHdr {
+    char magic[11];
+    uint8_t marker; //OS_UPDT_MARKER_INPROGRESS -> OS_UPDT_MARKER_DOWNLOADED -> OS_UPDT_MARKER_VERIFIED / OS_UPDT_INVALID
+    uint32_t size;  //does not include the mandatory signature (using device key) that follows
+};
+
+// payload header format: LAYOUT_DATA
+struct DataInfo {
+    uint32_t id;
+    uint32_t size;
+};
+
+#endif // _NANOHUB_NANOHUB_H_
diff --git a/firmware/inc/rsa.h b/lib/include/nanohub/rsa.h
similarity index 94%
rename from firmware/inc/rsa.h
rename to lib/include/nanohub/rsa.h
index ad8434f..d98ad07 100644
--- a/firmware/inc/rsa.h
+++ b/lib/include/nanohub/rsa.h
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-#ifndef _RSA_H_
-#define _RSA_H_
+#ifndef _NANOHUB_RSA_H_
+#define _NANOHUB_RSA_H_
 
 #include <stdint.h>
 
-#define RSA_LEN	    2048
+#define RSA_LEN     2048
 #define RSA_LIMBS   ((RSA_LEN + 31)/ 32)
 #define RSA_BYTES   sizeof(uint32_t[RSA_LIMBS])
 #define RSA_WORDS   (RSA_BYTES / sizeof(uint32_t)) //limbs may change in size, but words are always same :)
@@ -50,5 +50,5 @@
 #endif
 
 
-#endif
+#endif // _NANOHUB_RSA_H_
 
diff --git a/firmware/inc/sha2.h b/lib/include/nanohub/sha2.h
similarity index 94%
rename from firmware/inc/sha2.h
rename to lib/include/nanohub/sha2.h
index ac72e91..b29c5c4 100644
--- a/firmware/inc/sha2.h
+++ b/lib/include/nanohub/sha2.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef _SHA2_H_
-#define _SHA2_H_
+#ifndef _NANOHUB_SHA2_H_
+#define _NANOHUB_SHA2_H_
 
 //this is neither the fastest nor the smallest. but it is simple and matches the spec. cool.
 
@@ -45,5 +45,5 @@
 
 
 
-#endif
+#endif // _NANOHUB_SHA2_H_
 
diff --git a/lib/libm/ef_atan2.c b/lib/libm/ef_atan2.c
new file mode 100644
index 0000000..d57480b
--- /dev/null
+++ b/lib/libm/ef_atan2.c
@@ -0,0 +1,101 @@
+/* ef_atan2.c -- float version of e_atan2.c.
+ * Conversion to float by Ian Lance Taylor, Cygnus Support, ian@cygnus.com.
+ */
+
+/*
+ * ====================================================
+ * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved.
+ *
+ * Developed at SunPro, a Sun Microsystems, Inc. business.
+ * Permission to use, copy, modify, and distribute this
+ * software is freely granted, provided that this notice 
+ * is preserved.
+ * ====================================================
+ *
+ */
+
+#include "fdlibm.h"
+
+#ifdef __STDC__
+static const float 
+#else
+static float 
+#endif
+tiny  = 1.0e-30,
+zero  = 0.0,
+pi_o_4  = 7.8539818525e-01, /* 0x3f490fdb */
+pi_o_2  = 1.5707963705e+00, /* 0x3fc90fdb */
+pi      = 3.1415927410e+00,  /* 0x40490fdb */
+pi_lo   = -8.7422776573e-08; /* 0xb3bbbd2e */
+
+#ifdef __STDC__
+	float __ieee754_atan2f(float y, float x)
+#else
+	float __ieee754_atan2f(y,x)
+	float  y,x;
+#endif
+{  
+	float z;
+	__int32_t k,m,hx,hy,ix,iy;
+
+	GET_FLOAT_WORD(hx,x);
+	ix = hx&0x7fffffff;
+	GET_FLOAT_WORD(hy,y);
+	iy = hy&0x7fffffff;
+	if(FLT_UWORD_IS_NAN(ix)||
+	   FLT_UWORD_IS_NAN(iy))	/* x or y is NaN */
+	   return x+y;
+	if(hx==0x3f800000) return atanf(y);   /* x=1.0 */
+	m = ((hy>>31)&1)|((hx>>30)&2);	/* 2*sign(x)+sign(y) */
+
+    /* when y = 0 */
+	if(FLT_UWORD_IS_ZERO(iy)) {
+	    switch(m) {
+		case 0: 
+		case 1: return y; 	/* atan(+-0,+anything)=+-0 */
+		case 2: return  pi+tiny;/* atan(+0,-anything) = pi */
+		case 3: return -pi-tiny;/* atan(-0,-anything) =-pi */
+	    }
+	}
+    /* when x = 0 */
+	if(FLT_UWORD_IS_ZERO(ix)) return (hy<0)?  -pi_o_2-tiny: pi_o_2+tiny;
+	    
+    /* when x is INF */
+	if(FLT_UWORD_IS_INFINITE(ix)) {
+	    if(FLT_UWORD_IS_INFINITE(iy)) {
+		switch(m) {
+		    case 0: return  pi_o_4+tiny;/* atan(+INF,+INF) */
+		    case 1: return -pi_o_4-tiny;/* atan(-INF,+INF) */
+		    case 2: return  (float)3.0*pi_o_4+tiny;/*atan(+INF,-INF)*/
+		    case 3: return (float)-3.0*pi_o_4-tiny;/*atan(-INF,-INF)*/
+		}
+	    } else {
+		switch(m) {
+		    case 0: return  zero  ;	/* atan(+...,+INF) */
+		    case 1: return -zero  ;	/* atan(-...,+INF) */
+		    case 2: return  pi+tiny  ;	/* atan(+...,-INF) */
+		    case 3: return -pi-tiny  ;	/* atan(-...,-INF) */
+		}
+	    }
+	}
+    /* when y is INF */
+	if(FLT_UWORD_IS_INFINITE(iy)) return (hy<0)? -pi_o_2-tiny: pi_o_2+tiny;
+
+    /* compute y/x */
+	k = (iy-ix)>>23;
+	if(k > 60) z=pi_o_2+(float)0.5*pi_lo; 	/* |y/x| >  2**60 */
+	else if(hx<0&&k<-60) z=0.0; 	/* |y|/x < -2**60 */
+	else z=atanf(fabsf(y/x));	/* safe to do y/x */
+	switch (m) {
+	    case 0: return       z  ;	/* atan(+,+) */
+	    case 1: {
+	    	      __uint32_t zh;
+		      GET_FLOAT_WORD(zh,z);
+		      SET_FLOAT_WORD(z,zh ^ 0x80000000);
+		    }
+		    return       z  ;	/* atan(-,+) */
+	    case 2: return  pi-(z-pi_lo);/* atan(+,-) */
+	    default: /* case 3 */
+	    	    return  (z-pi_lo)-pi;/* atan(-,-) */
+	}
+}
diff --git a/lib/libm/ef_rem_pio2.c b/lib/libm/ef_rem_pio2.c
new file mode 100644
index 0000000..f1191d0
--- /dev/null
+++ b/lib/libm/ef_rem_pio2.c
@@ -0,0 +1,193 @@
+/* ef_rem_pio2.c -- float version of e_rem_pio2.c
+ * Conversion to float by Ian Lance Taylor, Cygnus Support, ian@cygnus.com.
+ */
+
+/*
+ * ====================================================
+ * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved.
+ *
+ * Developed at SunPro, a Sun Microsystems, Inc. business.
+ * Permission to use, copy, modify, and distribute this
+ * software is freely granted, provided that this notice 
+ * is preserved.
+ * ====================================================
+ *
+ */
+
+/* __ieee754_rem_pio2f(x,y)
+ * 
+ * return the remainder of x rem pi/2 in y[0]+y[1] 
+ * use __kernel_rem_pio2f()
+ */
+
+#include "fdlibm.h"
+
+/*
+ * Table of constants for 2/pi, 396 Hex digits (476 decimal) of 2/pi 
+ */
+#ifdef __STDC__
+static const __int32_t two_over_pi[] = {
+#else
+static __int32_t two_over_pi[] = {
+#endif
+0xA2, 0xF9, 0x83, 0x6E, 0x4E, 0x44, 0x15, 0x29, 0xFC,
+0x27, 0x57, 0xD1, 0xF5, 0x34, 0xDD, 0xC0, 0xDB, 0x62, 
+0x95, 0x99, 0x3C, 0x43, 0x90, 0x41, 0xFE, 0x51, 0x63,
+0xAB, 0xDE, 0xBB, 0xC5, 0x61, 0xB7, 0x24, 0x6E, 0x3A, 
+0x42, 0x4D, 0xD2, 0xE0, 0x06, 0x49, 0x2E, 0xEA, 0x09,
+0xD1, 0x92, 0x1C, 0xFE, 0x1D, 0xEB, 0x1C, 0xB1, 0x29, 
+0xA7, 0x3E, 0xE8, 0x82, 0x35, 0xF5, 0x2E, 0xBB, 0x44,
+0x84, 0xE9, 0x9C, 0x70, 0x26, 0xB4, 0x5F, 0x7E, 0x41, 
+0x39, 0x91, 0xD6, 0x39, 0x83, 0x53, 0x39, 0xF4, 0x9C,
+0x84, 0x5F, 0x8B, 0xBD, 0xF9, 0x28, 0x3B, 0x1F, 0xF8, 
+0x97, 0xFF, 0xDE, 0x05, 0x98, 0x0F, 0xEF, 0x2F, 0x11,
+0x8B, 0x5A, 0x0A, 0x6D, 0x1F, 0x6D, 0x36, 0x7E, 0xCF, 
+0x27, 0xCB, 0x09, 0xB7, 0x4F, 0x46, 0x3F, 0x66, 0x9E,
+0x5F, 0xEA, 0x2D, 0x75, 0x27, 0xBA, 0xC7, 0xEB, 0xE5, 
+0xF1, 0x7B, 0x3D, 0x07, 0x39, 0xF7, 0x8A, 0x52, 0x92,
+0xEA, 0x6B, 0xFB, 0x5F, 0xB1, 0x1F, 0x8D, 0x5D, 0x08, 
+0x56, 0x03, 0x30, 0x46, 0xFC, 0x7B, 0x6B, 0xAB, 0xF0,
+0xCF, 0xBC, 0x20, 0x9A, 0xF4, 0x36, 0x1D, 0xA9, 0xE3, 
+0x91, 0x61, 0x5E, 0xE6, 0x1B, 0x08, 0x65, 0x99, 0x85,
+0x5F, 0x14, 0xA0, 0x68, 0x40, 0x8D, 0xFF, 0xD8, 0x80, 
+0x4D, 0x73, 0x27, 0x31, 0x06, 0x06, 0x15, 0x56, 0xCA,
+0x73, 0xA8, 0xC9, 0x60, 0xE2, 0x7B, 0xC0, 0x8C, 0x6B, 
+};
+
+/* This array is like the one in e_rem_pio2.c, but the numbers are
+   single precision and the last 8 bits are forced to 0.  */
+#ifdef __STDC__
+static const __int32_t npio2_hw[] = {
+#else
+static __int32_t npio2_hw[] = {
+#endif
+0x3fc90f00, 0x40490f00, 0x4096cb00, 0x40c90f00, 0x40fb5300, 0x4116cb00,
+0x412fed00, 0x41490f00, 0x41623100, 0x417b5300, 0x418a3a00, 0x4196cb00,
+0x41a35c00, 0x41afed00, 0x41bc7e00, 0x41c90f00, 0x41d5a000, 0x41e23100,
+0x41eec200, 0x41fb5300, 0x4203f200, 0x420a3a00, 0x42108300, 0x4216cb00,
+0x421d1400, 0x42235c00, 0x4229a500, 0x422fed00, 0x42363600, 0x423c7e00,
+0x4242c700, 0x42490f00
+};
+
+/*
+ * invpio2:  24 bits of 2/pi
+ * pio2_1:   first  17 bit of pi/2
+ * pio2_1t:  pi/2 - pio2_1
+ * pio2_2:   second 17 bit of pi/2
+ * pio2_2t:  pi/2 - (pio2_1+pio2_2)
+ * pio2_3:   third  17 bit of pi/2
+ * pio2_3t:  pi/2 - (pio2_1+pio2_2+pio2_3)
+ */
+
+#ifdef __STDC__
+static const float 
+#else
+static float 
+#endif
+zero =  0.0000000000e+00, /* 0x00000000 */
+half =  5.0000000000e-01, /* 0x3f000000 */
+two8 =  2.5600000000e+02, /* 0x43800000 */
+invpio2 =  6.3661980629e-01, /* 0x3f22f984 */
+pio2_1  =  1.5707855225e+00, /* 0x3fc90f80 */
+pio2_1t =  1.0804334124e-05, /* 0x37354443 */
+pio2_2  =  1.0804273188e-05, /* 0x37354400 */
+pio2_2t =  6.0770999344e-11, /* 0x2e85a308 */
+pio2_3  =  6.0770943833e-11, /* 0x2e85a300 */
+pio2_3t =  6.1232342629e-17; /* 0x248d3132 */
+
+#ifdef __STDC__
+	__int32_t __ieee754_rem_pio2f(float x, float *y)
+#else
+	__int32_t __ieee754_rem_pio2f(x,y)
+	float x,y[];
+#endif
+{
+	float z,w,t,r,fn;
+	float tx[3];
+	__int32_t i,j,n,ix,hx;
+	int e0,nx;
+
+	GET_FLOAT_WORD(hx,x);
+	ix = hx&0x7fffffff;
+	if(ix<=0x3f490fd8)   /* |x| ~<= pi/4 , no need for reduction */
+	    {y[0] = x; y[1] = 0; return 0;}
+	if(ix<0x4016cbe4) {  /* |x| < 3pi/4, special case with n=+-1 */
+	    if(hx>0) { 
+		z = x - pio2_1;
+		if((ix&0xfffffff0)!=0x3fc90fd0) { /* 24+24 bit pi OK */
+		    y[0] = z - pio2_1t;
+		    y[1] = (z-y[0])-pio2_1t;
+		} else {		/* near pi/2, use 24+24+24 bit pi */
+		    z -= pio2_2;
+		    y[0] = z - pio2_2t;
+		    y[1] = (z-y[0])-pio2_2t;
+		}
+		return 1;
+	    } else {	/* negative x */
+		z = x + pio2_1;
+		if((ix&0xfffffff0)!=0x3fc90fd0) { /* 24+24 bit pi OK */
+		    y[0] = z + pio2_1t;
+		    y[1] = (z-y[0])+pio2_1t;
+		} else {		/* near pi/2, use 24+24+24 bit pi */
+		    z += pio2_2;
+		    y[0] = z + pio2_2t;
+		    y[1] = (z-y[0])+pio2_2t;
+		}
+		return -1;
+	    }
+	}
+	if(ix<=0x43490f80) { /* |x| ~<= 2^7*(pi/2), medium size */
+	    t  = fabsf(x);
+	    n  = (__int32_t) (t*invpio2+half);
+	    fn = (float)n;
+	    r  = t-fn*pio2_1;
+	    w  = fn*pio2_1t;	/* 1st round good to 40 bit */
+	    if(n<32&&(ix&0xffffff00)!=npio2_hw[n-1]) {	
+		y[0] = r-w;	/* quick check no cancellation */
+	    } else {
+	        __uint32_t high;
+	        j  = ix>>23;
+	        y[0] = r-w; 
+		GET_FLOAT_WORD(high,y[0]);
+	        i = j-((high>>23)&0xff);
+	        if(i>8) {  /* 2nd iteration needed, good to 57 */
+		    t  = r;
+		    w  = fn*pio2_2;	
+		    r  = t-w;
+		    w  = fn*pio2_2t-((t-r)-w);	
+		    y[0] = r-w;
+		    GET_FLOAT_WORD(high,y[0]);
+		    i = j-((high>>23)&0xff);
+		    if(i>25)  {	/* 3rd iteration need, 74 bits acc */
+		    	t  = r;	/* will cover all possible cases */
+		    	w  = fn*pio2_3;	
+		    	r  = t-w;
+		    	w  = fn*pio2_3t-((t-r)-w);	
+		    	y[0] = r-w;
+		    }
+		}
+	    }
+	    y[1] = (r-y[0])-w;
+	    if(hx<0) 	{y[0] = -y[0]; y[1] = -y[1]; return -n;}
+	    else	 return n;
+	}
+    /* 
+     * all other (large) arguments
+     */
+	if(!FLT_UWORD_IS_FINITE(ix)) {
+	    y[0]=y[1]=x-x; return 0;
+	}
+    /* set z = scalbn(|x|,ilogb(x)-7) */
+	e0 	= (int)((ix>>23)-134);	/* e0 = ilogb(z)-7; */
+	SET_FLOAT_WORD(z, ix - ((__int32_t)e0<<23));
+	for(i=0;i<2;i++) {
+		tx[i] = (float)((__int32_t)(z));
+		z     = (z-tx[i])*two8;
+	}
+	tx[2] = z;
+	nx = 3;
+	while(tx[nx-1]==zero) nx--;	/* skip zero term */
+	n  =  __kernel_rem_pio2f(tx,y,e0,nx,2,two_over_pi);
+	if(hx<0) {y[0] = -y[0]; y[1] = -y[1]; return -n;}
+	return n;
+}
diff --git a/lib/libm/fdlibm.h b/lib/libm/fdlibm.h
new file mode 100644
index 0000000..a4b7fff
--- /dev/null
+++ b/lib/libm/fdlibm.h
@@ -0,0 +1,404 @@
+
+/* @(#)fdlibm.h 5.1 93/09/24 */
+/*
+ * ====================================================
+ * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved.
+ *
+ * Developed at SunPro, a Sun Microsystems, Inc. business.
+ * Permission to use, copy, modify, and distribute this
+ * software is freely granted, provided that this notice 
+ * is preserved.
+ * ====================================================
+ */
+
+/* REDHAT LOCAL: Include files.  */
+#include <math.h>
+#include <sys/types.h>
+#include <machine/ieeefp.h>
+
+/* REDHAT LOCAL: Default to XOPEN_MODE.  */
+#define _XOPEN_MODE
+
+/* Most routines need to check whether a float is finite, infinite, or not a
+   number, and many need to know whether the result of an operation will
+   overflow.  These conditions depend on whether the largest exponent is
+   used for NaNs & infinities, or whether it's used for finite numbers.  The
+   macros below wrap up that kind of information:
+
+   FLT_UWORD_IS_FINITE(X)
+	True if a positive float with bitmask X is finite.
+
+   FLT_UWORD_IS_NAN(X)
+	True if a positive float with bitmask X is not a number.
+
+   FLT_UWORD_IS_INFINITE(X)
+	True if a positive float with bitmask X is +infinity.
+
+   FLT_UWORD_MAX
+	The bitmask of FLT_MAX.
+
+   FLT_UWORD_HALF_MAX
+	The bitmask of FLT_MAX/2.
+
+   FLT_UWORD_EXP_MAX
+	The bitmask of the largest finite exponent (129 if the largest
+	exponent is used for finite numbers, 128 otherwise).
+
+   FLT_UWORD_LOG_MAX
+	The bitmask of log(FLT_MAX), rounded down.  This value is the largest
+	input that can be passed to exp() without producing overflow.
+
+   FLT_UWORD_LOG_2MAX
+	The bitmask of log(2*FLT_MAX), rounded down.  This value is the
+	largest input than can be passed to cosh() without producing
+	overflow.
+
+   FLT_LARGEST_EXP
+	The largest biased exponent that can be used for finite numbers
+	(255 if the largest exponent is used for finite numbers, 254
+	otherwise) */
+
+#ifdef _FLT_LARGEST_EXPONENT_IS_NORMAL
+#define FLT_UWORD_IS_FINITE(x) 1
+#define FLT_UWORD_IS_NAN(x) 0
+#define FLT_UWORD_IS_INFINITE(x) 0
+#define FLT_UWORD_MAX 0x7fffffff
+#define FLT_UWORD_EXP_MAX 0x43010000
+#define FLT_UWORD_LOG_MAX 0x42b2d4fc
+#define FLT_UWORD_LOG_2MAX 0x42b437e0
+#define HUGE ((float)0X1.FFFFFEP128)
+#else
+#define FLT_UWORD_IS_FINITE(x) ((x)<0x7f800000L)
+#define FLT_UWORD_IS_NAN(x) ((x)>0x7f800000L)
+#define FLT_UWORD_IS_INFINITE(x) ((x)==0x7f800000L)
+#define FLT_UWORD_MAX 0x7f7fffffL
+#define FLT_UWORD_EXP_MAX 0x43000000
+#define FLT_UWORD_LOG_MAX 0x42b17217
+#define FLT_UWORD_LOG_2MAX 0x42b2d4fc
+#define HUGE ((float)3.40282346638528860e+38)
+#endif
+#define FLT_UWORD_HALF_MAX (FLT_UWORD_MAX-(1L<<23))
+#define FLT_LARGEST_EXP (FLT_UWORD_MAX>>23)
+
+/* Many routines check for zero and subnormal numbers.  Such things depend
+   on whether the target supports denormals or not:
+
+   FLT_UWORD_IS_ZERO(X)
+	True if a positive float with bitmask X is +0.	Without denormals,
+	any float with a zero exponent is a +0 representation.	With
+	denormals, the only +0 representation is a 0 bitmask.
+
+   FLT_UWORD_IS_SUBNORMAL(X)
+	True if a non-zero positive float with bitmask X is subnormal.
+	(Routines should check for zeros first.)
+
+   FLT_UWORD_MIN
+	The bitmask of the smallest float above +0.  Call this number
+	REAL_FLT_MIN...
+
+   FLT_UWORD_EXP_MIN
+	The bitmask of the float representation of REAL_FLT_MIN's exponent.
+
+   FLT_UWORD_LOG_MIN
+	The bitmask of |log(REAL_FLT_MIN)|, rounding down.
+
+   FLT_SMALLEST_EXP
+	REAL_FLT_MIN's exponent - EXP_BIAS (1 if denormals are not supported,
+	-22 if they are).
+*/
+
+#ifdef _FLT_NO_DENORMALS
+#define FLT_UWORD_IS_ZERO(x) ((x)<0x00800000L)
+#define FLT_UWORD_IS_SUBNORMAL(x) 0
+#define FLT_UWORD_MIN 0x00800000
+#define FLT_UWORD_EXP_MIN 0x42fc0000
+#define FLT_UWORD_LOG_MIN 0x42aeac50
+#define FLT_SMALLEST_EXP 1
+#else
+#define FLT_UWORD_IS_ZERO(x) ((x)==0)
+#define FLT_UWORD_IS_SUBNORMAL(x) ((x)<0x00800000L)
+#define FLT_UWORD_MIN 0x00000001
+#define FLT_UWORD_EXP_MIN 0x43160000
+#define FLT_UWORD_LOG_MIN 0x42cff1b5
+#define FLT_SMALLEST_EXP -22
+#endif
+
+#ifdef __STDC__
+#undef __P
+#define	__P(p)	p
+#else
+#define	__P(p)	()
+#endif
+
+/* 
+ * set X_TLOSS = pi*2**52, which is possibly defined in <values.h>
+ * (one may replace the following line by "#include <values.h>")
+ */
+
+#define X_TLOSS		1.41484755040568800000e+16 
+
+/* Functions that are not documented, and are not in <math.h>.  */
+
+#ifdef _SCALB_INT
+extern double scalb __P((double, int));
+#else
+extern double scalb __P((double, double));
+#endif
+extern double significand __P((double));
+
+/* ieee style elementary functions */
+extern double __ieee754_sqrt __P((double));			
+extern double __ieee754_acos __P((double));			
+extern double __ieee754_acosh __P((double));			
+extern double __ieee754_log __P((double));			
+extern double __ieee754_atanh __P((double));			
+extern double __ieee754_asin __P((double));			
+extern double __ieee754_atan2 __P((double,double));			
+extern double __ieee754_exp __P((double));
+extern double __ieee754_cosh __P((double));
+extern double __ieee754_fmod __P((double,double));
+extern double __ieee754_pow __P((double,double));
+extern double __ieee754_lgamma_r __P((double,int *));
+extern double __ieee754_gamma_r __P((double,int *));
+extern double __ieee754_log10 __P((double));
+extern double __ieee754_sinh __P((double));
+extern double __ieee754_hypot __P((double,double));
+extern double __ieee754_j0 __P((double));
+extern double __ieee754_j1 __P((double));
+extern double __ieee754_y0 __P((double));
+extern double __ieee754_y1 __P((double));
+extern double __ieee754_jn __P((int,double));
+extern double __ieee754_yn __P((int,double));
+extern double __ieee754_remainder __P((double,double));
+extern __int32_t __ieee754_rem_pio2 __P((double,double*));
+#ifdef _SCALB_INT
+extern double __ieee754_scalb __P((double,int));
+#else
+extern double __ieee754_scalb __P((double,double));
+#endif
+
+/* fdlibm kernel function */
+extern double __kernel_standard __P((double,double,int));
+extern double __kernel_sin __P((double,double,int));
+extern double __kernel_cos __P((double,double));
+extern double __kernel_tan __P((double,double,int));
+extern int    __kernel_rem_pio2 __P((double*,double*,int,int,int,const __int32_t*));
+
+/* Undocumented float functions.  */
+#ifdef _SCALB_INT
+extern float scalbf __P((float, int));
+#else
+extern float scalbf __P((float, float));
+#endif
+extern float significandf __P((float));
+
+/* ieee style elementary float functions */
+extern float __ieee754_sqrtf __P((float));			
+extern float __ieee754_acosf __P((float));			
+extern float __ieee754_acoshf __P((float));			
+extern float __ieee754_logf __P((float));			
+extern float __ieee754_atanhf __P((float));			
+extern float __ieee754_asinf __P((float));			
+extern float __ieee754_atan2f __P((float,float));			
+extern float __ieee754_expf __P((float));
+extern float __ieee754_coshf __P((float));
+extern float __ieee754_fmodf __P((float,float));
+extern float __ieee754_powf __P((float,float));
+extern float __ieee754_lgammaf_r __P((float,int *));
+extern float __ieee754_gammaf_r __P((float,int *));
+extern float __ieee754_log10f __P((float));
+extern float __ieee754_sinhf __P((float));
+extern float __ieee754_hypotf __P((float,float));
+extern float __ieee754_j0f __P((float));
+extern float __ieee754_j1f __P((float));
+extern float __ieee754_y0f __P((float));
+extern float __ieee754_y1f __P((float));
+extern float __ieee754_jnf __P((int,float));
+extern float __ieee754_ynf __P((int,float));
+extern float __ieee754_remainderf __P((float,float));
+extern __int32_t __ieee754_rem_pio2f __P((float,float*));
+#ifdef _SCALB_INT
+extern float __ieee754_scalbf __P((float,int));
+#else
+extern float __ieee754_scalbf __P((float,float));
+#endif
+
+/* float versions of fdlibm kernel functions */
+extern float __kernel_sinf __P((float,float,int));
+extern float __kernel_cosf __P((float,float));
+extern float __kernel_tanf __P((float,float,int));
+extern int   __kernel_rem_pio2f __P((float*,float*,int,int,int,const __int32_t*));
+
+/* The original code used statements like
+	n0 = ((*(int*)&one)>>29)^1;		* index of high word *
+	ix0 = *(n0+(int*)&x);			* high word of x *
+	ix1 = *((1-n0)+(int*)&x);		* low word of x *
+   to dig two 32 bit words out of the 64 bit IEEE floating point
+   value.  That is non-ANSI, and, moreover, the gcc instruction
+   scheduler gets it wrong.  We instead use the following macros.
+   Unlike the original code, we determine the endianness at compile
+   time, not at run time; I don't see much benefit to selecting
+   endianness at run time.  */
+
+#ifndef __IEEE_BIG_ENDIAN
+#ifndef __IEEE_LITTLE_ENDIAN
+ #error Must define endianness
+#endif
+#endif
+
+/* A union which permits us to convert between a double and two 32 bit
+   ints.  */
+
+#ifdef __IEEE_BIG_ENDIAN
+
+typedef union 
+{
+  double value;
+  struct 
+  {
+    __uint32_t msw;
+    __uint32_t lsw;
+  } parts;
+} ieee_double_shape_type;
+
+#endif
+
+#ifdef __IEEE_LITTLE_ENDIAN
+
+typedef union 
+{
+  double value;
+  struct 
+  {
+    __uint32_t lsw;
+    __uint32_t msw;
+  } parts;
+} ieee_double_shape_type;
+
+#endif
+
+/* Get two 32 bit ints from a double.  */
+
+#define EXTRACT_WORDS(ix0,ix1,d)				\
+do {								\
+  ieee_double_shape_type ew_u;					\
+  ew_u.value = (d);						\
+  (ix0) = ew_u.parts.msw;					\
+  (ix1) = ew_u.parts.lsw;					\
+} while (0)
+
+/* Get the more significant 32 bit int from a double.  */
+
+#define GET_HIGH_WORD(i,d)					\
+do {								\
+  ieee_double_shape_type gh_u;					\
+  gh_u.value = (d);						\
+  (i) = gh_u.parts.msw;						\
+} while (0)
+
+/* Get the less significant 32 bit int from a double.  */
+
+#define GET_LOW_WORD(i,d)					\
+do {								\
+  ieee_double_shape_type gl_u;					\
+  gl_u.value = (d);						\
+  (i) = gl_u.parts.lsw;						\
+} while (0)
+
+/* Set a double from two 32 bit ints.  */
+
+#define INSERT_WORDS(d,ix0,ix1)					\
+do {								\
+  ieee_double_shape_type iw_u;					\
+  iw_u.parts.msw = (ix0);					\
+  iw_u.parts.lsw = (ix1);					\
+  (d) = iw_u.value;						\
+} while (0)
+
+/* Set the more significant 32 bits of a double from an int.  */
+
+#define SET_HIGH_WORD(d,v)					\
+do {								\
+  ieee_double_shape_type sh_u;					\
+  sh_u.value = (d);						\
+  sh_u.parts.msw = (v);						\
+  (d) = sh_u.value;						\
+} while (0)
+
+/* Set the less significant 32 bits of a double from an int.  */
+
+#define SET_LOW_WORD(d,v)					\
+do {								\
+  ieee_double_shape_type sl_u;					\
+  sl_u.value = (d);						\
+  sl_u.parts.lsw = (v);						\
+  (d) = sl_u.value;						\
+} while (0)
+
+/* A union which permits us to convert between a float and a 32 bit
+   int.  */
+
+typedef union
+{
+  float value;
+  __uint32_t word;
+} ieee_float_shape_type;
+
+/* Get a 32 bit int from a float.  */
+
+#define GET_FLOAT_WORD(i,d)					\
+do {								\
+  ieee_float_shape_type gf_u;					\
+  gf_u.value = (d);						\
+  (i) = gf_u.word;						\
+} while (0)
+
+/* Set a float from a 32 bit int.  */
+
+#define SET_FLOAT_WORD(d,i)					\
+do {								\
+  ieee_float_shape_type sf_u;					\
+  sf_u.word = (i);						\
+  (d) = sf_u.value;						\
+} while (0)
+
+/* Macros to avoid undefined behaviour that can arise if the amount
+   of a shift is exactly equal to the size of the shifted operand.  */
+
+#define SAFE_LEFT_SHIFT(op,amt)					\
+  (((amt) < 8 * sizeof(op)) ? ((op) << (amt)) : 0)
+
+#define SAFE_RIGHT_SHIFT(op,amt)				\
+  (((amt) < 8 * sizeof(op)) ? ((op) >> (amt)) : 0)
+
+#ifdef  _COMPLEX_H
+
+/*
+ * Quoting from ISO/IEC 9899:TC2:
+ *
+ * 6.2.5.13 Types
+ * Each complex type has the same representation and alignment requirements as
+ * an array type containing exactly two elements of the corresponding real type;
+ * the first element is equal to the real part, and the second element to the
+ * imaginary part, of the complex number.
+ */
+typedef union {
+        float complex z;
+        float parts[2];
+} float_complex;
+
+typedef union {
+        double complex z;
+        double parts[2];
+} double_complex;
+
+typedef union {
+        long double complex z;
+        long double parts[2];
+} long_double_complex;
+
+#define REAL_PART(z)    ((z).parts[0])
+#define IMAG_PART(z)    ((z).parts[1])
+
+#endif  /* _COMPLEX_H */
+
diff --git a/lib/libm/kf_cos.c b/lib/libm/kf_cos.c
new file mode 100644
index 0000000..4f71af2
--- /dev/null
+++ b/lib/libm/kf_cos.c
@@ -0,0 +1,59 @@
+/* kf_cos.c -- float version of k_cos.c
+ * Conversion to float by Ian Lance Taylor, Cygnus Support, ian@cygnus.com.
+ */
+
+/*
+ * ====================================================
+ * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved.
+ *
+ * Developed at SunPro, a Sun Microsystems, Inc. business.
+ * Permission to use, copy, modify, and distribute this
+ * software is freely granted, provided that this notice 
+ * is preserved.
+ * ====================================================
+ */
+
+#include "fdlibm.h"
+
+#ifdef __STDC__
+static const float 
+#else
+static float 
+#endif
+one =  1.0000000000e+00, /* 0x3f800000 */
+C1  =  4.1666667908e-02, /* 0x3d2aaaab */
+C2  = -1.3888889225e-03, /* 0xbab60b61 */
+C3  =  2.4801587642e-05, /* 0x37d00d01 */
+C4  = -2.7557314297e-07, /* 0xb493f27c */
+C5  =  2.0875723372e-09, /* 0x310f74f6 */
+C6  = -1.1359647598e-11; /* 0xad47d74e */
+
+#ifdef __STDC__
+	float __kernel_cosf(float x, float y)
+#else
+	float __kernel_cosf(x, y)
+	float x,y;
+#endif
+{
+	float a,hz,z,r,qx;
+	__int32_t ix;
+	GET_FLOAT_WORD(ix,x);
+	ix &= 0x7fffffff;			/* ix = |x|'s high word*/
+	if(ix<0x32000000) {			/* if x < 2**27 */
+	    if(((int)x)==0) return one;		/* generate inexact */
+	}
+	z  = x*x;
+	r  = z*(C1+z*(C2+z*(C3+z*(C4+z*(C5+z*C6)))));
+	if(ix < 0x3e99999a) 			/* if |x| < 0.3 */ 
+	    return one - ((float)0.5*z - (z*r - x*y));
+	else {
+	    if(ix > 0x3f480000) {		/* x > 0.78125 */
+		qx = (float)0.28125;
+	    } else {
+	        SET_FLOAT_WORD(qx,ix-0x01000000);	/* x/4 */
+	    }
+	    hz = (float)0.5*z-qx;
+	    a  = one-qx;
+	    return a - (hz - (z*r-x*y));
+	}
+}
diff --git a/lib/libm/kf_rem_pio2.c b/lib/libm/kf_rem_pio2.c
new file mode 100644
index 0000000..261c481
--- /dev/null
+++ b/lib/libm/kf_rem_pio2.c
@@ -0,0 +1,208 @@
+/* kf_rem_pio2.c -- float version of k_rem_pio2.c
+ * Conversion to float by Ian Lance Taylor, Cygnus Support, ian@cygnus.com.
+ */
+
+/*
+ * ====================================================
+ * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved.
+ *
+ * Developed at SunPro, a Sun Microsystems, Inc. business.
+ * Permission to use, copy, modify, and distribute this
+ * software is freely granted, provided that this notice 
+ * is preserved.
+ * ====================================================
+ */
+
+#include "fdlibm.h"
+
+/* In the float version, the input parameter x contains 8 bit
+   integers, not 24 bit integers.  113 bit precision is not supported.  */
+
+#ifdef __STDC__
+static const int init_jk[] = {4,7,9}; /* initial value for jk */
+#else
+static int init_jk[] = {4,7,9}; 
+#endif
+
+#ifdef __STDC__
+static const float PIo2[] = {
+#else
+static float PIo2[] = {
+#endif
+  1.5703125000e+00, /* 0x3fc90000 */
+  4.5776367188e-04, /* 0x39f00000 */
+  2.5987625122e-05, /* 0x37da0000 */
+  7.5437128544e-08, /* 0x33a20000 */
+  6.0026650317e-11, /* 0x2e840000 */
+  7.3896444519e-13, /* 0x2b500000 */
+  5.3845816694e-15, /* 0x27c20000 */
+  5.6378512969e-18, /* 0x22d00000 */
+  8.3009228831e-20, /* 0x1fc40000 */
+  3.2756352257e-22, /* 0x1bc60000 */
+  6.3331015649e-25, /* 0x17440000 */
+};
+
+#ifdef __STDC__
+static const float			
+#else
+static float			
+#endif
+zero   = 0.0,
+one    = 1.0,
+two8   =  2.5600000000e+02, /* 0x43800000 */
+twon8  =  3.9062500000e-03; /* 0x3b800000 */
+
+#ifdef __STDC__
+	int __kernel_rem_pio2f(float *x, float *y, int e0, int nx, int prec, const __int32_t *ipio2) 
+#else
+	int __kernel_rem_pio2f(x,y,e0,nx,prec,ipio2) 	
+	float x[], y[]; int e0,nx,prec; __int32_t ipio2[];
+#endif
+{
+	__int32_t jz,jx,jv,jp,jk,carry,n,iq[20],i,j,k,m,q0,ih;
+	float z,fw,f[20],fq[20],q[20];
+
+    /* initialize jk*/
+	jk = init_jk[prec];
+	jp = jk;
+
+    /* determine jx,jv,q0, note that 3>q0 */
+	jx =  nx-1;
+	jv = (e0-3)/8; if(jv<0) jv=0;
+	q0 =  e0-8*(jv+1);
+
+    /* set up f[0] to f[jx+jk] where f[jx+jk] = ipio2[jv+jk] */
+	j = jv-jx; m = jx+jk;
+	for(i=0;i<=m;i++,j++) f[i] = (j<0)? zero : (float) ipio2[j];
+
+    /* compute q[0],q[1],...q[jk] */
+	for (i=0;i<=jk;i++) {
+	    for(j=0,fw=0.0;j<=jx;j++) fw += x[j]*f[jx+i-j]; q[i] = fw;
+	}
+
+	jz = jk;
+recompute:
+    /* distill q[] into iq[] reversingly */
+	for(i=0,j=jz,z=q[jz];j>0;i++,j--) {
+	    fw    =  (float)((__int32_t)(twon8* z));
+	    iq[i] =  (__int32_t)(z-two8*fw);
+	    z     =  q[j-1]+fw;
+	}
+
+    /* compute n */
+	z  = scalbnf(z,(int)q0);	/* actual value of z */
+	z -= (float)8.0*floorf(z*(float)0.125);	/* trim off integer >= 8 */
+	n  = (__int32_t) z;
+	z -= (float)n;
+	ih = 0;
+	if(q0>0) {	/* need iq[jz-1] to determine n */
+	    i  = (iq[jz-1]>>(8-q0)); n += i;
+	    iq[jz-1] -= i<<(8-q0);
+	    ih = iq[jz-1]>>(7-q0);
+	} 
+	else if(q0==0) ih = iq[jz-1]>>8;
+	else if(z>=(float)0.5) ih=2;
+
+	if(ih>0) {	/* q > 0.5 */
+	    n += 1; carry = 0;
+	    for(i=0;i<jz ;i++) {	/* compute 1-q */
+		j = iq[i];
+		if(carry==0) {
+		    if(j!=0) {
+			carry = 1; iq[i] = 0x100- j;
+		    }
+		} else  iq[i] = 0xff - j;
+	    }
+	    if(q0>0) {		/* rare case: chance is 1 in 12 */
+	        switch(q0) {
+	        case 1:
+	    	   iq[jz-1] &= 0x7f; break;
+	    	case 2:
+	    	   iq[jz-1] &= 0x3f; break;
+	        }
+	    }
+	    if(ih==2) {
+		z = one - z;
+		if(carry!=0) z -= scalbnf(one,(int)q0);
+	    }
+	}
+
+    /* check if recomputation is needed */
+	if(z==zero) {
+	    j = 0;
+	    for (i=jz-1;i>=jk;i--) j |= iq[i];
+	    if(j==0) { /* need recomputation */
+		for(k=1;iq[jk-k]==0;k++);   /* k = no. of terms needed */
+
+		for(i=jz+1;i<=jz+k;i++) {   /* add q[jz+1] to q[jz+k] */
+		    f[jx+i] = (float) ipio2[jv+i];
+		    for(j=0,fw=0.0;j<=jx;j++) fw += x[j]*f[jx+i-j];
+		    q[i] = fw;
+		}
+		jz += k;
+		goto recompute;
+	    }
+	}
+
+    /* chop off zero terms */
+	if(z==(float)0.0) {
+	    jz -= 1; q0 -= 8;
+	    while(iq[jz]==0) { jz--; q0-=8;}
+	} else { /* break z into 8-bit if necessary */
+	    z = scalbnf(z,-(int)q0);
+	    if(z>=two8) { 
+		fw = (float)((__int32_t)(twon8*z));
+		iq[jz] = (__int32_t)(z-two8*fw);
+		jz += 1; q0 += 8;
+		iq[jz] = (__int32_t) fw;
+	    } else iq[jz] = (__int32_t) z ;
+	}
+
+    /* convert integer "bit" chunk to floating-point value */
+	fw = scalbnf(one,(int)q0);
+	for(i=jz;i>=0;i--) {
+	    q[i] = fw*(float)iq[i]; fw*=twon8;
+	}
+
+    /* compute PIo2[0,...,jp]*q[jz,...,0] */
+	for(i=jz;i>=0;i--) {
+	    for(fw=0.0,k=0;k<=jp&&k<=jz-i;k++) fw += PIo2[k]*q[i+k];
+	    fq[jz-i] = fw;
+	}
+
+    /* compress fq[] into y[] */
+	switch(prec) {
+	    case 0:
+		fw = 0.0;
+		for (i=jz;i>=0;i--) fw += fq[i];
+		y[0] = (ih==0)? fw: -fw; 
+		break;
+	    case 1:
+	    case 2:
+		fw = 0.0;
+		for (i=jz;i>=0;i--) fw += fq[i]; 
+		y[0] = (ih==0)? fw: -fw; 
+		fw = fq[0]-fw;
+		for (i=1;i<=jz;i++) fw += fq[i];
+		y[1] = (ih==0)? fw: -fw; 
+		break;
+	    case 3:	/* painful */
+		for (i=jz;i>0;i--) {
+		    fw      = fq[i-1]+fq[i]; 
+		    fq[i]  += fq[i-1]-fw;
+		    fq[i-1] = fw;
+		}
+		for (i=jz;i>1;i--) {
+		    fw      = fq[i-1]+fq[i]; 
+		    fq[i]  += fq[i-1]-fw;
+		    fq[i-1] = fw;
+		}
+		for (fw=0.0,i=jz;i>=2;i--) fw += fq[i]; 
+		if(ih==0) {
+		    y[0] =  fq[0]; y[1] =  fq[1]; y[2] =  fw;
+		} else {
+		    y[0] = -fq[0]; y[1] = -fq[1]; y[2] = -fw;
+		}
+	}
+	return n&7;
+}
diff --git a/lib/libm/kf_sin.c b/lib/libm/kf_sin.c
new file mode 100644
index 0000000..e81fa0b
--- /dev/null
+++ b/lib/libm/kf_sin.c
@@ -0,0 +1,49 @@
+/* kf_sin.c -- float version of k_sin.c
+ * Conversion to float by Ian Lance Taylor, Cygnus Support, ian@cygnus.com.
+ */
+
+/*
+ * ====================================================
+ * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved.
+ *
+ * Developed at SunPro, a Sun Microsystems, Inc. business.
+ * Permission to use, copy, modify, and distribute this
+ * software is freely granted, provided that this notice 
+ * is preserved.
+ * ====================================================
+ */
+
+#include "fdlibm.h"
+
+#ifdef __STDC__
+static const float 
+#else
+static float 
+#endif
+half =  5.0000000000e-01,/* 0x3f000000 */
+S1  = -1.6666667163e-01, /* 0xbe2aaaab */
+S2  =  8.3333337680e-03, /* 0x3c088889 */
+S3  = -1.9841270114e-04, /* 0xb9500d01 */
+S4  =  2.7557314297e-06, /* 0x3638ef1b */
+S5  = -2.5050759689e-08, /* 0xb2d72f34 */
+S6  =  1.5896910177e-10; /* 0x2f2ec9d3 */
+
+#ifdef __STDC__
+	float __kernel_sinf(float x, float y, int iy)
+#else
+	float __kernel_sinf(x, y, iy)
+	float x,y; int iy;		/* iy=0 if y is zero */
+#endif
+{
+	float z,r,v;
+	__int32_t ix;
+	GET_FLOAT_WORD(ix,x);
+	ix &= 0x7fffffff;			/* high word of x */
+	if(ix<0x32000000)			/* |x| < 2**-27 */
+	   {if((int)x==0) return x;}		/* generate inexact */
+	z	=  x*x;
+	v	=  z*x;
+	r	=  S2+z*(S3+z*(S4+z*(S5+z*S6)));
+	if(iy==0) return x+v*(S1+z*r);
+	else      return x-((z*(half*y-v*r)-y)-v*S1);
+}
diff --git a/lib/libm/sf_atan.c b/lib/libm/sf_atan.c
new file mode 100644
index 0000000..6edf05f
--- /dev/null
+++ b/lib/libm/sf_atan.c
@@ -0,0 +1,129 @@
+/* sf_atan.c -- float version of s_atan.c.
+ * Conversion to float by Ian Lance Taylor, Cygnus Support, ian@cygnus.com.
+ */
+
+/*
+ * ====================================================
+ * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved.
+ *
+ * Developed at SunPro, a Sun Microsystems, Inc. business.
+ * Permission to use, copy, modify, and distribute this
+ * software is freely granted, provided that this notice 
+ * is preserved.
+ * ====================================================
+ *
+ */
+
+#include "fdlibm.h"
+
+#ifdef __STDC__
+static const float atanhi[] = {
+#else
+static float atanhi[] = {
+#endif
+  4.6364760399e-01, /* atan(0.5)hi 0x3eed6338 */
+  7.8539812565e-01, /* atan(1.0)hi 0x3f490fda */
+  9.8279368877e-01, /* atan(1.5)hi 0x3f7b985e */
+  1.5707962513e+00, /* atan(inf)hi 0x3fc90fda */
+};
+
+#ifdef __STDC__
+static const float atanlo[] = {
+#else
+static float atanlo[] = {
+#endif
+  5.0121582440e-09, /* atan(0.5)lo 0x31ac3769 */
+  3.7748947079e-08, /* atan(1.0)lo 0x33222168 */
+  3.4473217170e-08, /* atan(1.5)lo 0x33140fb4 */
+  7.5497894159e-08, /* atan(inf)lo 0x33a22168 */
+};
+
+#ifdef __STDC__
+static const float aT[] = {
+#else
+static float aT[] = {
+#endif
+  3.3333334327e-01, /* 0x3eaaaaaa */
+ -2.0000000298e-01, /* 0xbe4ccccd */
+  1.4285714924e-01, /* 0x3e124925 */
+ -1.1111110449e-01, /* 0xbde38e38 */
+  9.0908870101e-02, /* 0x3dba2e6e */
+ -7.6918758452e-02, /* 0xbd9d8795 */
+  6.6610731184e-02, /* 0x3d886b35 */
+ -5.8335702866e-02, /* 0xbd6ef16b */
+  4.9768779427e-02, /* 0x3d4bda59 */
+ -3.6531571299e-02, /* 0xbd15a221 */
+  1.6285819933e-02, /* 0x3c8569d7 */
+};
+
+#ifdef __STDC__
+	static const float 
+#else
+	static float 
+#endif
+one   = 1.0,
+huge   = 1.0e30;
+
+#ifdef __STDC__
+	float atanf(float x)
+#else
+	float atanf(x)
+	float x;
+#endif
+{
+	float w,s1,s2,z;
+	__int32_t ix,hx,id;
+
+	GET_FLOAT_WORD(hx,x);
+	ix = hx&0x7fffffff;
+	if(ix>=0x50800000) {	/* if |x| >= 2^34 */
+	    if(FLT_UWORD_IS_NAN(ix))
+		return x+x;		/* NaN */
+	    if(hx>0) return  atanhi[3]+atanlo[3];
+	    else     return -atanhi[3]-atanlo[3];
+	} if (ix < 0x3ee00000) {	/* |x| < 0.4375 */
+	    if (ix < 0x31000000) {	/* |x| < 2^-29 */
+		if(huge+x>one) return x;	/* raise inexact */
+	    }
+	    id = -1;
+	} else {
+	x = fabsf(x);
+	if (ix < 0x3f980000) {		/* |x| < 1.1875 */
+	    if (ix < 0x3f300000) {	/* 7/16 <=|x|<11/16 */
+		id = 0; x = ((float)2.0*x-one)/((float)2.0+x); 
+	    } else {			/* 11/16<=|x|< 19/16 */
+		id = 1; x  = (x-one)/(x+one); 
+	    }
+	} else {
+	    if (ix < 0x401c0000) {	/* |x| < 2.4375 */
+		id = 2; x  = (x-(float)1.5)/(one+(float)1.5*x);
+	    } else {			/* 2.4375 <= |x| < 2^66 */
+		id = 3; x  = -(float)1.0/x;
+	    }
+	}}
+    /* end of argument reduction */
+	z = x*x;
+	w = z*z;
+    /* break sum from i=0 to 10 aT[i]z**(i+1) into odd and even poly */
+	s1 = z*(aT[0]+w*(aT[2]+w*(aT[4]+w*(aT[6]+w*(aT[8]+w*aT[10])))));
+	s2 = w*(aT[1]+w*(aT[3]+w*(aT[5]+w*(aT[7]+w*aT[9]))));
+	if (id<0) return x - x*(s1+s2);
+	else {
+	    z = atanhi[id] - ((x*(s1+s2) - atanlo[id]) - x);
+	    return (hx<0)? -z:z;
+	}
+}
+
+#ifdef _DOUBLE_IS_32BITS
+
+#ifdef __STDC__
+	double atan(double x)
+#else
+	double atan(x)
+	double x;
+#endif
+{
+	return (double) atanf((float) x);
+}
+
+#endif /* defined(_DOUBLE_IS_32BITS) */
diff --git a/lib/libm/sf_cos.c b/lib/libm/sf_cos.c
new file mode 100644
index 0000000..4c0a9a5
--- /dev/null
+++ b/lib/libm/sf_cos.c
@@ -0,0 +1,68 @@
+/* sf_cos.c -- float version of s_cos.c.
+ * Conversion to float by Ian Lance Taylor, Cygnus Support, ian@cygnus.com.
+ */
+
+/*
+ * ====================================================
+ * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved.
+ *
+ * Developed at SunPro, a Sun Microsystems, Inc. business.
+ * Permission to use, copy, modify, and distribute this
+ * software is freely granted, provided that this notice 
+ * is preserved.
+ * ====================================================
+ */
+
+#include "fdlibm.h"
+
+#ifdef __STDC__
+static const float one=1.0;
+#else
+static float one=1.0;
+#endif
+
+#ifdef __STDC__
+	float cosf(float x)
+#else
+	float cosf(x)
+	float x;
+#endif
+{
+	float y[2],z=0.0;
+	__int32_t n,ix;
+
+	GET_FLOAT_WORD(ix,x);
+
+    /* |x| ~< pi/4 */
+	ix &= 0x7fffffff;
+	if(ix <= 0x3f490fd8) return __kernel_cosf(x,z);
+
+    /* cos(Inf or NaN) is NaN */
+	else if (!FLT_UWORD_IS_FINITE(ix)) return x-x;
+
+    /* argument reduction needed */
+	else {
+	    n = __ieee754_rem_pio2f(x,y);
+	    switch(n&3) {
+		case 0: return  __kernel_cosf(y[0],y[1]);
+		case 1: return -__kernel_sinf(y[0],y[1],1);
+		case 2: return -__kernel_cosf(y[0],y[1]);
+		default:
+		        return  __kernel_sinf(y[0],y[1],1);
+	    }
+	}
+}
+
+#ifdef _DOUBLE_IS_32BITS
+
+#ifdef __STDC__
+	double cos(double x)
+#else
+	double cos(x)
+	double x;
+#endif
+{
+	return (double) cosf((float) x);
+}
+
+#endif /* defined(_DOUBLE_IS_32BITS) */
diff --git a/lib/libm/sf_floor.c b/lib/libm/sf_floor.c
new file mode 100644
index 0000000..9264d81
--- /dev/null
+++ b/lib/libm/sf_floor.c
@@ -0,0 +1,80 @@
+/* sf_floor.c -- float version of s_floor.c.
+ * Conversion to float by Ian Lance Taylor, Cygnus Support, ian@cygnus.com.
+ */
+
+/*
+ * ====================================================
+ * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved.
+ *
+ * Developed at SunPro, a Sun Microsystems, Inc. business.
+ * Permission to use, copy, modify, and distribute this
+ * software is freely granted, provided that this notice 
+ * is preserved.
+ * ====================================================
+ */
+
+/*
+ * floorf(x)
+ * Return x rounded toward -inf to integral value
+ * Method:
+ *	Bit twiddling.
+ * Exception:
+ *	Inexact flag raised if x not equal to floorf(x).
+ */
+
+#include "fdlibm.h"
+
+#ifdef __STDC__
+static const float huge = 1.0e30;
+#else
+static float huge = 1.0e30;
+#endif
+
+#ifdef __STDC__
+	float floorf(float x)
+#else
+	float floorf(x)
+	float x;
+#endif
+{
+	__int32_t i0,j0;
+	__uint32_t i,ix;
+	GET_FLOAT_WORD(i0,x);
+	ix = (i0&0x7fffffff);
+	j0 = (ix>>23)-0x7f;
+	if(j0<23) {
+	    if(j0<0) { 	/* raise inexact if x != 0 */
+		if(huge+x>(float)0.0) {/* return 0*sign(x) if |x|<1 */
+		    if(i0>=0) {i0=0;} 
+		    else if(!FLT_UWORD_IS_ZERO(ix))
+			{ i0=0xbf800000;}
+		}
+	    } else {
+		i = (0x007fffff)>>j0;
+		if((i0&i)==0) return x; /* x is integral */
+		if(huge+x>(float)0.0) {	/* raise inexact flag */
+		    if(i0<0) i0 += (0x00800000)>>j0;
+		    i0 &= (~i);
+		}
+	    }
+	} else {
+	    if(!FLT_UWORD_IS_FINITE(ix)) return x+x;	/* inf or NaN */
+	    else return x;		/* x is integral */
+	}
+	SET_FLOAT_WORD(x,i0);
+	return x;
+}
+
+#ifdef _DOUBLE_IS_32BITS
+
+#ifdef __STDC__
+	double floor(double x)
+#else
+	double floor(x)
+	double x;
+#endif
+{
+	return (double) floorf((float) x);
+}
+
+#endif /* defined(_DOUBLE_IS_32BITS) */
diff --git a/lib/libm/sf_scalbn.c b/lib/libm/sf_scalbn.c
new file mode 100644
index 0000000..7000600
--- /dev/null
+++ b/lib/libm/sf_scalbn.c
@@ -0,0 +1,86 @@
+/* sf_scalbn.c -- float version of s_scalbn.c.
+ * Conversion to float by Ian Lance Taylor, Cygnus Support, ian@cygnus.com.
+ */
+
+/*
+ * ====================================================
+ * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved.
+ *
+ * Developed at SunPro, a Sun Microsystems, Inc. business.
+ * Permission to use, copy, modify, and distribute this
+ * software is freely granted, provided that this notice 
+ * is preserved.
+ * ====================================================
+ */
+
+#include "fdlibm.h"
+#include <limits.h>
+#include <float.h>
+
+#if INT_MAX > 50000
+#define OVERFLOW_INT 50000
+#else
+#define OVERFLOW_INT 30000
+#endif
+
+#ifdef __STDC__
+static const float
+#else
+static float
+#endif
+two25   =  3.355443200e+07,	/* 0x4c000000 */
+twom25  =  2.9802322388e-08,	/* 0x33000000 */
+huge   = 1.0e+30,
+tiny   = 1.0e-30;
+
+#ifdef __STDC__
+	float scalbnf (float x, int n)
+#else
+	float scalbnf (x,n)
+	float x; int n;
+#endif
+{
+	__int32_t  k,ix;
+	__uint32_t hx;
+
+	GET_FLOAT_WORD(ix,x);
+	hx = ix&0x7fffffff;
+        k = hx>>23;		/* extract exponent */
+	if (FLT_UWORD_IS_ZERO(hx))
+	    return x;
+        if (!FLT_UWORD_IS_FINITE(hx))
+	    return x+x;		/* NaN or Inf */
+        if (FLT_UWORD_IS_SUBNORMAL(hx)) {
+	    x *= two25;
+	    GET_FLOAT_WORD(ix,x);
+	    k = ((ix&0x7f800000)>>23) - 25; 
+            if (n< -50000) return tiny*x; 	/*underflow*/
+        }
+        k = k+n; 
+        if (k > FLT_LARGEST_EXP) return huge*copysignf(huge,x); /* overflow  */
+        if (k > 0) 				/* normal result */
+	    {SET_FLOAT_WORD(x,(ix&0x807fffff)|(k<<23)); return x;}
+        if (k < FLT_SMALLEST_EXP) {
+            if (n > OVERFLOW_INT) 	/* in case integer overflow in n+k */
+		return huge*copysignf(huge,x);	/*overflow*/
+	    else return tiny*copysignf(tiny,x);	/*underflow*/
+        }
+        k += 25;				/* subnormal result */
+	SET_FLOAT_WORD(x,(ix&0x807fffff)|(k<<23));
+        return x*twom25;
+}
+
+#ifdef _DOUBLE_IS_32BITS
+
+#ifdef __STDC__
+	double scalbn(double x, int n)
+#else
+	double scalbn(x,n)
+	double x;
+	int n;
+#endif
+{
+	return (double) scalbnf((float) x, n);
+}
+
+#endif /* defined(_DOUBLE_IS_32BITS) */
diff --git a/lib/libm/sf_sin.c b/lib/libm/sf_sin.c
new file mode 100644
index 0000000..da81845
--- /dev/null
+++ b/lib/libm/sf_sin.c
@@ -0,0 +1,62 @@
+/* sf_sin.c -- float version of s_sin.c.
+ * Conversion to float by Ian Lance Taylor, Cygnus Support, ian@cygnus.com.
+ */
+
+/*
+ * ====================================================
+ * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved.
+ *
+ * Developed at SunPro, a Sun Microsystems, Inc. business.
+ * Permission to use, copy, modify, and distribute this
+ * software is freely granted, provided that this notice 
+ * is preserved.
+ * ====================================================
+ */
+
+#include "fdlibm.h"
+
+#ifdef __STDC__
+	float sinf(float x)
+#else
+	float sinf(x)
+	float x;
+#endif
+{
+	float y[2],z=0.0;
+	__int32_t n,ix;
+
+	GET_FLOAT_WORD(ix,x);
+
+    /* |x| ~< pi/4 */
+	ix &= 0x7fffffff;
+	if(ix <= 0x3f490fd8) return __kernel_sinf(x,z,0);
+
+    /* sin(Inf or NaN) is NaN */
+	else if (!FLT_UWORD_IS_FINITE(ix)) return x-x;
+
+    /* argument reduction needed */
+	else {
+	    n = __ieee754_rem_pio2f(x,y);
+	    switch(n&3) {
+		case 0: return  __kernel_sinf(y[0],y[1],1);
+		case 1: return  __kernel_cosf(y[0],y[1]);
+		case 2: return -__kernel_sinf(y[0],y[1],1);
+		default:
+			return -__kernel_cosf(y[0],y[1]);
+	    }
+	}
+}
+
+#ifdef _DOUBLE_IS_32BITS
+
+#ifdef __STDC__
+	double sin(double x)
+#else
+	double sin(x)
+	double x;
+#endif
+{
+	return (double) sinf((float) x);
+}
+
+#endif /* defined(_DOUBLE_IS_32BITS) */
diff --git a/lib/libm/wf_atan2.c b/lib/libm/wf_atan2.c
new file mode 100644
index 0000000..eb2a76b
--- /dev/null
+++ b/lib/libm/wf_atan2.c
@@ -0,0 +1,46 @@
+/* wf_atan2.c -- float version of w_atan2.c.
+ * Conversion to float by Ian Lance Taylor, Cygnus Support, ian@cygnus.com.
+ */
+
+/*
+ * ====================================================
+ * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved.
+ *
+ * Developed at SunPro, a Sun Microsystems, Inc. business.
+ * Permission to use, copy, modify, and distribute this
+ * software is freely granted, provided that this notice 
+ * is preserved.
+ * ====================================================
+ *
+ */
+
+/* 
+ * wrapper atan2f(y,x)
+ */
+
+#include "fdlibm.h"
+#include <errno.h>
+
+#ifdef __STDC__
+	float atan2f(float y, float x)		/* wrapper atan2f */
+#else
+	float atan2f(y,x)			/* wrapper atan2 */
+	float y,x;
+#endif
+{
+	return __ieee754_atan2f(y,x);
+}
+
+#ifdef _DOUBLE_IS_32BITS
+
+#ifdef __STDC__
+	double atan2(double y, double x)
+#else
+	double atan2(y,x)
+	double y,x;
+#endif
+{
+	return (double) atan2f((float) y, (float) x);
+}
+
+#endif /* defined(_DOUBLE_IS_32BITS) */
diff --git a/firmware/src/aes.c b/lib/nanohub/aes.c
similarity index 99%
rename from firmware/src/aes.c
rename to lib/nanohub/aes.c
index 3496670..113c235 100644
--- a/firmware/src/aes.c
+++ b/lib/nanohub/aes.c
@@ -16,7 +16,7 @@
 
 #include <string.h>
 #include <stdint.h>
-#include <aes.h>
+#include <nanohub/aes.h>
 
 
 #define AES_NUM_ROUNDS    14
diff --git a/lib/nanohub/nanoapp.c b/lib/nanohub/nanoapp.c
new file mode 100644
index 0000000..cbb6039
--- /dev/null
+++ b/lib/nanohub/nanoapp.c
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdio.h>
+#include <inttypes.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <nanohub/nanoapp.h>
+
+void *reallocOrDie(void *buf, size_t bufSz)
+{
+    void *newBuf = realloc(buf, bufSz);
+    if (!newBuf) {
+        fprintf(stderr, "Failed to allocate %zu bytes\n", bufSz);
+        exit(2);
+    }
+    return newBuf;
+}
+
+void assertMem(size_t used, size_t total)
+{
+    if (used <= total)
+        return;
+    fprintf(stderr, "Buffer size %zu is not big enough to complete operation; we need %zu bytes\n", total, used);
+    exit(2);
+}
+
+// read file of known size, make sure the size is correct
+bool readFile(void *dst, uint32_t len, const char *fileName)
+{
+    FILE *f = fopen(fileName, "rb");
+    bool ret = false;
+
+    if (!f)
+        return false;
+
+    if (len != fread(dst, 1, len, f))
+        goto out;
+
+    if (fread(&len, 1, 1, f)) //make sure file is actually over
+        goto out;
+
+    ret = true;
+
+out:
+    fclose(f);
+    return ret;
+}
+
+// read complete file of unknown size, return malloced buffer and size
+void *loadFile(const char *fileName, uint32_t *size)
+{
+    FILE *f = fopen(fileName, "rb");
+    uint8_t *dst = NULL;
+    uint32_t len = 0, grow = 16384, total = 0;
+    uint32_t block;
+
+    if (!f) {
+        fprintf(stderr, "couldn't open %s: %s\n", fileName, strerror(errno));
+        exit(2);
+    }
+
+    do {
+        len += grow; dst = reallocOrDie(dst, len);
+
+        block = fread(dst + total, 1, grow, f);
+        total += block;
+    } while (block == grow);
+
+    *size = total;
+    if (!feof(f)) {
+        fprintf(stderr, "Failed to read entire file %s: %s\n",
+                fileName, strerror(errno));
+        free(dst);
+        fclose(f);
+        dst = NULL;
+        exit(2);
+    }
+
+    return dst;
+}
+
+static void doPrintHash(FILE *out, const char *pfx, const uint32_t *hash, size_t size, int increment)
+{
+    size_t i;
+    int pos;
+    fprintf(out, "%s: ", pfx);
+    for (i = 0, pos = 0; i < size; ++i, pos += increment)
+        fprintf(out, "%08" PRIx32, hash[pos]);
+    fprintf(out, "\n");
+}
+
+void printHash(FILE *out, const char *pfx, const uint32_t *hash, size_t size)
+{
+    doPrintHash(out, pfx, hash, size, 1);
+}
+
+void printHashRev(FILE *out, const char *pfx, const uint32_t *hash, size_t size)
+{
+    doPrintHash(out, pfx, hash + size - 1, size, -1);
+}
diff --git a/firmware/src/rsa.c b/lib/nanohub/rsa.c
similarity index 99%
rename from firmware/src/rsa.c
rename to lib/nanohub/rsa.c
index 3382b72..d718de0 100644
--- a/firmware/src/rsa.c
+++ b/lib/nanohub/rsa.c
@@ -17,7 +17,7 @@
 #include <stdint.h>
 #include <stdbool.h>
 #include <string.h>
-#include <rsa.h>
+#include <nanohub/rsa.h>
 
 
 static bool biModIterative(uint32_t *num, const uint32_t *denum, uint32_t *tmp, uint32_t *state1, uint32_t *state2, uint32_t step)
diff --git a/firmware/src/sha2.c b/lib/nanohub/sha2.c
similarity index 99%
rename from firmware/src/sha2.c
rename to lib/nanohub/sha2.c
index cc6e76b..58ce606 100644
--- a/firmware/src/sha2.c
+++ b/lib/nanohub/sha2.c
@@ -15,7 +15,7 @@
  */
 
 #include <string.h>
-#include <sha2.h>
+#include <nanohub/sha2.h>
 
 
 void sha2init(struct Sha2state *state)
diff --git a/firmware/src/softcrc.c b/lib/nanohub/softcrc.c
similarity index 98%
rename from firmware/src/softcrc.c
rename to lib/nanohub/softcrc.c
index 4a37f7e..fb2b711 100644
--- a/firmware/src/softcrc.c
+++ b/lib/nanohub/softcrc.c
@@ -15,7 +15,7 @@
  */
 
 #include <stdint.h>
-#include <crc.h>
+#include <nanohub/crc.h>
 
 /* this implements crc32 as crc.h defines it. It is not a normal CRC by any measure, so be careful with it */
 
diff --git a/sensorhal/Android.mk b/sensorhal/Android.mk
new file mode 100644
index 0000000..f0ae715
--- /dev/null
+++ b/sensorhal/Android.mk
@@ -0,0 +1,139 @@
+# Copyright (C) 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#
+# Nanohub sensor HAL usage instructions:
+#
+# Add the following to your device.mk file.
+#
+# # Enable the nanohub sensor HAL
+# TARGET_USES_NANOHUB_SENSORHAL := true
+#
+# # Nanohub sensor list source file
+# NANOHUB_SENSORHAL_SENSORLIST := $(LOCAL_PATH)/sensorhal/sensorlist.cpp
+#
+# # Sensor HAL name override (optional)
+# NANOHUB_SENSORHAL_NAME_OVERRIDE := sensors.nanohub
+#
+# # Enable lid-state reporting (optional)
+# NANOHUB_SENSORHAL_LID_STATE_ENABLED := true
+#
+# # Enable mag-bias reporting (optional)
+# NANOHUB_SENSORHAL_USB_MAG_BIAS_ENABLED := true
+#
+
+LOCAL_PATH := $(call my-dir)
+
+ifeq ($(TARGET_USES_NANOHUB_SENSORHAL), true)
+
+COMMON_CFLAGS := -Wall -Werror -Wextra
+
+################################################################################
+
+include $(CLEAR_VARS)
+
+ifeq ($(NANOHUB_SENSORHAL_NAME_OVERRIDE),)
+LOCAL_MODULE := sensors.$(TARGET_DEVICE)
+else
+LOCAL_MODULE := $(NANOHUB_SENSORHAL_NAME_OVERRIDE)
+endif
+
+LOCAL_MODULE_RELATIVE_PATH := hw
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_OWNER := google
+
+LOCAL_CFLAGS += $(COMMON_CFLAGS)
+
+LOCAL_C_INCLUDES += \
+	device/google/contexthub/firmware/inc \
+	device/google/contexthub/util/common
+
+LOCAL_SRC_FILES := \
+	sensors.cpp \
+	../../../../$(NANOHUB_SENSORHAL_SENSORLIST)
+
+LOCAL_SHARED_LIBRARIES := \
+	libcutils \
+	libhubconnection \
+	libstagefright_foundation \
+	libutils
+
+include $(BUILD_SHARED_LIBRARY)
+
+################################################################################
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := activity_recognition.$(TARGET_DEVICE)
+LOCAL_MODULE_RELATIVE_PATH := hw
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_OWNER := google
+
+LOCAL_CFLAGS += $(COMMON_CFLAGS)
+
+LOCAL_C_INCLUDES += \
+	device/google/contexthub/firmware/inc \
+	device/google/contexthub/util/common
+
+LOCAL_SRC_FILES := \
+	activity.cpp
+
+LOCAL_SHARED_LIBRARIES := \
+	libcutils \
+	libhubconnection \
+	liblog \
+	libstagefright_foundation \
+	libutils
+
+include $(BUILD_SHARED_LIBRARY)
+
+################################################################################
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := libhubconnection
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_OWNER := google
+
+LOCAL_CFLAGS += $(COMMON_CFLAGS)
+
+ifeq ($(NANOHUB_SENSORHAL_LID_STATE_ENABLED), true)
+LOCAL_CFLAGS += -DLID_STATE_REPORTING_ENABLED
+endif
+
+ifeq ($(NANOHUB_SENSORHAL_USB_MAG_BIAS_ENABLED), true)
+LOCAL_CFLAGS += -DUSB_MAG_BIAS_REPORTING_ENABLED
+endif
+
+LOCAL_C_INCLUDES += \
+	device/google/contexthub/firmware/inc \
+	device/google/contexthub/util/common
+
+LOCAL_SRC_FILES := \
+	hubconnection.cpp \
+	../util/common/file.cpp \
+	../util/common/JSONObject.cpp \
+	../util/common/ring.cpp
+
+LOCAL_SHARED_LIBRARIES := \
+	libcutils \
+	liblog \
+	libstagefright_foundation \
+	libutils
+
+include $(BUILD_SHARED_LIBRARY)
+
+################################################################################
+
+endif
diff --git a/sensorhal/activity.cpp b/sensorhal/activity.cpp
new file mode 100644
index 0000000..dda15a0
--- /dev/null
+++ b/sensorhal/activity.cpp
@@ -0,0 +1,367 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "ActivityRecognitionHAL"
+//#define LOG_NDEBUG  0
+#include <utils/Log.h>
+
+#include "activity.h"
+
+#include <media/stagefright/foundation/ADebug.h>
+
+using namespace android;
+
+static const int kVersionMajor = 1;
+static const int kVersionMinor = 0;
+
+static const int ACTIVITY_TYPE_TILTING_INDEX = 6;
+
+static const char *const kActivityList[] = {
+    ACTIVITY_TYPE_IN_VEHICLE,
+    ACTIVITY_TYPE_ON_BICYCLE,
+    ACTIVITY_TYPE_WALKING,
+    ACTIVITY_TYPE_RUNNING,
+    ACTIVITY_TYPE_STILL,
+    "com.google.android.contexthub.ar.inconsistent",
+    ACTIVITY_TYPE_TILTING
+};
+
+ActivityContext::ActivityContext(const struct hw_module_t *module)
+    : mHubConnection(HubConnection::getInstance()),
+      mHubAlive(true),
+      mCallback(NULL),
+      mPrevActivity(-1),
+      mInitExitDone(false) {
+    memset(&device, 0, sizeof(device));
+
+    device.common.tag = HARDWARE_DEVICE_TAG;
+    device.common.version = ACTIVITY_RECOGNITION_API_VERSION_0_1;
+    device.common.module = const_cast<hw_module_t *>(module);
+    device.common.close = CloseWrapper;
+    device.register_activity_callback = RegisterActivityCallbackWrapper;
+    device.enable_activity_event = EnableActivityEventWrapper;
+    device.disable_activity_event = DisableActivityEventWrapper;
+    device.flush = FlushWrapper;
+
+    if (mHubConnection->initCheck() != (status_t)OK) {
+        mHubAlive = false;
+    } else {
+        if (mHubConnection->getAliveCheck() != (status_t)OK) {
+            mHubAlive = false;
+        } else {
+            mHubConnection->setActivityCallback(
+                    this, &ActivityContext::HubCallbackWrapper);
+
+            mHubConnection->queueActivate(COMMS_SENSOR_ACTIVITY, false /* enable */);
+        }
+    }
+}
+
+ActivityContext::~ActivityContext() {
+    mHubConnection->setActivityCallback(NULL, NULL);
+}
+
+int ActivityContext::close() {
+    ALOGI("close");
+
+    delete this;
+
+    return 0;
+}
+
+void ActivityContext::onActivityEvent(
+        uint64_t when_us, bool is_flush, float x, float, float) {
+    Mutex::Autolock autoLock(mLock);
+
+    if (!mCallback) {
+        return;
+    }
+
+    if (is_flush) {
+        activity_event_t ev;
+        memset(&ev, 0, sizeof(ev));
+
+        ev.event_type = ACTIVITY_EVENT_FLUSH_COMPLETE;
+        ev.activity = 0;
+        ev.timestamp = 0ll;
+
+        (*mCallback->activity_callback)(mCallback, &ev, 1);
+        return;
+    }
+
+    int activityRaw = (int)x;
+
+    ALOGV("activityRaw = %d", activityRaw);
+
+    if (mPrevActivity >= 0 && mPrevActivity == activityRaw) {
+        // same old, same old...
+        return;
+    }
+
+    activity_event_t ev[8];
+    memset(&ev, 0, 8*sizeof(activity_event_t));
+    int num_events = 0;
+
+    // exit all other activities when first enabled.
+    if (!mInitExitDone) {
+        mInitExitDone = true;
+
+        int numActivities = sizeof(kActivityList) / sizeof(kActivityList[0]);
+        for (int i = 0; i < numActivities; ++i) {
+            if ((i == activityRaw) || !isEnabled(i, ACTIVITY_EVENT_EXIT)) {
+                continue;
+            }
+
+            activity_event_t *curr_ev = &ev[num_events];
+            curr_ev->event_type = ACTIVITY_EVENT_EXIT;
+            curr_ev->activity = i;
+            curr_ev->timestamp = when_us * 1000ll;  // timestamp is in ns.
+            curr_ev->reserved[0] = curr_ev->reserved[1] = curr_ev->reserved[2] = curr_ev->reserved[3] = 0;
+            num_events++;
+        }
+    }
+
+    // tilt activities do not change the current activity type, but have a
+    // simultaneous enter and exit event type
+    if (activityRaw == ACTIVITY_TYPE_TILTING_INDEX) {
+        if (isEnabled(activityRaw, ACTIVITY_EVENT_ENTER)) {
+            activity_event_t *curr_ev = &ev[num_events];
+            curr_ev->event_type = ACTIVITY_EVENT_ENTER;
+            curr_ev->activity = activityRaw;
+            curr_ev->timestamp = when_us * 1000ll;  // timestamp is in ns.
+            curr_ev->reserved[0] = curr_ev->reserved[1] = curr_ev->reserved[2] = curr_ev->reserved[3] = 0;
+            num_events++;
+        }
+
+        if (isEnabled(activityRaw, ACTIVITY_EVENT_EXIT)) {
+            activity_event_t *curr_ev = &ev[num_events];
+            curr_ev->event_type = ACTIVITY_EVENT_EXIT;
+            curr_ev->activity = activityRaw;
+            curr_ev->timestamp = when_us * 1000ll;  // timestamp is in ns.
+            curr_ev->reserved[0] = curr_ev->reserved[1] = curr_ev->reserved[2] = curr_ev->reserved[3] = 0;
+            num_events++;
+        }
+    } else {
+        if ((mPrevActivity >= 0) &&
+            (isEnabled(mPrevActivity, ACTIVITY_EVENT_EXIT))) {
+            activity_event_t *curr_ev = &ev[num_events];
+            curr_ev->event_type = ACTIVITY_EVENT_EXIT;
+            curr_ev->activity = mPrevActivity;
+            curr_ev->timestamp = when_us * 1000ll;  // timestamp is in ns.
+            curr_ev->reserved[0] = curr_ev->reserved[1] = curr_ev->reserved[2] = curr_ev->reserved[3] = 0;
+            num_events++;
+        }
+
+        if (isEnabled(activityRaw, ACTIVITY_EVENT_ENTER)) {
+            activity_event_t *curr_ev = &ev[num_events];
+            curr_ev->event_type = ACTIVITY_EVENT_ENTER;
+            curr_ev->activity = activityRaw;
+            curr_ev->timestamp = when_us * 1000ll;  // timestamp is in ns.
+            curr_ev->reserved[0] = curr_ev->reserved[1] = curr_ev->reserved[2] = curr_ev->reserved[3] = 0;
+            num_events++;
+        }
+
+        mPrevActivity = activityRaw;
+    }
+
+    if (num_events > 0) {
+        (*mCallback->activity_callback)(mCallback, ev, num_events);
+    }
+}
+
+void ActivityContext::registerActivityCallback(
+        const activity_recognition_callback_procs_t *callback) {
+    ALOGI("registerActivityCallback");
+
+    Mutex::Autolock autoLock(mLock);
+    mCallback = callback;
+}
+
+int ActivityContext::enableActivityEvent(
+        uint32_t activity_handle,
+        uint32_t event_type,
+        int64_t max_batch_report_latency_ns) {
+    ALOGI("enableActivityEvent");
+
+    bool wasEnabled = !mMaxBatchReportLatencyNs.isEmpty();
+    int64_t prev_latency = calculateReportLatencyNs();
+
+    ALOGD_IF(DEBUG_ACTIVITY_RECOGNITION, "ACTVT type = %u, latency = %d sec", (unsigned) event_type,
+          (int)(max_batch_report_latency_ns/1000000000ull));
+
+    mMaxBatchReportLatencyNs.add(
+            ((uint64_t)activity_handle << 32) | event_type,
+            max_batch_report_latency_ns);
+
+    if (!wasEnabled) {
+        mPrevActivity = -1;
+        mInitExitDone = false;
+
+        mHubConnection->queueBatch(
+            COMMS_SENSOR_ACTIVITY, SENSOR_FLAG_ON_CHANGE_MODE, 1000000, max_batch_report_latency_ns);
+        mHubConnection->queueActivate(COMMS_SENSOR_ACTIVITY, true /* enable */);
+    } else if (max_batch_report_latency_ns != prev_latency) {
+        mHubConnection->queueBatch(
+            COMMS_SENSOR_ACTIVITY, SENSOR_FLAG_ON_CHANGE_MODE, 1000000, max_batch_report_latency_ns);
+    }
+
+    return 0;
+}
+
+int64_t ActivityContext::calculateReportLatencyNs() {
+    int64_t ret = INT64_MAX;
+
+    for (size_t i = 0 ; i < mMaxBatchReportLatencyNs.size(); ++i) {
+        if (mMaxBatchReportLatencyNs[i] <ret) {
+            ret = mMaxBatchReportLatencyNs[i];
+        }
+    }
+    return ret;
+}
+
+int ActivityContext::disableActivityEvent(
+        uint32_t activity_handle, uint32_t event_type) {
+    ALOGI("disableActivityEvent");
+
+    bool wasEnabled = !mMaxBatchReportLatencyNs.isEmpty();
+
+    mMaxBatchReportLatencyNs.removeItem(
+            ((uint64_t)activity_handle << 32) | event_type);
+
+    bool isEnabled = !mMaxBatchReportLatencyNs.isEmpty();
+
+    if (wasEnabled && !isEnabled) {
+        mHubConnection->queueActivate(COMMS_SENSOR_ACTIVITY, false /* enable */);
+    }
+
+    return 0;
+}
+
+bool ActivityContext::isEnabled(
+        uint32_t activity_handle, uint32_t event_type) const {
+    return mMaxBatchReportLatencyNs.indexOfKey(
+            ((uint64_t)activity_handle << 32) | event_type) >= 0;
+}
+
+int ActivityContext::flush() {
+    mHubConnection->queueFlush(COMMS_SENSOR_ACTIVITY);
+    return 0;
+}
+
+// static
+int ActivityContext::CloseWrapper(struct hw_device_t *dev) {
+    return reinterpret_cast<ActivityContext *>(dev)->close();
+}
+
+// static
+void ActivityContext::RegisterActivityCallbackWrapper(
+        const struct activity_recognition_device *dev,
+        const activity_recognition_callback_procs_t *callback) {
+    const_cast<ActivityContext *>(
+            reinterpret_cast<const ActivityContext *>(dev))
+        ->registerActivityCallback(callback);
+}
+
+// static
+int ActivityContext::EnableActivityEventWrapper(
+        const struct activity_recognition_device *dev,
+        uint32_t activity_handle,
+        uint32_t event_type,
+        int64_t max_batch_report_latency_ns) {
+    return const_cast<ActivityContext *>(
+            reinterpret_cast<const ActivityContext *>(dev))
+        ->enableActivityEvent(
+            activity_handle, event_type, max_batch_report_latency_ns);
+}
+
+// static
+int ActivityContext::DisableActivityEventWrapper(
+        const struct activity_recognition_device *dev,
+        uint32_t activity_handle,
+        uint32_t event_type) {
+    return const_cast<ActivityContext *>(
+            reinterpret_cast<const ActivityContext *>(dev))
+        ->disableActivityEvent(activity_handle, event_type);
+}
+
+// static
+int ActivityContext::FlushWrapper(
+        const struct activity_recognition_device *dev) {
+    return const_cast<ActivityContext *>(
+            reinterpret_cast<const ActivityContext *>(dev))->flush();
+}
+
+// static
+void ActivityContext::HubCallbackWrapper(
+        void *me, uint64_t time_ms, bool is_flush, float x, float y, float z) {
+    static_cast<ActivityContext *>(me)->onActivityEvent(time_ms, is_flush, x, y, z);
+}
+
+bool ActivityContext::getHubAlive() {
+    return mHubAlive;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+static bool gHubAlive = false;
+
+static int open_activity(
+        const struct hw_module_t *module,
+        const char *,
+        struct hw_device_t **dev) {
+    ALOGI("open_activity");
+
+    ActivityContext *ctx = new ActivityContext(module);
+
+    gHubAlive = ctx->getHubAlive();
+    *dev = &ctx->device.common;
+
+    return 0;
+}
+
+static struct hw_module_methods_t activity_module_methods = {
+    .open = open_activity
+};
+
+static int get_activity_list(
+        struct activity_recognition_module *,
+        char const* const **activity_list) {
+    ALOGI("get_activity_list");
+
+    if (gHubAlive) {
+        *activity_list = kActivityList;
+        return sizeof(kActivityList) / sizeof(kActivityList[0]);
+    } else {
+        *activity_list = {};
+        return 0;
+    }
+}
+
+struct activity_recognition_module HAL_MODULE_INFO_SYM = {
+        .common = {
+                .tag = HARDWARE_MODULE_TAG,
+                .version_major = kVersionMajor,
+                .version_minor = kVersionMinor,
+                .id = ACTIVITY_RECOGNITION_HARDWARE_MODULE_ID,
+                .name = "Google Activity Recognition module",
+                .author = "Google",
+                .methods = &activity_module_methods,
+                .dso  = NULL,
+                .reserved = {0},
+        },
+        .get_supported_activities_list = get_activity_list,
+};
+
diff --git a/sensorhal/activity.h b/sensorhal/activity.h
new file mode 100644
index 0000000..347c5da
--- /dev/null
+++ b/sensorhal/activity.h
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ACTIVITY_H_
+
+#define ACTIVITY_H_
+
+#include <hardware/activity_recognition.h>
+#include <media/stagefright/foundation/ABase.h>
+#include <utils/KeyedVector.h>
+
+#include "hubconnection.h"
+
+#define DEBUG_ACTIVITY_RECOGNITION  0
+
+struct ActivityContext {
+    activity_recognition_device_t device;
+
+    explicit ActivityContext(const struct hw_module_t *module);
+
+    void onActivityEvent(
+            uint64_t when_us, bool is_flush, float x, float y, float z);
+
+    bool getHubAlive();
+
+private:
+    android::Mutex mLock;
+
+    android::sp<android::HubConnection> mHubConnection;
+
+    bool mHubAlive;
+
+    const activity_recognition_callback_procs_t *mCallback;
+
+    android::KeyedVector<uint64_t, int64_t> mMaxBatchReportLatencyNs;
+
+    int mPrevActivity;
+
+    bool mInitExitDone;
+
+    ~ActivityContext();
+
+    int close();
+
+    void registerActivityCallback(
+            const activity_recognition_callback_procs_t *callback);
+
+    bool isEnabled(uint32_t activity_handle, uint32_t event_type) const;
+
+    int enableActivityEvent(
+            uint32_t activity_handle,
+            uint32_t event_type,
+            int64_t max_batch_report_latency_ns);
+
+    int disableActivityEvent(uint32_t activity_handle, uint32_t event_type);
+
+    int flush();
+
+    int64_t calculateReportLatencyNs();
+
+    static int CloseWrapper(struct hw_device_t *dev);
+
+    static void RegisterActivityCallbackWrapper(
+            const struct activity_recognition_device *dev,
+            const activity_recognition_callback_procs_t *callback);
+
+    static int EnableActivityEventWrapper(
+            const struct activity_recognition_device *dev,
+            uint32_t activity_handle,
+            uint32_t event_type,
+            int64_t max_batch_report_latency_ns);
+
+    static int DisableActivityEventWrapper(
+            const struct activity_recognition_device *dev,
+            uint32_t activity_handle,
+            uint32_t event_type);
+
+    static int FlushWrapper(const struct activity_recognition_device *dev);
+
+    static void HubCallbackWrapper(
+            void *me, uint64_t time_ms, bool is_flush, float x, float y, float z);
+
+    DISALLOW_EVIL_CONSTRUCTORS(ActivityContext);
+};
+
+#endif  // ACTIVITY_H_
+
diff --git a/sensorhal/hubconnection.cpp b/sensorhal/hubconnection.cpp
new file mode 100644
index 0000000..b49644b
--- /dev/null
+++ b/sensorhal/hubconnection.cpp
@@ -0,0 +1,1230 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "hubconnection.h"
+#include "eventnums.h"
+#include "sensType.h"
+
+#define LOG_TAG "nanohub"
+#include <utils/Log.h>
+#include <utils/SystemClock.h>
+
+#include "file.h"
+#include "JSONObject.h"
+
+#include <errno.h>
+#include <unistd.h>
+#include <math.h>
+#include <inttypes.h>
+
+#include <cutils/properties.h>
+#include <linux/input.h>
+#include <linux/uinput.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <sys/inotify.h>
+
+#define APP_ID_GET_VENDOR(appid)       ((appid) >> 24)
+#define APP_ID_MAKE(vendor, app)       ((((uint64_t)(vendor)) << 24) | ((app) & 0x00FFFFFF))
+#define APP_ID_VENDOR_GOOGLE           0x476f6f676cULL // "Googl"
+#define APP_ID_APP_BMI160              2
+
+#define SENS_TYPE_TO_EVENT(_sensorType) (EVT_NO_FIRST_SENSOR_EVENT + (_sensorType))
+
+#define NANOHUB_FILE_PATH       "/dev/nanohub"
+#define NANOHUB_LOCK_DIR        "/data/system/nanohub_lock"
+#define NANOHUB_LOCK_FILE       NANOHUB_LOCK_DIR "/lock"
+#define MAG_BIAS_FILE_PATH      "/sys/class/power_supply/battery/compass_compensation"
+
+#define NANOHUB_LOCK_DIR_PERMS  (S_IRUSR | S_IWUSR | S_IXUSR)
+
+#define SENSOR_RATE_ONCHANGE    0xFFFFFF01UL
+#define SENSOR_RATE_ONESHOT     0xFFFFFF02UL
+
+#define MIN_MAG_SQ              (10.0f * 10.0f)
+#define MAX_MAG_SQ              (80.0f * 80.0f)
+
+#define ACCEL_RAW_KSCALE        (8.0f * 9.81f / 32768.0f)
+
+#define OS_LOG_EVENT            0x474F4C41  // ascii: ALOG
+
+#ifdef LID_STATE_REPORTING_ENABLED
+const char LID_STATE_PROPERTY[] = "sensors.contexthub.lid_state";
+const char LID_STATE_UNKNOWN[]  = "unknown";
+const char LID_STATE_OPEN[]     = "open";
+const char LID_STATE_CLOSED[]   = "closed";
+#endif  // LID_STATE_REPORTING_ENABLED
+
+static const uint32_t delta_time_encoded = 1;
+static const uint32_t delta_time_shift_table[2] = {9, 0};
+
+namespace android {
+
+// static
+Mutex HubConnection::sInstanceLock;
+
+// static
+HubConnection *HubConnection::sInstance = NULL;
+
+HubConnection *HubConnection::getInstance()
+{
+    Mutex::Autolock autoLock(sInstanceLock);
+    if (sInstance == NULL) {
+        sInstance = new HubConnection;
+    }
+    return sInstance;
+}
+
+HubConnection::HubConnection()
+    : Thread(false /* canCallJava */),
+      mRing(10 *1024),
+      mActivityCbCookie(NULL),
+      mActivityCb(NULL),
+      mStepCounterOffset(0ull),
+      mLastStepCount(0ull)
+{
+    mMagBias[0] = mMagBias[1] = mMagBias[2] = 0.0f;
+    mMagAccuracy = SENSOR_STATUS_UNRELIABLE;
+    mMagAccuracyRestore = SENSOR_STATUS_UNRELIABLE;
+    mGyroBias[0] = mGyroBias[1] = mGyroBias[2] = 0.0f;
+
+    memset(&mSensorState, 0x00, sizeof(mSensorState));
+    mFd = open(NANOHUB_FILE_PATH, O_RDWR);
+    mPollFds[0].fd = mFd;
+    mPollFds[0].events = POLLIN;
+    mPollFds[0].revents = 0;
+    mNumPollFds = 1;
+
+    initNanohubLock();
+
+#ifdef USB_MAG_BIAS_REPORTING_ENABLED
+    mUsbMagBias = 0;
+    mMagBiasPollIndex = -1;
+    int magBiasFd = open(MAG_BIAS_FILE_PATH, O_RDONLY);
+    if (magBiasFd < 0) {
+        ALOGW("Mag bias file open failed: %s", strerror(errno));
+    } else {
+        mPollFds[mNumPollFds].fd = magBiasFd;
+        mPollFds[mNumPollFds].events = 0;
+        mPollFds[mNumPollFds].revents = 0;
+        mMagBiasPollIndex = mNumPollFds;
+        mNumPollFds++;
+    }
+#endif  // USB_MAG_BIAS_REPORTING_ENABLED
+
+    mSensorState[COMMS_SENSOR_ACCEL].sensorType = SENS_TYPE_ACCEL;
+    mSensorState[COMMS_SENSOR_GYRO].sensorType = SENS_TYPE_GYRO;
+    mSensorState[COMMS_SENSOR_GYRO].alt = COMMS_SENSOR_GYRO_UNCALIBRATED;
+    mSensorState[COMMS_SENSOR_GYRO_UNCALIBRATED].sensorType = SENS_TYPE_GYRO;
+    mSensorState[COMMS_SENSOR_GYRO_UNCALIBRATED].alt = COMMS_SENSOR_GYRO;
+    mSensorState[COMMS_SENSOR_MAG].sensorType = SENS_TYPE_MAG;
+    mSensorState[COMMS_SENSOR_MAG].alt = COMMS_SENSOR_MAG_UNCALIBRATED;
+    mSensorState[COMMS_SENSOR_MAG_UNCALIBRATED].sensorType = SENS_TYPE_MAG;
+    mSensorState[COMMS_SENSOR_MAG_UNCALIBRATED].alt = COMMS_SENSOR_MAG;
+    mSensorState[COMMS_SENSOR_LIGHT].sensorType = SENS_TYPE_ALS;
+    mSensorState[COMMS_SENSOR_PROXIMITY].sensorType = SENS_TYPE_PROX;
+    mSensorState[COMMS_SENSOR_PRESSURE].sensorType = SENS_TYPE_BARO;
+    mSensorState[COMMS_SENSOR_TEMPERATURE].sensorType = SENS_TYPE_TEMP;
+    mSensorState[COMMS_SENSOR_ORIENTATION].sensorType = SENS_TYPE_ORIENTATION;
+    mSensorState[COMMS_SENSOR_WINDOW_ORIENTATION].sensorType = SENS_TYPE_WIN_ORIENTATION;
+    mSensorState[COMMS_SENSOR_WINDOW_ORIENTATION].rate = SENSOR_RATE_ONCHANGE;
+    mSensorState[COMMS_SENSOR_STEP_DETECTOR].sensorType = SENS_TYPE_STEP_DETECT;
+    mSensorState[COMMS_SENSOR_STEP_DETECTOR].rate = SENSOR_RATE_ONCHANGE;
+    mSensorState[COMMS_SENSOR_STEP_COUNTER].sensorType = SENS_TYPE_STEP_COUNT;
+    mSensorState[COMMS_SENSOR_SIGNIFICANT_MOTION].sensorType = SENS_TYPE_SIG_MOTION;
+    mSensorState[COMMS_SENSOR_SIGNIFICANT_MOTION].rate = SENSOR_RATE_ONESHOT;
+    mSensorState[COMMS_SENSOR_GRAVITY].sensorType = SENS_TYPE_GRAVITY;
+    mSensorState[COMMS_SENSOR_LINEAR_ACCEL].sensorType = SENS_TYPE_LINEAR_ACCEL;
+    mSensorState[COMMS_SENSOR_ROTATION_VECTOR].sensorType = SENS_TYPE_ROTATION_VECTOR;
+    mSensorState[COMMS_SENSOR_GEO_MAG].sensorType = SENS_TYPE_GEO_MAG_ROT_VEC;
+    mSensorState[COMMS_SENSOR_GAME_ROTATION_VECTOR].sensorType = SENS_TYPE_GAME_ROT_VECTOR;
+    mSensorState[COMMS_SENSOR_HALL].sensorType = SENS_TYPE_HALL;
+    mSensorState[COMMS_SENSOR_HALL].rate = SENSOR_RATE_ONCHANGE;
+    mSensorState[COMMS_SENSOR_SYNC].sensorType = SENS_TYPE_VSYNC;
+    mSensorState[COMMS_SENSOR_SYNC].rate = SENSOR_RATE_ONCHANGE;
+    mSensorState[COMMS_SENSOR_ACTIVITY].sensorType = SENS_TYPE_ACTIVITY;
+    mSensorState[COMMS_SENSOR_ACTIVITY].rate = SENSOR_RATE_ONCHANGE;
+    mSensorState[COMMS_SENSOR_TILT].sensorType = SENS_TYPE_TILT;
+    mSensorState[COMMS_SENSOR_TILT].rate = SENSOR_RATE_ONCHANGE;
+    mSensorState[COMMS_SENSOR_GESTURE].sensorType = SENS_TYPE_GESTURE;
+    mSensorState[COMMS_SENSOR_GESTURE].rate = SENSOR_RATE_ONESHOT;
+    mSensorState[COMMS_SENSOR_DOUBLE_TWIST].sensorType = SENS_TYPE_DOUBLE_TWIST;
+    mSensorState[COMMS_SENSOR_DOUBLE_TWIST].rate = SENSOR_RATE_ONCHANGE;
+    mSensorState[COMMS_SENSOR_DOUBLE_TAP].sensorType = SENS_TYPE_DOUBLE_TAP;
+    mSensorState[COMMS_SENSOR_DOUBLE_TAP].rate = SENSOR_RATE_ONCHANGE;
+
+#ifdef LID_STATE_REPORTING_ENABLED
+    initializeUinputNode();
+
+    // set initial lid state
+    if (property_set(LID_STATE_PROPERTY, LID_STATE_UNKNOWN) < 0) {
+        ALOGE("could not set lid_state property");
+    }
+
+    // enable hall sensor for folio
+    if (mFd >= 0) {
+        queueActivate(COMMS_SENSOR_HALL, true /* enable */);
+    }
+#endif  // LID_STATE_REPORTING_ENABLED
+}
+
+HubConnection::~HubConnection()
+{
+    close(mFd);
+}
+
+void HubConnection::onFirstRef()
+{
+    run("HubConnection", PRIORITY_URGENT_DISPLAY);
+}
+
+status_t HubConnection::initCheck() const
+{
+    return mFd < 0 ? UNKNOWN_ERROR : OK;
+}
+
+status_t HubConnection::getAliveCheck()
+{
+    return OK;
+}
+
+static sp<JSONObject> readSettings(File *file) {
+    off64_t size = file->seekTo(0, SEEK_END);
+    file->seekTo(0, SEEK_SET);
+
+    sp<JSONObject> root;
+
+    if (size > 0) {
+        char *buf = (char *)malloc(size);
+        CHECK_EQ(file->read(buf, size), (ssize_t)size);
+        file->seekTo(0, SEEK_SET);
+
+        sp<JSONCompound> in = JSONCompound::Parse(buf, size);
+        free(buf);
+        buf = NULL;
+
+        if (in != NULL && in->isObject()) {
+            root = (JSONObject *)in.get();
+        }
+    }
+
+    if (root == NULL) {
+        root = new JSONObject;
+    }
+
+    return root;
+}
+
+static bool getCalibrationInt32(
+        const sp<JSONObject> &settings, const char *key, int32_t *out,
+        size_t numArgs) {
+    sp<JSONArray> array;
+    if (!settings->getArray(key, &array)) {
+        return false;
+    } else {
+        for (size_t i = 0; i < numArgs; i++) {
+            if (!array->getInt32(i, &out[i])) {
+                return false;
+            }
+        }
+    }
+    return true;
+}
+
+static bool getCalibrationFloat(
+        const sp<JSONObject> &settings, const char *key, float out[3]) {
+    sp<JSONArray> array;
+    if (!settings->getArray(key, &array)) {
+        return false;
+    } else {
+        for (size_t i = 0; i < 3; i++) {
+            if (!array->getFloat(i, &out[i])) {
+                return false;
+            }
+        }
+    }
+    return true;
+}
+
+static void loadSensorSettings(sp<JSONObject>* settings,
+                               sp<JSONObject>* saved_settings) {
+    File settings_file(CONTEXTHUB_SETTINGS_PATH, "r");
+    File saved_settings_file(CONTEXTHUB_SAVED_SETTINGS_PATH, "r");
+
+    status_t err;
+    if ((err = settings_file.initCheck()) != OK) {
+        ALOGE("settings file open failed: %d (%s)",
+              err,
+              strerror(-err));
+
+        *settings = new JSONObject;
+    } else {
+        *settings = readSettings(&settings_file);
+    }
+
+    if ((err = saved_settings_file.initCheck()) != OK) {
+        ALOGE("saved settings file open failed: %d (%s)",
+              err,
+              strerror(-err));
+        *saved_settings = new JSONObject;
+    } else {
+        *saved_settings = readSettings(&saved_settings_file);
+    }
+}
+
+void HubConnection::saveSensorSettings() const {
+    File saved_settings_file(CONTEXTHUB_SAVED_SETTINGS_PATH, "w");
+
+    status_t err;
+    if ((err = saved_settings_file.initCheck()) != OK) {
+        ALOGE("saved settings file open failed %d (%s)",
+              err,
+              strerror(-err));
+        return;
+    }
+
+    // Build a settings object.
+    sp<JSONArray> magArray = new JSONArray;
+#ifdef USB_MAG_BIAS_REPORTING_ENABLED
+    magArray->addFloat(mMagBias[0] + mUsbMagBias);
+#else
+    magArray->addFloat(mMagBias[0]);
+#endif  // USB_MAG_BIAS_REPORTING_ENABLED
+    magArray->addFloat(mMagBias[1]);
+    magArray->addFloat(mMagBias[2]);
+
+    sp<JSONObject> settingsObject = new JSONObject;
+    settingsObject->setArray("mag", magArray);
+
+    // Write the JSON string to disk.
+    AString serializedSettings = settingsObject->toString();
+    size_t size = serializedSettings.size();
+    if ((err = saved_settings_file.write(serializedSettings.c_str(), size)) != (ssize_t)size) {
+        ALOGE("saved settings file write failed %d (%s)",
+              err,
+              strerror(-err));
+    }
+}
+
+sensors_event_t *HubConnection::initEv(sensors_event_t *ev, uint64_t timestamp, uint32_t type, uint32_t sensor)
+{
+    memset(ev, 0x00, sizeof(sensors_event_t));
+    ev->version = sizeof(sensors_event_t);
+    ev->timestamp = timestamp;
+    ev->type = type;
+    ev->sensor = sensor;
+
+    return ev;
+}
+
+void HubConnection::processSample(uint64_t timestamp, uint32_t type, uint32_t sensor, struct OneAxisSample *sample, __attribute__((unused)) bool highAccuracy)
+{
+    sensors_event_t nev[1];
+    int cnt = 0;
+
+    switch (sensor) {
+    case COMMS_SENSOR_ACTIVITY:
+        if (mActivityCb != NULL) {
+            (*mActivityCb)(mActivityCbCookie, timestamp / 1000ull,
+                false, /* is_flush */
+                (float)(sample->idata & 0x7), 0.0, 0.0);
+        }
+        break;
+    case COMMS_SENSOR_PRESSURE:
+        initEv(&nev[cnt++], timestamp, type, sensor)->pressure = sample->fdata;
+        break;
+    case COMMS_SENSOR_TEMPERATURE:
+        initEv(&nev[cnt++], timestamp, type, sensor)->temperature = sample->fdata;
+        break;
+    case COMMS_SENSOR_PROXIMITY:
+        initEv(&nev[cnt++], timestamp, type, sensor)->distance = sample->fdata;
+        break;
+    case COMMS_SENSOR_LIGHT:
+        initEv(&nev[cnt++], timestamp, type, sensor)->light = sample->fdata;
+        break;
+    case COMMS_SENSOR_STEP_COUNTER:
+        // We'll stash away the last step count in case we need to reset
+        // the hub. This last step count would then become the new offset.
+        mLastStepCount = mStepCounterOffset + sample->idata;
+        initEv(&nev[cnt++], timestamp, type, sensor)->u64.step_counter = mLastStepCount;
+        break;
+    case COMMS_SENSOR_STEP_DETECTOR:
+    case COMMS_SENSOR_SIGNIFICANT_MOTION:
+    case COMMS_SENSOR_TILT:
+    case COMMS_SENSOR_DOUBLE_TWIST:
+        initEv(&nev[cnt++], timestamp, type, sensor)->data[0] = 1.0f;
+        break;
+    case COMMS_SENSOR_GESTURE:
+    case COMMS_SENSOR_SYNC:
+        initEv(&nev[cnt++], timestamp, type, sensor)->data[0] = sample->idata;
+        break;
+    case COMMS_SENSOR_HALL:
+#ifdef LID_STATE_REPORTING_ENABLED
+        sendFolioEvent(sample->idata);
+#endif  // LID_STATE_REPORTING_ENABLED
+        break;
+    case COMMS_SENSOR_WINDOW_ORIENTATION:
+        initEv(&nev[cnt++], timestamp, type, sensor)->data[0] = sample->idata;
+        break;
+    default:
+        break;
+    }
+
+    if (cnt > 0)
+        mRing.write(nev, cnt);
+}
+
+void HubConnection::magAccuracyUpdate(float x, float y, float z)
+{
+    float magSq = x * x + y * y + z * z;
+
+    if (magSq < MIN_MAG_SQ || magSq > MAX_MAG_SQ) {
+        // save last good accuracy (either MEDIUM or HIGH)
+        if (mMagAccuracy != SENSOR_STATUS_UNRELIABLE)
+            mMagAccuracyRestore = mMagAccuracy;
+        mMagAccuracy = SENSOR_STATUS_UNRELIABLE;
+    } else if (mMagAccuracy == SENSOR_STATUS_UNRELIABLE) {
+        // restore
+        mMagAccuracy = mMagAccuracyRestore;
+    }
+}
+
+void HubConnection::processSample(uint64_t timestamp, uint32_t type, uint32_t sensor, struct RawThreeAxisSample *sample, __attribute__((unused)) bool highAccuracy)
+{
+    sensors_vec_t *sv;
+    sensors_event_t nev[2];
+    int cnt = 0;
+
+    switch (sensor) {
+    case COMMS_SENSOR_ACCEL:
+        sv = &initEv(&nev[cnt++], timestamp, type, sensor)->acceleration;
+        sv->x = sample->ix * ACCEL_RAW_KSCALE;
+        sv->y = sample->iy * ACCEL_RAW_KSCALE;
+        sv->z = sample->iz * ACCEL_RAW_KSCALE;
+        sv->status = SENSOR_STATUS_ACCURACY_HIGH;
+        break;
+    default:
+        break;
+    }
+
+    if (cnt > 0)
+        mRing.write(nev, cnt);
+}
+
+void HubConnection::processSample(uint64_t timestamp, uint32_t type, uint32_t sensor, struct ThreeAxisSample *sample, bool highAccuracy)
+{
+    sensors_vec_t *sv;
+    uncalibrated_event_t *ue;
+    sensors_event_t *ev;
+    sensors_event_t nev[2];
+    static const float heading_accuracy = M_PI / 6.0f;
+    float w;
+    int cnt = 0;
+
+    switch (sensor) {
+    case COMMS_SENSOR_ACCEL:
+        sv = &initEv(&nev[cnt++], timestamp, type, sensor)->acceleration;
+        sv->x = sample->x;
+        sv->y = sample->y;
+        sv->z = sample->z;
+        sv->status = SENSOR_STATUS_ACCURACY_HIGH;
+        break;
+    case COMMS_SENSOR_GYRO:
+        if (mSensorState[sensor].enable) {
+            sv = &initEv(&nev[cnt++], timestamp, type, sensor)->gyro;
+            sv->x = sample->x;
+            sv->y = sample->y;
+            sv->z = sample->z;
+            sv->status = SENSOR_STATUS_ACCURACY_HIGH;
+        }
+
+        if (mSensorState[COMMS_SENSOR_GYRO_UNCALIBRATED].enable) {
+            ue = &initEv(&nev[cnt++], timestamp,
+                SENSOR_TYPE_GYROSCOPE_UNCALIBRATED,
+                COMMS_SENSOR_GYRO_UNCALIBRATED)->uncalibrated_gyro;
+            ue->x_uncalib = sample->x + mGyroBias[0];
+            ue->y_uncalib = sample->y + mGyroBias[1];
+            ue->z_uncalib = sample->z + mGyroBias[2];
+            ue->x_bias = mGyroBias[0];
+            ue->y_bias = mGyroBias[1];
+            ue->z_bias = mGyroBias[2];
+        }
+        break;
+    case COMMS_SENSOR_GYRO_BIAS:
+        mGyroBias[0] = sample->x;
+        mGyroBias[1] = sample->y;
+        mGyroBias[2] = sample->z;
+        break;
+    case COMMS_SENSOR_MAG:
+        magAccuracyUpdate(sample->x, sample->y, sample->z);
+
+        if (mSensorState[sensor].enable) {
+            sv = &initEv(&nev[cnt++], timestamp, type, sensor)->magnetic;
+            sv->x = sample->x;
+            sv->y = sample->y;
+            sv->z = sample->z;
+            sv->status = mMagAccuracy;
+        }
+
+        if (mSensorState[COMMS_SENSOR_MAG_UNCALIBRATED].enable) {
+            ue = &initEv(&nev[cnt++], timestamp,
+                SENSOR_TYPE_MAGNETIC_FIELD_UNCALIBRATED,
+                COMMS_SENSOR_MAG_UNCALIBRATED)->uncalibrated_magnetic;
+            ue->x_uncalib = sample->x + mMagBias[0];
+            ue->y_uncalib = sample->y + mMagBias[1];
+            ue->z_uncalib = sample->z + mMagBias[2];
+            ue->x_bias = mMagBias[0];
+            ue->y_bias = mMagBias[1];
+            ue->z_bias = mMagBias[2];
+        }
+        break;
+    case COMMS_SENSOR_MAG_BIAS:
+        mMagAccuracy = highAccuracy ? SENSOR_STATUS_ACCURACY_HIGH : SENSOR_STATUS_ACCURACY_MEDIUM;
+        mMagBias[0] = sample->x;
+        mMagBias[1] = sample->y;
+        mMagBias[2] = sample->z;
+
+        saveSensorSettings();
+        break;
+    case COMMS_SENSOR_ORIENTATION:
+    case COMMS_SENSOR_LINEAR_ACCEL:
+    case COMMS_SENSOR_GRAVITY:
+        sv = &initEv(&nev[cnt++], timestamp, type, sensor)->orientation;
+        sv->x = sample->x;
+        sv->y = sample->y;
+        sv->z = sample->z;
+        sv->status = mMagAccuracy;
+        break;
+    case COMMS_SENSOR_DOUBLE_TAP:
+        ev = initEv(&nev[cnt++], timestamp, type, sensor);
+        ev->data[0] = sample->x;
+        ev->data[1] = sample->y;
+        ev->data[2] = sample->z;
+        break;
+    case COMMS_SENSOR_ROTATION_VECTOR:
+        ev = initEv(&nev[cnt++], timestamp, type, sensor);
+        w = sample->x * sample->x + sample->y * sample->y + sample->z * sample->z;
+        if (w < 1.0f)
+            w = sqrt(1.0f - w);
+        else
+            w = 0.0f;
+        ev->data[0] = sample->x;
+        ev->data[1] = sample->y;
+        ev->data[2] = sample->z;
+        ev->data[3] = w;
+        ev->data[4] = (4 - mMagAccuracy) * heading_accuracy;
+        break;
+    case COMMS_SENSOR_GEO_MAG:
+    case COMMS_SENSOR_GAME_ROTATION_VECTOR:
+        ev = initEv(&nev[cnt++], timestamp, type, sensor);
+        w = sample->x * sample->x + sample->y * sample->y + sample->z * sample->z;
+        if (w < 1.0f)
+            w = sqrt(1.0f - w);
+        else
+            w = 0.0f;
+        ev->data[0] = sample->x;
+        ev->data[1] = sample->y;
+        ev->data[2] = sample->z;
+        ev->data[3] = w;
+        break;
+    default:
+        break;
+    }
+
+    if (cnt > 0)
+        mRing.write(nev, cnt);
+}
+
+void HubConnection::discardInotifyEvent() {
+    // Read & discard an inotify event. We only use the presence of an event as
+    // a trigger to perform the file existence check (for simplicity)
+    if (mInotifyPollIndex >= 0) {
+        char buf[sizeof(struct inotify_event) + NAME_MAX + 1];
+        int ret = ::read(mPollFds[mInotifyPollIndex].fd, buf, sizeof(buf));
+        ALOGD("Discarded %d bytes of inotify data", ret);
+    }
+}
+
+void HubConnection::waitOnNanohubLock() {
+    if (mInotifyPollIndex < 0) {
+        return;
+    }
+    struct pollfd *pfd = &mPollFds[mInotifyPollIndex];
+
+    // While the lock file exists, poll on the inotify fd (with timeout)
+    while (access(NANOHUB_LOCK_FILE, F_OK) == 0) {
+        ALOGW("Nanohub is locked; blocking read thread");
+        int ret = poll(pfd, 1, 5000);
+        if ((ret > 0) && (pfd->revents & POLLIN)) {
+            discardInotifyEvent();
+        }
+    }
+}
+
+void HubConnection::restoreSensorState()
+{
+    Mutex::Autolock autoLock(mLock);
+
+    sendCalibrationOffsets();
+
+    for (int i = 0; i < NUM_COMMS_SENSORS_PLUS_1; i++) {
+        if (mSensorState[i].sensorType && mSensorState[i].enable) {
+            struct ConfigCmd cmd;
+
+            initConfigCmd(&cmd, i);
+
+            ALOGI("restoring: sensor=%d, handle=%d, enable=%d, period=%" PRId64 ", latency=%" PRId64,
+                  cmd.sensorType, i, mSensorState[i].enable, frequency_q10_to_period_ns(mSensorState[i].rate),
+                  mSensorState[i].latency);
+
+            int ret = TEMP_FAILURE_RETRY(write(mFd, &cmd, sizeof(cmd)));
+            if (ret != sizeof(cmd)) {
+                ALOGE("failed to send config command to restore sensor %d\n", cmd.sensorType);
+            }
+
+            cmd.cmd = CONFIG_CMD_FLUSH;
+
+            for (int j = 0; j < mSensorState[i].flushCnt; j++) {
+                int ret = TEMP_FAILURE_RETRY(write(mFd, &cmd, sizeof(cmd)));
+                if (ret != sizeof(cmd)) {
+                    ALOGE("failed to send flush command to sensor %d\n", cmd.sensorType);
+                }
+            }
+        }
+    }
+
+    mStepCounterOffset = mLastStepCount;
+}
+
+void HubConnection::postOsLog(uint8_t *buf, ssize_t len)
+{
+    // if len is less than 6, it's either an invalid or an empty log message.
+    if (len < 6)
+        return;
+
+    buf[len] = 0x00;
+    switch (buf[4]) {
+    case 'E':
+        ALOGE("osLog: %s", &buf[5]);
+        break;
+    case 'W':
+        ALOGW("osLog: %s", &buf[5]);
+        break;
+    case 'I':
+        ALOGI("osLog: %s", &buf[5]);
+        break;
+    case 'D':
+        ALOGD("osLog: %s", &buf[5]);
+        break;
+    default:
+        break;
+    }
+}
+
+ssize_t HubConnection::processBuf(uint8_t *buf, ssize_t len)
+{
+    struct nAxisEvent *data = (struct nAxisEvent *)buf;
+    uint32_t type, sensor, bias, currSensor;
+    int i, numSamples;
+    bool one, rawThree, three;
+    sensors_event_t ev;
+    uint64_t timestamp;
+    ssize_t ret = 0;
+
+    if (len >= 4) {
+        ret = sizeof(data->evtType);
+        one = three = rawThree = false;
+        bias = 0;
+        switch (data->evtType) {
+        case OS_LOG_EVENT:
+            postOsLog(buf, len);
+            return 0;
+        case SENS_TYPE_TO_EVENT(SENS_TYPE_ACCEL):
+            type = SENSOR_TYPE_ACCELEROMETER;
+            sensor = COMMS_SENSOR_ACCEL;
+            three = true;
+            break;
+        case SENS_TYPE_TO_EVENT(SENS_TYPE_ACCEL_RAW):
+            type = SENSOR_TYPE_ACCELEROMETER;
+            sensor = COMMS_SENSOR_ACCEL;
+            rawThree = true;
+            break;
+        case SENS_TYPE_TO_EVENT(SENS_TYPE_GYRO):
+            type = SENSOR_TYPE_GYROSCOPE;
+            sensor = COMMS_SENSOR_GYRO;
+            bias = COMMS_SENSOR_GYRO_BIAS;
+            three = true;
+            break;
+        case SENS_TYPE_TO_EVENT(SENS_TYPE_MAG):
+            type = SENSOR_TYPE_MAGNETIC_FIELD;
+            sensor = COMMS_SENSOR_MAG;
+            bias = COMMS_SENSOR_MAG_BIAS;
+            three = true;
+            break;
+        case SENS_TYPE_TO_EVENT(SENS_TYPE_ALS):
+            type = SENSOR_TYPE_LIGHT;
+            sensor = COMMS_SENSOR_LIGHT;
+            one = true;
+            break;
+        case SENS_TYPE_TO_EVENT(SENS_TYPE_PROX):
+            type = SENSOR_TYPE_PROXIMITY;
+            sensor = COMMS_SENSOR_PROXIMITY;
+            one = true;
+            break;
+        case SENS_TYPE_TO_EVENT(SENS_TYPE_BARO):
+            type = SENSOR_TYPE_PRESSURE;
+            sensor = COMMS_SENSOR_PRESSURE;
+            one = true;
+            break;
+        case SENS_TYPE_TO_EVENT(SENS_TYPE_TEMP):
+            type = SENSOR_TYPE_AMBIENT_TEMPERATURE;
+            sensor = COMMS_SENSOR_TEMPERATURE;
+            one = true;
+            break;
+        case SENS_TYPE_TO_EVENT(SENS_TYPE_ORIENTATION):
+            type = SENSOR_TYPE_ORIENTATION;
+            sensor = COMMS_SENSOR_ORIENTATION;
+            three = true;
+            break;
+        case SENS_TYPE_TO_EVENT(SENS_TYPE_WIN_ORIENTATION):
+            type = SENSOR_TYPE_DEVICE_ORIENTATION;
+            sensor = COMMS_SENSOR_WINDOW_ORIENTATION;
+            one = true;
+            break;
+        case SENS_TYPE_TO_EVENT(SENS_TYPE_STEP_DETECT):
+            type = SENSOR_TYPE_STEP_DETECTOR;
+            sensor = COMMS_SENSOR_STEP_DETECTOR;
+            one = true;
+            break;
+        case SENS_TYPE_TO_EVENT(SENS_TYPE_STEP_COUNT):
+            type = SENSOR_TYPE_STEP_COUNTER;
+            sensor = COMMS_SENSOR_STEP_COUNTER;
+            one = true;
+            break;
+        case SENS_TYPE_TO_EVENT(SENS_TYPE_SIG_MOTION):
+            type = SENSOR_TYPE_SIGNIFICANT_MOTION;
+            sensor = COMMS_SENSOR_SIGNIFICANT_MOTION;
+            one = true;
+            break;
+        case SENS_TYPE_TO_EVENT(SENS_TYPE_GRAVITY):
+            type = SENSOR_TYPE_GRAVITY;
+            sensor = COMMS_SENSOR_GRAVITY;
+            three = true;
+            break;
+        case SENS_TYPE_TO_EVENT(SENS_TYPE_LINEAR_ACCEL):
+            type = SENSOR_TYPE_LINEAR_ACCELERATION;
+            sensor = COMMS_SENSOR_LINEAR_ACCEL;
+            three = true;
+            break;
+        case SENS_TYPE_TO_EVENT(SENS_TYPE_ROTATION_VECTOR):
+            type = SENSOR_TYPE_ROTATION_VECTOR;
+            sensor = COMMS_SENSOR_ROTATION_VECTOR;
+            three = true;
+            break;
+        case SENS_TYPE_TO_EVENT(SENS_TYPE_GEO_MAG_ROT_VEC):
+            type = SENSOR_TYPE_GEOMAGNETIC_ROTATION_VECTOR;
+            sensor = COMMS_SENSOR_GEO_MAG;
+            three = true;
+            break;
+        case SENS_TYPE_TO_EVENT(SENS_TYPE_GAME_ROT_VECTOR):
+            type = SENSOR_TYPE_GAME_ROTATION_VECTOR;
+            sensor = COMMS_SENSOR_GAME_ROTATION_VECTOR;
+            three = true;
+            break;
+        case SENS_TYPE_TO_EVENT(SENS_TYPE_HALL):
+            type = 0;
+            sensor = COMMS_SENSOR_HALL;
+            one = true;
+            break;
+        case SENS_TYPE_TO_EVENT(SENS_TYPE_VSYNC):
+            type = SENSOR_TYPE_SYNC;
+            sensor = COMMS_SENSOR_SYNC;
+            one = true;
+            break;
+        case SENS_TYPE_TO_EVENT(SENS_TYPE_ACTIVITY):
+            type = 0;
+            sensor = COMMS_SENSOR_ACTIVITY;
+            one = true;
+            break;
+        case SENS_TYPE_TO_EVENT(SENS_TYPE_TILT):
+            type = SENSOR_TYPE_TILT_DETECTOR;
+            sensor = COMMS_SENSOR_TILT;
+            one = true;
+            break;
+        case SENS_TYPE_TO_EVENT(SENS_TYPE_GESTURE):
+            type = SENSOR_TYPE_PICK_UP_GESTURE;
+            sensor = COMMS_SENSOR_GESTURE;
+            one = true;
+            break;
+        case SENS_TYPE_TO_EVENT(SENS_TYPE_DOUBLE_TWIST):
+            type = SENSOR_TYPE_DOUBLE_TWIST;
+            sensor = COMMS_SENSOR_DOUBLE_TWIST;
+            one = true;
+            break;
+        case SENS_TYPE_TO_EVENT(SENS_TYPE_DOUBLE_TAP):
+            type = SENSOR_TYPE_DOUBLE_TAP;
+            sensor = COMMS_SENSOR_DOUBLE_TAP;
+            three = true;
+            break;
+        case EVT_RESET_REASON:
+            uint32_t resetReason;
+            memcpy(&resetReason, data->buffer, sizeof(resetReason));
+            ALOGI("Observed hub reset: 0x%08" PRIx32, resetReason);
+            restoreSensorState();
+            return 0;
+        default:
+            return 0;
+        }
+    }
+
+    if (len >= 16) {
+        ret += sizeof(data->referenceTime);
+        timestamp = data->referenceTime;
+        numSamples = data->firstSample.numSamples;
+        for (i=0; i<numSamples; i++) {
+            if (data->firstSample.biasPresent && data->firstSample.biasSample == i)
+                currSensor = bias;
+            else
+                currSensor = sensor;
+
+            if (one) {
+                if (i > 0)
+                    timestamp += ((uint64_t)data->oneSamples[i].deltaTime) << delta_time_shift_table[data->oneSamples[i].deltaTime & delta_time_encoded];
+                processSample(timestamp, type, currSensor, &data->oneSamples[i], data->firstSample.highAccuracy);
+                ret += sizeof(data->oneSamples[i]);
+            } else if (rawThree) {
+                if (i > 0)
+                    timestamp += ((uint64_t)data->rawThreeSamples[i].deltaTime) << delta_time_shift_table[data->rawThreeSamples[i].deltaTime & delta_time_encoded];
+                processSample(timestamp, type, currSensor, &data->rawThreeSamples[i], data->firstSample.highAccuracy);
+                ret += sizeof(data->rawThreeSamples[i]);
+            } else if (three) {
+                if (i > 0)
+                    timestamp += ((uint64_t)data->threeSamples[i].deltaTime) << delta_time_shift_table[data->threeSamples[i].deltaTime & delta_time_encoded];
+                processSample(timestamp, type, currSensor, &data->threeSamples[i], data->firstSample.highAccuracy);
+                ret += sizeof(data->threeSamples[i]);
+            }
+        }
+
+        if (!numSamples)
+            ret += sizeof(data->firstSample);
+
+        for (i=0; i<data->firstSample.numFlushes; i++) {
+            if (sensor == COMMS_SENSOR_ACTIVITY) {
+                if (mActivityCb != NULL) {
+                    (*mActivityCb)(mActivityCbCookie, 0ull, /* when_us */
+                        true, /* is_flush */
+                        0.0f, 0.0f, 0.0f);
+                }
+            } else {
+                memset(&ev, 0x00, sizeof(sensors_event_t));
+                ev.version = META_DATA_VERSION;
+                ev.timestamp = 0;
+                ev.type = SENSOR_TYPE_META_DATA;
+                ev.sensor = 0;
+                ev.meta_data.what = META_DATA_FLUSH_COMPLETE;
+                if (mSensorState[sensor].alt && mSensorState[mSensorState[sensor].alt].flushCnt > 0) {
+                    mSensorState[mSensorState[sensor].alt].flushCnt --;
+                    ev.meta_data.sensor = mSensorState[sensor].alt;
+                } else {
+                    mSensorState[sensor].flushCnt --;
+                    ev.meta_data.sensor = sensor;
+                }
+
+                mRing.write(&ev, 1);
+                ALOGI("flushing %d", ev.meta_data.sensor);
+            }
+        }
+    }
+
+    return ret;
+}
+
+void HubConnection::sendCalibrationOffsets()
+{
+    sp<JSONObject> settings;
+    sp<JSONObject> saved_settings;
+    int32_t accel[3], gyro[3], proximity, proximity_array[4];
+    float barometer, mag[3], light;
+
+    loadSensorSettings(&settings, &saved_settings);
+
+    if (getCalibrationInt32(settings, "accel", accel, 3))
+        queueDataInternal(COMMS_SENSOR_ACCEL, accel, sizeof(accel));
+
+    if (getCalibrationInt32(settings, "gyro", gyro, 3))
+        queueDataInternal(COMMS_SENSOR_GYRO, gyro, sizeof(gyro));
+
+    if (settings->getFloat("barometer", &barometer))
+        queueDataInternal(COMMS_SENSOR_PRESSURE, &barometer, sizeof(barometer));
+
+    if (settings->getInt32("proximity", &proximity))
+        queueDataInternal(COMMS_SENSOR_PROXIMITY, &proximity, sizeof(proximity));
+
+    if (getCalibrationInt32(settings, "proximity", proximity_array, 4))
+        queueDataInternal(COMMS_SENSOR_PROXIMITY, proximity_array, sizeof(proximity_array));
+
+    if (settings->getFloat("light", &light))
+        queueDataInternal(COMMS_SENSOR_LIGHT, &light, sizeof(light));
+
+    if (getCalibrationFloat(saved_settings, "mag", mag))
+        queueDataInternal(COMMS_SENSOR_MAG, mag, sizeof(mag));
+}
+
+bool HubConnection::threadLoop() {
+    ALOGI("threadLoop: starting");
+
+    if (mFd < 0) {
+        ALOGE("threadLoop: exiting prematurely: nanohub is unavailable");
+        return false;
+    }
+    waitOnNanohubLock();
+
+    sendCalibrationOffsets();
+
+    while (!Thread::exitPending()) {
+        ssize_t ret;
+
+        do {
+            ret = poll(mPollFds, mNumPollFds, -1);
+        } while (ret < 0 && errno == EINTR);
+
+        if (mInotifyPollIndex >= 0 && mPollFds[mInotifyPollIndex].revents & POLLIN) {
+            discardInotifyEvent();
+            waitOnNanohubLock();
+        }
+
+#ifdef USB_MAG_BIAS_REPORTING_ENABLED
+        if (mMagBiasPollIndex >= 0 && mPollFds[mMagBiasPollIndex].revents & POLLERR) {
+            // Read from mag bias file
+            char buf[16];
+            lseek(mPollFds[mMagBiasPollIndex].fd, 0, SEEK_SET);
+            ::read(mPollFds[mMagBiasPollIndex].fd, buf, 16);
+            float bias = atof(buf);
+            mUsbMagBias = bias;
+            queueUsbMagBias();
+        }
+#endif // USB_MAG_BIAS_REPORTING_ENABLED
+
+        if (mPollFds[0].revents & POLLIN) {
+            uint8_t recv[256];
+            ssize_t len = ::read(mFd, recv, sizeof(recv));
+
+            for (ssize_t offset = 0; offset < len;) {
+                ret = processBuf(recv + offset, len - offset);
+
+                if (ret > 0)
+                    offset += ret;
+                else
+                    break;
+            }
+        }
+    }
+
+    return false;
+}
+
+ssize_t HubConnection::read(sensors_event_t *ev, size_t size) {
+    return mRing.read(ev, size);
+}
+
+void HubConnection::setActivityCallback(
+        void *cookie,
+        void (*cb)(void *, uint64_t time_ms, bool, float x, float y, float z))
+{
+    Mutex::Autolock autoLock(mLock);
+    mActivityCbCookie = cookie;
+    mActivityCb = cb;
+}
+
+void HubConnection::initConfigCmd(struct ConfigCmd *cmd, int handle)
+{
+    uint8_t alt = mSensorState[handle].alt;
+
+    memset(cmd, 0x00, sizeof(*cmd));
+
+    cmd->evtType = EVT_NO_SENSOR_CONFIG_EVENT;
+    cmd->sensorType = mSensorState[handle].sensorType;
+
+    if (alt && mSensorState[alt].enable && mSensorState[handle].enable) {
+        cmd->cmd = CONFIG_CMD_ENABLE;
+        if (mSensorState[alt].rate > mSensorState[handle].rate)
+            cmd->rate = mSensorState[alt].rate;
+        else
+            cmd->rate = mSensorState[handle].rate;
+        if (mSensorState[alt].latency < mSensorState[handle].latency)
+            cmd->latency = mSensorState[alt].latency;
+        else
+            cmd->latency = mSensorState[handle].latency;
+    } else if (alt && mSensorState[alt].enable) {
+        cmd->cmd = mSensorState[alt].enable ? CONFIG_CMD_ENABLE : CONFIG_CMD_DISABLE;
+        cmd->rate = mSensorState[alt].rate;
+        cmd->latency = mSensorState[alt].latency;
+    } else { /* !alt || !mSensorState[alt].enable */
+        cmd->cmd = mSensorState[handle].enable ? CONFIG_CMD_ENABLE : CONFIG_CMD_DISABLE;
+        cmd->rate = mSensorState[handle].rate;
+        cmd->latency = mSensorState[handle].latency;
+    }
+}
+
+void HubConnection::queueActivate(int handle, bool enable)
+{
+    struct ConfigCmd cmd;
+    int ret;
+
+    Mutex::Autolock autoLock(mLock);
+
+    if (mSensorState[handle].sensorType) {
+        mSensorState[handle].enable = enable;
+
+        initConfigCmd(&cmd, handle);
+
+        ALOGI("queueActivate: sensor=%d, handle=%d, enable=%d", cmd.sensorType, handle, enable);
+        do {
+            ret = write(mFd, &cmd, sizeof(cmd));
+        } while(ret != sizeof(cmd));
+    } else {
+        ALOGI("queueActivate: unhandled handle=%d, enable=%d", handle, enable);
+    }
+}
+
+void HubConnection::queueSetDelay(int handle, nsecs_t sampling_period_ns)
+{
+    struct ConfigCmd cmd;
+    int ret;
+
+    Mutex::Autolock autoLock(mLock);
+
+    if (mSensorState[handle].sensorType) {
+        if (sampling_period_ns > 0 &&
+                mSensorState[handle].rate != SENSOR_RATE_ONCHANGE &&
+                mSensorState[handle].rate != SENSOR_RATE_ONESHOT) {
+            mSensorState[handle].rate = period_ns_to_frequency_q10(sampling_period_ns);
+        }
+
+        initConfigCmd(&cmd, handle);
+
+        ALOGI("queueSetDelay: sensor=%d, handle=%d, period=%" PRId64,
+                cmd.sensorType, handle, sampling_period_ns);
+        do {
+            ret = write(mFd, &cmd, sizeof(cmd));
+        } while(ret != sizeof(cmd));
+    } else {
+        ALOGI("queueSetDelay: unhandled handle=%d, period=%" PRId64, handle, sampling_period_ns);
+    }
+}
+
+void HubConnection::queueBatch(
+        int handle,
+        __attribute__((unused)) int flags,
+        nsecs_t sampling_period_ns,
+        nsecs_t max_report_latency_ns)
+{
+    struct ConfigCmd cmd;
+    int ret;
+
+    Mutex::Autolock autoLock(mLock);
+
+    if (mSensorState[handle].sensorType) {
+        if (sampling_period_ns > 0 &&
+                mSensorState[handle].rate != SENSOR_RATE_ONCHANGE &&
+                mSensorState[handle].rate != SENSOR_RATE_ONESHOT) {
+            mSensorState[handle].rate = period_ns_to_frequency_q10(sampling_period_ns);
+        }
+        mSensorState[handle].latency = max_report_latency_ns;
+
+        initConfigCmd(&cmd, handle);
+
+        ALOGI("queueBatch: sensor=%d, handle=%d, period=%" PRId64 ", latency=%" PRId64,
+                cmd.sensorType, handle, sampling_period_ns, max_report_latency_ns);
+        do {
+            ret = write(mFd, &cmd, sizeof(cmd));
+        } while(ret != sizeof(cmd));
+    } else {
+        ALOGI("queueBatch: unhandled handle=%d, period=%" PRId64 ", latency=%" PRId64,
+                handle, sampling_period_ns, max_report_latency_ns);
+    }
+}
+
+void HubConnection::queueFlush(int handle)
+{
+    struct ConfigCmd cmd;
+    int ret;
+
+    Mutex::Autolock autoLock(mLock);
+
+    if (mSensorState[handle].sensorType) {
+        mSensorState[handle].flushCnt++;
+
+        initConfigCmd(&cmd, handle);
+        cmd.cmd = CONFIG_CMD_FLUSH;
+
+        ALOGI("queueFlush: sensor=%d, handle=%d", cmd.sensorType, handle);
+        do {
+            ret = write(mFd, &cmd, sizeof(cmd));
+        } while(ret != sizeof(cmd));
+    } else {
+        ALOGI("queueFlush: unhandled handle=%d", handle);
+    }
+}
+
+void HubConnection::queueDataInternal(int handle, void *data, size_t length)
+{
+    struct ConfigCmd *cmd = (struct ConfigCmd *)malloc(sizeof(struct ConfigCmd) + length);
+    size_t ret;
+
+    if (cmd && mSensorState[handle].sensorType) {
+        initConfigCmd(cmd, handle);
+        memcpy(cmd->data, data, length);
+        cmd->cmd = CONFIG_CMD_CFG_DATA;
+
+        ALOGI("queueData: sensor=%d, length=%zu", cmd->sensorType, length);
+        do {
+            ret = write(mFd, cmd, sizeof(*cmd) + length);
+        } while(ret != sizeof(*cmd) + length);
+        free(cmd);
+    } else {
+        ALOGI("queueData: unhandled handle=%d", handle);
+    }
+}
+
+void HubConnection::queueData(int handle, void *data, size_t length)
+{
+    Mutex::Autolock autoLock(mLock);
+    queueDataInternal(handle, data, length);
+}
+
+void HubConnection::initNanohubLock() {
+    // Create the lock directory (if it doesn't already exist)
+    if (mkdir(NANOHUB_LOCK_DIR, NANOHUB_LOCK_DIR_PERMS) < 0 && errno != EEXIST) {
+        ALOGE("Couldn't create Nanohub lock directory: %s", strerror(errno));
+        return;
+    }
+
+    mInotifyPollIndex = -1;
+    int inotifyFd = inotify_init1(IN_NONBLOCK);
+    if (inotifyFd < 0) {
+        ALOGE("Couldn't initialize inotify: %s", strerror(errno));
+    } else if (inotify_add_watch(inotifyFd, NANOHUB_LOCK_DIR, IN_CREATE | IN_DELETE) < 0) {
+        ALOGE("Couldn't add inotify watch: %s", strerror(errno));
+        close(inotifyFd);
+    } else {
+        mPollFds[mNumPollFds].fd = inotifyFd;
+        mPollFds[mNumPollFds].events = POLLIN;
+        mPollFds[mNumPollFds].revents = 0;
+        mInotifyPollIndex = mNumPollFds;
+        mNumPollFds++;
+    }
+}
+
+#ifdef USB_MAG_BIAS_REPORTING_ENABLED
+void HubConnection::queueUsbMagBias()
+{
+    struct MsgCmd *cmd = (struct MsgCmd *)malloc(sizeof(struct MsgCmd) + sizeof(float));
+    size_t ret;
+
+    if (cmd) {
+        cmd->evtType = EVT_APP_FROM_HOST;
+        cmd->msg.appId = APP_ID_MAKE(APP_ID_VENDOR_GOOGLE, APP_ID_APP_BMI160);
+        cmd->msg.dataLen = sizeof(float);
+        memcpy((float *)(cmd+1), &mUsbMagBias, sizeof(float));
+
+        ALOGI("queueUsbMagBias: bias=%f\n", mUsbMagBias);
+        do {
+            ret = write(mFd, cmd, sizeof(*cmd) + sizeof(float));
+        } while(ret != sizeof(*cmd) + sizeof(float));
+        free(cmd);
+    }
+}
+#endif  // USB_MAG_BIAS_REPORTING_ENABLED
+
+#ifdef LID_STATE_REPORTING_ENABLED
+status_t HubConnection::initializeUinputNode()
+{
+    int ret = 0;
+
+    // Open uinput dev node
+    mUinputFd = TEMP_FAILURE_RETRY(open("/dev/uinput", O_WRONLY | O_NONBLOCK));
+    if (mUinputFd < 0) {
+        ALOGE("could not open uinput node: %s", strerror(errno));
+        return UNKNOWN_ERROR;
+    }
+
+    // Enable SW_LID events
+    ret  = TEMP_FAILURE_RETRY(ioctl(mUinputFd, UI_SET_EVBIT, EV_SW));
+    ret |= TEMP_FAILURE_RETRY(ioctl(mUinputFd, UI_SET_EVBIT, EV_SYN));
+    ret |= TEMP_FAILURE_RETRY(ioctl(mUinputFd, UI_SET_SWBIT, SW_LID));
+    if (ret < 0) {
+        ALOGE("could not send ioctl to uinput node: %s", strerror(errno));
+        return UNKNOWN_ERROR;
+    }
+
+    // Create uinput node for SW_LID
+    struct uinput_user_dev uidev;
+    memset(&uidev, 0, sizeof(uidev));
+    snprintf(uidev.name, UINPUT_MAX_NAME_SIZE, "uinput-folio");
+    uidev.id.bustype = BUS_SPI;
+    uidev.id.vendor  = 0;
+    uidev.id.product = 0;
+    uidev.id.version = 0;
+
+    ret = TEMP_FAILURE_RETRY(write(mUinputFd, &uidev, sizeof(uidev)));
+    if (ret < 0) {
+        ALOGE("write to uinput node failed: %s", strerror(errno));
+        return UNKNOWN_ERROR;
+    }
+
+    ret = TEMP_FAILURE_RETRY(ioctl(mUinputFd, UI_DEV_CREATE));
+    if (ret < 0) {
+        ALOGE("could not send ioctl to uinput node: %s", strerror(errno));
+        return UNKNOWN_ERROR;
+    }
+
+    return OK;
+}
+
+void HubConnection::sendFolioEvent(int32_t data) {
+    ssize_t ret = 0;
+    struct input_event ev;
+
+    memset(&ev, 0, sizeof(ev));
+
+    ev.type = EV_SW;
+    ev.code = SW_LID;
+    ev.value =  data;
+    ret = TEMP_FAILURE_RETRY(write(mUinputFd, &ev, sizeof(ev)));
+    if (ret < 0) {
+        ALOGE("write to uinput node failed: %s", strerror(errno));
+        return;
+    }
+
+    // Force flush with EV_SYN event
+    ev.type = EV_SYN;
+    ev.code = SYN_REPORT;
+    ev.value =  0;
+    ret = TEMP_FAILURE_RETRY(write(mUinputFd, &ev, sizeof(ev)));
+    if (ret < 0) {
+        ALOGE("write to uinput node failed: %s", strerror(errno));
+        return;
+    }
+
+    // Set lid state property
+    if (property_set(LID_STATE_PROPERTY,
+                     (data ? LID_STATE_CLOSED : LID_STATE_OPEN)) < 0) {
+        ALOGE("could not set lid_state property");
+    }
+}
+#endif  // LID_STATE_REPORTING_ENABLED
+
+} // namespace android
diff --git a/sensorhal/hubconnection.h b/sensorhal/hubconnection.h
new file mode 100644
index 0000000..00d2d9b
--- /dev/null
+++ b/sensorhal/hubconnection.h
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef HUB_CONNECTION_H_
+
+#define HUB_CONNECTION_H_
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <poll.h>
+
+#include <utils/Errors.h>
+#include <utils/Mutex.h>
+#include <utils/Thread.h>
+
+#include "eventnums.h"
+#include "hubdefs.h"
+#include "ring.h"
+
+namespace android {
+
+struct HubConnection : public Thread {
+    static HubConnection *getInstance();
+
+    status_t initCheck() const;
+
+    enum ProximitySensorType {
+        PROXIMITY_UNKNOWN,
+        PROXIMITY_ROHM,
+        PROXIMITY_AMS,
+    };
+
+    // Blocks until it can return a status
+    status_t getAliveCheck();
+
+    virtual bool threadLoop();
+
+    void queueActivate(int handle, bool enable);
+    void queueSetDelay(int handle, nsecs_t delayNs);
+    void queueBatch(
+            int handle,
+            int flags,
+            nsecs_t sampling_period_ns,
+            nsecs_t max_report_latency_ns);
+    void queueFlush(int handle);
+    void queueData(int handle, void *data, size_t length);
+
+    ssize_t read(sensors_event_t *ev, size_t size);
+
+    typedef void (*ActivityFunc)(
+            void *, uint64_t time_us, bool is_flush, float x, float y, float z);
+
+    void setActivityCallback(void *cookie, ActivityFunc cb);
+
+    void saveSensorSettings() const;
+
+protected:
+    HubConnection();
+    virtual ~HubConnection();
+
+    virtual void onFirstRef();
+
+private:
+    typedef uint32_t rate_q10_t;  // q10 means lower 10 bits are for fractions
+
+    static inline uint64_t period_ns_to_frequency_q10(nsecs_t period_ns) {
+        return 1024000000000ULL / period_ns;
+    }
+
+    static inline nsecs_t frequency_q10_to_period_ns(uint64_t frequency_q10) {
+        if (frequency_q10)
+            return 1024000000000LL / frequency_q10;
+        else
+            return (nsecs_t)0;
+    }
+
+    enum
+    {
+        CONFIG_CMD_DISABLE      = 0,
+        CONFIG_CMD_ENABLE       = 1,
+        CONFIG_CMD_FLUSH        = 2,
+        CONFIG_CMD_CFG_DATA     = 3,
+        CONFIG_CMD_CALIBRATE    = 4,
+    };
+
+    struct ConfigCmd
+    {
+        uint32_t evtType;
+        uint64_t latency;
+        rate_q10_t rate;
+        uint8_t sensorType;
+        uint8_t cmd;
+        uint16_t flags;
+        uint8_t data[];
+    } __attribute__((packed));
+
+    struct MsgCmd
+    {
+        uint32_t evtType;
+        struct HostHubRawPacket msg;
+    } __attribute__((packed));
+
+    struct SensorState {
+        uint64_t latency;
+        rate_q10_t rate;
+        uint8_t sensorType;
+        uint8_t alt;
+        uint8_t flushCnt;
+        bool enable;
+    };
+
+    struct FirstSample
+    {
+        uint8_t numSamples;
+        uint8_t numFlushes;
+        uint8_t highAccuracy : 1;
+        uint8_t biasPresent : 1;
+        uint8_t biasSample : 6;
+        uint8_t pad;
+    };
+
+    struct RawThreeAxisSample
+    {
+        uint32_t deltaTime;
+        int16_t ix, iy, iz;
+    } __attribute__((packed));
+
+    struct ThreeAxisSample
+    {
+        uint32_t deltaTime;
+        float x, y, z;
+    } __attribute__((packed));
+
+    struct OneAxisSample
+    {
+        uint32_t deltaTime;
+        union
+        {
+            float fdata;
+            uint32_t idata;
+        };
+    } __attribute__((packed));
+
+    // The following structure should match struct HostIntfDataBuffer found in
+    // firmware/inc/hostIntf.h
+    struct nAxisEvent
+    {
+        uint32_t evtType;
+        union
+        {
+            struct
+            {
+                uint64_t referenceTime;
+                union
+                {
+                    struct FirstSample firstSample;
+                    struct OneAxisSample oneSamples[];
+                    struct RawThreeAxisSample rawThreeSamples[];
+                    struct ThreeAxisSample threeSamples[];
+                };
+            };
+            uint8_t buffer[];
+        };
+    } __attribute__((packed));
+
+    static Mutex sInstanceLock;
+    static HubConnection *sInstance;
+
+    // This lock is used for synchronization between the write thread (from
+    // sensorservice) and the read thread polling from the nanohub driver.
+    Mutex mLock;
+
+    RingBuffer mRing;
+
+    void *mActivityCbCookie;
+    ActivityFunc mActivityCb;
+
+    float mMagBias[3];
+    uint8_t mMagAccuracy;
+    uint8_t mMagAccuracyRestore;
+
+    float mGyroBias[3];
+
+    SensorState mSensorState[NUM_COMMS_SENSORS_PLUS_1];
+
+    uint64_t mStepCounterOffset;
+    uint64_t mLastStepCount;
+
+    int mFd;
+    int mInotifyPollIndex;
+    struct pollfd mPollFds[3];
+    int mNumPollFds;
+
+    sensors_event_t *initEv(sensors_event_t *ev, uint64_t timestamp, uint32_t type, uint32_t sensor);
+    void magAccuracyUpdate(float x, float y, float z);
+    void processSample(uint64_t timestamp, uint32_t type, uint32_t sensor, struct OneAxisSample *sample, bool highAccuracy);
+    void processSample(uint64_t timestamp, uint32_t type, uint32_t sensor, struct RawThreeAxisSample *sample, bool highAccuracy);
+    void processSample(uint64_t timestamp, uint32_t type, uint32_t sensor, struct ThreeAxisSample *sample, bool highAccuracy);
+    void postOsLog(uint8_t *buf, ssize_t len);
+    ssize_t processBuf(uint8_t *buf, ssize_t len);
+
+    void initConfigCmd(struct ConfigCmd *cmd, int handle);
+
+    void queueDataInternal(int handle, void *data, size_t length);
+
+    void discardInotifyEvent();
+    void waitOnNanohubLock();
+
+    void initNanohubLock();
+
+    void restoreSensorState();
+    void sendCalibrationOffsets();
+
+#ifdef LID_STATE_REPORTING_ENABLED
+    int mUinputFd;
+
+    status_t initializeUinputNode();
+    void sendFolioEvent(int32_t data);
+#endif  // LID_STATE_REPORTING_ENABLED
+
+#ifdef USB_MAG_BIAS_REPORTING_ENABLED
+    int mMagBiasPollIndex;
+    float mUsbMagBias;
+
+    void queueUsbMagBias();
+#endif  // USB_MAG_BIAS_REPORTING_ENABLED
+
+    DISALLOW_EVIL_CONSTRUCTORS(HubConnection);
+};
+
+}  // namespace android
+
+#endif  // HUB_CONNECTION_H_
diff --git a/sensorhal/hubdefs.h b/sensorhal/hubdefs.h
new file mode 100644
index 0000000..63492ab
--- /dev/null
+++ b/sensorhal/hubdefs.h
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef HUB_DEFS_H_
+#define HUB_DEFS_H_
+
+#include <hardware/sensors.h>
+
+#define MAX_SPI_PAYLOAD_SIZE            256
+
+namespace android {
+
+#define CONTEXTHUB_SETTINGS_PATH        "/persist/sensorcal.json"
+#define CONTEXTHUB_SAVED_SETTINGS_PATH  "/data/misc/sensorcal_saved.json"
+#define MAG_BIAS_FILE_PATH              "/sys/class/power_supply/battery/compass_compensation"
+
+static const uint32_t kMinClockRateHz = 960000;
+static const uint32_t kClockRateHz = kMinClockRateHz * 5;  // 4.8MHz
+
+enum comms_sensor_t {
+    COMMS_SENSOR_INVALID                = 0,
+    COMMS_SENSOR_ACCEL                  = 1,
+    COMMS_SENSOR_GYRO                   = 2,
+    COMMS_SENSOR_MAG                    = 3,
+    COMMS_SENSOR_PRESSURE               = 4,
+    COMMS_SENSOR_TEMPERATURE            = 5,
+    COMMS_SENSOR_PROXIMITY              = 6,
+    COMMS_SENSOR_LIGHT                  = 7,
+    COMMS_SENSOR_ORIENTATION            = 8,
+    COMMS_SENSOR_STEP_DETECTOR          = 9,
+    COMMS_SENSOR_ANY_MOTION             = 10,
+    COMMS_SENSOR_NO_MOTION              = 11,
+    COMMS_SENSOR_SIGNIFICANT_MOTION     = 12,
+    COMMS_SENSOR_FLAT                   = 13,
+    COMMS_SENSOR_ACTIVITY               = 14,
+    COMMS_SENSOR_GRAVITY                = 15,
+    COMMS_SENSOR_LINEAR_ACCEL           = 16,
+    COMMS_SENSOR_ROTATION_VECTOR        = 17,
+    COMMS_SENSOR_HALL                   = 18,
+    COMMS_SENSOR_GEO_MAG                = 19,
+    COMMS_SENSOR_GAME_ROTATION_VECTOR   = 20,
+    COMMS_SENSOR_GESTURE                = 21,
+    COMMS_SENSOR_TILT                   = 22,
+    COMMS_SENSOR_MAG_BIAS               = 23,
+    COMMS_SENSOR_STEP_COUNTER           = 24,
+    COMMS_SENSOR_MAG_UNCALIBRATED       = 25,
+    COMMS_SENSOR_GYRO_UNCALIBRATED      = 26,
+    COMMS_SENSOR_GYRO_BIAS              = 27,
+    COMMS_SENSOR_SYNC                   = 28,
+    COMMS_SENSOR_DOUBLE_TWIST           = 29,
+    COMMS_SENSOR_DOUBLE_TAP             = 30,
+    COMMS_SENSOR_WINDOW_ORIENTATION     = 31,
+
+    NUM_COMMS_SENSORS_PLUS_1,
+
+    COMMS_SENSOR_DEBUG                  = 0x99,
+};
+
+enum {
+    SPI_COMMS_CMD_SYNC                  = 0,
+    SPI_COMMS_CMD_SWITCH_SENSOR         = 1,
+    SPI_COMMS_CMD_ABSOLUTE_TIME         = 2,
+    SPI_COMMS_SENSOR_DATA_SCALAR        = 3,
+    SPI_COMMS_SENSOR_DATA_VEC3          = 4,
+    SPI_COMMS_SENSOR_DATA_VEC4          = 5,
+    SPI_COMMS_SENSOR_DATA_FLUSH         = 6,
+    SPI_COMMS_CMD_UPDATE_MAG_BIAS       = 7,
+    SPI_COMMS_CMD_UPDATE_MAG_ACCURACY   = 8,
+    SPI_COMMS_CMD_UPDATE_GYRO_BIAS      = 9,
+    SPI_COMMS_CMD_ACK_SUSPEND_STATE     = 10,
+    SPI_COMMS_DEBUG_OUTPUT              = 0xff,
+};
+
+// Please keep existing values unchanged when adding or removing SENSOR_TYPE
+enum {
+    SENSOR_TYPE_INTERNAL_TEMPERATURE    = SENSOR_TYPE_DEVICE_PRIVATE_BASE + 0,
+    SENSOR_TYPE_SYNC                    = SENSOR_TYPE_DEVICE_PRIVATE_BASE + 1,
+    SENSOR_TYPE_DOUBLE_TWIST            = SENSOR_TYPE_DEVICE_PRIVATE_BASE + 2,
+    SENSOR_TYPE_DOUBLE_TAP              = SENSOR_TYPE_DEVICE_PRIVATE_BASE + 3,
+};
+
+}  // namespace android
+
+#endif  // HUB_DEFS_H_
diff --git a/sensorhal/sensorlist.h b/sensorhal/sensorlist.h
new file mode 100644
index 0000000..b26110b
--- /dev/null
+++ b/sensorhal/sensorlist.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SENSORLIST_H_
+
+#define SENSORLIST_H_
+
+#include <hardware/sensors.h>
+
+// A list of sensors provided by a device.
+extern const sensor_t kSensorList[];
+extern const size_t kSensorCount;
+
+// TODO(aarossig): Consider de-duplicating Google sensors.
+
+#endif  // SENSORLIST_H_
diff --git a/sensorhal/sensors.cpp b/sensorhal/sensors.cpp
new file mode 100644
index 0000000..7d10869
--- /dev/null
+++ b/sensorhal/sensors.cpp
@@ -0,0 +1,244 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "sensors"
+// #defined LOG_NDEBUG  1
+#include <utils/Log.h>
+
+#include "hubconnection.h"
+#include "sensorlist.h"
+#include "sensors.h"
+
+#include <errno.h>
+#include <math.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <string.h>
+
+using namespace android;
+
+////////////////////////////////////////////////////////////////////////////////
+
+SensorContext::SensorContext(const struct hw_module_t *module)
+    : mHubConnection(HubConnection::getInstance()), mHubAlive(true) {
+    memset(&device, 0, sizeof(device));
+
+    device.common.tag = HARDWARE_DEVICE_TAG;
+    device.common.version = SENSORS_DEVICE_API_VERSION_1_3;
+    device.common.module = const_cast<hw_module_t *>(module);
+    device.common.close = CloseWrapper;
+    device.activate = ActivateWrapper;
+    device.setDelay = SetDelayWrapper;
+    device.poll = PollWrapper;
+    device.batch = BatchWrapper;
+    device.flush = FlushWrapper;
+
+    mHubAlive = (mHubConnection->initCheck() == OK
+        && mHubConnection->getAliveCheck() == OK);
+}
+
+int SensorContext::close() {
+    ALOGI("close");
+
+    delete this;
+
+    return 0;
+}
+
+int SensorContext::activate(int handle, int enabled) {
+    ALOGI("activate");
+
+    mHubConnection->queueActivate(handle, enabled);
+
+    return 0;
+}
+
+int SensorContext::setDelay(int handle, int64_t delayNs) {
+    ALOGI("setDelay");
+
+    // clamp sample rate based on minDelay and maxDelay defined in kSensorList
+    int64_t delayNsClamped = delayNs;
+    for (size_t i = 0; i < kSensorCount; i++) {
+        sensor_t sensor = kSensorList[i];
+        if (sensor.handle != handle) {
+            continue;
+        }
+
+        if ((sensor.flags & REPORTING_MODE_MASK) == SENSOR_FLAG_CONTINUOUS_MODE) {
+            if ((delayNs/1000) < sensor.minDelay) {
+                delayNsClamped = sensor.minDelay * 1000;
+            } else if ((delayNs/1000) > sensor.maxDelay) {
+                delayNsClamped = sensor.maxDelay * 1000;
+            }
+        }
+
+        break;
+    }
+
+    mHubConnection->queueSetDelay(handle, delayNsClamped);
+
+    return 0;
+}
+
+int SensorContext::poll(sensors_event_t *data, int count) {
+    ALOGV("poll");
+
+    ssize_t n = mHubConnection->read(data, count);
+
+    if (n < 0) {
+        return -1;
+    }
+
+    return n;
+}
+
+int SensorContext::batch(
+        int handle,
+        int flags,
+        int64_t sampling_period_ns,
+        int64_t max_report_latency_ns) {
+    ALOGI("batch");
+
+    // clamp sample rate based on minDelay and maxDelay defined in kSensorList
+    int64_t sampling_period_ns_clamped = sampling_period_ns;
+    for (size_t i = 0; i < kSensorCount; i++) {
+        sensor_t sensor = kSensorList[i];
+        if (sensor.handle != handle) {
+            continue;
+        }
+
+        if ((sensor.flags & REPORTING_MODE_MASK) == SENSOR_FLAG_CONTINUOUS_MODE) {
+            if ((sampling_period_ns/1000) < sensor.minDelay) {
+                sampling_period_ns_clamped = sensor.minDelay * 1000;
+            } else if ((sampling_period_ns/1000) > sensor.maxDelay) {
+                sampling_period_ns_clamped = sensor.maxDelay * 1000;
+            }
+        }
+
+        break;
+    }
+
+    mHubConnection->queueBatch(
+            handle, flags, sampling_period_ns_clamped, max_report_latency_ns);
+
+    return 0;
+}
+
+int SensorContext::flush(int handle) {
+    ALOGI("flush");
+
+    mHubConnection->queueFlush(handle);
+    return 0;
+}
+
+// static
+int SensorContext::CloseWrapper(struct hw_device_t *dev) {
+    return reinterpret_cast<SensorContext *>(dev)->close();
+}
+
+// static
+int SensorContext::ActivateWrapper(
+        struct sensors_poll_device_t *dev, int handle, int enabled) {
+    return reinterpret_cast<SensorContext *>(dev)->activate(handle, enabled);
+}
+
+// static
+int SensorContext::SetDelayWrapper(
+        struct sensors_poll_device_t *dev, int handle, int64_t delayNs) {
+    return reinterpret_cast<SensorContext *>(dev)->setDelay(handle, delayNs);
+}
+
+// static
+int SensorContext::PollWrapper(
+        struct sensors_poll_device_t *dev, sensors_event_t *data, int count) {
+    return reinterpret_cast<SensorContext *>(dev)->poll(data, count);
+}
+
+// static
+int SensorContext::BatchWrapper(
+        struct sensors_poll_device_1 *dev,
+        int handle,
+        int flags,
+        int64_t sampling_period_ns,
+        int64_t max_report_latency_ns) {
+    return reinterpret_cast<SensorContext *>(dev)->batch(
+            handle, flags, sampling_period_ns, max_report_latency_ns);
+}
+
+// static
+int SensorContext::FlushWrapper(struct sensors_poll_device_1 *dev, int handle) {
+    return reinterpret_cast<SensorContext *>(dev)->flush(handle);
+}
+
+bool SensorContext::getHubAlive() {
+    return mHubAlive;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+static bool gHubAlive;
+
+static int open_sensors(
+        const struct hw_module_t *module,
+        const char *,
+        struct hw_device_t **dev) {
+    ALOGI("open_sensors");
+
+    SensorContext *ctx = new SensorContext(module);
+
+    gHubAlive = ctx->getHubAlive();
+    *dev = &ctx->device.common;
+
+    return 0;
+}
+
+static struct hw_module_methods_t sensors_module_methods = {
+    .open = open_sensors
+};
+
+static int get_sensors_list(
+        struct sensors_module_t *,
+        struct sensor_t const **list) {
+    ALOGI("get_sensors_list");
+
+    if (gHubAlive) {
+        *list = kSensorList;
+        return kSensorCount;
+    } else {
+        *list = {};
+        return 0;
+    }
+}
+
+static int set_operation_mode(unsigned int mode) {
+    ALOGI("set_operation_mode");
+    return (mode) ? -EINVAL : 0;
+}
+
+struct sensors_module_t HAL_MODULE_INFO_SYM = {
+        .common = {
+                .tag = HARDWARE_MODULE_TAG,
+                .version_major = 1,
+                .version_minor = 0,
+                .id = SENSORS_HARDWARE_MODULE_ID,
+                .name = "Google Sensor module",
+                .author = "Google",
+                .methods = &sensors_module_methods,
+                .dso  = NULL,
+                .reserved = {0},
+        },
+        .get_sensors_list = get_sensors_list,
+        .set_operation_mode = set_operation_mode,
+};
diff --git a/sensorhal/sensors.h b/sensorhal/sensors.h
new file mode 100644
index 0000000..fd034fa
--- /dev/null
+++ b/sensorhal/sensors.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SENSORS_H_
+
+#define SENSORS_H_
+
+#include <hardware/hardware.h>
+#include <hardware/sensors.h>
+#include <media/stagefright/foundation/ABase.h>
+
+#include "hubconnection.h"
+
+struct SensorContext {
+    struct sensors_poll_device_1 device;
+
+    explicit SensorContext(const struct hw_module_t *module);
+
+    bool getHubAlive();
+
+private:
+    android::sp<android::HubConnection> mHubConnection;
+    bool mHubAlive;
+
+    int close();
+    int activate(int handle, int enabled);
+    int setDelay(int handle, int64_t delayNs);
+    int poll(sensors_event_t *data, int count);
+
+    int batch(
+            int handle,
+            int flags,
+            int64_t sampling_period_ns,
+            int64_t max_report_latency_ns);
+
+    int flush(int handle);
+
+    static int CloseWrapper(struct hw_device_t *dev);
+
+    static int ActivateWrapper(
+            struct sensors_poll_device_t *dev, int handle, int enabled);
+
+    static int SetDelayWrapper(
+            struct sensors_poll_device_t *dev, int handle, int64_t delayNs);
+
+    static int PollWrapper(
+            struct sensors_poll_device_t *dev, sensors_event_t *data, int count);
+
+    static int BatchWrapper(
+            struct sensors_poll_device_1 *dev,
+            int handle,
+            int flags,
+            int64_t sampling_period_ns,
+            int64_t max_report_latency_ns);
+
+    static int FlushWrapper(struct sensors_poll_device_1 *dev, int handle);
+
+    DISALLOW_EVIL_CONSTRUCTORS(SensorContext);
+};
+
+#endif  // SENSORS_H_
diff --git a/util/common/JSONObject.cpp b/util/common/JSONObject.cpp
new file mode 100644
index 0000000..83ed14e
--- /dev/null
+++ b/util/common/JSONObject.cpp
@@ -0,0 +1,754 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "JSONObject"
+#include <utils/Log.h>
+
+#include "JSONObject.h"
+
+#include <ctype.h>
+#include <math.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AString.h>
+#include <media/stagefright/MediaErrors.h>
+
+namespace android {
+
+// Returns ERROR_MALFORMED if the value overflows a signed int, returns
+//     0 otherwise.
+// This method will assert if it is asked to parse a character which is not
+//     a digit.
+static ssize_t parseInt32(const char *data, size_t numDigits, int32_t *out) {
+    int32_t x = 0;
+    for (size_t i = 0; i < numDigits; ++i) {
+        int32_t old_x = x;
+        x *= 10;
+        x += data[i] - '0';
+
+        CHECK(isdigit(data[i]));
+
+        if (x < old_x) {
+            // We've overflowed.
+            return ERROR_MALFORMED;
+        }
+    }
+
+    *out = x;
+    return 0;
+}
+
+// static
+ssize_t JSONValue::Parse(const char *data, size_t size, JSONValue *out) {
+    size_t offset = 0;
+    while (offset < size && isspace(data[offset])) {
+        ++offset;
+    }
+
+    if (offset == size) {
+        return ERROR_MALFORMED;
+    }
+
+    if (data[offset] == '[') {
+        sp<JSONArray> array = new JSONArray;
+        ++offset;
+
+        for (;;) {
+            while (offset < size && isspace(data[offset])) {
+                ++offset;
+            }
+
+            if (offset == size) {
+                return ERROR_MALFORMED;
+            }
+
+            if (data[offset] == ']') {
+                ++offset;
+                break;
+            }
+
+            JSONValue val;
+            ssize_t n = Parse(&data[offset], size - offset, &val);
+
+            if (n < 0) {
+                return n;
+            }
+
+            array->addValue(val);
+
+            offset += n;
+
+            while (offset < size && isspace(data[offset])) {
+                ++offset;
+            }
+
+            if (offset == size) {
+                return ERROR_MALFORMED;
+            }
+
+            if (data[offset] == ',') {
+                ++offset;
+            } else if (data[offset] != ']') {
+                return ERROR_MALFORMED;
+            }
+        };
+
+        out->setArray(array);
+
+        return offset;
+    } else if (data[offset] == '{') {
+        sp<JSONObject> obj = new JSONObject;
+        ++offset;
+
+        for (;;) {
+            while (offset < size && isspace(data[offset])) {
+                ++offset;
+            }
+
+            if (offset == size) {
+                return ERROR_MALFORMED;
+            }
+
+            if (data[offset] == '}') {
+                ++offset;
+                break;
+            }
+
+            JSONValue key;
+            ssize_t n = Parse(&data[offset], size - offset, &key);
+
+            if (n < 0) {
+                return n;
+            }
+
+            if (key.type() != TYPE_STRING) {
+                return ERROR_MALFORMED;
+            }
+
+            offset += n;
+
+            while (offset < size && isspace(data[offset])) {
+                ++offset;
+            }
+
+            if (offset == size || data[offset] != ':') {
+                return ERROR_MALFORMED;
+            }
+
+            ++offset;
+
+            JSONValue val;
+            n = Parse(&data[offset], size - offset, &val);
+
+            if (n < 0) {
+                return n;
+            }
+
+            AString keyVal;
+            CHECK(key.getString(&keyVal));
+
+            obj->setValue(keyVal.c_str(), val);
+
+            offset += n;
+
+            while (offset < size && isspace(data[offset])) {
+                ++offset;
+            }
+
+            if (offset == size) {
+                return ERROR_MALFORMED;
+            }
+
+            if (data[offset] == ',') {
+                ++offset;
+            } else if (data[offset] != '}') {
+                return ERROR_MALFORMED;
+            }
+        };
+
+        out->setObject(obj);
+
+        return offset;
+    } else if (data[offset] == '"') {
+        ++offset;
+
+        AString s;
+        bool escaped = false;
+        while (offset < size) {
+            if (escaped) {
+                char c;
+                switch (data[offset]) {
+                    case '\"':
+                    case '\\':
+                    case '/':
+                        c = data[offset];
+                        break;
+                    case 'b':
+                        c = '\x08';
+                        break;
+                    case 'f':
+                        c = '\x0c';
+                        break;
+                    case 'n':
+                        c = '\x0a';
+                        break;
+                    case 'r':
+                        c = '\x0d';
+                        break;
+                    case 't':
+                        c = '\x09';
+                        break;
+                    default:
+                        return ERROR_MALFORMED;
+                }
+
+                s.append(c);
+                ++offset;
+
+                escaped = false;
+            } else if (data[offset] == '\\') {
+                escaped = true;
+            } else if (data[offset] == '"') {
+                break;
+            }
+
+            s.append(data[offset++]);
+        }
+
+        if (offset == size) {
+            return ERROR_MALFORMED;
+        }
+
+        ++offset;
+        out->setString(s);
+
+        return offset;
+    } else if (isdigit(data[offset]) || data[offset] == '-') {
+        bool negate = false;
+        if (data[offset] == '-') {
+            negate = true;
+            ++offset;
+
+            if (offset == size) {
+                return ERROR_MALFORMED;
+            }
+        }
+
+        size_t firstDigitOffset = offset;
+        while (offset < size && isdigit(data[offset])) {
+            ++offset;
+        }
+
+        size_t numDigits = offset - firstDigitOffset;
+        if (numDigits > 1 && data[firstDigitOffset] == '0') {
+            // No leading zeros.
+            return ERROR_MALFORMED;
+        }
+
+        size_t firstFracDigitOffset = 0;
+        size_t numFracDigits = 0;
+
+        if (offset < size && data[offset] == '.') {
+            ++offset;
+
+            firstFracDigitOffset = offset;
+            while (offset < size && isdigit(data[offset])) {
+                ++offset;
+            }
+
+            numFracDigits = offset - firstFracDigitOffset;
+            if (numFracDigits == 0) {
+                return ERROR_MALFORMED;
+            }
+        }
+
+        bool negateExponent = false;
+        size_t firstExpDigitOffset = 0;
+        size_t numExpDigits = 0;
+
+        if (offset < size && (data[offset] == 'e' || data[offset] == 'E')) {
+            ++offset;
+
+            if (offset == size) {
+                return ERROR_MALFORMED;
+            }
+
+            if (data[offset] == '+' || data[offset] == '-') {
+                if (data[offset] == '-') {
+                    negateExponent = true;
+                }
+
+                ++offset;
+            }
+
+            firstExpDigitOffset = offset;
+            while (offset < size && isdigit(data[offset])) {
+                ++offset;
+            }
+
+            numExpDigits = offset - firstExpDigitOffset;
+            if (numExpDigits == 0) {
+                return ERROR_MALFORMED;
+            }
+        }
+
+        if (numFracDigits == 0 && numExpDigits == 0) {
+            int32_t x;
+            if (parseInt32(&data[firstDigitOffset], numDigits, &x) != 0) {
+                return ERROR_MALFORMED;
+            }
+
+            out->setInt32(negate ? -x : x);
+        } else {
+            int32_t mantissa;
+            if (parseInt32(&data[firstDigitOffset], numDigits, &mantissa) != 0) {
+                return ERROR_MALFORMED;
+            }
+
+            int32_t fraction;
+            if (parseInt32(&data[firstFracDigitOffset], numFracDigits, &fraction) != 0) {
+                return ERROR_MALFORMED;
+            }
+
+            int32_t exponent;
+            if (parseInt32(&data[firstExpDigitOffset], numExpDigits, &exponent) != 0) {
+                return ERROR_MALFORMED;
+            }
+
+            if (negateExponent) {
+                exponent = -exponent;
+            }
+
+            float x = (float)mantissa;
+            x += (float)fraction * powf(10.0f, exponent - (int32_t)numFracDigits);
+
+            out->setFloat(negate ? -x : x);
+        }
+
+        return offset;
+    } else if (offset + 4 <= size && !strncmp("null", &data[offset], 4)) {
+        out->unset();
+        return offset + 4;
+    } else if (offset + 4 <= size && !strncmp("true", &data[offset], 4)) {
+        out->setBoolean(true);
+        return offset + 4;
+    } else if (offset + 5 <= size && !strncmp("false", &data[offset], 5)) {
+        out->setBoolean(false);
+        return offset + 5;
+    }
+
+    return ERROR_MALFORMED;
+}
+
+JSONValue::JSONValue()
+    : mType(TYPE_NULL) {
+}
+
+JSONValue::JSONValue(const JSONValue &other)
+    : mType(TYPE_NULL) {
+    *this = other;
+}
+
+JSONValue &JSONValue::operator=(const JSONValue &other) {
+    if (&other != this) {
+        unset();
+        mType = other.mType;
+        mValue = other.mValue;
+
+        switch (mType) {
+            case TYPE_STRING:
+                mValue.mString = new AString(*other.mValue.mString);
+                break;
+            case TYPE_OBJECT:
+            case TYPE_ARRAY:
+                mValue.mObjectOrArray->incStrong(this /* id */);
+                break;
+
+            default:
+                break;
+        }
+    }
+
+    return *this;
+}
+
+JSONValue::~JSONValue() {
+    unset();
+}
+
+JSONValue::FieldType JSONValue::type() const {
+    return mType;
+}
+
+bool JSONValue::getInt32(int32_t *value) const {
+    if (mType != TYPE_INT32) {
+        return false;
+    }
+
+    *value = mValue.mInt32;
+    return true;
+}
+
+bool JSONValue::getFloat(float *value) const {
+    switch (mType) {
+        case TYPE_INT32:
+        {
+            *value = mValue.mInt32;
+            break;
+        }
+
+        case TYPE_FLOAT:
+        {
+            *value = mValue.mFloat;
+            break;
+        }
+
+        default:
+            return false;
+    }
+
+    return true;
+}
+
+bool JSONValue::getString(AString *value) const {
+    if (mType != TYPE_STRING) {
+        return false;
+    }
+
+    *value = *mValue.mString;
+    return true;
+}
+
+bool JSONValue::getBoolean(bool *value) const {
+    if (mType != TYPE_BOOLEAN) {
+        return false;
+    }
+
+    *value = mValue.mBoolean;
+    return true;
+}
+
+bool JSONValue::getObject(sp<JSONObject> *value) const {
+    if (mType != TYPE_OBJECT) {
+        return false;
+    }
+
+    *value = static_cast<JSONObject *>(mValue.mObjectOrArray);
+    return true;
+}
+
+bool JSONValue::getArray(sp<JSONArray> *value) const {
+    if (mType != TYPE_ARRAY) {
+        return false;
+    }
+
+    *value = static_cast<JSONArray *>(mValue.mObjectOrArray);
+    return true;
+}
+
+void JSONValue::setInt32(int32_t value) {
+    unset();
+
+    mValue.mInt32 = value;
+    mType = TYPE_INT32;
+}
+
+void JSONValue::setFloat(float value) {
+    unset();
+
+    mValue.mFloat = value;
+    mType = TYPE_FLOAT;
+}
+
+void JSONValue::setString(const AString &value) {
+    unset();
+
+    mValue.mString = new AString(value);
+    mType = TYPE_STRING;
+}
+
+void JSONValue::setBoolean(bool value) {
+    unset();
+
+    mValue.mBoolean = value;
+    mType = TYPE_BOOLEAN;
+}
+
+void JSONValue::setObject(const sp<JSONObject> &obj) {
+    unset();
+
+    mValue.mObjectOrArray = obj.get();
+    mValue.mObjectOrArray->incStrong(this /* id */);
+
+    mType = TYPE_OBJECT;
+}
+
+void JSONValue::setArray(const sp<JSONArray> &array) {
+    unset();
+
+    mValue.mObjectOrArray = array.get();
+    mValue.mObjectOrArray->incStrong(this /* id */);
+
+    mType = TYPE_ARRAY;
+}
+
+void JSONValue::unset() {
+    switch (mType) {
+        case TYPE_STRING:
+            delete mValue.mString;
+            break;
+        case TYPE_OBJECT:
+        case TYPE_ARRAY:
+            mValue.mObjectOrArray->decStrong(this /* id */);
+            break;
+
+        default:
+            break;
+    }
+
+    mType = TYPE_NULL;
+}
+
+static void EscapeString(const char *in, size_t inSize, AString *out) {
+    CHECK(in != out->c_str());
+    out->clear();
+
+    for (size_t i = 0; i < inSize; ++i) {
+        char c = in[i];
+        switch (c) {
+            case '\"':
+                out->append("\\\"");
+                break;
+            case '\\':
+                out->append("\\\\");
+                break;
+            case '/':
+                out->append("\\/");
+                break;
+            case '\x08':
+                out->append("\\b");
+                break;
+            case '\x0c':
+                out->append("\\f");
+                break;
+            case '\x0a':
+                out->append("\\n");
+                break;
+            case '\x0d':
+                out->append("\\r");
+                break;
+            case '\x09':
+                out->append("\\t");
+                break;
+            default:
+                out->append(c);
+                break;
+        }
+    }
+}
+
+AString JSONValue::toString(size_t depth, bool indentFirstLine) const {
+    static const char kIndent[] = "                                        ";
+
+    AString out;
+
+    switch (mType) {
+        case TYPE_STRING:
+        {
+            AString escaped;
+            EscapeString(
+                    mValue.mString->c_str(), mValue.mString->size(), &escaped);
+
+            out.append("\"");
+            out.append(escaped);
+            out.append("\"");
+            break;
+        }
+
+        case TYPE_INT32:
+        {
+            out = AStringPrintf("%d", mValue.mInt32);
+            break;
+        }
+
+        case TYPE_FLOAT:
+        {
+            out = AStringPrintf("%f", mValue.mFloat);
+            break;
+        }
+
+        case TYPE_BOOLEAN:
+        {
+            out = mValue.mBoolean ? "true" : "false";
+            break;
+        }
+
+        case TYPE_NULL:
+        {
+            out = "null";
+            break;
+        }
+
+        case TYPE_OBJECT:
+        case TYPE_ARRAY:
+        {
+            out = (mType == TYPE_OBJECT) ? "{\n" : "[\n";
+            out.append(mValue.mObjectOrArray->internalToString(depth + 1));
+            out.append("\n");
+            out.append(kIndent, 2 * depth);
+            out.append(mType == TYPE_OBJECT ? "}" : "]");
+            break;
+        }
+
+        default:
+            TRESPASS();
+    }
+
+    if (indentFirstLine) {
+        out.insert(kIndent, 2 * depth, 0);
+    }
+
+    return out;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+// static
+sp<JSONCompound> JSONCompound::Parse(const char *data, size_t size) {
+    JSONValue value;
+    ssize_t result = JSONValue::Parse(data, size, &value);
+
+    if (result < 0) {
+        return NULL;
+    }
+
+    sp<JSONObject> obj;
+    if (value.getObject(&obj)) {
+        return obj;
+    }
+
+    sp<JSONArray> array;
+    if (value.getArray(&array)) {
+        return array;
+    }
+
+    return NULL;
+}
+
+AString JSONCompound::toString(size_t depth, bool indentFirstLine) const {
+    JSONValue val;
+    if (isObject()) {
+        val.setObject((JSONObject *)this);
+    } else {
+        val.setArray((JSONArray *)this);
+    }
+
+    return val.toString(depth, indentFirstLine);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+JSONObject::JSONObject() {}
+JSONObject::~JSONObject() {}
+
+bool JSONObject::isObject() const {
+    return true;
+}
+
+bool JSONObject::getValue(const char *key, JSONValue *value) const {
+    ssize_t index = mValues.indexOfKey(key);
+    if (index < 0) {
+        return false;
+    }
+
+    *value = mValues.valueAt(index);
+
+    return true;
+}
+
+void JSONObject::setValue(const char *key, const JSONValue &value) {
+    mValues.add(AString(key), value);
+}
+
+AString JSONObject::internalToString(size_t depth) const {
+    static const char kIndent[] = "                                        ";
+
+    AString out;
+    for (size_t i = 0; i < mValues.size(); ++i) {
+        AString key = mValues.keyAt(i);
+        AString escapedKey;
+        EscapeString(key.c_str(), key.size(), &escapedKey);
+
+        out.append(kIndent, 2 * depth);
+        out.append("\"");
+        out.append(escapedKey);
+        out.append("\": ");
+
+        out.append(mValues.valueAt(i).toString(depth + 1, false));
+
+        if (i + 1 < mValues.size()) {
+            out.append(",\n");
+        }
+    }
+
+    return out;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+JSONArray::JSONArray() {}
+
+JSONArray::~JSONArray() {}
+
+bool JSONArray::isObject() const {
+    return false;
+}
+
+size_t JSONArray::size() const {
+    return mValues.size();
+}
+
+bool JSONArray::getValue(size_t key, JSONValue *value) const {
+    if (key >= mValues.size()) {
+        return false;
+    }
+
+    *value = mValues.itemAt(key);
+
+    return true;
+}
+
+void JSONArray::addValue(const JSONValue &value) {
+    mValues.push_back(value);
+}
+
+AString JSONArray::internalToString(size_t depth) const {
+    AString out;
+    for (size_t i = 0; i < mValues.size(); ++i) {
+        out.append(mValues.itemAt(i).toString(depth));
+
+        if (i + 1 < mValues.size()) {
+            out.append(",\n");
+        }
+    }
+
+    return out;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+}  // namespace android
+
diff --git a/util/common/JSONObject.h b/util/common/JSONObject.h
new file mode 100644
index 0000000..eafc565
--- /dev/null
+++ b/util/common/JSONObject.h
@@ -0,0 +1,272 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef JSON_OBJECT_H_
+
+#define JSON_OBJECT_H_
+
+#include <media/stagefright/foundation/ABase.h>
+#include <media/stagefright/foundation/AString.h>
+#include <utils/Errors.h>
+#include <utils/KeyedVector.h>
+#include <utils/RefBase.h>
+#include <utils/Vector.h>
+
+namespace android {
+
+struct JSONArray;
+struct JSONCompound;
+struct JSONObject;
+
+struct JSONValue {
+    enum FieldType {
+        TYPE_STRING,
+        TYPE_INT32,
+        TYPE_FLOAT,
+        TYPE_BOOLEAN,
+        TYPE_NULL,
+        TYPE_OBJECT,
+        TYPE_ARRAY,
+    };
+
+    // Returns the number of bytes consumed or an error.
+    static ssize_t Parse(const char *data, size_t size, JSONValue *out);
+
+    JSONValue();
+    JSONValue(const JSONValue &);
+    JSONValue &operator=(const JSONValue &);
+    ~JSONValue();
+
+    FieldType type() const;
+    bool getInt32(int32_t *value) const;
+    bool getFloat(float *value) const;
+    bool getString(AString *value) const;
+    bool getBoolean(bool *value) const;
+    bool getObject(sp<JSONObject> *value) const;
+    bool getArray(sp<JSONArray> *value) const;
+
+    void setInt32(int32_t value);
+    void setFloat(float value);
+    void setString(const AString &value);
+    void setBoolean(bool value);
+    void setObject(const sp<JSONObject> &obj);
+    void setArray(const sp<JSONArray> &array);
+    void unset();  // i.e. setNull()
+
+    AString toString(size_t depth = 0, bool indentFirstLine = true) const;
+
+private:
+    FieldType mType;
+
+    union {
+        int32_t mInt32;
+        float mFloat;
+        AString *mString;
+        bool mBoolean;
+        JSONCompound *mObjectOrArray;
+    } mValue;
+};
+
+struct JSONCompound : public RefBase {
+    static sp<JSONCompound> Parse(const char *data, size_t size);
+
+    AString toString(size_t depth = 0, bool indentFirstLine = true) const;
+
+    virtual bool isObject() const = 0;
+
+protected:
+    virtual ~JSONCompound() {}
+
+    virtual AString internalToString(size_t depth) const = 0;
+
+    JSONCompound() {}
+
+private:
+    friend struct JSONValue;
+
+    DISALLOW_EVIL_CONSTRUCTORS(JSONCompound);
+};
+
+template<class KEY>
+struct JSONBase : public JSONCompound {
+    JSONBase() {}
+
+#define PREAMBLE()                              \
+    JSONValue value;                            \
+    if (!getValue(key, &value)) {               \
+        return false;                           \
+    }
+
+    bool getFieldType(KEY key, JSONValue::FieldType *type) const {
+        PREAMBLE()
+        *type = value.type();
+        return true;
+    }
+
+    bool getInt32(KEY key, int32_t *out) const {
+        PREAMBLE()
+        return value.getInt32(out);
+    }
+
+    bool getFloat(KEY key, float *out) const {
+        PREAMBLE()
+        return value.getFloat(out);
+    }
+
+    bool getString(KEY key, AString *out) const {
+        PREAMBLE()
+        return value.getString(out);
+    }
+
+    bool getBoolean(KEY key, bool *out) const {
+        PREAMBLE()
+        return value.getBoolean(out);
+    }
+
+    bool getObject(KEY key, sp<JSONObject> *obj) const {
+        PREAMBLE()
+        return value.getObject(obj);
+    }
+
+    bool getArray(KEY key, sp<JSONArray> *obj) const {
+        PREAMBLE()
+        return value.getArray(obj);
+    }
+
+#undef PREAMBLE
+
+protected:
+    virtual ~JSONBase() {}
+
+    virtual bool getValue(KEY key, JSONValue *value) const = 0;
+
+private:
+    DISALLOW_EVIL_CONSTRUCTORS(JSONBase);
+};
+
+struct JSONObject : public JSONBase<const char *> {
+    JSONObject();
+
+    virtual bool isObject() const;
+    void setValue(const char *key, const JSONValue &value);
+
+    void setInt32(const char *key, int32_t in) {
+        JSONValue val;
+        val.setInt32(in);
+        setValue(key, val);
+    }
+
+    void setFloat(const char *key, float in) {
+        JSONValue val;
+        val.setFloat(in);
+        setValue(key, val);
+    }
+
+    void setString(const char *key, AString in) {
+        JSONValue val;
+        val.setString(in);
+        setValue(key, val);
+    }
+
+    void setBoolean(const char *key, bool in) {
+        JSONValue val;
+        val.setBoolean(in);
+        setValue(key, val);
+    }
+
+    void setObject(const char *key, const sp<JSONObject> &obj) {
+        JSONValue val;
+        val.setObject(obj);
+        setValue(key, val);
+    }
+
+    void setArray(const char *key, const sp<JSONArray> &obj) {
+        JSONValue val;
+        val.setArray(obj);
+        setValue(key, val);
+    }
+
+protected:
+    virtual ~JSONObject();
+
+    virtual bool getValue(const char *key, JSONValue *value) const;
+    virtual AString internalToString(size_t depth) const;
+
+private:
+    KeyedVector<AString, JSONValue> mValues;
+
+    DISALLOW_EVIL_CONSTRUCTORS(JSONObject);
+};
+
+struct JSONArray : public JSONBase<size_t> {
+    JSONArray();
+
+    virtual bool isObject() const;
+    size_t size() const;
+    void addValue(const JSONValue &value);
+
+    void addInt32(int32_t in) {
+        JSONValue val;
+        val.setInt32(in);
+        addValue(val);
+    }
+
+    void addFloat(float in) {
+        JSONValue val;
+        val.setFloat(in);
+        addValue(val);
+    }
+
+    void addString(AString in) {
+        JSONValue val;
+        val.setString(in);
+        addValue(val);
+    }
+
+    void addBoolean(bool in) {
+        JSONValue val;
+        val.setBoolean(in);
+        addValue(val);
+    }
+
+    void addObject(const sp<JSONObject> &obj) {
+        JSONValue val;
+        val.setObject(obj);
+        addValue(val);
+    }
+
+    void addArray(const sp<JSONArray> &obj) {
+        JSONValue val;
+        val.setArray(obj);
+        addValue(val);
+    }
+
+protected:
+    virtual ~JSONArray();
+
+    virtual bool getValue(size_t key, JSONValue *value) const;
+    virtual AString internalToString(size_t depth) const;
+
+
+private:
+    Vector<JSONValue> mValues;
+
+    DISALLOW_EVIL_CONSTRUCTORS(JSONArray);
+};
+
+}  // namespace android
+
+#endif  // JSON_OBJECT_H_
diff --git a/util/common/file.cpp b/util/common/file.cpp
new file mode 100644
index 0000000..1757601
--- /dev/null
+++ b/util/common/file.cpp
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG    0
+#define LOG_TAG "file"
+#include <utils/Log.h>
+#include "file.h"
+
+#include <fcntl.h>
+#include <string.h>
+#include <unistd.h>
+
+namespace android {
+
+File::File()
+    : mInitCheck(NO_INIT),
+      mFd(-1) {
+}
+
+File::File(const char *path, const char *mode)
+    : mInitCheck(NO_INIT),
+      mFd(-1) {
+    mInitCheck = setTo(path, mode);
+}
+
+File::~File() {
+    close();
+}
+
+status_t File::initCheck() const {
+    return mInitCheck;
+}
+
+status_t File::setTo(const char *path, const char *mode) {
+    close();
+
+    int modeval = 0;
+    if (!strcmp("r", mode)) {
+        modeval = O_RDONLY;
+    } else if (!strcmp("w", mode)) {
+        modeval = O_WRONLY | O_CREAT | O_TRUNC;
+    } else if (!strcmp("rw", mode)) {
+        modeval = O_RDWR | O_CREAT;
+    }
+
+    int filemode = 0;
+    if (modeval & O_CREAT) {
+        filemode = S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH;
+    }
+
+    mFd = open(path, modeval, filemode);
+
+    mInitCheck = (mFd >= 0) ? OK : -errno;
+
+    return mInitCheck;
+}
+
+void File::close() {
+    if (mFd >= 0) {
+        ::close(mFd);
+        mFd = -1;
+    }
+
+    mInitCheck = NO_INIT;
+}
+
+ssize_t File::read(void *data, size_t size) {
+    return ::read(mFd, data, size);
+}
+
+ssize_t File::write(const void *data, size_t size) {
+    return ::write(mFd, data, size);
+}
+
+off64_t File::seekTo(off64_t pos, int whence) {
+    off64_t new_pos = lseek64(mFd, pos, whence);
+    return new_pos < 0 ? -errno : new_pos;
+}
+
+}  // namespace android
diff --git a/util/common/file.h b/util/common/file.h
new file mode 100644
index 0000000..97d74b0
--- /dev/null
+++ b/util/common/file.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FILE_H_
+
+#define FILE_H_
+
+#include <media/stagefright/foundation/ABase.h>
+#include <utils/Errors.h>
+
+namespace android {
+
+struct File {
+    File();
+    File(const char *path, const char *mode);
+
+    ~File();
+
+    status_t initCheck() const;
+    status_t setTo(const char *path, const char *mode);
+    void close();
+
+    ssize_t read(void *data, size_t size);
+    ssize_t write(const void *data, size_t size);
+
+    off64_t seekTo(off64_t pos, int whence = SEEK_SET);
+
+private:
+    status_t mInitCheck;
+    int mFd;
+
+    DISALLOW_EVIL_CONSTRUCTORS(File);
+};
+
+}  // namespace android
+
+#endif  // FILE_H_
diff --git a/util/common/ring.cpp b/util/common/ring.cpp
new file mode 100644
index 0000000..1d23b82
--- /dev/null
+++ b/util/common/ring.cpp
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_DEBUG     1
+#define LOG_TAG "ring"
+#include <utils/Log.h>
+
+#include "ring.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+namespace android {
+
+RingBuffer::RingBuffer(size_t size)
+    : mSize(size),
+      mData((sensors_event_t *)malloc(sizeof(sensors_event_t) * mSize)),
+      mReadPos(0),
+      mWritePos(0) {
+}
+
+RingBuffer::~RingBuffer() {
+    free(mData);
+    mData = NULL;
+}
+
+ssize_t RingBuffer::write(const sensors_event_t *ev, size_t size) {
+    Mutex::Autolock autoLock(mLock);
+
+    size_t numAvailableToRead = mWritePos - mReadPos;
+    size_t numAvailableToWrite = mSize - numAvailableToRead;
+
+    if (size > numAvailableToWrite) {
+        size = numAvailableToWrite;
+    }
+
+    size_t writePos = (mWritePos % mSize);
+    size_t copy = mSize - writePos;
+
+    if (copy > size) {
+        copy = size;
+    }
+
+    memcpy(&mData[writePos], ev, copy * sizeof(sensors_event_t));
+
+    if (size > copy) {
+        memcpy(mData, &ev[copy], (size - copy) * sizeof(sensors_event_t));
+    }
+
+    mWritePos += size;
+
+    if (numAvailableToRead == 0 && size > 0) {
+        mNotEmptyCondition.broadcast();
+    }
+
+    return size;
+}
+
+ssize_t RingBuffer::read(sensors_event_t *ev, size_t size) {
+    Mutex::Autolock autoLock(mLock);
+
+    size_t numAvailableToRead;
+    for (;;) {
+        numAvailableToRead = mWritePos - mReadPos;
+        if (numAvailableToRead > 0) {
+            break;
+        }
+
+        mNotEmptyCondition.wait(mLock);
+    }
+
+    if (size > numAvailableToRead) {
+        size = numAvailableToRead;
+    }
+
+    size_t readPos = (mReadPos % mSize);
+    size_t copy = mSize - readPos;
+
+    if (copy > size) {
+        copy = size;
+    }
+
+    memcpy(ev, &mData[readPos], copy * sizeof(sensors_event_t));
+
+    if (size > copy) {
+        memcpy(&ev[copy], mData, (size - copy) * sizeof(sensors_event_t));
+    }
+
+    mReadPos += size;
+
+    return size;
+}
+
+}  // namespace android
+
diff --git a/util/common/ring.h b/util/common/ring.h
new file mode 100644
index 0000000..c77c3de
--- /dev/null
+++ b/util/common/ring.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef RING_BUFFER_H_
+
+#define RING_BUFFER_H_
+
+#include <media/stagefright/foundation/ABase.h>
+
+#include <hardware/sensors.h>
+#include <utils/threads.h>
+
+namespace android {
+
+struct RingBuffer {
+    explicit RingBuffer(size_t size);
+    ~RingBuffer();
+
+    ssize_t write(const sensors_event_t *ev, size_t size);
+    ssize_t read(sensors_event_t *ev, size_t size);
+
+private:
+    Mutex mLock;
+    Condition mNotEmptyCondition;
+
+    size_t mSize;
+    sensors_event_t *mData;
+    size_t mReadPos, mWritePos;
+
+    DISALLOW_EVIL_CONSTRUCTORS(RingBuffer);
+};
+
+}  // namespace android
+
+#endif  // RING_BUFFER_H_
diff --git a/util/common/util.h b/util/common/util.h
new file mode 100644
index 0000000..a1c233c
--- /dev/null
+++ b/util/common/util.h
@@ -0,0 +1,12 @@
+#ifndef COMMON_UTIL_H_
+#define COMMON_UTIL_H_
+
+#include <chrono>
+
+namespace android {
+
+typedef std::chrono::time_point<std::chrono::steady_clock> SteadyClock;
+
+}  // namespace android
+
+#endif  // COMMON_UTIL_H_
diff --git a/util/nanoapp_cmd/Android.mk b/util/nanoapp_cmd/Android.mk
index 00cac60..b9eb493 100644
--- a/util/nanoapp_cmd/Android.mk
+++ b/util/nanoapp_cmd/Android.mk
@@ -26,7 +26,6 @@
 
 LOCAL_MODULE_TAGS:= optional
 LOCAL_MODULE_OWNER := google
-LOCAL_PROPRIETARY_MODULE := true
 
 LOCAL_LDLIBS := \
 	-L$(SYSROOT)/usr/lib -llog
diff --git a/util/nanoapp_cmd/nanoapp_cmd.c b/util/nanoapp_cmd/nanoapp_cmd.c
index 1cdf9ae..0d36bb8 100644
--- a/util/nanoapp_cmd/nanoapp_cmd.c
+++ b/util/nanoapp_cmd/nanoapp_cmd.c
@@ -29,11 +29,14 @@
 #include <sensType.h>
 #include <signal.h>
 #include <inttypes.h>
+#include <errno.h>
 
+#define LOG_TAG "nanoapp_cmd"
 #define SENSOR_RATE_ONCHANGE    0xFFFFFF01UL
 #define SENSOR_RATE_ONESHOT     0xFFFFFF02UL
 #define SENSOR_HZ(_hz)          ((uint32_t)((_hz) * 1024.0f))
 #define MAX_INSTALL_CNT         8
+#define MAX_DOWNLOAD_RETRIES    3
 
 enum ConfigCmds
 {
@@ -150,7 +153,6 @@
 struct AppInfo apps[32];
 uint8_t appCount = 0;
 char appsToInstall[MAX_INSTALL_CNT][32];
-uint8_t installCnt = 0;
 
 void sig_handle(__attribute__((unused)) int sig)
 {
@@ -159,6 +161,16 @@
     stop = true;
 }
 
+FILE *openFile(const char *fname, const char *mode)
+{
+    FILE *f = fopen(fname, mode);
+    if (f == NULL) {
+        __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "Failed to open %s: err=%d [%s]", fname, errno, strerror(errno));
+        printf("\nFailed to open %s: err=%d [%s]\n", fname, errno, strerror(errno));
+    }
+    return f;
+}
+
 void parseInstalledAppInfo()
 {
     FILE *fp;
@@ -166,12 +178,9 @@
     size_t len;
     ssize_t numRead;
 
-    fp = fopen("/sys/class/nanohub/nanohub/app_info", "r");
-    if (fp == NULL) {
-        __android_log_write(ANDROID_LOG_ERROR, "nanoapp_cmd", "Failed to open app_info!");
-        printf("Failed to open app_info!\n");
+    fp = openFile("/sys/class/nanohub/nanohub/app_info", "r");
+    if (!fp)
         return;
-    }
 
     while ((numRead = getline(&line, &len, fp)) != -1) {
         struct AppInfo *currApp = &apps[appCount++];
@@ -197,19 +206,17 @@
     return NULL;
 }
 
-void parseConfigAppInfo()
+int parseConfigAppInfo()
 {
     FILE *fp;
     char *line = NULL;
     size_t len;
     ssize_t numRead;
+    int installCnt;
 
-    fp = fopen("/vendor/firmware/napp_list.cfg", "r");
-    if (fp == NULL) {
-        __android_log_write(ANDROID_LOG_WARN, "nanoapp_cmd", "Could not find napp_list.cfg!");
-        printf("Could not open napp_list.cfg!\n");
-        return;
-    }
+    fp = openFile("/vendor/firmware/napp_list.cfg", "r");
+    if (!fp)
+        return -1;
 
     parseInstalledAppInfo();
 
@@ -231,76 +238,70 @@
 
     if (line)
         free(line);
+
+    return installCnt;
+}
+
+bool fileWriteData(const char *fname, const void *data, size_t size)
+{
+    int fd;
+    bool result;
+
+    fd = open(fname, O_WRONLY);
+    if (fd < 0) {
+        __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "Failed to open %s: err=%d [%s]", fname, errno, strerror(errno));
+        printf("\nFailed to open %s: err=%d [%s]\n", fname, errno, strerror(errno));
+        return false;
+    }
+
+    result = true;
+    if ((size_t)write(fd, data, size) != size) {
+        __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "Failed to write to %s; err=%d [%s]", fname, errno, strerror(errno));
+        printf("\nFailed to write %s; err=%d [%s]\n", fname, errno, strerror(errno));
+        result = false;
+    }
+    close(fd);
+
+    return result;
 }
 
 void downloadNanohub()
 {
-    int fd;
     char c = '1';
 
-    fd = open("/sys/class/nanohub/nanohub/download_bl", O_WRONLY);
-    if (fd < 0) {
-        __android_log_write(ANDROID_LOG_ERROR, "nanoapp_cmd", "Failed to open download_bl!");
-        printf("Failed to open download_bl!\n");
-        return;
-    }
-
-    if (write(fd, &c, 1) <= 0) {
-        __android_log_write(ANDROID_LOG_ERROR, "nanoapp_cmd", "Failed to write download_bl!");
-        printf("Failed to write download_bl!\n");
-    }
-    close(fd);
+    printf("Updating nanohub OS [if required]...");
+    fflush(stdout);
+    if (fileWriteData("/sys/class/nanohub/nanohub/download_bl", &c, sizeof(c)))
+        printf("done\n");
 }
 
-void downloadApps()
+void downloadApps(int updateCnt)
 {
-    int fd;
-    ssize_t ret;
-    uint8_t i;
+    int i;
 
-    fd = open("/sys/class/nanohub/nanohub/download_app", O_WRONLY);
-    if (fd < 0) {
-        __android_log_write(ANDROID_LOG_ERROR, "nanoapp_cmd", "Failed to open download_app!");
-        printf("Failed to open download_app!\n");
-        return;
+    for (i = 0; i < updateCnt; i++) {
+        printf("Downloading \"%s.napp\"...", appsToInstall[i]);
+        fflush(stdout);
+        if (fileWriteData("/sys/class/nanohub/nanohub/download_app", appsToInstall[i], strlen(appsToInstall[i])))
+            printf("done\n");
     }
-
-    for (i = 0; i < installCnt; i++) {
-        printf("Downloading \"%s.napp\"...\n", appsToInstall[i]);
-        ret = write(fd, appsToInstall[i], strlen(appsToInstall[i]));
-        if (ret < 0) {
-            __android_log_print(ANDROID_LOG_WARN, "nanoapp_cmd", "Failed to download %s.napp!\n", appsToInstall[i]);
-            printf("Failed to download %s.napp!\n", appsToInstall[i]);
-        }
-    }
-
-    close(fd);
 }
 
 void resetHub()
 {
-    int fd;
     char c = '1';
 
-    fd = open("/sys/class/nanohub/nanohub/reset", O_WRONLY);
-    if (fd < 0) {
-        __android_log_write(ANDROID_LOG_ERROR, "nanoapp_cmd", "Failed to open reset sysfs file!");
-        printf("Failed to open reset sysfs file!\n");
-        return;
-    }
-
-    if (write(fd, &c, 1) <= 0) {
-        __android_log_write(ANDROID_LOG_ERROR, "nanoapp_cmd", "Failed to write reset sysfs file!");
-        printf("Failed to write reset sysfs file!\n");
-    }
-    close(fd);
+    printf("Resetting nanohub...");
+    fflush(stdout);
+    if (fileWriteData("/sys/class/nanohub/nanohub/reset", &c, sizeof(c)))
+        printf("done\n");
 }
 
 int main(int argc, char *argv[])
 {
     struct ConfigCmd mConfigCmd;
     int fd;
-    int ret;
+    int i;
 
     if (argc < 3 && strcmp(argv[1], "download") != 0) {
         printf("usage: %s <action> <sensor> <data> -d\n", argv[0]);
@@ -377,22 +378,28 @@
             return 1;
         }
         downloadNanohub();
-        parseConfigAppInfo();
-        if (installCnt) {
-            downloadApps();
-            resetHub();
+        for (i = 0; i < MAX_DOWNLOAD_RETRIES; i++) {
+            int updateCnt = parseConfigAppInfo();
+            if (updateCnt > 0) {
+                downloadApps(updateCnt);
+                resetHub();
+            } else if (!updateCnt){
+                return 0;
+            }
         }
-        return 0;
+
+        if (parseConfigAppInfo() != 0) {
+            __android_log_write(ANDROID_LOG_ERROR, LOG_TAG, "Failed to download all apps!");
+            printf("Failed to download all apps!\n");
+        }
+        return 1;
     } else {
         printf("Unsupported action: %s\n", argv[1]);
         return 1;
     }
 
-    fd = open("/dev/nanohub", O_RDWR);
-    do {
-        ret = write(fd, &mConfigCmd, sizeof(mConfigCmd));
-    } while (ret < 0);
-    close(fd);
+    while (!fileWriteData("/dev/nanohub", &mConfigCmd, sizeof(mConfigCmd)))
+        continue;
 
     if (drain) {
         signal(SIGINT, sig_handle);
diff --git a/util/nanoapp_encr/Android.mk b/util/nanoapp_encr/Android.mk
new file mode 100644
index 0000000..4d64acc
--- /dev/null
+++ b/util/nanoapp_encr/Android.mk
@@ -0,0 +1,45 @@
+#
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+
+LOCAL_SRC_FILES := \
+    ../../lib/nanohub/aes.c \
+    ../../lib/nanohub/sha2.c \
+    ../../lib/nanohub/nanoapp.c \
+    nanoapp_encr.c \
+
+
+LOCAL_CFLAGS := \
+        -Wall \
+        -Werror \
+        -Wextra \
+        -DHOST_BUILD \
+        -DBOOTLOADER= \
+        -DBOOTLOADER_RO= \
+
+
+LOCAL_C_INCLUDES := \
+        device/google/contexthub/lib/include \
+
+LOCAL_MODULE := nanoapp_encr
+
+LOCAL_MODULE_TAGS := optional
+
+include $(BUILD_HOST_EXECUTABLE)
diff --git a/util/nanoapp_encr/Makefile b/util/nanoapp_encr/Makefile
index eb920b8..d55669e 100644
--- a/util/nanoapp_encr/Makefile
+++ b/util/nanoapp_encr/Makefile
@@ -15,12 +15,14 @@
 #
 
 APP = nanoapp_encr
-SRC = nanoapp_encr.c ../../firmware/src/aes.c
+SRC = nanoapp_encr.c ../../lib/nanohub/aes.c ../../lib/nanohub/sha2.c
 CC ?= gcc
 CC_FLAGS = -Wall -Wextra -Werror
 
 $(APP): $(SRC) Makefile
-	$(CC) $(CC_FLAGS) -o $(APP) -O2 $(SRC) -I../../firmware/inc -DHOST_BUILD -DBOOTLOADER= -DBOOTLOADER_RO=
+	$(CC) $(CC_FLAGS) -o $(APP) -O2 $(SRC) \
+	-I../../lib/include \
+	-DHOST_BUILD -DBOOTLOADER= -DBOOTLOADER_RO=
 
 clean:
 	rm -f $(APP)
diff --git a/util/nanoapp_encr/nanoapp_encr.c b/util/nanoapp_encr/nanoapp_encr.c
index b92ece5..231266a 100644
--- a/util/nanoapp_encr/nanoapp_encr.c
+++ b/util/nanoapp_encr/nanoapp_encr.c
@@ -19,33 +19,15 @@
 #include <stdint.h>
 #include <stdlib.h>
 #include <stdio.h>
-#include <aes.h>
+#include <inttypes.h>
 
+#include <nanohub/aes.h>
+#include <nanohub/sha2.h>
+#include <nanohub/nanohub.h>
+#include <nanohub/nanoapp.h>
 
 static FILE* urandom = NULL;
 
-
-static bool readFile(void *dst, uint32_t len, const char *fileName)
-{
-    FILE *f = fopen(fileName, "rb");
-    bool ret = false;
-
-    if (!f)
-        return false;
-
-    if (len != fread(dst, 1, len, f))
-        goto out;
-
-    if (fread(&len, 1, 1, f)) //make sure file is actually over
-        goto out;
-
-    ret = true;
-
-out:
-    fclose(f);
-    return ret;
-}
-
 static void cleanup(void)
 {
     if (urandom)
@@ -58,7 +40,7 @@
         urandom = fopen("/dev/urandom", "rb");
         if (!urandom) {
             fprintf(stderr, "Failed to open /dev/urandom. Cannot procceed!\n");
-            exit(-2);
+            exit(2);
         }
 
         //it might not matter, but we still like to try to cleanup after ourselves
@@ -67,163 +49,302 @@
 
     if (len != fread(dst, 1, len, urandom)) {
         fprintf(stderr, "Failed to read /dev/urandom. Cannot procceed!\n");
-        exit(-3);
+        exit(2);
     }
 }
 
-static void appendToBuf(void **inputDataP, uint32_t *inputLenP, uint32_t *inputBufLenP, uint8_t c)
+static int handleEncrypt(uint8_t **pbuf, uint32_t bufUsed, FILE *out, uint64_t keyId, uint32_t *key)
 {
-    if (*inputLenP == *inputBufLenP) {
-        uint32_t newSz = ((uint64_t)*inputBufLenP * 17) / 16 + 1;
-        void *ptr = realloc(*inputDataP, newSz);
-        if (!ptr) {
-            fprintf(stderr, "Failed to realloc a buffer from %lub to %lub\n", (unsigned long)*inputBufLenP, (unsigned long)newSz);
-            exit(-5);
+    uint32_t i;
+    struct AesCbcContext ctx;
+    struct ImageHeader *image;
+    uint32_t *data;
+    struct Sha2state shaState;
+    bool err = false;
+    struct AppSecEncrHdr encr;
+    uint32_t padLen = 0;
+    uint8_t *buf = *pbuf;
+
+    encr.keyID = keyId;
+
+//FIXME: compatibility: all the devices has google secret key with id 1, so we
+//       can't simply change and enforce new key naming policy;
+//       first, key upload mechanism shall start working, and then we can have
+//       all the policies we want; for now, disable enforcement
+
+//        if (encr.keyID <= 0xFFFF)
+//            encr.keyID = AES_KEY_ID(encr.keyID);
+
+    fprintf(stderr, "Using Key ID: %016" PRIX64 "\n", encr.keyID);
+    rand_bytes(encr.IV, sizeof(encr.IV));
+    printHash(stderr, "Using IV", encr.IV, AES_BLOCK_WORDS);
+
+    if (bufUsed <= sizeof(*image)) {
+        fprintf(stderr, "Input file is too small\n");
+        return 2;
+    }
+
+    encr.dataLen = bufUsed;
+
+    if (((bufUsed - sizeof(*image)) % AES_BLOCK_SIZE) != 0)
+        padLen = AES_BLOCK_SIZE - ((bufUsed - sizeof(*image)) % AES_BLOCK_SIZE);
+
+    if (padLen) {
+        reallocOrDie(buf, bufUsed + padLen);
+        rand_bytes(buf + bufUsed, padLen);
+        bufUsed += padLen;
+        fprintf(stderr, "Padded to %" PRIu32 " bytes\n", bufUsed);
+        *pbuf = buf;
+    }
+
+    image = (struct ImageHeader *)buf;
+
+    if (bufUsed >= sizeof(*image) && image->aosp.magic == NANOAPP_AOSP_MAGIC &&
+        image->aosp.header_version == 1 && image->layout.magic == GOOGLE_LAYOUT_MAGIC) {
+        fprintf(stderr, "Found AOSP header\n");
+    } else {
+        fprintf(stderr, "Unknown binary format\n");
+        return 2;
+    }
+
+    if ((image->aosp.flags & NANOAPP_SIGNED_FLAG) != 0) {
+        fprintf(stderr, "data is marked as signed; encryption is not possible for signed data\n");
+        return 2;
+    }
+    if ((image->aosp.flags & NANOAPP_ENCRYPTED_FLAG) != 0) {
+        fprintf(stderr, "data is marked as encrypted; encryption is not possible for encrypted data\n");
+        return 2;
+    }
+
+    image->aosp.flags |= NANOAPP_ENCRYPTED_FLAG;
+    fwrite(image, sizeof(*image), 1, out);
+    data = (uint32_t *)(image + 1);
+    fprintf(stderr, "orig len: %" PRIu32 " bytes\n", encr.dataLen);
+    bufUsed -= sizeof(*image);
+    encr.dataLen -= sizeof(*image);
+    fwrite(&encr, sizeof(encr), 1, out);
+    sha2init(&shaState);
+
+    //encrypt and emit data
+    aesCbcInitForEncr(&ctx, key, encr.IV);
+    uint32_t outBuf[AES_BLOCK_WORDS];
+    for (i = 0; i < bufUsed/sizeof(uint32_t); i += AES_BLOCK_WORDS) {
+        aesCbcEncr(&ctx, data + i, outBuf);
+        int32_t sz = encr.dataLen - (i * sizeof(uint32_t));
+        sz = sz > AES_BLOCK_SIZE ? AES_BLOCK_SIZE : sz;
+        if (sz > 0) {
+            sha2processBytes(&shaState, data + i, sz);
+            fwrite(outBuf, AES_BLOCK_SIZE, 1, out);
         }
-        *inputDataP = ptr;
-        *inputBufLenP = newSz;
     }
-    ((uint8_t*)*inputDataP)[(*inputLenP)++] = c;;
+    const uint32_t *hash = sha2finish(&shaState);
+
+    printHash(stderr, "HASH", hash, SHA2_HASH_WORDS);
+
+    // finally, encrypt and output SHA2 hash
+    aesCbcEncr(&ctx, hash, outBuf);
+    fwrite(outBuf, AES_BLOCK_SIZE, 1, out);
+    aesCbcEncr(&ctx, hash + AES_BLOCK_WORDS, outBuf);
+    err = fwrite(outBuf, AES_BLOCK_SIZE, 1, out) != 1;
+
+    return err ? 2 : 0;
 }
 
-static void readInput(void **inputDataP, uint32_t *inputLenP, uint32_t *inputBufLenP)
+static int handleDecrypt(uint8_t **pbuf, uint32_t bufUsed, FILE *out, uint32_t *key)
 {
-    int c;
+    struct AesCbcContext ctx;
+    struct ImageHeader *image;
+    struct Sha2state shaState;
+    struct AppSecEncrHdr *encr;
+    uint32_t *data;
+    bool err = false;
+    uint32_t fileHash[((SHA2_HASH_WORDS + AES_BLOCK_WORDS - 1) / AES_BLOCK_WORDS) * AES_BLOCK_WORDS], fileHashSz;
+    uint32_t outBuf[AES_BLOCK_WORDS];
+    uint32_t i;
+    uint8_t *buf = *pbuf;
 
-    while ((c = getchar()) != EOF)
-        appendToBuf(inputDataP, inputLenP, inputBufLenP, c);
+    //parse header
+    image = (struct ImageHeader*)buf;
+    if (bufUsed >= (sizeof(*image) + sizeof(*encr)) &&
+        image->aosp.header_version == 1 && image->aosp.magic == NANOAPP_AOSP_MAGIC &&
+        image->layout.magic == GOOGLE_LAYOUT_MAGIC) {
+        fprintf(stderr, "Found AOSP header\n");
+        if (!(image->aosp.flags & NANOAPP_ENCRYPTED_FLAG)) {
+            fprintf(stderr, "data is not marked as encrypted; can't decrypt\n");
+            return 2;
+        }
+        image->aosp.flags &= ~NANOAPP_ENCRYPTED_FLAG;
+        data = (uint32_t *)(image + 1);
+        encr = (struct AppSecEncrHdr *)data;
+        data = (uint32_t *)(encr + 1);
+        bufUsed -= sizeof(*image) + sizeof(*encr);
+    } else {
+        fprintf(stderr, "Unknown binary format\n");
+        return 2;
+    }
+
+    if (encr->dataLen > bufUsed) {
+        fprintf(stderr, "Claimed output size of %" PRIu32 "b invalid\n", encr->dataLen);
+        return 2;
+    }
+    fprintf(stderr, "Original size %" PRIu32 "b (%" PRIu32 "b of padding present)\n",
+            encr->dataLen, bufUsed - encr->dataLen);
+    if (!encr->keyID)  {
+        fprintf(stderr, "Input data has invalid key ID\n");
+        return 2;
+    }
+    fprintf(stderr, "Using Key ID: %016" PRIX64 "\n", encr->keyID);
+    printHash(stderr, "Using IV", encr->IV, AES_BLOCK_WORDS);
+
+    fwrite(image, sizeof(*image), 1, out);
+        //decrypt and emit data
+    aesCbcInitForDecr(&ctx, key, encr->IV);
+    fileHashSz = 0;
+    sha2init(&shaState);
+    for (i = 0; i < bufUsed / sizeof(uint32_t); i += AES_BLOCK_WORDS) {
+        int32_t size = encr->dataLen - i * sizeof(uint32_t);
+        aesCbcDecr(&ctx, data + i, outBuf);
+        if (size > AES_BLOCK_SIZE)
+            size = AES_BLOCK_SIZE;
+        if (size > 0) {
+            sha2processBytes(&shaState, outBuf, size);
+            err = fwrite(outBuf, size, 1, out) != 1;
+        } else if (fileHashSz < sizeof(fileHash)) {
+            memcpy(((uint8_t*)fileHash) + fileHashSz, outBuf, AES_BLOCK_SIZE);
+            fileHashSz += AES_BLOCK_SIZE;
+        } else {
+            fprintf(stderr, "Too much input data\n");
+            return 2;
+        }
+    }
+    const uint32_t *calcHash = sha2finish(&shaState);
+    printHash(stderr, "HASH [calc]", calcHash, SHA2_HASH_WORDS);
+    printHash(stderr, "HASH [file]", fileHash, SHA2_HASH_WORDS);
+
+    bool verify = memcmp(fileHash, calcHash, SHA2_HASH_SIZE) == 0;
+    fprintf(stderr, "hash verification: %s\n", verify ? "passed" : "failed");
+    if (!verify)
+        return 2;
+
+    if (!err)
+        fprintf(stderr, "Done\n");
+
+    return err ? 2 : 0;
+}
+
+static void fatalUsage(const char *name, const char *msg, const char *arg)
+{
+    if (msg && arg)
+        fprintf(stderr, "Error: %s: %s\n\n", msg, arg);
+    else if (msg)
+        fprintf(stderr, "Error: %s\n\n", msg);
+
+    fprintf(stderr, "USAGE: %s [-e] [-d] [-i <key id>] [-k <key file>] <input file> [<output file>]\n"
+                    "       -i : 64-bit hex number != 0\n"
+                    "       -e : encrypt post-processed file\n"
+                    "       -d : decrypt encrypted post-processed file\n"
+                    "       -k : binary file (32 byte size) containing AES-256 secret key\n"
+                    , name);
+    exit(1);
 }
 
 int main(int argc, char **argv)
 {
-    uint32_t *inputData = NULL, inputLen = 0, inputBufLen = 0, origLen;
-    uint32_t i, j, iv[AES_BLOCK_WORDS], key[AES_KEY_WORDS];
-    const char *selfExeName = argv[0];
-    struct AesCbcContext ctx;
-    uint64_t keyId;
-    uint8_t tmp;
+    uint32_t bufUsed = 0;
+    uint8_t *buf = NULL;
+    uint64_t keyId = 0;
+    int ret = -1;
+    uint32_t *u32Arg = NULL;
+    uint64_t *u64Arg = NULL;
+    const char **strArg = NULL;
+    const char *appName = argv[0];
+    const char *posArg[2] = { NULL };
+    uint32_t posArgCnt = 0;
+    FILE *out = NULL;
+    const char *prev = NULL;
+    bool decrypt = false;
+    bool encrypt = false;
+    const char *keyFile = NULL;
+    int multi = 0;
+    uint32_t key[AES_KEY_WORDS];
 
-    if (argc == 4 && !strcmp(argv[1], "encr")) {
-
-        if (argv[2][0] == '0' && argv[2][1] == 'x')
-            keyId = strtoull(argv[2] + 2, NULL, 16);
-        else
-            keyId = strtoull(argv[2], NULL, 10);
-
-        if (!keyId) {
-            fprintf(stderr, "Key ID cannot be zero (given '%s')\n", argv[2]);
-            goto usage;
-        }
-
-        fprintf(stderr, "Using Key ID of 0x%08lX%08lX\n", (unsigned long)(keyId >> 32), (unsigned long)(keyId & 0xffffffffull));
-        rand_bytes(iv, sizeof(iv));
-        fprintf(stderr, "Using IV '");
-        for (i = 0; i < AES_BLOCK_WORDS; i++)
-            fprintf(stderr, "%08lX", (unsigned long)iv[i]);
-        fprintf(stderr, "'\n");
-
-        //read key
-        if (!readFile(key, sizeof(key), argv[3])) {
-            fprintf(stderr, "Key file '%s' does not exist or is not %u bytes\n", argv[3], (unsigned int)sizeof(key));
-            goto usage;
-        }
-
-        //read data
-        readInput((void**)&inputData, &inputLen, &inputBufLen);
-        origLen = inputLen;
-        fprintf(stderr, "Read %lu bytes\n", (unsigned long)origLen);
-        while (inputLen & 15) { //round to 16 bytes
-            rand_bytes(&tmp, 1);
-            appendToBuf((void**)&inputData, &inputLen, &inputBufLen, tmp);
-        }
-        fprintf(stderr, "Padded to %lu bytes\n", (unsigned long)inputLen);
-
-        //emit header
-        fwrite("Encr", 4, 1, stdout);
-        fwrite(&origLen, 4, 1, stdout);
-        fwrite(&keyId, 8, 1, stdout);
-        for (i = 0; i < AES_BLOCK_WORDS; i++)
-            fwrite(iv + i, 4, 1, stdout);
-
-        //encrypt and emit data
-        aesCbcInitForEncr(&ctx, key, iv); //todo
-        for (i = 0; i < inputLen / sizeof(uint32_t); i += AES_BLOCK_WORDS) {
-            uint32_t out[AES_BLOCK_WORDS];
-            aesCbcEncr(&ctx, inputData + i, out);
-            for (j = 0; j < AES_BLOCK_WORDS; j++)
-                fwrite(out + j, 4, 1, stdout);
-        }
-
-        fprintf(stderr, "Done\n");
-    }
-    else if (argc == 3 && !strcmp(argv[1], "decr")) {
-        //read key
-        if (!readFile(key, sizeof(key), argv[2])) {
-            fprintf(stderr, "Key file '%s' does not exist or is not %u bytes\n", argv[3], (unsigned int)sizeof(key));
-            goto usage;
-        }
-
-        //read data
-        readInput((void**)&inputData, &inputLen, &inputBufLen);
-        origLen = inputLen;
-        fprintf(stderr, "Read %lu bytes\n", (unsigned long)origLen);
-
-        if (inputLen < 32) {
-            fprintf(stderr, "Implausibly small input\n");
-            goto usage;
-        }
-
-        //parse header
-        if (memcmp(inputData, "Encr", 4)) {
-            fprintf(stderr, "Input data lacks 'Encr' header\n");
-            goto usage;
-        }
-        origLen = inputData[1];
-        if (origLen > inputLen - 32) {
-            fprintf(stderr, "Claimed output size of %lub invalid\n", (unsigned long)origLen);
-            goto usage;
-        }
-        fprintf(stderr, "Original size %lub (%lub of padding present)\n", (unsigned long)origLen, (unsigned long)(inputLen - 32 - origLen));
-        keyId = *(uint64_t*)(inputData + 2);
-        if (!keyId)  {
-            fprintf(stderr, "Input data has invaid key ID\n");
-            goto usage;
-        }
-        fprintf(stderr, "Using Key ID of 0x%08lX%08lX\n", (unsigned long)(keyId >> 32), (unsigned long)(keyId & 0xffffffffull));
-        for (i = 0; i < AES_BLOCK_WORDS; i++)
-            iv[i] = inputData[4 + i];
-        fprintf(stderr, "Using IV '");
-        for (i = 0; i < AES_BLOCK_WORDS; i++)
-            fprintf(stderr, "%08lX", (unsigned long)iv[i]);
-        fprintf(stderr, "'\n");
-
-        //decrypt and emit data
-        aesCbcInitForDecr(&ctx, key, iv); //todo
-        for (i = 0; i < (inputLen - 32)/ sizeof(uint32_t); i += AES_BLOCK_WORDS) {
-            uint32_t out[AES_BLOCK_WORDS];
-            aesCbcDecr(&ctx, inputData + i + 8, out);
-            for (j = 0; j < AES_BLOCK_WORDS; j++) {
-                uint32_t now = origLen >= 4 ? 4 : origLen;
-                fwrite(out + j, now, 1, stdout);
-                origLen -= now;
+    for (int i = 1; i < argc; i++) {
+        char *end = NULL;
+        if (argv[i][0] == '-') {
+            prev = argv[i];
+            if (!strcmp(argv[i], "-d"))
+                decrypt = true;
+            else if (!strcmp(argv[i], "-e"))
+                encrypt = true;
+            else if (!strcmp(argv[i], "-k"))
+                strArg = &keyFile;
+            else if (!strcmp(argv[i], "-i"))
+                u64Arg = &keyId;
+            else
+                fatalUsage(appName, "unknown argument", argv[i]);
+        } else {
+            if (u64Arg) {
+                uint64_t tmp = strtoull(argv[i], &end, 16);
+                if (*end == '\0')
+                    *u64Arg = tmp;
+                u64Arg = NULL;
+            } else if (u32Arg) {
+                uint32_t tmp = strtoul(argv[i], &end, 16);
+                if (*end == '\0')
+                    *u32Arg = tmp;
+                u32Arg = NULL;
+            } else if (strArg) {
+                    *strArg = argv[i];
+                strArg = NULL;
+            } else {
+                if (posArgCnt < 2)
+                    posArg[posArgCnt++] = argv[i];
+                else
+                    fatalUsage(appName, "too many positional arguments", argv[i]);
             }
+            prev = 0;
         }
-
-        fprintf(stderr, "Done\n");
     }
+    if (prev)
+        fatalUsage(appName, "missing argument after", prev);
+
+    if (!posArgCnt)
+        fatalUsage(appName, "missing input file name", NULL);
+
+    if (encrypt)
+        multi++;
+    if (decrypt)
+        multi++;
+
+    if (multi != 1)
+        fatalUsage(appName, "select either -d or -e", NULL);
+
+    if (!keyFile)
+        fatalUsage(appName, "no key file given", NULL);
+
+    if (encrypt && !keyId)
+        fatalUsage(appName, "Non-zero Key ID must be given to encrypt data", NULL);
+
+    //read key
+    if (!readFile(key, sizeof(key), keyFile))
+        fatalUsage(appName, "Key file does not exist or has incorrect size", keyFile);
+
+    buf = loadFile(posArg[0], &bufUsed);
+    fprintf(stderr, "Read %" PRIu32 " bytes\n", bufUsed);
+
+    if (!posArg[1])
+        out = stdout;
     else
-        goto usage;
-    free(inputData);
-    return 0;
+        out = fopen(posArg[1], "w");
+    if (!out)
+        fatalUsage(appName, "failed to create/open output file", posArg[1]);
 
-usage:
-    fprintf(stderr, "USAGE: %s encr <KEY_ID> <KEY_FILE>< data_to_encr > file_out\n"
-                    "       %s decr <KEY_FILE> < data_to_decr > file_out\n",
-                    selfExeName, selfExeName);
-    free(inputData);
-    return -1;
+    if (encrypt)
+        ret = handleEncrypt(&buf, bufUsed, out, keyId, key);
+    else if (decrypt)
+        ret = handleDecrypt(&buf, bufUsed, out, key);
+
+    free(buf);
+    fclose(out);
+    return ret;
 }
-
-
-
-
diff --git a/util/nanoapp_postprocess/Android.mk b/util/nanoapp_postprocess/Android.mk
index f6a10dc..9ed9c63 100644
--- a/util/nanoapp_postprocess/Android.mk
+++ b/util/nanoapp_postprocess/Android.mk
@@ -19,10 +19,14 @@
 include $(CLEAR_VARS)
 
 LOCAL_SRC_FILES := \
-        postprocess.c
+    postprocess.c \
+    ../../lib/nanohub/nanoapp.c \
 
 LOCAL_CFLAGS := -Wall -Werror -Wextra
 
+LOCAL_C_INCLUDES += \
+    device/google/contexthub/lib/include \
+
 LOCAL_MODULE := nanoapp_postprocess
 
 LOCAL_MODULE_TAGS := optional
diff --git a/util/nanoapp_postprocess/Makefile b/util/nanoapp_postprocess/Makefile
index ef6e596..1b2e518 100644
--- a/util/nanoapp_postprocess/Makefile
+++ b/util/nanoapp_postprocess/Makefile
@@ -17,10 +17,10 @@
 APP = nanoapp_postprocess
 SRC = postprocess.c
 CC ?= gcc
-CC_FLAGS = -Wall -Wextra -Werror
+CC_FLAGS = -Wall -Wextra -Werror -I../../lib/include
 
 $(APP): $(SRC) Makefile
 	$(CC) $(CC_FLAGS) -o $(APP) -O2 $(SRC)
 
 clean:
-	rm -f $(APP)
+	rm -f $(APP)
\ No newline at end of file
diff --git a/util/nanoapp_postprocess/postprocess.c b/util/nanoapp_postprocess/postprocess.c
index 5d527b1..88dd556 100644
--- a/util/nanoapp_postprocess/postprocess.c
+++ b/util/nanoapp_postprocess/postprocess.c
@@ -14,7 +14,6 @@
  * limitations under the License.
  */
 
-#include "../../firmware/inc/cpu/cortexm4f/appRelocFormat.h"
 #include <assert.h>
 #include <sys/types.h>
 #include <stdbool.h>
@@ -23,15 +22,15 @@
 #include <string.h>
 #include <stdint.h>
 #include <stdio.h>
+#include <stddef.h>
+#include <errno.h>
+
+#include <nanohub/nanohub.h>
+#include <nanohub/nanoapp.h>
+#include <nanohub/appRelocFormat.h>
 
 //This code assumes it is run on a LE CPU with unaligned access abilities. Sorry.
 
-
-#define APP_MAGIX_0    0x676f6f47
-#define APP_MAGIX_1    0x614e656c
-#define APP_MAGIX_2    0x70416f6e
-#define APP_MAGIX_3    0xffff0070
-
 #define FLASH_BASE  0x10000000
 #define RAM_BASE    0x80000000
 
@@ -49,32 +48,6 @@
 #define NANO_RELOC_TYPE_FLASH  1
 #define NANO_RELOC_LAST        2 //must be <= (RELOC_TYPE_MASK >> RELOC_TYPE_SHIFT)
 
-
-struct AppHeader {
-    uint32_t magic[4];
-
-    uint32_t appID[2];
-
-    uint32_t __data_start;
-    uint32_t __data_end;
-    uint32_t __data_data;
-
-    uint32_t __bss_start;
-    uint32_t __bss_end;
-
-    uint32_t __got_start;
-    uint32_t __got_end;
-    uint32_t __rel_start;
-    uint32_t __rel_end;
-
-    uint32_t version;
-    uint32_t rfu;
-
-    uint32_t start_task;
-    uint32_t end_task;
-    uint32_t handle_event;
-};
-
 struct RelocEntry {
     uint32_t where;
     uint32_t info;  //bottom 8 bits is type, top 24 is sym idx
@@ -91,101 +64,87 @@
     uint32_t b, c;
 };
 
-
-
 struct NanoRelocEntry {
     uint32_t ofstInRam;
     uint8_t type;
 };
 
+static void fatalUsage(const char *name, const char *msg, const char *arg)
+{
+    if (msg && arg)
+        fprintf(stderr, "Error: %s: %s\n\n", msg, arg);
+    else if (msg)
+        fprintf(stderr, "Error: %s\n\n", msg);
 
-int main(int argc, char **argv)
+    fprintf(stderr, "USAGE: %s [-v] [-k <key id>] [-a <app id>] [-r] [-n <layout name>] [-i <layout id>] <input file> [<output file>]\n"
+                    "       -v               : be verbose\n"
+                    "       -n <layout name> : app, os, key\n"
+                    "       -i <layout id>   : 1 (app), 2 (key), 3 (os)\n"
+                    "       -f <layout flags>: 16-bit hex value, stored as layout-specific flags\n"
+                    "       -a <app ID>      : 64-bit hex number != 0\n"
+                    "       -k <key ID>      : 64-bit hex number != 0\n"
+                    "       -r               : bare (no AOSP header); used only for inner OS image generation\n"
+                    "       layout ID and layout name control the same parameter, so only one of them needs to be used\n"
+                    , name);
+    exit(1);
+}
+
+static int handleApp(uint8_t **pbuf, uint32_t bufUsed, FILE *out, uint32_t layoutFlags, uint64_t appId, bool verbose)
 {
     uint32_t i, numRelocs, numSyms, outNumRelocs = 0, packedNanoRelocSz, j, k, lastOutType = 0, origin = 0;
     struct NanoRelocEntry *nanoRelocs = NULL;
     struct RelocEntry *relocs;
     struct SymtabEntry *syms;
     uint8_t *packedNanoRelocs;
-    uint32_t t, bufUsed = 0;
-    struct AppHeader *hdr;
-    bool verbose = false;
-    uint8_t *buf = NULL;
-    uint32_t bufSz = 0;
-    uint64_t appId = 0;
-    int c, ret = -1;
+    uint32_t t;
+    struct BinHdr *bin;
+    int ret = -1;
+    struct SectInfo *sect;
+    struct AppInfo app;
+    uint8_t *buf = *pbuf;
+    uint32_t bufSz = bufUsed * 3 /2;
 
-
-    argc--;
-    assert(argc >= 0);
-    argv++;
-
-    for (i = 0; i < (uint32_t)argc; i++) {
-        if (!strcmp(argv[i], "-v")) {
-            verbose = true;
-            continue;
-        }
-        appId = strtoul(argv[i], NULL, 16);
-    }
-
-    if (!appId) {
-        fprintf(stderr, "USAGE: %s [-v] 0123456789abcdef < app.bin >app.ap\n\twhere 0123456789abcdef is app ID in hex\n", argv[-1]);
-        return -2;
-    }
-
-    // read file
-    while ((c = getchar()) != EOF) {
-        if (bufSz == bufUsed) {
-            uint8_t *t;
-            bufSz = (bufSz * 5) / 4 + 1;
-            t = realloc(buf, bufSz);
-            if (!t) {
-                fprintf(stderr, "Realloc to %u fails - you're SOL\n", (unsigned)bufSz);
-                goto out;
-            }
-            buf = t;
-        }
-        buf[bufUsed++] = c;
-    }
-
-    //make buffer bigger by 50% in case relocs grow out of hand
-    buf = realloc(buf, 3 * bufUsed / 2);
-    if (!buf) {
-        fprintf(stderr, "MEMERR\n");
-        exit(-7);
-    }
+    //make buffer 50% bigger than bufUsed in case relocs grow out of hand
+    buf = reallocOrDie(buf, bufSz);
+    *pbuf = buf;
 
     //sanity checks
-    hdr = (struct AppHeader*)buf;
-    if (bufUsed < sizeof(struct AppHeader)) {
+    bin = (struct BinHdr*)buf;
+    if (bufUsed < sizeof(*bin)) {
         fprintf(stderr, "File size too small\n");
         goto out;
     }
 
-    if (hdr->magic[0] != APP_MAGIX_0 || hdr->magic[1] != APP_MAGIX_1 || hdr->magic[2] != APP_MAGIX_2 || hdr->magic[3] != APP_MAGIX_3) {
-        fprintf(stderr, "Magic value wrong\n");
+    if (bin->hdr.magic != NANOAPP_FW_MAGIC) {
+        fprintf(stderr, "Magic value is wrong: found %08" PRIX32
+                        "; expected %08" PRIX32 "\n",
+                        bin->hdr.magic, NANOAPP_FW_MAGIC);
         goto out;
     }
+
+    sect = &bin->sect;
+
     //do some math
-    relocs = (struct RelocEntry*)(buf + hdr->__rel_start - FLASH_BASE);
-    syms = (struct SymtabEntry*)(buf + hdr->__rel_end - FLASH_BASE);
-    numRelocs = (hdr->__rel_end - hdr->__rel_start) / sizeof(struct RelocEntry);
-    numSyms = (bufUsed + FLASH_BASE - hdr->__rel_end) / sizeof(struct SymtabEntry);
+    relocs = (struct RelocEntry*)(buf + sect->rel_start - FLASH_BASE);
+    syms = (struct SymtabEntry*)(buf + sect->rel_end - FLASH_BASE);
+    numRelocs = (sect->rel_end - sect->rel_start) / sizeof(struct RelocEntry);
+    numSyms = (bufUsed + FLASH_BASE - sect->rel_end) / sizeof(struct SymtabEntry);
 
     //sanity
-    if (numRelocs * sizeof(struct RelocEntry) + hdr->__rel_start != hdr->__rel_end) {
+    if (numRelocs * sizeof(struct RelocEntry) + sect->rel_start != sect->rel_end) {
         fprintf(stderr, "Relocs of nonstandard size\n");
         goto out;
     }
-    if (numSyms * sizeof(struct SymtabEntry) + hdr->__rel_end != bufUsed + FLASH_BASE) {
+    if (numSyms * sizeof(struct SymtabEntry) + sect->rel_end != bufUsed + FLASH_BASE) {
         fprintf(stderr, "Syms of nonstandard size\n");
         goto out;
     }
 
     //show some info
-    fprintf(stderr, "\nRead %u bytes of binary.\n", (unsigned)bufUsed);
+    fprintf(stderr, "\nRead %" PRIu32 " bytes of binary.\n", bufUsed);
 
     if (verbose)
-        fprintf(stderr, "Found %u relocs and a %u-entry symbol table\n", numRelocs, numSyms);
+        fprintf(stderr, "Found %" PRIu32 " relocs and a %" PRIu32 "-entry symbol table\n", numRelocs, numSyms);
 
     //handle relocs
     nanoRelocs = malloc(sizeof(struct NanoRelocEntry[numRelocs]));
@@ -200,44 +159,60 @@
         uint32_t *valThereP;
 
         if (whichSym >= numSyms) {
-            fprintf(stderr, "Reloc %u references a nonexistent symbol!\nINFO:\n\tWhere: 0x%08X\n\ttype: %u\n\tsym: %u\n",
+            fprintf(stderr, "Reloc %" PRIu32 " references a nonexistent symbol!\n"
+                            "INFO:\n"
+                            "        Where: 0x%08" PRIX32 "\n"
+                            "        type: %" PRIu32 "\n"
+                            "        sym: %" PRIu32 "\n",
                 i, relocs[i].where, relocs[i].info & 0xff, whichSym);
             goto out;
         }
 
         if (verbose) {
+            const char *seg;
 
-            fprintf(stderr, "Reloc[%3u]:\n {@0x%08X, type %3d, -> sym[%3u]: {@0x%08x}, ",
+            fprintf(stderr, "Reloc[%3" PRIu32 "]:\n {@0x%08" PRIX32 ", type %3" PRIu32 ", -> sym[%3" PRIu32 "]: {@0x%08" PRIX32 "}, ",
                 i, relocs[i].where, relocs[i].info & 0xff, whichSym, syms[whichSym].addr);
 
-            if (IS_IN_RANGE_E(relocs[i].where, hdr->__bss_start, hdr->__bss_end))
-                fprintf(stderr, "in   .bss}\n");
-            else if (IS_IN_RANGE_E(relocs[i].where, hdr->__data_start, hdr->__data_end))
-                fprintf(stderr, "in  .data}\n");
-            else if (IS_IN_RANGE_E(relocs[i].where, hdr->__got_start, hdr->__got_end))
-                fprintf(stderr, "in   .got}\n");
-            else if (IS_IN_RANGE_E(relocs[i].where, FLASH_BASE, FLASH_BASE + sizeof(struct AppHeader)))
-                fprintf(stderr, "in APPHDR}\n");
+            if (IS_IN_RANGE_E(relocs[i].where, sect->bss_start, sect->bss_end))
+                seg = ".bss";
+            else if (IS_IN_RANGE_E(relocs[i].where, sect->data_start, sect->data_end))
+                seg = ".data";
+            else if (IS_IN_RANGE_E(relocs[i].where, sect->got_start, sect->got_end))
+                seg = ".got";
+            else if (IS_IN_RANGE_E(relocs[i].where, FLASH_BASE, FLASH_BASE + sizeof(struct BinHdr)))
+                seg = "APPHDR";
             else
-                fprintf(stderr, "in	???}\n");
+                seg = "???";
 
+            fprintf(stderr, "in   %s}\n", seg);
         }
         /* handle relocs inside the header */
-        if (IS_IN_FLASH(relocs[i].where) && relocs[i].where - FLASH_BASE < sizeof(struct AppHeader) && relocType == RELOC_TYPE_SECT) {
+        if (IS_IN_FLASH(relocs[i].where) && relocs[i].where - FLASH_BASE < sizeof(struct BinHdr) && relocType == RELOC_TYPE_SECT) {
             /* relocs in header are special - runtime corrects for them */
             if (syms[whichSym].addr) {
-                fprintf(stderr, "Weird in-header sect reloc %u to symbol %u with nonzero addr 0x%08x\n", i, whichSym, syms[whichSym].addr);
+                fprintf(stderr, "Weird in-header sect reloc %" PRIu32 " to symbol %" PRIu32 " with nonzero addr 0x%08" PRIX32 "\n",
+                        i, whichSym, syms[whichSym].addr);
                 goto out;
             }
 
             valThereP = (uint32_t*)(buf + relocs[i].where - FLASH_BASE);
             if (!IS_IN_FLASH(*valThereP)) {
-                fprintf(stderr, "In-header reloc %u of location 0x%08X is outside of FLASH!\nINFO:\n\ttype: %u\n\tsym: %u\n\tSym Addr: 0x%08X\n",
-                    i, relocs[i].where, relocType, whichSym, syms[whichSym].addr);
+                fprintf(stderr, "In-header reloc %" PRIu32 " of location 0x%08" PRIX32 " is outside of FLASH!\n"
+                                "INFO:\n"
+                                "        type: %" PRIu32 "\n"
+                                "        sym: %" PRIu32 "\n"
+                                "        Sym Addr: 0x%08" PRIX32 "\n",
+                                i, relocs[i].where, relocType, whichSym, syms[whichSym].addr);
                 goto out;
             }
 
-            *valThereP -= FLASH_BASE;
+            // binary header generated by objcopy, .napp header and final FW header in flash are of different size.
+            // we subtract binary header offset here, so all the entry points are relative to beginning of "sect".
+            // FW will use &sect as a base to call these vectors; no more problems with different header sizes;
+            // Assumption: offsets between sect & vec, vec & code are the same in all images (or, in a simpler words, { sect, vec, code }
+            // must go together). this is enforced by linker script, and maintained by all tools and FW download code in the OS.
+            *valThereP -= FLASH_BASE + BINARY_RELOC_OFFSET;
 
             if (verbose)
                 fprintf(stderr, "  -> Nano reloc skipped for in-header reloc\n");
@@ -246,12 +221,16 @@
         }
 
         if (!IS_IN_RAM(relocs[i].where)) {
-            fprintf(stderr, "Reloc %u of location 0x%08X is outside of RAM!\nINFO:\n\ttype: %u\n\tsym: %u\n\tSym Addr: 0x%08X\n",
-                i, relocs[i].where, relocType, whichSym, syms[whichSym].addr);
+            fprintf(stderr, "In-header reloc %" PRIu32 " of location 0x%08" PRIX32 " is outside of RAM!\n"
+                            "INFO:\n"
+                            "        type: %" PRIu32 "\n"
+                            "        sym: %" PRIu32 "\n"
+                            "        Sym Addr: 0x%08" PRIX32 "\n",
+                            i, relocs[i].where, relocType, whichSym, syms[whichSym].addr);
             goto out;
         }
 
-        valThereP = (uint32_t*)(buf + relocs[i].where + hdr-> __data_data - RAM_BASE - FLASH_BASE);
+        valThereP = (uint32_t*)(buf + relocs[i].where + sect->data_data - RAM_BASE - FLASH_BASE);
 
         nanoRelocs[outNumRelocs].ofstInRam = relocs[i].where - RAM_BASE;
 
@@ -263,7 +242,7 @@
                 (*valThereP) += syms[whichSym].addr;
 
                 if (IS_IN_FLASH(syms[whichSym].addr)) {
-                    (*valThereP) -= FLASH_BASE;
+                    (*valThereP) -= FLASH_BASE + BINARY_RELOC_OFFSET;
                     nanoRelocs[outNumRelocs].type = NANO_RELOC_TYPE_FLASH;
                 }
                 else if (IS_IN_RAM(syms[whichSym].addr)) {
@@ -271,16 +250,18 @@
                     nanoRelocs[outNumRelocs].type = NANO_RELOC_TYPE_RAM;
                 }
                 else {
-                    fprintf(stderr, "Weird reloc %u to symbol %u in unknown memory space (addr 0x%08x)\n", i, whichSym, syms[whichSym].addr);
+                    fprintf(stderr, "Weird reloc %" PRIu32 " to symbol %" PRIu32 " in unknown memory space (addr 0x%08" PRIX32 ")\n",
+                            i, whichSym, syms[whichSym].addr);
                     goto out;
                 }
                 if (verbose)
-                    fprintf(stderr, "  -> Abs reference fixed up 0x%08X -> 0x%08X\n", t, *valThereP);
+                    fprintf(stderr, "  -> Abs reference fixed up 0x%08" PRIX32 " -> 0x%08" PRIX32 "\n", t, *valThereP);
                 break;
 
             case RELOC_TYPE_SECT:
                 if (syms[whichSym].addr) {
-                    fprintf(stderr, "Weird sect reloc %u to symbol %u with nonzero addr 0x%08x\n", i, whichSym, syms[whichSym].addr);
+                    fprintf(stderr, "Weird sect reloc %" PRIu32 " to symbol %" PRIu32 " with nonzero addr 0x%08" PRIX32 "\n",
+                            i, whichSym, syms[whichSym].addr);
                     goto out;
                 }
 
@@ -288,32 +269,34 @@
 
                 if (IS_IN_FLASH(*valThereP)) {
                     nanoRelocs[outNumRelocs].type = NANO_RELOC_TYPE_FLASH;
-                    *valThereP -= FLASH_BASE;
+                    *valThereP -= FLASH_BASE + BINARY_RELOC_OFFSET;
                 }
                 else if (IS_IN_RAM(*valThereP)) {
                     nanoRelocs[outNumRelocs].type = NANO_RELOC_TYPE_RAM;
                     *valThereP -= RAM_BASE;
                 }
                 else {
-                    fprintf(stderr, "Weird sec reloc %u to symbol %u in unknown memory space (addr 0x%08x)\n", i, whichSym, *valThereP);
+                    fprintf(stderr, "Weird sec reloc %" PRIu32 " to symbol %" PRIu32
+                                    " in unknown memory space (addr 0x%08" PRIX32 ")\n",
+                                    i, whichSym, *valThereP);
                     goto out;
                 }
                 if (verbose)
-                    fprintf(stderr, "  -> Sect reference fixed up 0x%08X -> 0x%08X\n", t, *valThereP);
+                    fprintf(stderr, "  -> Sect reference fixed up 0x%08" PRIX32 " -> 0x%08" PRIX32 "\n", t, *valThereP);
                 break;
 
             default:
-                fprintf(stderr, "Weird reloc %u type %u to symbol %u\n", i, relocType, whichSym);
+                fprintf(stderr, "Weird reloc %" PRIX32 " type %" PRIX32 " to symbol %" PRIX32 "\n", i, relocType, whichSym);
                 goto out;
         }
 
         if (verbose)
-            fprintf(stderr, "  -> Nano reloc calculated as 0x%08X,0x%02x\n", nanoRelocs[i].ofstInRam, nanoRelocs[i].type);
+            fprintf(stderr, "  -> Nano reloc calculated as 0x%08" PRIX32 ",0x%02" PRIX8 "\n", nanoRelocs[i].ofstInRam, nanoRelocs[i].type);
         outNumRelocs++;
     }
 
     //sort by type and then offset
-        for (i = 0; i < outNumRelocs; i++) {
+    for (i = 0; i < outNumRelocs; i++) {
         struct NanoRelocEntry t;
 
         for (k = i, j = k + 1; j < outNumRelocs; j++) {
@@ -327,27 +310,26 @@
         memcpy(nanoRelocs + k, &t, sizeof(struct NanoRelocEntry));
 
         if (verbose)
-            fprintf(stderr, "SortedReloc[%3u] = {0x%08X,0x%02X}\n", i, nanoRelocs[i].ofstInRam, nanoRelocs[i].type);
+            fprintf(stderr, "SortedReloc[%3" PRIu32 "] = {0x%08" PRIX32 ",0x%02" PRIX8 "}\n", i, nanoRelocs[i].ofstInRam, nanoRelocs[i].type);
     }
 
     //produce output nanorelocs in packed format
     packedNanoRelocs = malloc(outNumRelocs * 6); //definitely big enough
     packedNanoRelocSz = 0;
-        for (i = 0; i < outNumRelocs; i++) {
-
+    for (i = 0; i < outNumRelocs; i++) {
         uint32_t displacement;
 
         if (lastOutType != nanoRelocs[i].type) {  //output type if ti changed
             if (nanoRelocs[i].type - lastOutType == 1) {
                 packedNanoRelocs[packedNanoRelocSz++] = TOKEN_RELOC_TYPE_NEXT;
                 if (verbose)
-                    fprintf(stderr, "Out: RelocTC (1) // to 0x%02X\n", nanoRelocs[i].type);
+                    fprintf(stderr, "Out: RelocTC (1) // to 0x%02" PRIX8 "\n", nanoRelocs[i].type);
             }
             else {
                 packedNanoRelocs[packedNanoRelocSz++] = TOKEN_RELOC_TYPE_CHG;
                 packedNanoRelocs[packedNanoRelocSz++] = nanoRelocs[i].type - lastOutType - 1;
                 if (verbose)
-                    fprintf(stderr, "Out: RelocTC (0x%02X)  // to 0x%02X\n", nanoRelocs[i].type - lastOutType - 1, nanoRelocs[i].type);
+                    fprintf(stderr, "Out: RelocTC (0x%02" PRIX8 ")  // to 0x%02" PRIX8 "\n", (uint8_t)(nanoRelocs[i].type - lastOutType - 1), nanoRelocs[i].type);
             }
             lastOutType = nanoRelocs[i].type;
             origin = 0;
@@ -365,7 +347,7 @@
             for (j = 1; j + i < outNumRelocs && j < MAX_RUN_LEN && nanoRelocs[j + i].type == lastOutType && nanoRelocs[j + i].ofstInRam - nanoRelocs[j + i - 1].ofstInRam == 4; j++);
             if (j >= MIN_RUN_LEN) {
                 if (verbose)
-                    fprintf(stderr, "Out: Reloc0  x%u\n", j);
+                    fprintf(stderr, "Out: Reloc0  x%" PRIX32 "\n", j);
                 packedNanoRelocs[packedNanoRelocSz++] = TOKEN_CONSECUTIVE;
                 packedNanoRelocs[packedNanoRelocSz++] = j - MIN_RUN_LEN;
                 origin = nanoRelocs[j + i - 1].ofstInRam + 4;  //reset origin to last one
@@ -377,12 +359,12 @@
         //produce output
         if (displacement <= MAX_8_BIT_NUM) {
             if (verbose)
-                fprintf(stderr, "Out: Reloc8  0x%02X\n", displacement);
+                fprintf(stderr, "Out: Reloc8  0x%02" PRIX32 "\n", displacement);
             packedNanoRelocs[packedNanoRelocSz++] = displacement;
         }
         else if (displacement <= MAX_16_BIT_NUM) {
             if (verbose)
-                fprintf(stderr, "Out: Reloc16 0x%06X\n", displacement);
+                fprintf(stderr, "Out: Reloc16 0x%06" PRIX32 "\n", displacement);
                         displacement -= MAX_8_BIT_NUM;
             packedNanoRelocs[packedNanoRelocSz++] = TOKEN_16BIT_OFST;
             packedNanoRelocs[packedNanoRelocSz++] = displacement;
@@ -390,7 +372,7 @@
         }
         else if (displacement <= MAX_24_BIT_NUM) {
             if (verbose)
-                fprintf(stderr, "Out: Reloc24 0x%08X\n", displacement);
+                fprintf(stderr, "Out: Reloc24 0x%08" PRIX32 "\n", displacement);
                         displacement -= MAX_16_BIT_NUM;
             packedNanoRelocs[packedNanoRelocSz++] = TOKEN_24BIT_OFST;
             packedNanoRelocs[packedNanoRelocSz++] = displacement;
@@ -399,7 +381,7 @@
         }
         else  {
             if (verbose)
-                fprintf(stderr, "Out: Reloc32 0x%08X\n", displacement);
+                fprintf(stderr, "Out: Reloc32 0x%08" PRIX32 "\n", displacement);
             packedNanoRelocs[packedNanoRelocSz++] = TOKEN_32BIT_OFST;
             packedNanoRelocs[packedNanoRelocSz++] = displacement;
             packedNanoRelocs[packedNanoRelocSz++] = displacement >> 8;
@@ -408,74 +390,269 @@
         }
     }
 
-    //put in app id
-    hdr->appID[0] = appId;
-    hdr->appID[1] = appId >> 32;
-
     //overwrite original relocs and symtab with nanorelocs and adjust sizes
     memcpy(relocs, packedNanoRelocs, packedNanoRelocSz);
     bufUsed -= sizeof(struct RelocEntry[numRelocs]);
     bufUsed -= sizeof(struct SymtabEntry[numSyms]);
     bufUsed += packedNanoRelocSz;
-    hdr->__rel_end = hdr->__rel_start + packedNanoRelocSz;
+    assertMem(bufUsed, bufSz);
+    sect->rel_end = sect->rel_start + packedNanoRelocSz;
 
     //sanity
-    if (hdr->__rel_end - FLASH_BASE != bufUsed) {
+    if (sect->rel_end - FLASH_BASE != bufUsed) {
         fprintf(stderr, "Relocs end and file end not coincident\n");
         goto out;
     }
 
     //adjust headers for easy access (RAM)
-    if (!IS_IN_RAM(hdr->__data_start) || !IS_IN_RAM(hdr->__data_end) || !IS_IN_RAM(hdr->__bss_start) || !IS_IN_RAM(hdr->__bss_end) || !IS_IN_RAM(hdr->__got_start) || !IS_IN_RAM(hdr->__got_end)) {
+    if (!IS_IN_RAM(sect->data_start) || !IS_IN_RAM(sect->data_end) || !IS_IN_RAM(sect->bss_start) ||
+        !IS_IN_RAM(sect->bss_end) || !IS_IN_RAM(sect->got_start) || !IS_IN_RAM(sect->got_end)) {
         fprintf(stderr, "data, bss, or got not in ram\n");
         goto out;
     }
-    hdr->__data_start -= RAM_BASE;
-    hdr->__data_end -= RAM_BASE;
-    hdr->__bss_start -= RAM_BASE;
-    hdr->__bss_end -= RAM_BASE;
-    hdr->__got_start -= RAM_BASE;
-    hdr->__got_end -= RAM_BASE;
+    sect->data_start -= RAM_BASE;
+    sect->data_end -= RAM_BASE;
+    sect->bss_start -= RAM_BASE;
+    sect->bss_end -= RAM_BASE;
+    sect->got_start -= RAM_BASE;
+    sect->got_end -= RAM_BASE;
 
     //adjust headers for easy access (FLASH)
-    if (!IS_IN_FLASH(hdr->__data_data) || !IS_IN_FLASH(hdr->__rel_start) || !IS_IN_FLASH(hdr->__rel_end)) {
-        fprintf(stderr, "data.data, or rel not in ram\n");
+    if (!IS_IN_FLASH(sect->data_data) || !IS_IN_FLASH(sect->rel_start) || !IS_IN_FLASH(sect->rel_end)) {
+        fprintf(stderr, "data.data, or rel not in flash\n");
         goto out;
     }
-    hdr->__data_data -= FLASH_BASE;
-    hdr->__rel_start -= FLASH_BASE;
-    hdr->__rel_end -= FLASH_BASE;
+    sect->data_data -= FLASH_BASE + BINARY_RELOC_OFFSET;
+    sect->rel_start -= FLASH_BASE + BINARY_RELOC_OFFSET;
+    sect->rel_end -= FLASH_BASE + BINARY_RELOC_OFFSET;
+
+    struct ImageHeader outHeader = {
+        .aosp = (struct nano_app_binary_t) {
+            .header_version = 1,
+            .magic = NANOAPP_AOSP_MAGIC,
+            .app_id = appId,
+            .app_version = bin->hdr.appVer,
+            .flags       = 0, // encrypted (1), signed (2) (will be set by other tools)
+        },
+        .layout = (struct ImageLayout) {
+            .magic = GOOGLE_LAYOUT_MAGIC,
+            .version = 1,
+            .payload = LAYOUT_APP,
+            .flags = layoutFlags,
+        },
+    };
+    uint32_t dataOffset = sizeof(outHeader) + sizeof(app);
+    uint32_t hdrDiff = dataOffset - sizeof(*bin);
+    app.sect = bin->sect;
+    app.vec  = bin->vec;
+
+    assertMem(bufUsed + hdrDiff, bufSz);
+
+    memmove(buf + dataOffset, buf + sizeof(*bin), bufUsed - sizeof(*bin));
+    bufUsed += hdrDiff;
+    memcpy(buf, &outHeader, sizeof(outHeader));
+    memcpy(buf + sizeof(outHeader), &app, sizeof(app));
+    sect = &app.sect;
 
     //if we have any bytes to output, show stats
     if (bufUsed) {
-        uint32_t codeAndRoDataSz = hdr->__data_data;
-        uint32_t relocsSz = hdr->__rel_end - hdr->__rel_start;
-        uint32_t gotSz = hdr->__got_end - hdr->__data_start;
-        uint32_t bssSz = hdr->__bss_end - hdr->__bss_start;
+        uint32_t codeAndRoDataSz = sect->data_data;
+        uint32_t relocsSz = sect->rel_end - sect->rel_start;
+        uint32_t gotSz = sect->got_end - sect->data_start;
+        uint32_t bssSz = sect->bss_end - sect->bss_start;
 
-        fprintf(stderr,"Final binary size %u bytes\n", bufUsed);
+        fprintf(stderr,"Final binary size %" PRIu32 " bytes\n", bufUsed);
         fprintf(stderr, "\n");
-        fprintf(stderr, "\tCode + RO data (flash):	     %6u bytes\n", (unsigned)codeAndRoDataSz);
-        fprintf(stderr, "\tRelocs (flash):		     %6u bytes\n", (unsigned)relocsSz);
-        fprintf(stderr, "\tGOT + RW data (flash & RAM): %6u bytes\n", (unsigned)gotSz);
-        fprintf(stderr, "\tBSS (RAM):		     %6u bytes\n", (unsigned)bssSz);
+        fprintf(stderr, "       FW header size (flash):      %6zu bytes\n", FLASH_RELOC_OFFSET);
+        fprintf(stderr, "       Code + RO data (flash):      %6" PRIu32 " bytes\n", codeAndRoDataSz);
+        fprintf(stderr, "       Relocs (flash):              %6" PRIu32 " bytes\n", relocsSz);
+        fprintf(stderr, "       GOT + RW data (flash & RAM): %6" PRIu32 " bytes\n", gotSz);
+        fprintf(stderr, "       BSS (RAM):                   %6" PRIu32 " bytes\n", bssSz);
         fprintf(stderr, "\n");
-        fprintf(stderr,"Runtime flash use: %u bytes\n", codeAndRoDataSz + relocsSz + gotSz);
-        fprintf(stderr,"Runtime RAM use: %u bytes\n", gotSz + bssSz);
+        fprintf(stderr,"Runtime flash use: %" PRIu32 " bytes\n", (uint32_t)(codeAndRoDataSz + relocsSz + gotSz + FLASH_RELOC_OFFSET));
+        fprintf(stderr,"Runtime RAM use: %" PRIu32 " bytes\n", gotSz + bssSz);
     }
 
-    //output the data
-    for (i = 0; i < bufUsed; i++)
-        putchar(buf[i]);
-
-    //success!
-    ret = 0;
+    ret = fwrite(buf, bufUsed, 1, out) == 1 ? 0 : 2;
+    if (ret)
+        fprintf(stderr, "Failed to write output file: %s\n", strerror(errno));
 
 out:
     free(nanoRelocs);
-    free(buf);
     return ret;
 }
 
+static int handleKey(uint8_t **pbuf, uint32_t bufUsed, FILE *out, uint32_t layoutFlags, uint64_t appId, uint64_t keyId)
+{
+    uint8_t *buf = *pbuf;
+    struct KeyInfo ki = { .data = keyId };
+    bool good = true;
 
+    struct ImageHeader outHeader = {
+        .aosp = (struct nano_app_binary_t) {
+            .header_version = 1,
+            .magic = NANOAPP_AOSP_MAGIC,
+            .app_id = appId,
+        },
+        .layout = (struct ImageLayout) {
+            .magic = GOOGLE_LAYOUT_MAGIC,
+            .version = 1,
+            .payload = LAYOUT_KEY,
+            .flags = layoutFlags,
+        },
+    };
 
+    good = good && fwrite(&outHeader, sizeof(outHeader), 1, out) == 1;
+    good = good && fwrite(&ki, sizeof(ki), 1, out) ==  1;
+    good = good && fwrite(buf, bufUsed, 1, out) == 1;
+
+    return good ? 0 : 2;
+}
+
+static int handleOs(uint8_t **pbuf, uint32_t bufUsed, FILE *out, uint32_t layoutFlags, bool bare)
+{
+    uint8_t *buf = *pbuf;
+    bool good;
+
+    struct OsUpdateHdr os = {
+        .magic = OS_UPDT_MAGIC,
+        .marker = OS_UPDT_MARKER_INPROGRESS,
+        .size = bufUsed
+    };
+
+    struct ImageHeader outHeader = {
+        .aosp = (struct nano_app_binary_t) {
+            .header_version = 1,
+            .magic = NANOAPP_AOSP_MAGIC,
+        },
+        .layout = (struct ImageLayout) {
+            .magic = GOOGLE_LAYOUT_MAGIC,
+            .version = 1,
+            .payload = LAYOUT_OS,
+            .flags = layoutFlags,
+        },
+    };
+
+    if (!bare)
+        good = fwrite(&outHeader, sizeof(outHeader), 1, out) == 1;
+    else
+        good = fwrite(&os, sizeof(os), 1, out) == 1;
+    good = good && fwrite(buf, bufUsed, 1, out) == 1;
+
+    return good ? 0 : 2;
+}
+
+int main(int argc, char **argv)
+{
+    uint32_t bufUsed = 0;
+    bool verbose = false;
+    uint8_t *buf = NULL;
+    uint64_t appId = 0;
+    uint64_t keyId = 0;
+    uint32_t layoutId = 0;
+    uint32_t layoutFlags = 0;
+    int ret = -1;
+    uint32_t *u32Arg = NULL;
+    uint64_t *u64Arg = NULL;
+    const char **strArg = NULL;
+    const char *appName = argv[0];
+    int posArgCnt = 0;
+    const char *posArg[2] = { NULL };
+    FILE *out = NULL;
+    const char *layoutName = "app";
+    const char *prev = NULL;
+    bool bareData = false;
+
+    for (int i = 1; i < argc; i++) {
+        char *end = NULL;
+        if (argv[i][0] == '-') {
+            prev = argv[i];
+            if (!strcmp(argv[i], "-v"))
+                verbose = true;
+            else if (!strcmp(argv[i], "-r"))
+                bareData = true;
+            else if (!strcmp(argv[i], "-a"))
+                u64Arg = &appId;
+            else if (!strcmp(argv[i], "-k"))
+                u64Arg = &keyId;
+            else if (!strcmp(argv[i], "-n"))
+                strArg = &layoutName;
+            else if (!strcmp(argv[i], "-i"))
+                u32Arg = &layoutId;
+            else if (!strcmp(argv[i], "-f"))
+                u32Arg = &layoutFlags;
+            else
+                fatalUsage(appName, "unknown argument", argv[i]);
+        } else {
+            if (u64Arg) {
+                uint64_t tmp = strtoull(argv[i], &end, 16);
+                if (*end == '\0')
+                    *u64Arg = tmp;
+                u64Arg = NULL;
+            } else if (u32Arg) {
+                uint32_t tmp = strtoul(argv[i], &end, 16);
+                if (*end == '\0')
+                    *u32Arg = tmp;
+                u32Arg = NULL;
+            } else if (strArg) {
+                    *strArg = argv[i];
+                strArg = NULL;
+            } else {
+                if (posArgCnt < 2)
+                    posArg[posArgCnt++] = argv[i];
+                else
+                    fatalUsage(appName, "too many positional arguments", argv[i]);
+            }
+            prev = NULL;
+        }
+    }
+    if (prev)
+        fatalUsage(appName, "missing argument after", prev);
+
+    if (!posArgCnt)
+        fatalUsage(appName, "missing input file name", NULL);
+
+    if (!layoutId) {
+        if (strcmp(layoutName, "app") == 0)
+            layoutId = LAYOUT_APP;
+        else if (strcmp(layoutName, "os") == 0)
+            layoutId = LAYOUT_OS;
+        else if (strcmp(layoutName, "key") == 0)
+            layoutId = LAYOUT_KEY;
+        else
+            fatalUsage(appName, "Invalid layout name", layoutName);
+    }
+
+    if (layoutId == LAYOUT_APP && !appId)
+        fatalUsage(appName, "App layout requires app ID", NULL);
+    if (layoutId == LAYOUT_KEY && !keyId)
+        fatalUsage(appName, "Key layout requires key ID", NULL);
+    if (layoutId == LAYOUT_OS && (keyId || appId))
+        fatalUsage(appName, "OS layout does not need any ID", NULL);
+
+    buf = loadFile(posArg[0], &bufUsed);
+    fprintf(stderr, "Read %" PRIu32 " bytes\n", bufUsed);
+
+    if (!posArg[1])
+        out = stdout;
+    else
+        out = fopen(posArg[1], "w");
+    if (!out)
+        fatalUsage(appName, "failed to create/open output file", posArg[1]);
+
+    switch(layoutId) {
+    case LAYOUT_APP:
+        ret = handleApp(&buf, bufUsed, out, layoutFlags, appId, verbose);
+        break;
+    case LAYOUT_KEY:
+        ret = handleKey(&buf, bufUsed, out, layoutFlags, appId, keyId);
+        break;
+    case LAYOUT_OS:
+        ret = handleOs(&buf, bufUsed, out, layoutFlags, bareData);
+        break;
+    }
+
+    free(buf);
+    fclose(out);
+    return ret;
+}
diff --git a/util/nanoapp_prepare.sh b/util/nanoapp_prepare.sh
index cb8a535..546b698 100755
--- a/util/nanoapp_prepare.sh
+++ b/util/nanoapp_prepare.sh
@@ -19,60 +19,20 @@
 # Exit in error if we use an undefined variable (i.e. commit a typo).
 set -u
 
-terminate() { #cleanup and exit
-	rm -rf $stage
-	exit $1
-}
-
 usage () { #show usage and bail out
 	echo "USAGE:" >&2
-	echo "    $1 [-e <ENCR_KEY_NUM> <ENCR_KEY_FILE>] [-s <PRIV_KEY_FILE> <PUB_KEY_FILE> [<SIG_TO_CHAIN_1> [<SIG_TO_CHAIN_2> [...]]]] < app.napp > app.final.napp" >&2
-	terminate -1
+	echo "    $1 <app.napp> [-e <ENCR_KEY_NUM> <ENCR_KEY_FILE>] [-s <PRIV_KEY_FILE> <PUB_KEY_FILE> [<SIG_TO_CHAIN_1> [<SIG_TO_CHAIN_2> [...]]]]" >&2
+	exit 1
 }
 
-putchar() {
-	hexch="0123456789abcdef"
-	h=$[$1/16]
-	l=$[$1%16]
-	h=${hexch:$h:1}
-	l=${hexch:$l:1}
-	e="\x"$h$l
-	echo -ne $e
-}
-
-printhex() {
-	w3=$[$1/16777216]
-	t=$[$w3*16777216]
-	a=$[$1-$t]
-
-	w2=$[$a/65536]
-	t=$[$w2*65536]
-	a=$[$a-$t]
-
-	w1=$[$a/256]
-	w0=$[$a%256]
-
-	putchar $w0
-	putchar $w1
-	putchar $w2
-	putchar $w3
-}
-
-#save args and create temp dir
-stage=$(mktemp -dt "$(basename $0).XXXXXXXXXX")
-args=( "$@" )
-
-#sanity checks (on the user)
-if [ -t 1 ]
-then
-	usage $0
+if [ $# -ge 1 ] ; then
+app=${1%.napp}
+shift
+else
+usage $0
 fi
 
-if [ -t 0 ]
-then
-	usage $0
-fi
-
+args=( $@ )
 
 #get encryption key if it exists & encrypt app
 encr_key_num=""
@@ -92,16 +52,11 @@
 			usage $0
 		fi
 
-		nanoapp_encr encr "$encr_key_num" "$encr_key_file" > "$stage/postencr"
+		nanoapp_encr -e -i "$encr_key_num" -k "$encr_key_file" "${app}.napp" "${app}.encr.napp"
+		app="${app}.encr"
 	fi
 fi
 
-
-#if app is not encrypted, just copy it to staging area
-if [ ! -f "$stage/postencr" ]; then
-	cat > "$stage/postencr"
-fi
-
 #handle signing
 if [ ${#args[@]} -ge 1 ]
 then
@@ -124,50 +79,16 @@
 			i=$[$i+1]
 		done
 
-		#get and save file size
-		signed_sz=$(du -b "$stage/postencr" | cut -f1)
-
-		nanoapp_sign sign "$priv1" "$pub1" < "$stage/postencr" > "$stage/sig"
-
-		#pad data to 16 bytes
-		t=$signed_sz
-		while [ $[$t%16] -ne 0 ]
-		do
-			echo -ne "\0" >> "$stage/postencr"
-			t=$(du -b "$stage/postencr" | cut -f1)
-		done
-
-		#produce signed output
-		cat "$stage/postencr" "$stage/sig" "$pub1" > "$stage/signed"
+		nanoapp_sign -s -e "$priv1" -m "$pub1" "${app}.napp" "${app}.sign.napp"
 
 		#append remaining chunks
 		i=3
 		while [ $i -lt ${#args[@]} ]
 		do
-			cat "${args[$i]}" >> "$stage/signed"
+			cat "${args[$i]}" >> "${app}.sign.napp"
 			i=$[$i+1]
 		done
-
-		#create header
-		num_sigs=$[${#args[@]}-2]
-
-		echo -n SigndApp > "$stage/finished"
-		printhex $signed_sz >> "$stage/finished"
-		printhex $num_sigs >> "$stage/finished"
-		echo -ne "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" >> "$stage/finished"
-		cat "$stage/signed" >> "$stage/finished"
 	else
 		usage $0
 	fi
 fi
-
-#if app is not signed, just copy it to staging area
-if [ ! -f "$stage/finished" ]; then
-	mv "$stage/postencr" "$stage/finished"
-fi
-
-#produce output
-cat "$stage/finished"
-
-terminate 0
-
diff --git a/util/nanoapp_sign/Android.mk b/util/nanoapp_sign/Android.mk
new file mode 100644
index 0000000..4a9cd96
--- /dev/null
+++ b/util/nanoapp_sign/Android.mk
@@ -0,0 +1,46 @@
+#
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+    ../../lib/nanohub/rsa.c \
+    ../../lib/nanohub/sha2.c \
+    ../../lib/nanohub/nanoapp.c \
+    nanoapp_sign.c \
+
+
+LOCAL_CFLAGS := \
+    -Wall \
+    -Werror \
+    -Wextra \
+    -DRSA_SUPPORT_PRIV_OP_BIGRAM \
+    -DHOST_BUILD \
+    -DBOOTLOADER= \
+    -DBOOTLOADER_RO= \
+
+
+LOCAL_C_INCLUDES := \
+    device/google/contexthub/lib/include \
+
+
+LOCAL_MODULE := nanoapp_sign
+
+LOCAL_MODULE_TAGS := optional
+
+include $(BUILD_HOST_EXECUTABLE)
diff --git a/util/nanoapp_sign/Makefile b/util/nanoapp_sign/Makefile
index 480c57f..d3310c3 100644
--- a/util/nanoapp_sign/Makefile
+++ b/util/nanoapp_sign/Makefile
@@ -15,12 +15,14 @@
 #
 
 APP = nanoapp_sign
-SRC = nanoapp_sign.c ../../firmware/src/rsa.c ../../firmware/src/sha2.c
+SRC = nanoapp_sign.c ../../lib/nanohub/rsa.c ../../lib/nanohub/sha2.c
 CC ?= gcc
 CC_FLAGS = -Wall -Werror -Wextra
 
 $(APP): $(SRC) Makefile
-	$(CC) $(CC_FLAGS) -o $(APP) -O2 $(SRC) -I../../firmware/inc -DRSA_SUPPORT_PRIV_OP_BIGRAM -DHOST_BUILD -DBOOTLOADER= -DBOOTLOADER_RO=
+	$(CC) $(CC_FLAGS) -o $(APP) -O2 $(SRC) \
+	        -I../../lib/include \
+	        -DRSA_SUPPORT_PRIV_OP_BIGRAM -DHOST_BUILD -DBOOTLOADER= -DBOOTLOADER_RO=
 
 clean:
 	rm -f $(APP)
diff --git a/util/nanoapp_sign/nanoapp_sign.c b/util/nanoapp_sign/nanoapp_sign.c
index 65f7627..f4304c2 100644
--- a/util/nanoapp_sign/nanoapp_sign.c
+++ b/util/nanoapp_sign/nanoapp_sign.c
@@ -19,25 +19,45 @@
 #include <stdint.h>
 #include <stdlib.h>
 #include <stdio.h>
-#include <sha2.h>
-#include <rsa.h>
 
+#include <nanohub/nanohub.h>
+#include <nanohub/nanoapp.h>
+#include <nanohub/sha2.h>
+#include <nanohub/rsa.h>
 
 static FILE* urandom = NULL;
 
+#if defined(__APPLE__) || defined(_WIN32)
+inline uint32_t bswap32 (uint32_t x) {
+    uint32_t out = 0;
+    for (int i=0; i < 4; ++i, x >>= 8)
+        out = (out << 8) | (x & 0xFF);
+    return out;
+}
+
+#define htobe32(x)  bswap32((x))
+#define htole32(x)  ((uint32_t)(x))
+#define be32toh(x)  bswap32((x))
+#define le32toh(x)  ((uint32_t)(x))
+#else
+#include <endian.h>
+#endif
+
 //read exactly one hex-encoded byte from a file, skipping all the fluff
-static int getHexEncodedByte(void)
+static int getHexEncodedByte(uint8_t *buf, uint32_t *ppos, uint32_t size)
 {
     int c, i;
+    uint32_t pos = *ppos;
     uint8_t val = 0;
 
     //for first byte
     for (i = 0; i < 2; i++) {
         val <<= 4;
         while(1) {
-            c = getchar();
-            if (c == EOF)
+            if (pos == size)
                 return -1;
+            c = buf[pos++];
+            *ppos = pos;
 
             if (c >= '0' && c <= '9')
                 val += c - '0';
@@ -60,31 +80,6 @@
     return val;
 }
 
-static bool readRsaDataFile(const char *fileName, uint32_t *buf)
-{
-    uint32_t sz = sizeof(uint32_t[RSA_LIMBS]);
-    FILE *f = fopen(fileName, "rb");
-    bool ret = false;
-
-    if (!f)
-        goto out_noclose;
-
-    if (sz != fread(buf, 1, sz, f))
-        goto out;
-
-    //verify file is empty
-    if (fread(&sz, 1, 1, f))
-        goto out;
-
-    ret = true;
-
-out:
-    fclose(f);
-
-out_noclose:
-    return ret;
-}
-
 //provide a random number for which the following property is true ((ret & 0xFF000000) && (ret & 0xFF0000) && (ret & 0xFF00) && (ret & 0xFF))
 static uint32_t rand32_no_zero_bytes(void)
 {
@@ -119,251 +114,430 @@
         fclose(urandom);
 }
 
-int main(int argc, char **argv)
-{
-    const char *selfExeName = argv[0];
-    const uint32_t *hash, *rsaResult;
-    uint32_t rsanum[RSA_LIMBS];
+struct RsaData {
+    uint32_t num[RSA_LIMBS];
     uint32_t exponent[RSA_LIMBS];
     uint32_t modulus[RSA_LIMBS];
-    struct Sha2state shaState;
-    struct RsaState rsaState;
-    uint8_t buf[RSA_BYTES];
-    uint64_t fileSz = 0;
-    bool verbose = false;
-    int c, ret = -1;
-    unsigned int i;
+    struct RsaState state;
+};
 
-    argc--;
-    argv++;
+static bool validateSignature(uint8_t *sigPack, struct RsaData *rsa, bool verbose, uint32_t *refHash, bool preset)
+{
+    int i;
+    const uint32_t *rsaResult;
+    const uint32_t *le32SigPack = (const uint32_t*)sigPack;
+    //convert to native uint32_t; ignore possible alignment issues
+    for (i = 0; i < RSA_LIMBS; i++)
+        rsa->num[i] = le32toh(le32SigPack[i]);
+    //update the user
+    if (verbose)
+        printHashRev(stderr, "RSA cyphertext", rsa->num, RSA_LIMBS);
+    if (!preset)
+        memcpy(rsa->modulus, sigPack + RSA_BYTES, RSA_BYTES);
 
-    if (argc >= 1 && !strcmp(argv[0], "-v")) {
-        verbose = true;
-        argc--;
-        argv++;
+    //do rsa op
+    rsaResult = rsaPubOp(&rsa->state, rsa->num, rsa->modulus);
+
+    //update the user
+    if (verbose)
+        printHashRev(stderr, "RSA plaintext", rsaResult, RSA_LIMBS);
+
+    //verify padding is appropriate and valid
+    if ((rsaResult[RSA_LIMBS - 1] & 0xffff0000) != 0x00020000) {
+        fprintf(stderr, "Padding header is invalid\n");
+        return false;
     }
 
-    if (argc < 1)
-        goto usage;
-
-    if (!strcmp(argv[0], "txt2bin")) {
-
-        bool  haveNonzero = false;
-        uint32_t v;
-
-        for (i = 0; i < RSA_BYTES; i++) {
-
-            //get a byte, skipping all zeroes (openssl likes to prepend one at times)
-            do {
-                c = getHexEncodedByte();
-            } while (c == 0 && !haveNonzero);
-            haveNonzero = true;
-            if (c < 0) {
-                fprintf(stderr, "Invalid text RSA input data\n");
-                goto usage;
-            }
-
-            buf[i] = c;
-        }
-
-        for (i = 0; i < RSA_LIMBS; i++) {
-            for (v = 0, c = 0; c < 4; c++)
-                v = (v << 8) | buf[i * 4 + c];
-            rsanum[RSA_LIMBS - i - 1] = v;
-        }
-
-        //output in our binary format (little-endina)
-        fwrite(rsanum, 1, sizeof(uint32_t[RSA_LIMBS]), stdout);
-    }
-    else if (!strcmp(argv[0], "sigdecode")) {
-        if (argc < 2)
-            goto usage;
-
-        //get modulus
-        if (!readRsaDataFile(argv[1], modulus)) {
-            fprintf(stderr, "failed to read RSA modulus\n");
-            goto usage;
-        }
-
-        //update the user
-        if (verbose) {
-            fprintf(stderr, "RSA modulus: 0x");
-            for (i = 0; i < RSA_LIMBS; i++)
-                fprintf(stderr, "%08lx", (unsigned long)modulus[RSA_LIMBS - i - 1]);
-            fprintf(stderr, "\n");
-        }
-
-        //read input data as bytes
-        i = 0;
-        while ((c = getchar()) != EOF) {
-            if (i == RSA_BYTES) {
-                fprintf(stderr, "too many signature bytes\n");
-                goto usage;
-            }
-            buf[i++] = c;
-        }
-        if (i != RSA_BYTES) {
-            fprintf(stderr, "too few signature bytes\n");
-            goto usage;
-        }
-
-        //convert into uint32_ts (just read as little endian and pack)
-        for (i = 0; i < RSA_LIMBS; i++) {
-            uint32_t v, j;
-
-            for (v = 0, j = 0; j < 4; j++)
-                v = (v << 8) | buf[i * 4 + 3 - j];
-
-            rsanum[i] = v;
-        }
-
-        //update the user
-        if (verbose) {
-            fprintf(stderr, "RSA cyphertext: 0x");
-            for (i = 0; i < RSA_LIMBS; i++)
-                fprintf(stderr, "%08lx", (unsigned long)rsanum[RSA_LIMBS - i - 1]);
-            fprintf(stderr, "\n");
-        }
-
-        //do rsa op
-        rsaResult = rsaPubOp(&rsaState, rsanum, modulus);
-
-        //update the user
-        if (verbose) {
-            fprintf(stderr, "RSA plaintext: 0x");
-            for (i = 0; i < RSA_LIMBS; i++)
-                fprintf(stderr, "%08lx", (unsigned long)rsaResult[RSA_LIMBS - i - 1]);
-            fprintf(stderr, "\n");
-        }
-
-        //verify padding is appropriate and valid
-        if ((rsaResult[RSA_LIMBS - 1] & 0xffff0000) != 0x00020000) {
-            fprintf(stderr, "Padding header is invalid\n");
-            goto out;
-        }
-
-        //verify first two bytes of padding
-        if (!(rsaResult[RSA_LIMBS - 1] & 0xff00) || !(rsaResult[RSA_LIMBS - 1] & 0xff)) {
-            fprintf(stderr, "Padding bytes 0..1 are invalid\n");
-            goto out;
-        }
-
-        //verify last 3 bytes of padding and the zero terminator
-        if (!(rsaResult[8] & 0xff000000) || !(rsaResult[8] & 0xff0000) || !(rsaResult[8] & 0xff00) || (rsaResult[8] & 0xff)) {
-            fprintf(stderr, "Padding last bytes & terminator invalid\n");
-            goto out;
-        }
-
-        //verify middle padding bytes
-        for (i = 9; i < RSA_LIMBS - 1; i++) {
-            if (!(rsaResult[i] & 0xff000000) || !(rsaResult[i] & 0xff0000) || !(rsaResult[i] & 0xff00) || !(rsaResult[i] & 0xff)) {
-                fprintf(stderr, "Padding word %d invalid\n", i);
-                goto out;
-            }
-        }
-
-        //show the hash
-        for (i = 0; i < 8; i++)
-            printf("%08lx", (unsigned long)rsaResult[i]);
-        printf("\n");
-        ret = 0;
-    }
-    else if (!strcmp(argv[0], "sign")) {
-        if (argc != 3)
-            goto usage;
-
-        //it might not matter, but we still like to try to cleanup after ourselves
-        (void)atexit(cleanup);
-
-        //prepare & get RSA info
-        if (!readRsaDataFile(argv[1], exponent)) {
-            fprintf(stderr, "failed to read RSA private exponent\n");
-            goto usage;
-        }
-
-        if (!readRsaDataFile(argv[2], modulus)) {
-            fprintf(stderr, "failed to read RSA modulus\n");
-            goto usage;
-        }
-
-        //update the user
-        if (verbose) {
-            fprintf(stderr, "RSA modulus: 0x");
-            for (i = 0; i < RSA_LIMBS; i++)
-                fprintf(stderr, "%08lx", (unsigned long)modulus[RSA_LIMBS - i - 1]);
-            fprintf(stderr, "\n");
-        }
-
-        //hash input
-        sha2init(&shaState);
-        fprintf(stderr, "Reading data to sign...");
-        while ((c = getchar()) != EOF) {  //this is slow but our data is small, so deal with it!
-            uint8_t byte = c;
-            sha2processBytes(&shaState, &byte, 1);
-            fileSz++;
-        }
-        fprintf(stderr, " read %llu bytes\n", (unsigned long long)fileSz);
-
-        //update the user on the progress
-        hash = sha2finish(&shaState);
-        if (verbose) {
-            fprintf(stderr, "SHA2 hash: 0x");
-            for (i = 0; i < 8; i++)
-                fprintf(stderr, "%08lx", (unsigned long)hash[i]);
-            fprintf(stderr, "\n");
-        }
-
-        //write into our "data to sign" area
-        for (i = 0; i < 8; i++)
-            rsanum[i] = hash[i];
-
-        //write padding
-        rsanum[i++] = rand32_no_zero_bytes() << 8; //low byte here must be zero as per padding spec
-        for (;i < RSA_LIMBS - 1; i++)
-            rsanum[i] = rand32_no_zero_bytes();
-        rsanum[i] = (rand32_no_zero_bytes() >> 16) | 0x00020000; //as per padding spec
-
-        //update the user
-        if (verbose) {
-            fprintf(stderr, "RSA plaintext: 0x");
-            for (i = 0; i < RSA_LIMBS; i++)
-                fprintf(stderr, "%08lx", (unsigned long)rsanum[RSA_LIMBS - i - 1]);
-            fprintf(stderr, "\n");
-        }
-
-        //do the RSA thing
-        fprintf(stderr, "Retriculating splines...");
-        rsaResult = rsaPrivOp(&rsaState, rsanum, exponent, modulus);
-        fprintf(stderr, "DONE\n");
-
-        //update the user
-        if (verbose) {
-            fprintf(stderr, "RSA cyphertext: 0x");
-            for (i = 0; i < RSA_LIMBS; i++)
-                fprintf(stderr, "%08lx", (unsigned long)rsaResult[RSA_LIMBS - i - 1]);
-            fprintf(stderr, "\n");
-        }
-
-        //output in a format that our microcontroller will be able to digest easily & directly (an array of bytes representing little-endian 32-bit words)
-        fwrite(rsaResult, 1, sizeof(uint32_t[RSA_LIMBS]), stdout);
-
-        fprintf(stderr, "success\n");
-        ret = 0;
+    //verify first two bytes of padding
+    if (!(rsaResult[RSA_LIMBS - 1] & 0xff00) || !(rsaResult[RSA_LIMBS - 1] & 0xff)) {
+        fprintf(stderr, "Padding bytes 0..1 are invalid\n");
+        return false;
     }
 
-out:
-    return ret;
+    //verify last 3 bytes of padding and the zero terminator
+    if (!(rsaResult[8] & 0xff000000) || !(rsaResult[8] & 0xff0000) || !(rsaResult[8] & 0xff00) || (rsaResult[8] & 0xff)) {
+        fprintf(stderr, "Padding last bytes & terminator invalid\n");
+        return false;
+    }
 
-usage:
-    fprintf(stderr, "USAGE: %s [-v] sign <BINARY_PRIVATE_EXPONENT_FILE> <BINARY_MODULUS_FILE> < data_to_sign > signature_out\n"
-                    "       %s [-v] sigdecode <BINARY_MODULUS_FILE> < signature_out > expected_sha2\n"
-                    "       %s [-v] txt2bin < TEXT_RSA_FILE > BINARY_RSA_FILE\n"
-                    "\t<BINARY_PRIVATE_EXPONENT> and <BINARY_MODULUS> files contain RSA numbers, binary little endian format\n"
-                    "\t<TEXT_RSA_FILE> file contain RSA numbers, text, big-endian format as exported by 'openssl rsa -in YOURKEY -text -noout'\n",
-                    selfExeName, selfExeName, selfExeName);
-    return -1;
+    //verify middle padding bytes
+    for (i = 9; i < RSA_LIMBS - 1; i++) {
+        if (!(rsaResult[i] & 0xff000000) || !(rsaResult[i] & 0xff0000) || !(rsaResult[i] & 0xff00) || !(rsaResult[i] & 0xff)) {
+            fprintf(stderr, "Padding word %d invalid\n", i);
+            return false;
+        }
+    }
+    if (verbose) {
+        printHash(stderr, "Recovered hash ", rsaResult, SHA2_HASH_WORDS);
+        printHash(stderr, "Calculated hash", refHash, SHA2_HASH_WORDS);
+    }
+
+    if (!preset) {
+        // we're doing full verification, with key extracted from signature pack
+        if (memcmp(rsaResult, refHash, SHA2_HASH_SIZE)) {
+            fprintf(stderr, "hash mismatch\n");
+            return false;
+        }
+    } else {
+        // we just decode the signature with key passed as an argument
+        // in this case we return recovered hash
+        memcpy(refHash, rsaResult, SHA2_HASH_SIZE);
+    }
+    return true;
 }
 
+#define SIGNATURE_BLOCK_SIZE    (2 * RSA_BYTES)
 
+static int handleConvertKey(uint8_t **pbuf, uint32_t bufUsed, FILE *out, struct RsaData *rsa)
+{
+    bool  haveNonzero = false;
+    uint8_t *buf = *pbuf;
+    int i, c;
+    uint32_t pos = 0;
+    int ret;
 
+    for (i = 0; i < (int)RSA_BYTES; i++) {
 
+        //get a byte, skipping all zeroes (openssl likes to prepend one at times)
+        do {
+            c = getHexEncodedByte(buf, &pos, bufUsed);
+        } while (c == 0 && !haveNonzero);
+        haveNonzero = true;
+        if (c < 0) {
+            fprintf(stderr, "Invalid text RSA input data\n");
+            return 2;
+        }
+
+        buf[i] = c;
+    }
+
+    // change form BE to native; ignore alignment
+    uint32_t *be32Buf = (uint32_t*)buf;
+    for (i = 0; i < RSA_LIMBS; i++)
+        rsa->num[RSA_LIMBS - i - 1] = be32toh(be32Buf[i]);
+
+    //output in our binary format (little-endian)
+    ret = fwrite(rsa->num, 1, RSA_BYTES, out) == RSA_BYTES ? 0 : 2;
+    fprintf(stderr, "Conversion status: %d\n", ret);
+
+    return ret;
+}
+
+static int handleVerify(uint8_t **pbuf, uint32_t bufUsed, struct RsaData *rsa, bool verbose, bool bareData)
+{
+    struct Sha2state shaState;
+    uint8_t *buf = *pbuf;
+    uint32_t masterPubKey[RSA_LIMBS];
+
+    memcpy(masterPubKey, rsa->modulus, RSA_BYTES);
+    if (!bareData) {
+        struct ImageHeader *image = (struct ImageHeader *)buf;
+        struct AppSecSignHdr *secHdr = (struct AppSecSignHdr *)&image[1];
+        int block = 0;
+        uint8_t *sigPack;
+        bool trusted = false;
+        bool lastTrusted = false;
+        int sigData;
+
+        if (verbose)
+            fprintf(stderr, "Original Data len=%" PRIu32 " b; file size=%" PRIu32 " b; diff=%" PRIu32 " b\n",
+                    secHdr->appDataLen, bufUsed, bufUsed - secHdr->appDataLen);
+
+        if (!(image->aosp.flags & NANOAPP_SIGNED_FLAG)) {
+            fprintf(stderr, "image is not marked as signed, can not verify\n");
+            return 2;
+        }
+        sigData = bufUsed - (secHdr->appDataLen + sizeof(*image) + sizeof(*secHdr));
+        if (sigData <= 0 || (sigData % SIGNATURE_BLOCK_SIZE) != 0) {
+            fprintf(stderr, "Invalid signature header: data size mismatch\n");
+            return 2;
+        }
+
+        sha2init(&shaState);
+        sha2processBytes(&shaState, buf, bufUsed - sigData);
+        int nSig = sigData / SIGNATURE_BLOCK_SIZE;
+        sigPack = buf + bufUsed - sigData;
+        for (block = 0; block < nSig; ++block) {
+            if (!validateSignature(sigPack, rsa, verbose, (uint32_t*)sha2finish(&shaState), false)) {
+                fprintf(stderr, "Signature verification failed: signature block #%d\n", block);
+                return 2;
+            }
+            if (memcmp(masterPubKey, rsa->modulus, RSA_BYTES) == 0) {
+                fprintf(stderr, "Key in block %d is trusted\n", block);
+                trusted = true;
+                lastTrusted = true;
+            } else {
+                lastTrusted = false;
+            }
+            sha2init(&shaState);
+            sha2processBytes(&shaState, sigPack+RSA_BYTES, RSA_BYTES);
+            sigPack += SIGNATURE_BLOCK_SIZE;
+        }
+        if (trusted && !lastTrusted) {
+            fprintf(stderr, "Trusted key is not the last in key sequence\n");
+        }
+        return trusted ? 0 : 2;
+    } else {
+        uint8_t *sigPack = buf + bufUsed - SIGNATURE_BLOCK_SIZE;
+        uint32_t *hash;
+        // can not do signature chains in bare mode
+        if (bufUsed > SIGNATURE_BLOCK_SIZE) {
+            sha2init(&shaState);
+            sha2processBytes(&shaState, buf, bufUsed - SIGNATURE_BLOCK_SIZE);
+            hash = (uint32_t*)sha2finish(&shaState);
+            printHash(stderr, "File hash", hash, SHA2_HASH_WORDS);
+            if (verbose)
+                printHashRev(stderr, "File PubKey", (uint32_t *)(sigPack + RSA_BYTES), RSA_LIMBS);
+            if (!validateSignature(sigPack, rsa, verbose, hash, false)) {
+                fprintf(stderr, "Signature verification failed on raw data\n");
+                return 2;
+            }
+            if (memcmp(masterPubKey, sigPack + RSA_BYTES, RSA_BYTES) == 0) {
+                fprintf(stderr, "Signature verification passed and the key is trusted\n");
+                return 0;
+            } else {
+                fprintf(stderr, "Signature verification passed but the key is not trusted\n");
+                return 2;
+            }
+        } else {
+            fprintf(stderr, "Not enough raw data to extract signature from\n");
+            return 2;
+        }
+    }
+
+    return 0;
+}
+
+static int handleSign(uint8_t **pbuf, uint32_t bufUsed, FILE *out, struct RsaData *rsa, bool verbose, bool bareData)
+{
+    struct Sha2state shaState;
+    uint8_t *buf = *pbuf;
+    uint32_t i;
+    const uint32_t *hash;
+    const uint32_t *rsaResult;
+    int ret;
+
+    if (!bareData) {
+        struct ImageHeader *image = (struct ImageHeader *)buf;
+        struct AppSecSignHdr *secHdr = (struct AppSecSignHdr *)&image[1];
+        uint32_t grow = sizeof(*secHdr);
+        if (!(image->aosp.flags & NANOAPP_SIGNED_FLAG)) {
+            // this is the 1st signature in the chain; inject header, set flag
+            buf = reallocOrDie(buf, bufUsed + grow);
+            *pbuf = buf;
+            image = (struct ImageHeader *)buf;
+            secHdr = (struct AppSecSignHdr *)&image[1];
+
+            fprintf(stderr, "Generating signature header\n");
+            image->aosp.flags |= NANOAPP_SIGNED_FLAG;
+            memmove((uint8_t*)&image[1] + grow, &image[1], bufUsed - sizeof(*image));
+            secHdr->appDataLen = bufUsed - sizeof(*image);
+            bufUsed += grow;
+            fprintf(stderr, "Rehashing file\n");
+            sha2init(&shaState);
+            sha2processBytes(&shaState, buf, bufUsed);
+        } else {
+            int sigSz = bufUsed - sizeof(*image) - sizeof(*secHdr) - secHdr->appDataLen;
+            int numSigs = sigSz / SIGNATURE_BLOCK_SIZE;
+            if ((numSigs * SIGNATURE_BLOCK_SIZE) != sigSz) {
+                fprintf(stderr, "Invalid signature block(s) detected\n");
+                return 2;
+            } else {
+                fprintf(stderr, "Found %d appended signature(s)\n", numSigs);
+                // generating SHA256 of the last PubKey in chain
+                fprintf(stderr, "Hashing last signature's PubKey\n");
+                sha2init(&shaState);
+                sha2processBytes(&shaState, buf + bufUsed- RSA_BYTES, RSA_BYTES);
+            }
+        }
+    } else {
+        fprintf(stderr, "Signing raw data\n");
+        sha2init(&shaState);
+        sha2processBytes(&shaState, buf, bufUsed);
+    }
+
+    //update the user on the progress
+    hash = sha2finish(&shaState);
+    if (verbose)
+        printHash(stderr, "SHA2 hash", hash, SHA2_HASH_WORDS);
+
+    memcpy(rsa->num, hash, SHA2_HASH_SIZE);
+
+    i = SHA2_HASH_WORDS;
+    //write padding
+    rsa->num[i++] = rand32_no_zero_bytes() << 8; //low byte here must be zero as per padding spec
+    for (;i < RSA_LIMBS - 1; i++)
+        rsa->num[i] = rand32_no_zero_bytes();
+    rsa->num[i] = (rand32_no_zero_bytes() >> 16) | 0x00020000; //as per padding spec
+
+    //update the user
+    if (verbose)
+        printHashRev(stderr, "RSA plaintext", rsa->num, RSA_LIMBS);
+
+    //do the RSA thing
+    fprintf(stderr, "Retriculating splines...");
+    rsaResult = rsaPrivOp(&rsa->state, rsa->num, rsa->exponent, rsa->modulus);
+    fprintf(stderr, "DONE\n");
+
+    //update the user
+    if (verbose)
+        printHashRev(stderr, "RSA cyphertext", rsaResult, RSA_LIMBS);
+
+    // output in a format that our microcontroller will be able to digest easily & directly
+    // (an array of bytes representing little-endian 32-bit words)
+    fwrite(buf, 1, bufUsed, out);
+    fwrite(rsaResult, 1, sizeof(uint32_t[RSA_LIMBS]), out);
+    ret = (fwrite(rsa->modulus, 1, RSA_BYTES, out) == RSA_BYTES) ? 0 : 2;
+
+    fprintf(stderr, "Status: %s (%d)\n", ret == 0 ? "success" : "failed", ret);
+    return ret;
+
+}
+
+static void fatalUsage(const char *name, const char *msg, const char *arg)
+{
+    if (msg && arg)
+        fprintf(stderr, "Error: %s: %s\n\n", msg, arg);
+    else if (msg)
+        fprintf(stderr, "Error: %s\n\n", msg);
+
+    fprintf(stderr, "USAGE: %s [-v] [-e <pvt key>] [-m <pub key>] [-t] [-s] [-b] <input file> [<output file>]\n"
+                    "       -v : be verbose\n"
+                    "       -b : generate binary key from text file created by OpenSSL\n"
+                    "       -s : sign post-processed file\n"
+                    "       -t : verify signature of signed post-processed file\n"
+                    "       -e : RSA binary private key\n"
+                    "       -m : RSA binary public key\n"
+                    "       -r : do not parse headers, do not generate headers (with -t, -s)\n"
+                    , name);
+    exit(1);
+}
+
+int main(int argc, char **argv)
+{
+    uint32_t bufUsed = 0;
+    uint8_t *buf = NULL;
+    int ret = -1;
+    const char **strArg = NULL;
+    const char *appName = argv[0];
+    const char *posArg[2] = { NULL };
+    uint32_t posArgCnt = 0;
+    FILE *out = NULL;
+    const char *prev = NULL;
+    bool verbose = false;
+    bool sign = false;
+    bool verify = false;
+    bool txt2bin = false;
+    bool bareData = false;
+    const char *keyPvtFile = NULL;
+    const char *keyPubFile = NULL;
+    int multi = 0;
+    struct RsaData rsa;
+    struct ImageHeader *image;
+
+    //it might not matter, but we still like to try to cleanup after ourselves
+    (void)atexit(cleanup);
+
+    for (int i = 1; i < argc; i++) {
+        if (argv[i][0] == '-') {
+            prev = argv[i];
+            if (!strcmp(argv[i], "-v"))
+                verbose = true;
+            else if (!strcmp(argv[i], "-s"))
+                sign = true;
+            else if (!strcmp(argv[i], "-t"))
+                verify = true;
+            else if (!strcmp(argv[i], "-b"))
+                txt2bin = true;
+            else if (!strcmp(argv[i], "-e"))
+                strArg = &keyPvtFile;
+            else if (!strcmp(argv[i], "-m"))
+                strArg = &keyPubFile;
+            else if (!strcmp(argv[i], "-r"))
+                bareData = true;
+            else
+                fatalUsage(appName, "unknown argument", argv[i]);
+        } else {
+            if (strArg) {
+                    *strArg = argv[i];
+                strArg = NULL;
+            } else {
+                if (posArgCnt < 2)
+                    posArg[posArgCnt++] = argv[i];
+                else
+                    fatalUsage(appName, "too many positional arguments", argv[i]);
+            }
+            prev = 0;
+        }
+    }
+    if (prev)
+        fatalUsage(appName, "missing argument after", prev);
+
+    if (!posArgCnt)
+        fatalUsage(appName, "missing input file name", NULL);
+
+    if (sign)
+        multi++;
+    if (verify)
+        multi++;
+    if (txt2bin)
+        multi++;
+
+    if (multi != 1)
+        fatalUsage(appName, "select either -s, -t, or -b", NULL);
+
+    memset(&rsa, 0, sizeof(rsa));
+
+    if (sign && !(keyPvtFile && keyPubFile))
+        fatalUsage(appName, "We need both PUB (-m) and PVT (-e) keys for signing", NULL);
+
+    if (verify && (!keyPubFile || keyPvtFile))
+        fatalUsage(appName, "We only need PUB (-m)  key for signature checking", NULL);
+
+    if (keyPvtFile) {
+        if (!readFile(rsa.exponent, sizeof(rsa.exponent), keyPvtFile))
+            fatalUsage(appName, "Can't read PVT key from", keyPvtFile);
+        else if (verbose)
+            printHashRev(stderr, "RSA exponent", rsa.exponent, RSA_LIMBS);
+    }
+
+    if (keyPubFile) {
+        if (!readFile(rsa.modulus, sizeof(rsa.modulus), keyPubFile))
+            fatalUsage(appName, "Can't read PUB key from", keyPubFile);
+        else if (verbose)
+            printHashRev(stderr, "RSA modulus", rsa.modulus, RSA_LIMBS);
+    }
+
+    buf = loadFile(posArg[0], &bufUsed);
+    fprintf(stderr, "Read %" PRIu32 " bytes\n", bufUsed);
+
+    image = (struct ImageHeader *)buf;
+    if (!bareData && !txt2bin) {
+        if (image->aosp.header_version == 1 &&
+            image->aosp.magic == NANOAPP_AOSP_MAGIC &&
+            image->layout.magic == GOOGLE_LAYOUT_MAGIC) {
+            fprintf(stderr, "Found AOSP header\n");
+        } else {
+            fprintf(stderr, "Unknown binary format\n");
+            return 2;
+        }
+    }
+
+    if (!posArg[1])
+        out = stdout;
+    else
+        out = fopen(posArg[1], "w");
+    if (!out)
+        fatalUsage(appName, "failed to create/open output file", posArg[1]);
+
+    if (sign)
+        ret = handleSign(&buf, bufUsed, out, &rsa, verbose, bareData);
+    else if (verify)
+        ret = handleVerify(&buf, bufUsed, &rsa, verbose, bareData);
+    else if (txt2bin)
+        ret = handleConvertKey(&buf, bufUsed, out, &rsa);
+
+    free(buf);
+    fclose(out);
+    return ret;
+}
diff --git a/util/nanohub_os_update_prepare.sh b/util/nanohub_os_update_prepare.sh
index a18c532..0227e43 100755
--- a/util/nanohub_os_update_prepare.sh
+++ b/util/nanohub_os_update_prepare.sh
@@ -19,98 +19,25 @@
 # Exit in error if we use an undefined variable (i.e. commit a typo).
 set -u
 
-terminate() { #cleanup and exit
-	rm -rf $stage
-	exit $1
-}
-
 usage () { #show usage and bail out
 	echo "USAGE:" >&2
-	echo "    $1 <PRIV_KEY_FILE> <PUB_KEY_FILE> < nanohub.update.bin > nanohub.update.signed.bin" >&2
-	terminate -1
+	echo "    $1 <PRIV_KEY_FILE> <PUB_KEY_FILE> nanohub.update.bin" >&2
+	exit 1
 }
 
-putchar() {
-	hexch="0123456789abcdef"
-	h=$[$1/16]
-	l=$[$1%16]
-	h=${hexch:$h:1}
-	l=${hexch:$l:1}
-	e="\x"$h$l
-	echo -ne $e
-}
-
-printhex() {
-	w3=$[$1/16777216]
-	t=$[$w3*16777216]
-	a=$[$1-$t]
-
-	w2=$[$a/65536]
-	t=$[$w2*65536]
-	a=$[$a-$t]
-
-	w1=$[$a/256]
-	w0=$[$a%256]
-
-	putchar $w0
-	putchar $w1
-	putchar $w2
-	putchar $w3
-}
-
-#create temp dir
-stage=$(mktemp -dt "$(basename $0).XXXXXXXXXX")
-
-
-#sanity checks (on the user)
-if [ -t 1 ]
-then
-	usage $0
+if [ $# != 3 ] ; then
+usage $0
 fi
 
-if [ -t 0 ]
-then
-	usage $0
-fi
+priv=$1
+pub=$2
+raw_image=$3
 
-#handle signing
-if [ $# -ne 2 ]
-then
-	usage $0
-fi
-priv1="$1"
-pub1="$2"
+# make signed image with header; suitable for BL
+# to be consumed by BL it has to be named nanohub.kernel.signed
+nanoapp_postprocess -n os -r ${raw_image} ${raw_image}.oshdr
+nanoapp_sign -s -e ${priv} -m ${pub} -r ${raw_image}.oshdr nanohub.kernel.signed
 
+# embed this image inside nanoapp container
 
-#save update to file in dir
-cat > "$stage/raw"
-
-#pad update to 4 byte boundary
-t=$(du -b "$stage/raw" | cut -f1)
-while [ $[$t%4] -ne 0 ]
-do
-	echo -ne "\0" >> "$stage/raw"
-	t=$(du -b "$stage/raw" | cut -f1)
-done
-
-#get and save the file size
-signed_sz=$(du -b "$stage/raw" | cut -f1)
-
-#create the header (with the marker set for signing
-echo -ne "Nanohub OS\x00\xFE" > "$stage/hdr"
-printhex $signed_sz >> "$stage/hdr"
-
-#concat the data to header
-cat "$stage/hdr" "$stage/raw" > "$stage/with_hdr"
-
-#create the signature
-nanoapp_sign sign "$priv1" "$pub1" < "$stage/with_hdr" > "$stage/sig"
-
-#insert proper upload marker
-echo -ne "\xff" | dd bs=1 seek=11 count=1 conv=notrunc of="$stage/with_hdr" 2>/dev/null
-
-#produce signed output
-cat "$stage/with_hdr" "$stage/sig" "$pub1"
-
-terminate 0
-
+nanoapp_postprocess -n os nanohub.kernel.signed ${raw_image}.napp
diff --git a/util/nanotool/Android.mk b/util/nanotool/Android.mk
new file mode 100644
index 0000000..7410b34
--- /dev/null
+++ b/util/nanotool/Android.mk
@@ -0,0 +1,57 @@
+#
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_PATH := $(call my-dir)
+
+NANOTOOL_VERSION := 1.1.0
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+    androidcontexthub.cpp \
+    apptohostevent.cpp \
+    calibrationfile.cpp \
+    contexthub.cpp \
+    log.cpp \
+    nanomessage.cpp \
+    nanotool.cpp \
+    resetreasonevent.cpp \
+    sensorevent.cpp
+
+# JSON file handling from chinook
+COMMON_UTILS_DIR := ../common
+LOCAL_SRC_FILES += \
+    $(COMMON_UTILS_DIR)/file.cpp \
+    $(COMMON_UTILS_DIR)/JSONObject.cpp
+
+LOCAL_C_INCLUDES := \
+    $(LOCAL_PATH)/$(COMMON_UTILS_DIR)
+
+LOCAL_SHARED_LIBRARIES := \
+    liblog \
+    libstagefright_foundation \
+    libutils
+
+LOCAL_CFLAGS += -Wall -Werror -Wextra
+LOCAL_CFLAGS += -std=c++11
+LOCAL_CFLAGS += -DNANOTOOL_VERSION_STR='"version $(NANOTOOL_VERSION)"'
+
+LOCAL_MODULE := nanotool
+
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_OWNER := google
+
+include $(BUILD_EXECUTABLE)
diff --git a/util/nanotool/androidcontexthub.cpp b/util/nanotool/androidcontexthub.cpp
new file mode 100644
index 0000000..f477977
--- /dev/null
+++ b/util/nanotool/androidcontexthub.cpp
@@ -0,0 +1,395 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "androidcontexthub.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <time.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+#include <chrono>
+#include <cstdint>
+#include <cstdio>
+#include <cstring>
+#include <thread>
+#include <vector>
+
+#include "calibrationfile.h"
+#include "log.h"
+
+namespace android {
+
+constexpr char kSensorDeviceFile[] = "/dev/nanohub";
+constexpr char kCommsDeviceFile[] = "/dev/nanohub_comms";
+constexpr char kLockDirectory[] = "/data/system/nanohub_lock";
+constexpr char kLockFile[] = "/data/system/nanohub_lock/lock";
+
+constexpr mode_t kLockDirPermissions = (S_IRUSR | S_IWUSR | S_IXUSR);
+
+constexpr auto kLockDelay = std::chrono::milliseconds(100);
+
+constexpr int kDeviceFileCount = 2;
+constexpr int kPollNoTimeout = -1;
+
+static const std::vector<std::tuple<const char *, SensorType>> kCalibrationKeys = {
+    std::make_tuple("accel",     SensorType::Accel),
+    std::make_tuple("gyro",      SensorType::Gyro),
+    std::make_tuple("proximity", SensorType::Proximity),
+    std::make_tuple("barometer", SensorType::Barometer),
+    std::make_tuple("light",     SensorType::AmbientLightSensor),
+};
+
+static void AppendBytes(const void *data, size_t length, std::vector<uint8_t>& buffer) {
+    const uint8_t *bytes = (const uint8_t *) data;
+    for (size_t i = 0; i < length; i++) {
+        buffer.push_back(bytes[i]);
+    }
+}
+
+static bool CopyInt32Array(const char *key,
+        sp<JSONObject> json, std::vector<uint8_t>& bytes) {
+    sp<JSONArray> array;
+    if (json->getArray(key, &array)) {
+        for (size_t i = 0; i < array->size(); i++) {
+            int32_t val = 0;
+            array->getInt32(i, &val);
+            AppendBytes(&val, sizeof(uint32_t), bytes);
+        }
+
+        return true;
+    }
+    return false;
+}
+
+static bool GetCalibrationBytes(const char *key, SensorType sensor_type,
+        std::vector<uint8_t>& bytes) {
+    bool success = true;
+    auto json = CalibrationFile::Instance()->GetJSONObject();
+
+    switch (sensor_type) {
+      case SensorType::Accel:
+      case SensorType::Gyro:
+        success = CopyInt32Array(key, json, bytes);
+        break;
+
+      case SensorType::AmbientLightSensor:
+      case SensorType::Barometer: {
+        float value = 0;
+        success = json->getFloat(key, &value);
+        if (success) {
+            AppendBytes(&value, sizeof(float), bytes);
+        }
+        break;
+      }
+
+      case SensorType::Proximity: {
+        // Proximity might be an int32 array with 4 values (CRGB) or a single
+        // int32 value - try both
+        success = CopyInt32Array(key, json, bytes);
+        if (!success) {
+            int32_t value = 0;
+            success = json->getInt32(key, &value);
+            if (success) {
+                AppendBytes(&value, sizeof(int32_t), bytes);
+            }
+        }
+        break;
+      }
+
+      default:
+        // If this log message gets printed, code needs to be added in this
+        // switch statement
+        LOGE("Missing sensor type to calibration data mapping sensor %d",
+             static_cast<int>(sensor_type));
+        success = false;
+    }
+
+    return success;
+}
+
+AndroidContextHub::~AndroidContextHub() {
+    if (unlink(kLockFile) < 0) {
+        LOGE("Couldn't remove lock file: %s", strerror(errno));
+    }
+    if (sensor_fd_ >= 0) {
+        DisableActiveSensors();
+        (void) close(sensor_fd_);
+    }
+    if (comms_fd_ >= 0) {
+        (void) close(comms_fd_);
+    }
+}
+
+void AndroidContextHub::TerminateHandler() {
+    (void) unlink(kLockFile);
+}
+
+bool AndroidContextHub::Initialize() {
+    // Acquire a lock on nanohub, so the HAL read threads won't take our events.
+    // We need to delay after creating the file to have good confidence that
+    // the HALs noticed the lock file creation.
+    if (access(kLockDirectory, F_OK) < 0) {
+        if (mkdir(kLockDirectory, kLockDirPermissions) < 0 && errno != EEXIST) {
+            LOGE("Couldn't create lock directory: %s", strerror(errno));
+        }
+    }
+    int lock_fd = open(kLockFile, O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
+    if (lock_fd < 0) {
+        LOGE("Couldn't create lock file: %s", strerror(errno));
+        if (errno != EEXIST) {
+            return false;
+        }
+    } else {
+        close(lock_fd);
+        std::this_thread::sleep_for(kLockDelay);
+        LOGD("Lock sleep complete");
+    }
+
+    // Sensor device file is used for sensor requests, e.g. configure, etc., and
+    // returns sensor events
+    sensor_fd_ = open(kSensorDeviceFile, O_RDWR);
+    if (sensor_fd_ < 0) {
+        LOGE("Couldn't open device file: %s", strerror(errno));
+        return false;
+    }
+
+    // The comms device file is used for more generic communication with
+    // nanoapps. Calibration results are returned through this channel.
+    comms_fd_ = open(kCommsDeviceFile, O_RDONLY);
+    if (comms_fd_ < 0) {
+        // TODO(bduddie): Currently informational only, as the kernel change
+        // that adds this device file is not available/propagated yet.
+        // Eventually this should be an error.
+        LOGI("Couldn't open comms device file: %s", strerror(errno));
+    }
+
+    return true;
+}
+
+void AndroidContextHub::SetLoggingEnabled(bool logging_enabled) {
+    if (logging_enabled) {
+        LOGE("Logging is not supported on this platform");
+    }
+}
+
+ContextHub::TransportResult AndroidContextHub::WriteEvent(
+        const std::vector<uint8_t>& message) {
+    ContextHub::TransportResult result;
+
+    LOGD("Writing %zu bytes", message.size());
+    LOGD_BUF(message.data(), message.size());
+    int ret = write(sensor_fd_, message.data(), message.size());
+    if (ret == -1) {
+        LOGE("Couldn't write %zu bytes to device file: %s", message.size(),
+             strerror(errno));
+        result = TransportResult::GeneralFailure;
+    } else if (ret != (int) message.size()) {
+        LOGW("Write returned %d, expected %zu", ret, message.size());
+        result = TransportResult::GeneralFailure;
+    } else {
+        LOGD("Successfully sent event");
+        result = TransportResult::Success;
+    }
+
+    return result;
+}
+
+ContextHub::TransportResult AndroidContextHub::ReadEvent(
+        std::vector<uint8_t>& message, int timeout_ms) {
+    ContextHub::TransportResult result = TransportResult::GeneralFailure;
+
+    struct pollfd pollfds[kDeviceFileCount];
+    int fd_count = ResetPollFds(pollfds, kDeviceFileCount);
+
+    int timeout = timeout_ms > 0 ? timeout_ms : kPollNoTimeout;
+    int ret = poll(pollfds, fd_count, timeout);
+    if (ret < 0) {
+        LOGE("Polling failed: %s", strerror(errno));
+        if (errno == EINTR) {
+            result = TransportResult::Canceled;
+        }
+    } else if (ret == 0) {
+        LOGD("Poll timed out");
+        result = TransportResult::Timeout;
+    } else {
+        int read_fd = -1;
+        for (int i = 0; i < kDeviceFileCount; i++) {
+            if (pollfds[i].revents & POLLIN) {
+                read_fd = pollfds[i].fd;
+                break;
+            }
+        }
+
+        if (read_fd == sensor_fd_) {
+            LOGD("Data ready on sensors device file");
+        } else if (read_fd == comms_fd_) {
+            LOGD("Data ready on comms device file");
+        }
+
+        if (read_fd >= 0) {
+            result = ReadEventFromFd(read_fd, message);
+        } else {
+            LOGE("Poll returned but none of expected files are ready");
+        }
+    }
+
+    return result;
+}
+
+bool AndroidContextHub::FlashSensorHub(const std::vector<uint8_t>& bytes) {
+    (void)bytes;
+    LOGE("Flashing is not supported on this platform");
+    return false;
+}
+
+bool AndroidContextHub::LoadCalibration() {
+    std::vector<uint8_t> cal_data;
+    bool success = true;
+
+    for (size_t i = 0; success && i < kCalibrationKeys.size(); i++) {
+        std::string key;
+        SensorType sensor_type;
+
+        std::tie(key, sensor_type) = kCalibrationKeys[i];
+        if (GetCalibrationBytes(key.c_str(), sensor_type, cal_data)) {
+            success = SendCalibrationData(sensor_type, cal_data);
+        }
+
+        cal_data.clear();
+    }
+
+    return success;
+}
+
+bool AndroidContextHub::SetCalibration(SensorType sensor_type, int32_t data) {
+    LOGI("Setting calibration for sensor %d (%s) to %d",
+         static_cast<int>(sensor_type),
+         ContextHub::SensorTypeToAbbrevName(sensor_type).c_str(), data);
+    auto cal_file = CalibrationFile::Instance();
+    const char *key = AndroidContextHub::SensorTypeToCalibrationKey(sensor_type);
+    if (cal_file && key) {
+        return cal_file->SetSingleAxis(key, data);
+    }
+    return false;
+}
+
+bool AndroidContextHub::SetCalibration(SensorType sensor_type, float data) {
+    LOGI("Setting calibration for sensor %d (%s) to %f",
+         static_cast<int>(sensor_type),
+         ContextHub::SensorTypeToAbbrevName(sensor_type).c_str(), data);
+    auto cal_file = CalibrationFile::Instance();
+    const char *key = AndroidContextHub::SensorTypeToCalibrationKey(sensor_type);
+    if (cal_file && key) {
+        return cal_file->SetSingleAxis(key, data);
+    }
+    return false;
+}
+
+bool AndroidContextHub::SetCalibration(SensorType sensor_type, int32_t x,
+        int32_t y, int32_t z) {
+    LOGI("Setting calibration for %d to %d %d %d", static_cast<int>(sensor_type),
+         x, y, z);
+    auto cal_file = CalibrationFile::Instance();
+    const char *key = AndroidContextHub::SensorTypeToCalibrationKey(sensor_type);
+    if (cal_file && key) {
+        return cal_file->SetTripleAxis(key, x, y, z);
+    }
+    return false;
+}
+
+bool AndroidContextHub::SetCalibration(SensorType sensor_type, int32_t x,
+        int32_t y, int32_t z, int32_t w) {
+    LOGI("Setting calibration for %d to %d %d %d %d", static_cast<int>(sensor_type),
+         x, y, z, w);
+    auto cal_file = CalibrationFile::Instance();
+    const char *key = AndroidContextHub::SensorTypeToCalibrationKey(sensor_type);
+    if (cal_file && key) {
+        return cal_file->SetFourAxis(key, x, y, z, w);
+    }
+    return false;
+}
+
+bool AndroidContextHub::SaveCalibration() {
+    LOGI("Saving calibration data");
+    auto cal_file = CalibrationFile::Instance();
+    if (cal_file) {
+        return cal_file->Save();
+    }
+    return false;
+}
+
+ContextHub::TransportResult AndroidContextHub::ReadEventFromFd(
+        int fd, std::vector<uint8_t>& message) {
+    ContextHub::TransportResult result = TransportResult::GeneralFailure;
+
+    // Set the size to the maximum, so when we resize later, it's always a
+    // shrink (otherwise it will end up clearing the bytes)
+    message.resize(message.capacity());
+
+    LOGD("Calling into read()");
+    int ret = read(fd, message.data(), message.capacity());
+    if (ret < 0) {
+        LOGE("Couldn't read from device file: %s", strerror(errno));
+        if (errno == EINTR) {
+            result = TransportResult::Canceled;
+        }
+    } else if (ret == 0) {
+        // We might need to handle this specially, if the driver implements this
+        // to mean something specific
+        LOGE("Read unexpectedly returned 0 bytes");
+    } else {
+        message.resize(ret);
+        LOGD_VEC(message);
+        result = TransportResult::Success;
+    }
+
+    return result;
+}
+
+int AndroidContextHub::ResetPollFds(struct pollfd *pfds, size_t count) {
+    memset(pfds, 0, sizeof(struct pollfd) * count);
+    pfds[0].fd = sensor_fd_;
+    pfds[0].events = POLLIN;
+
+    int nfds = 1;
+    if (count > 1 && comms_fd_ >= 0) {
+        pfds[1].fd = comms_fd_;
+        pfds[1].events = POLLIN;
+        nfds++;
+    }
+    return nfds;
+}
+
+const char *AndroidContextHub::SensorTypeToCalibrationKey(SensorType sensor_type) {
+    for (size_t i = 0; i < kCalibrationKeys.size(); i++) {
+        const char *key;
+        SensorType sensor_type_for_key;
+
+        std::tie(key, sensor_type_for_key) = kCalibrationKeys[i];
+        if (sensor_type == sensor_type_for_key) {
+            return key;
+        }
+    }
+
+    LOGE("No calibration key mapping for sensor type %d",
+         static_cast<int>(sensor_type));
+    return nullptr;
+}
+
+}  // namespace android
diff --git a/util/nanotool/androidcontexthub.h b/util/nanotool/androidcontexthub.h
new file mode 100644
index 0000000..720561d
--- /dev/null
+++ b/util/nanotool/androidcontexthub.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROIDCONTEXTHUB_H_
+#define ANDROIDCONTEXTHUB_H_
+
+#include "contexthub.h"
+
+#include <poll.h>
+#include <unistd.h>
+
+namespace android {
+
+/*
+ * Communicates with a context hub via the /dev/nanohub interface
+ */
+class AndroidContextHub : public ContextHub {
+  public:
+    ~AndroidContextHub();
+
+    // Performs system resource cleanup in the event that the program is
+    // terminated abnormally (via std::terminate)
+    static void TerminateHandler();
+
+    bool Initialize() override;
+    bool LoadCalibration() override;
+    void SetLoggingEnabled(bool logging_enabled) override;
+
+  protected:
+    ContextHub::TransportResult WriteEvent(
+        const std::vector<uint8_t>& request) override;
+    ContextHub::TransportResult ReadEvent(std::vector<uint8_t>& response,
+        int timeout_ms) override;
+    bool FlashSensorHub(const std::vector<uint8_t>& bytes) override;
+
+    bool SetCalibration(SensorType sensor_type, int32_t data) override;
+    bool SetCalibration(SensorType sensor_type, float data) override;
+    bool SetCalibration(SensorType sensor_type, int32_t x, int32_t y, int32_t z)
+        override;
+    bool SetCalibration(SensorType sensor_type, int32_t x, int32_t y,
+        int32_t z, int32_t w) override;
+    bool SaveCalibration() override;
+
+  private:
+    int sensor_fd_ = -1;
+    int comms_fd_ = -1;
+
+    ContextHub::TransportResult ReadEventFromFd(int fd,
+        std::vector<uint8_t>& message);
+    int ResetPollFds(struct pollfd *pfds, size_t count);
+    static const char *SensorTypeToCalibrationKey(SensorType sensor_type);
+};
+
+}  // namespace android
+
+#endif  // ANDROIDCONTEXTHUB_H_
diff --git a/util/nanotool/apptohostevent.cpp b/util/nanotool/apptohostevent.cpp
new file mode 100644
index 0000000..9ab4fb3
--- /dev/null
+++ b/util/nanotool/apptohostevent.cpp
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "apptohostevent.h"
+
+#include "contexthub.h"
+#include "log.h"
+
+namespace android {
+
+/* AppToHostEvent *************************************************************/
+
+std::unique_ptr<AppToHostEvent> AppToHostEvent::FromBytes(
+        const std::vector<uint8_t>& buffer) {
+    auto event = std::unique_ptr<AppToHostEvent>(new AppToHostEvent());
+    event->Populate(buffer);
+    if (!event->IsValid()) {
+        return nullptr;
+    }
+
+    return event;
+}
+
+uint64_t AppToHostEvent::GetAppId() const {
+    return GetTypedData()->appId;
+}
+
+uint8_t AppToHostEvent::GetDataLen() const {
+    return GetTypedData()->dataLen;
+}
+
+const uint8_t *AppToHostEvent::GetDataPtr() const {
+    return (reinterpret_cast<const uint8_t*>(GetTypedData())
+              + sizeof(struct HostHubRawPacket));
+}
+
+bool AppToHostEvent::IsCalibrationEventForSensor(SensorType sensor_type) const {
+    if (GetDataLen() < sizeof(struct SensorAppEventHeader)) {
+        return false;
+    }
+
+    // Make sure the app ID matches what we expect for the sensor type, bail out
+    // early if it doesn't
+    switch (sensor_type) {
+      case SensorType::Accel:
+      case SensorType::Gyro:
+        if (GetAppId() != kAppIdBoschBmi160Bmm150) {
+            return false;
+        }
+        break;
+
+      case SensorType::Proximity:
+        if (GetAppId() != kAppIdAmsTmd2772 && GetAppId() != kAppIdRohmRpr0521 &&
+            GetAppId() != kAppIdAmsTmd4903) {
+            return false;
+        }
+        break;
+
+      case SensorType::Barometer:
+        if (GetAppId() != kAppIdBoschBmp280) {
+            return false;
+        }
+        break;
+
+      case SensorType::AmbientLightSensor:
+        if (GetAppId() != kAppIdAmsTmd4903) {
+            return false;
+        }
+        break;
+
+      default:
+        return false;
+    }
+
+    // If we made it this far, we only need to confirm the message ID
+    auto header = reinterpret_cast<const struct SensorAppEventHeader *>(
+        GetDataPtr());
+    return (header->msgId == SENSOR_APP_MSG_CALIBRATION_RESULT);
+}
+
+bool AppToHostEvent::IsValid() const {
+    const HostHubRawPacket *packet = GetTypedData();
+    if (!packet) {
+        return false;
+    }
+
+    // dataLen should specify the amount of data that follows the event type
+    // and HostHubRawPacket headers
+    if (event_data.size() < (sizeof(uint32_t) + sizeof(struct HostHubRawPacket)
+                               + packet->dataLen)) {
+        LOGW("Invalid/short AppToHost event of size %zu", event_data.size());
+        return false;
+    }
+
+    return true;
+}
+
+const HostHubRawPacket *AppToHostEvent::GetTypedData() const {
+    // After the event type header (uint32_t), we should have struct
+    // HostHubRawPacket
+    if (event_data.size() < sizeof(uint32_t) + sizeof(struct HostHubRawPacket)) {
+        LOGW("Invalid/short AppToHost event of size %zu", event_data.size());
+        return nullptr;
+    }
+    return reinterpret_cast<const HostHubRawPacket *>(
+        event_data.data() + sizeof(uint32_t));
+}
+
+}  // namespace android
diff --git a/util/nanotool/apptohostevent.h b/util/nanotool/apptohostevent.h
new file mode 100644
index 0000000..e638062
--- /dev/null
+++ b/util/nanotool/apptohostevent.h
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef APP_TO_HOST_EVENT_H_
+#define APP_TO_HOST_EVENT_H_
+
+#include "contexthub.h"
+#include "nanomessage.h"
+
+namespace android {
+
+// Copied from nanohub eventnums.h
+struct HostHubRawPacket {
+    uint64_t appId;
+    uint8_t dataLen; //not incl this header, 128 bytes max
+    //raw data in unspecified format here
+} __attribute((packed));
+
+// The u64 appId used in nanohub is 40 bits vendor ID + 24 bits app ID (see seos.h)
+constexpr uint64_t MakeAppId(uint64_t vendorId, uint32_t appId) {
+    return (vendorId << 24) | (appId & 0x00FFFFFF);
+}
+
+constexpr uint64_t kAppIdVendorGoogle = 0x476f6f676cULL; // "Googl"
+
+constexpr uint64_t kAppIdBoschBmi160Bmm150 = MakeAppId(kAppIdVendorGoogle, 2);
+constexpr uint64_t kAppIdBoschBmp280       = MakeAppId(kAppIdVendorGoogle, 5);
+constexpr uint64_t kAppIdAmsTmd2772        = MakeAppId(kAppIdVendorGoogle, 9);
+constexpr uint64_t kAppIdRohmRpr0521       = MakeAppId(kAppIdVendorGoogle, 10);
+constexpr uint64_t kAppIdAmsTmd4903        = MakeAppId(kAppIdVendorGoogle, 12);
+
+/*
+ * These classes represent events sent with event type EVT_APP_TO_HOST. This is
+ * a generic container for arbitrary application-specific data, and is used for
+ * passing back sensor calibration results, implementing app download, etc. The
+ * parser must know the application ID to determine the data format.
+ */
+
+class AppToHostEvent : public ReadEventResponse {
+  public:
+    /*
+     * Constructs and populates an AppToHostEvent instance. Returns nullptr if
+     * the packet is malformed. The rest of the methods in this class are not
+     * guaranteed to be safe unless the object is constructed from this
+     * function.
+     */
+    static std::unique_ptr<AppToHostEvent> FromBytes(
+        const std::vector<uint8_t>& buffer);
+
+    uint64_t GetAppId() const;
+    // Gets the length of the application-specific data segment
+    uint8_t GetDataLen() const;
+    // Returns a pointer to the application-specific data (i.e. past the header)
+    const uint8_t *GetDataPtr() const;
+
+    bool IsCalibrationEventForSensor(SensorType sensor_type) const;
+    virtual bool IsValid() const;
+
+  protected:
+    const HostHubRawPacket *GetTypedData() const;
+};
+
+#define SENSOR_APP_MSG_CALIBRATION_RESULT (0)
+
+struct SensorAppEventHeader {
+    uint8_t msgId;
+    uint8_t sensorType;
+    uint8_t status; // 0 for success
+} __attribute__((packed));
+
+struct SingleAxisCalibrationResult : public SensorAppEventHeader {
+    int32_t bias;
+} __attribute__((packed));
+
+struct TripleAxisCalibrationResult : public SensorAppEventHeader {
+    int32_t xBias;
+    int32_t yBias;
+    int32_t zBias;
+} __attribute__((packed));
+
+struct FloatCalibrationResult : public SensorAppEventHeader {
+    float value;
+} __attribute__((packed));
+
+struct FourAxisCalibrationResult : public SensorAppEventHeader {
+    int32_t xBias;
+    int32_t yBias;
+    int32_t zBias;
+    int32_t wBias;
+} __attribute__((packed));
+
+
+}  // namespace android
+
+#endif // APP_TO_HOST_EVENT_H_
diff --git a/util/nanotool/calibrationfile.cpp b/util/nanotool/calibrationfile.cpp
new file mode 100644
index 0000000..3f79397
--- /dev/null
+++ b/util/nanotool/calibrationfile.cpp
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "calibrationfile.h"
+
+#include "file.h"
+#include "log.h"
+
+namespace android {
+
+constexpr char kCalibrationFile[] = "/persist/sensorcal.json";
+
+std::shared_ptr<CalibrationFile> CalibrationFile::instance_;
+
+std::shared_ptr<CalibrationFile> CalibrationFile::Instance() {
+    if (!CalibrationFile::instance_) {
+        auto inst = std::shared_ptr<CalibrationFile>(new CalibrationFile());
+        if (inst->Initialize()) {
+            CalibrationFile::instance_ = inst;
+        }
+    }
+
+    return CalibrationFile::instance_;
+}
+
+bool CalibrationFile::Initialize() {
+    file_ = std::unique_ptr<File>(new File(kCalibrationFile, "rw"));
+
+    status_t err = file_->initCheck();
+    if (err != OK) {
+        LOGE("Couldn't open calibration file: %d (%s)", err, strerror(-err));
+        return false;
+    }
+
+    off64_t file_size = file_->seekTo(0, SEEK_END);
+    if (file_size > 0) {
+        auto file_data = std::vector<char>(file_size);
+        file_->seekTo(0, SEEK_SET);
+        ssize_t bytes_read = file_->read(file_data.data(), file_size);
+        if (bytes_read != file_size) {
+            LOGE("Read of configuration file returned %zd, expected %" PRIu64,
+                 bytes_read, file_size);
+            return false;
+        }
+
+        sp<JSONCompound> json = JSONCompound::Parse(file_data.data(), file_size);
+        if (json == nullptr || !json->isObject()) {
+            // If there's an existing file and we couldn't parse it, or it
+            // parsed to something unexpected, then we don't want to wipe out
+            // the file - the user needs to decide what to do, e.g. they can
+            // manually edit to fix corruption, or delete it, etc.
+            LOGE("Couldn't parse sensor calibration file (requires manual "
+                 "resolution)");
+            return false;
+        } else {
+            json_root_ = reinterpret_cast<JSONObject*>(json.get());
+            LOGD("Parsed JSONObject from file:\n%s",
+                 json_root_->toString().c_str());
+        }
+    }
+
+    // No errors, but there was no existing calibration data so construct a new
+    // object
+    if (json_root_ == nullptr) {
+        json_root_ = new JSONObject();
+    }
+
+    return true;
+}
+
+const sp<JSONObject> CalibrationFile::GetJSONObject() const {
+    return json_root_;
+}
+
+bool CalibrationFile::SetSingleAxis(const char *key, int32_t value) {
+    json_root_->setInt32(key, value);
+    return true;
+}
+
+bool CalibrationFile::SetSingleAxis(const char *key, float value) {
+    json_root_->setFloat(key, value);
+    return true;
+}
+
+bool CalibrationFile::SetTripleAxis(const char *key, int32_t x, int32_t y,
+        int32_t z) {
+    sp<JSONArray> json_array = new JSONArray();
+    json_array->addInt32(x);
+    json_array->addInt32(y);
+    json_array->addInt32(z);
+    json_root_->setArray(key, json_array);
+    return true;
+}
+
+bool CalibrationFile::SetFourAxis(const char *key, int32_t x, int32_t y,
+        int32_t z, int32_t w) {
+    sp<JSONArray> json_array = new JSONArray();
+    json_array->addInt32(x);
+    json_array->addInt32(y);
+    json_array->addInt32(z);
+    json_array->addInt32(w);
+    json_root_->setArray(key, json_array);
+    return true;
+}
+
+bool CalibrationFile::Save() {
+    AString json_str = json_root_->toString();
+    LOGD("Saving JSONObject to file (%zd bytes):\n%s", json_str.size(),
+         json_str.c_str());
+    file_->seekTo(0, SEEK_SET);
+    ssize_t bytes_written = file_->write(json_str.c_str(), json_str.size());
+    if (bytes_written < 0 || static_cast<size_t>(bytes_written) != json_str.size()) {
+        LOGE("Write returned %zd, expected %zu", bytes_written, json_str.size());
+        return false;
+    }
+    return true;
+}
+
+}  // namespace android
diff --git a/util/nanotool/calibrationfile.h b/util/nanotool/calibrationfile.h
new file mode 100644
index 0000000..22b44ce
--- /dev/null
+++ b/util/nanotool/calibrationfile.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CALIBRATION_FILE_H_
+#define CALIBRATION_FILE_H_
+
+#include <inttypes.h>
+#include <unistd.h>
+
+#include <memory>
+
+#include "file.h"
+#include "noncopyable.h"
+#include "JSONObject.h"
+#include <utils/RefBase.h>
+
+namespace android {
+
+class CalibrationFile : public NonCopyable {
+  public:
+    // Get a pointer to the singleton instance
+    static std::shared_ptr<CalibrationFile> Instance();
+
+    const sp<JSONObject> GetJSONObject() const;
+
+    bool SetSingleAxis(const char *key, int32_t value);
+    bool SetSingleAxis(const char *key, float value);
+    bool SetTripleAxis(const char *key, int32_t x, int32_t y, int32_t z);
+    bool SetFourAxis(const char *key, int32_t x, int32_t y, int32_t z,
+                     int32_t w);
+
+    bool Save();
+
+  private:
+    CalibrationFile() : file_(nullptr), json_root_(nullptr) {}
+
+    static std::shared_ptr<CalibrationFile> instance_;
+    bool Initialize();
+
+    std::unique_ptr<File> file_;
+    sp<JSONObject> json_root_;
+
+    bool Read();
+};
+
+}  // namespace android
+
+#endif  // CALIBRATION_FILE_H_
diff --git a/util/nanotool/contexthub.cpp b/util/nanotool/contexthub.cpp
new file mode 100644
index 0000000..b734e3a
--- /dev/null
+++ b/util/nanotool/contexthub.cpp
@@ -0,0 +1,546 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "contexthub.h"
+
+#include <cstring>
+#include <errno.h>
+#include <vector>
+
+#include "apptohostevent.h"
+#include "log.h"
+#include "resetreasonevent.h"
+#include "sensorevent.h"
+#include "util.h"
+
+namespace android {
+
+#define UNUSED_PARAM(param) (void) (param)
+
+constexpr int kCalibrationTimeoutMs(10000);
+
+struct SensorTypeNames {
+    SensorType sensor_type;
+    const char *name_abbrev;
+};
+
+static const SensorTypeNames sensor_names_[] = {
+    { SensorType::Accel,                "accel" },
+    { SensorType::AnyMotion,            "anymo" },
+    { SensorType::NoMotion,             "nomo" },
+    { SensorType::SignificantMotion,    "sigmo" },
+    { SensorType::Flat,                 "flat" },
+    { SensorType::Gyro,                 "gyro" },
+    //{ SensorType::GyroUncal,            "gyro_uncal" },
+    { SensorType::Magnetometer,         "mag" },
+    //{ SensorType::MagnetometerUncal,    "mag_uncal" },
+    { SensorType::Barometer,            "baro" },
+    { SensorType::Temperature,          "temp" },
+    { SensorType::AmbientLightSensor,   "als" },
+    { SensorType::Proximity,            "prox" },
+    { SensorType::Orientation,          "orien" },
+    //{ SensorType::HeartRateECG,         "ecg" },
+    //{ SensorType::HeartRatePPG,         "ppg" },
+    { SensorType::Gravity,              "gravity" },
+    { SensorType::LinearAccel,          "linear_acc" },
+    { SensorType::RotationVector,       "rotation" },
+    { SensorType::GeomagneticRotationVector, "geomag" },
+    { SensorType::GameRotationVector,   "game" },
+    { SensorType::StepCount,            "step_cnt" },
+    { SensorType::StepDetect,           "step_det" },
+    { SensorType::Gesture,              "gesture" },
+    { SensorType::Tilt,                 "tilt" },
+    { SensorType::DoubleTwist,          "twist" },
+    { SensorType::DoubleTap,            "doubletap" },
+    { SensorType::WindowOrientation,    "win_orien" },
+    { SensorType::Hall,                 "hall" },
+    { SensorType::Activity,             "activity" },
+    { SensorType::Vsync,                "vsync" },
+};
+
+struct SensorTypeAlias {
+    SensorType sensor_type;
+    SensorType sensor_alias;
+    const char *name_abbrev;
+};
+
+static const SensorTypeAlias sensor_aliases_[] = {
+    { SensorType::Accel, SensorType::CompressedAccel, "compressed_accel" },
+};
+
+bool SensorTypeIsAliasOf(SensorType sensor_type, SensorType alias) {
+    for (size_t i = 0; i < ARRAY_LEN(sensor_aliases_); i++) {
+        if (sensor_aliases_[i].sensor_type == sensor_type
+                && sensor_aliases_[i].sensor_alias == alias) {
+            return true;
+        }
+    }
+
+    return false;
+}
+
+SensorType ContextHub::SensorAbbrevNameToType(const char *sensor_name_abbrev) {
+    for (unsigned int i = 0; i < ARRAY_LEN(sensor_names_); i++) {
+        if (strcmp(sensor_names_[i].name_abbrev, sensor_name_abbrev) == 0) {
+            return sensor_names_[i].sensor_type;
+        }
+    }
+
+    return SensorType::Invalid_;
+}
+
+SensorType ContextHub::SensorAbbrevNameToType(const std::string& abbrev_name) {
+    return ContextHub::SensorAbbrevNameToType(abbrev_name.c_str());
+}
+
+std::string ContextHub::SensorTypeToAbbrevName(SensorType sensor_type) {
+    for (unsigned int i = 0; i < ARRAY_LEN(sensor_names_); i++) {
+        if (sensor_names_[i].sensor_type == sensor_type) {
+            return std::string(sensor_names_[i].name_abbrev);
+        }
+    }
+
+    for (unsigned int i = 0; i < ARRAY_LEN(sensor_aliases_); i++) {
+        if (sensor_aliases_[i].sensor_alias == sensor_type) {
+            return std::string(sensor_aliases_[i].name_abbrev);
+        }
+    }
+
+    char buffer[24];
+    snprintf(buffer, sizeof(buffer), "unknown (%d)",
+             static_cast<int>(sensor_type));
+    return std::string(buffer);
+}
+
+std::string ContextHub::ListAllSensorAbbrevNames() {
+    std::string sensor_list;
+    for (unsigned int i = 0; i < ARRAY_LEN(sensor_names_); i++) {
+        sensor_list += sensor_names_[i].name_abbrev;
+        if (i < ARRAY_LEN(sensor_names_) - 1) {
+            sensor_list += ", ";
+        }
+    }
+
+    return sensor_list;
+}
+
+bool ContextHub::Flash(const std::string& filename) {
+    FILE *firmware_file = fopen(filename.c_str(), "r");
+    if (!firmware_file) {
+        LOGE("Failed to open firmware image: %d (%s)", errno, strerror(errno));
+        return false;
+    }
+
+    fseek(firmware_file, 0, SEEK_END);
+    long file_size = ftell(firmware_file);
+    fseek(firmware_file, 0, SEEK_SET);
+
+    auto firmware_data = std::vector<uint8_t>(file_size);
+    size_t bytes_read = fread(firmware_data.data(), sizeof(uint8_t),
+        file_size, firmware_file);
+    fclose(firmware_file);
+
+    if (bytes_read != static_cast<size_t>(file_size)) {
+        LOGE("Read of firmware file returned %zu, expected %ld",
+            bytes_read, file_size);
+        return false;
+    }
+    return FlashSensorHub(firmware_data);
+}
+
+bool ContextHub::CalibrateSensors(const std::vector<SensorSpec>& sensors) {
+    bool success = ForEachSensor(sensors, [this](const SensorSpec &spec) -> bool {
+        return CalibrateSingleSensor(spec);
+    });
+
+    if (success) {
+        success = SaveCalibration();
+    }
+    return success;
+}
+
+bool ContextHub::EnableSensor(const SensorSpec& spec) {
+    ConfigureSensorRequest req;
+
+    req.config.event_type = static_cast<uint32_t>(EventType::ConfigureSensor);
+    req.config.sensor_type = static_cast<uint8_t>(spec.sensor_type);
+    req.config.command = static_cast<uint8_t>(
+        ConfigureSensorRequest::CommandType::Enable);
+    if (spec.special_rate != SensorSpecialRate::None) {
+        req.config.rate = static_cast<uint32_t>(spec.special_rate);
+    } else {
+        req.config.rate = ConfigureSensorRequest::FloatRateToFixedPoint(
+            spec.rate_hz);
+    }
+    req.config.latency = spec.latency_ns;
+
+    LOGI("Enabling sensor %d at rate %.0f Hz (special 0x%x) and latency %.2f ms",
+         spec.sensor_type, spec.rate_hz, spec.special_rate,
+         spec.latency_ns / 1000000.0f);
+    auto result = WriteEvent(req);
+    if (result == TransportResult::Success) {
+        sensor_is_active_[static_cast<int>(spec.sensor_type)] = true;
+        return true;
+    }
+
+    LOGE("Could not enable sensor %d", spec.sensor_type);
+    return false;
+}
+
+bool ContextHub::EnableSensors(const std::vector<SensorSpec>& sensors) {
+    return ForEachSensor(sensors, [this](const SensorSpec &spec) -> bool {
+        return EnableSensor(spec);
+    });
+}
+
+bool ContextHub::DisableSensor(SensorType sensor_type) {
+    ConfigureSensorRequest req;
+
+    req.config.event_type = static_cast<uint32_t>(EventType::ConfigureSensor);
+    req.config.sensor_type = static_cast<uint8_t>(sensor_type);
+    req.config.command = static_cast<uint8_t>(
+        ConfigureSensorRequest::CommandType::Disable);
+
+    // Note that nanohub treats us as a single client, so if we call enable
+    // twice then disable once, the sensor will be disabled
+    LOGI("Disabling sensor %d", sensor_type);
+    auto result = WriteEvent(req);
+    if (result == TransportResult::Success) {
+        sensor_is_active_[static_cast<int>(sensor_type)] = false;
+        return true;
+    }
+
+    LOGE("Could not disable sensor %d", sensor_type);
+    return false;
+}
+
+bool ContextHub::DisableSensors(const std::vector<SensorSpec>& sensors) {
+    return ForEachSensor(sensors, [this](const SensorSpec &spec) -> bool {
+        return DisableSensor(spec.sensor_type);
+    });
+}
+
+bool ContextHub::DisableAllSensors() {
+    bool success = true;
+
+    for (int sensor_type = static_cast<int>(SensorType::Invalid_) + 1;
+            sensor_type < static_cast<int>(SensorType::Max_);
+            ++sensor_type) {
+        success &= DisableSensor(static_cast<SensorType>(sensor_type));
+    }
+
+    return success;
+}
+
+bool ContextHub::DisableActiveSensors() {
+    bool success = true;
+
+    LOGD("Disabling all active sensors");
+    for (int sensor_type = static_cast<int>(SensorType::Invalid_) + 1;
+            sensor_type < static_cast<int>(SensorType::Max_);
+            ++sensor_type) {
+        if (sensor_is_active_[sensor_type]) {
+            success &= DisableSensor(static_cast<SensorType>(sensor_type));
+        }
+    }
+
+    return success;
+}
+
+void ContextHub::PrintAllEvents(unsigned int limit) {
+    bool continuous = (limit == 0);
+    auto event_printer = [&limit, continuous](const SensorEvent& event) -> bool {
+        printf("%s", event.ToString().c_str());
+        return (continuous || --limit > 0);
+    };
+    ReadSensorEvents(event_printer);
+}
+
+void ContextHub::PrintSensorEvents(SensorType type, int limit) {
+    bool continuous = (limit == 0);
+    auto event_printer = [type, &limit, continuous](const SensorEvent& event) -> bool {
+        SensorType event_source = event.GetSensorType();
+        if (event_source == type || SensorTypeIsAliasOf(type, event_source)) {
+            printf("%s", event.ToString().c_str());
+            limit -= event.GetNumSamples();
+        }
+        return (continuous || limit > 0);
+    };
+    ReadSensorEvents(event_printer);
+}
+
+void ContextHub::PrintSensorEvents(const std::vector<SensorSpec>& sensors, int limit) {
+    bool continuous = (limit == 0);
+    auto event_printer = [&sensors, &limit, continuous](const SensorEvent& event) -> bool {
+        SensorType event_source = event.GetSensorType();
+        for (unsigned int i = 0; i < sensors.size(); i++) {
+            if (sensors[i].sensor_type == event_source
+                    || SensorTypeIsAliasOf(sensors[i].sensor_type, event_source)) {
+                printf("%s", event.ToString().c_str());
+                limit -= event.GetNumSamples();
+                break;
+            }
+        }
+        return (continuous || limit > 0);
+    };
+    ReadSensorEvents(event_printer);
+}
+
+// Protected methods -----------------------------------------------------------
+
+bool ContextHub::CalibrateSingleSensor(const SensorSpec& sensor) {
+    ConfigureSensorRequest req;
+
+    req.config.event_type = static_cast<uint32_t>(EventType::ConfigureSensor);
+    req.config.sensor_type = static_cast<uint8_t>(sensor.sensor_type);
+    req.config.command = static_cast<uint8_t>(
+        ConfigureSensorRequest::CommandType::Calibrate);
+
+    LOGI("Issuing calibration request to sensor %d (%s)", sensor.sensor_type,
+         ContextHub::SensorTypeToAbbrevName(sensor.sensor_type).c_str());
+    auto result = WriteEvent(req);
+    if (result != TransportResult::Success) {
+        LOGE("Failed to calibrate sensor %d", sensor.sensor_type);
+        return false;
+    }
+
+    bool success = false;
+    auto calEventHandler = [this, &sensor, &success](const AppToHostEvent &event) -> bool {
+        if (event.IsCalibrationEventForSensor(sensor.sensor_type)) {
+            success = HandleCalibrationResult(sensor, event);
+            return false;
+        }
+        return true;
+    };
+
+    result = ReadAppEvents(calEventHandler, kCalibrationTimeoutMs);
+    if (result != TransportResult::Success) {
+      LOGE("Error reading calibration response %d", static_cast<int>(result));
+      return false;
+    }
+
+    return success;
+}
+
+bool ContextHub::ForEachSensor(const std::vector<SensorSpec>& sensors,
+        std::function<bool(const SensorSpec&)> callback) {
+    bool success = true;
+
+    for (unsigned int i = 0; success && i < sensors.size(); i++) {
+        success &= callback(sensors[i]);
+    }
+
+    return success;
+}
+
+bool ContextHub::HandleCalibrationResult(const SensorSpec& sensor,
+        const AppToHostEvent &event) {
+    auto hdr = reinterpret_cast<const SensorAppEventHeader *>(event.GetDataPtr());
+    if (hdr->status) {
+        LOGE("Calibration of sensor %d (%s) failed with status %u",
+             sensor.sensor_type,
+             ContextHub::SensorTypeToAbbrevName(sensor.sensor_type).c_str(),
+             hdr->status);
+        return false;
+    }
+
+    bool success = false;
+    switch (sensor.sensor_type) {
+      case SensorType::Accel:
+      case SensorType::Gyro: {
+        auto result = reinterpret_cast<const TripleAxisCalibrationResult *>(
+            event.GetDataPtr());
+        success = SetCalibration(sensor.sensor_type, result->xBias,
+                                 result->yBias, result->zBias);
+        break;
+      }
+
+      case SensorType::Barometer: {
+        auto result = reinterpret_cast<const FloatCalibrationResult *>(
+            event.GetDataPtr());
+        if (sensor.have_cal_ref) {
+            success = SetCalibration(sensor.sensor_type,
+                                     (sensor.cal_ref - result->value));
+        }
+        break;
+      }
+
+      case SensorType::Proximity: {
+        auto result = reinterpret_cast<const FourAxisCalibrationResult *>(
+            event.GetDataPtr());
+        success = SetCalibration(sensor.sensor_type, result->xBias,
+                                 result->yBias, result->zBias, result->wBias);
+        break;
+      }
+
+      case SensorType::AmbientLightSensor: {
+        auto result = reinterpret_cast<const FloatCalibrationResult *>(
+            event.GetDataPtr());
+        if (sensor.have_cal_ref && (result->value != 0.0f)) {
+            success = SetCalibration(sensor.sensor_type,
+                                     (sensor.cal_ref / result->value));
+        }
+        break;
+      }
+
+      default:
+        LOGE("Calibration not supported for sensor type %d",
+             static_cast<int>(sensor.sensor_type));
+    }
+
+    return success;
+}
+
+ContextHub::TransportResult ContextHub::ReadAppEvents(
+        std::function<bool(const AppToHostEvent&)> callback, int timeout_ms) {
+    using Milliseconds = std::chrono::milliseconds;
+
+    TransportResult result;
+    bool timeout_required = timeout_ms > 0;
+    bool keep_going = true;
+
+    while (keep_going) {
+        if (timeout_required && timeout_ms <= 0) {
+            return TransportResult::Timeout;
+        }
+
+        std::unique_ptr<ReadEventResponse> event;
+
+        SteadyClock start_time = std::chrono::steady_clock::now();
+        result = ReadEvent(&event, timeout_ms);
+        SteadyClock end_time = std::chrono::steady_clock::now();
+
+        auto delta = end_time - start_time;
+        timeout_ms -= std::chrono::duration_cast<Milliseconds>(delta).count();
+
+        if (result == TransportResult::Success && event->IsAppToHostEvent()) {
+            AppToHostEvent *app_event = reinterpret_cast<AppToHostEvent*>(
+                event.get());
+            keep_going = callback(*app_event);
+        } else {
+            if (result != TransportResult::Success) {
+                LOGE("Error %d while reading", static_cast<int>(result));
+                if (result != TransportResult::ParseFailure) {
+                    return result;
+                }
+            } else {
+                LOGD("Ignoring non-app-to-host event");
+            }
+        }
+    }
+
+    return TransportResult::Success;
+}
+
+void ContextHub::ReadSensorEvents(std::function<bool(const SensorEvent&)> callback) {
+    TransportResult result;
+    bool keep_going = true;
+
+    while (keep_going) {
+        std::unique_ptr<ReadEventResponse> event;
+        result = ReadEvent(&event);
+        if (result == TransportResult::Success && event->IsSensorEvent()) {
+            SensorEvent *sensor_event = reinterpret_cast<SensorEvent*>(
+                event.get());
+            keep_going = callback(*sensor_event);
+        } else {
+            if (result != TransportResult::Success) {
+                LOGE("Error %d while reading", static_cast<int>(result));
+                if (result != TransportResult::ParseFailure) {
+                    break;
+                }
+            } else {
+                LOGD("Ignoring non-sensor event");
+            }
+        }
+    }
+}
+
+bool ContextHub::SendCalibrationData(SensorType sensor_type,
+        const std::vector<uint8_t>& cal_data) {
+    ConfigureSensorRequest req;
+
+    req.config.event_type = static_cast<uint32_t>(EventType::ConfigureSensor);
+    req.config.sensor_type = static_cast<uint8_t>(sensor_type);
+    req.config.command = static_cast<uint8_t>(
+        ConfigureSensorRequest::CommandType::ConfigData);
+    req.SetAdditionalData(cal_data);
+
+    auto result = WriteEvent(req);
+    return (result == TransportResult::Success);
+}
+
+ContextHub::TransportResult ContextHub::WriteEvent(
+        const WriteEventRequest& request) {
+    return WriteEvent(request.GetBytes());
+}
+
+ContextHub::TransportResult ContextHub::ReadEvent(
+        std::unique_ptr<ReadEventResponse>* response, int timeout_ms) {
+    std::vector<uint8_t> responseBuf(256);
+    ContextHub::TransportResult result = ReadEvent(responseBuf, timeout_ms);
+    if (result == TransportResult::Success) {
+        *response = ReadEventResponse::FromBytes(responseBuf);
+        if (*response == nullptr) {
+            result = TransportResult::ParseFailure;
+        }
+    }
+    return result;
+}
+
+// Stubs for subclasses that don't implement calibration support
+bool ContextHub::LoadCalibration() {
+    LOGE("Loading calibration data not implemented");
+    return false;
+}
+
+bool ContextHub::SetCalibration(SensorType sensor_type, int32_t data) {
+    UNUSED_PARAM(sensor_type);
+    UNUSED_PARAM(data);
+    return false;
+}
+
+bool ContextHub::SetCalibration(SensorType sensor_type, float data) {
+    UNUSED_PARAM(sensor_type);
+    UNUSED_PARAM(data);
+    return false;
+}
+
+bool ContextHub::SetCalibration(SensorType sensor_type, int32_t x,
+        int32_t y, int32_t z) {
+    UNUSED_PARAM(sensor_type);
+    UNUSED_PARAM(x);
+    UNUSED_PARAM(y);
+    UNUSED_PARAM(z);
+    return false;
+}
+
+bool ContextHub::SetCalibration(SensorType sensor_type, int32_t x,
+        int32_t y, int32_t z, int32_t w) {
+    UNUSED_PARAM(sensor_type);
+    UNUSED_PARAM(x);
+    UNUSED_PARAM(y);
+    UNUSED_PARAM(z);
+    UNUSED_PARAM(w);
+    return false;
+}
+
+bool ContextHub::SaveCalibration() {
+    LOGE("Saving calibration data not implemented");
+    return false;
+}
+
+}  // namespace android
diff --git a/util/nanotool/contexthub.h b/util/nanotool/contexthub.h
new file mode 100644
index 0000000..0c6489f
--- /dev/null
+++ b/util/nanotool/contexthub.h
@@ -0,0 +1,264 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CONTEXTHUB_H_
+#define CONTEXTHUB_H_
+
+#include "nanomessage.h"
+#include "noncopyable.h"
+
+#include <bitset>
+#include <functional>
+#include <vector>
+
+namespace android {
+
+class AppToHostEvent;
+class SensorEvent;
+
+// Array length helper macro
+#define ARRAY_LEN(arr) (sizeof(arr) / sizeof(arr[0]))
+
+enum class SensorType {
+    Invalid_ = 0,
+
+    // The order of this enum must correspond to sensor types in nanohub's
+    // sensType.h
+    Accel,
+    AnyMotion,
+    NoMotion,
+    SignificantMotion,
+    Flat,
+    Gyro,
+    GyroUncal,
+    Magnetometer,
+    MagnetometerUncal,
+    Barometer,
+    Temperature,
+    AmbientLightSensor,
+    Proximity,
+    Orientation,
+    HeartRateECG,
+    HeartRatePPG,
+    Gravity,
+    LinearAccel,
+    RotationVector,
+    GeomagneticRotationVector,
+    GameRotationVector,
+    StepCount,
+    StepDetect,
+    Gesture,
+    Tilt,
+    DoubleTwist,
+    DoubleTap,
+    WindowOrientation,
+    Hall,
+    Activity,
+    Vsync,
+    CompressedAccel,
+
+    Max_
+};
+
+// Overloaded values of rate used in sensor enable request (see sensors.h)
+enum class SensorSpecialRate : uint32_t {
+    None     = 0,
+    OnDemand = 0xFFFFFF00,
+    OnChange = 0xFFFFFF01,
+    OneShot  = 0xFFFFFF02,
+};
+
+struct SensorSpec {
+    SensorType sensor_type = SensorType::Invalid_;
+
+    // When enabling a sensor, rate can be specified in Hz or as one of the
+    // special values
+    SensorSpecialRate special_rate = SensorSpecialRate::None;
+    float rate_hz = -1;
+    uint64_t latency_ns = 0;
+
+    // Reference value (ground truth) used for calibration
+    bool have_cal_ref = false;
+    float cal_ref;
+};
+
+/*
+ * An interface for communicating with a ContextHub.
+ */
+class ContextHub : public NonCopyable {
+  public:
+    virtual ~ContextHub() {};
+
+    static std::string SensorTypeToAbbrevName(SensorType sensor_type);
+    static SensorType SensorAbbrevNameToType(const char *abbrev_name);
+    static SensorType SensorAbbrevNameToType(const std::string& abbrev_name);
+    static std::string ListAllSensorAbbrevNames();
+
+    /*
+     * Performs initialization to allow commands to be sent to the context hub.
+     * Must be called before any other functions that send commands. Returns
+     * true on success, false on failure.
+     */
+    virtual bool Initialize() = 0;
+
+    /*
+     * Configures the ContextHub to allow logs to be printed to stdout.
+     */
+    virtual void SetLoggingEnabled(bool logging_enabled) = 0;
+
+    /*
+     * Loads a new firmware image to the ContextHub. The firmware image is
+     * specified by filename. Returns false if an error occurs.
+     */
+    bool Flash(const std::string& filename);
+
+    /*
+     * Performs the sensor calibration routine and writes the resulting data to
+     * a file.
+     */
+    bool CalibrateSensors(const std::vector<SensorSpec>& sensors);
+
+    /*
+     * Sends a sensor enable request to the context hub.
+     */
+    bool EnableSensor(const SensorSpec& sensor);
+    bool EnableSensors(const std::vector<SensorSpec>& sensors);
+
+    /*
+     * Sends a disable sensor request to context hub. Note that this always
+     * results in sending a request, i.e. this does not check whether the sensor
+     * is currently enabled or not.
+     */
+    bool DisableSensor(SensorType sensor_type);
+    bool DisableSensors(const std::vector<SensorSpec>& sensors);
+
+    /*
+     * Sends a disable sensor request for every sensor type we know about.
+     */
+    bool DisableAllSensors();
+
+    /*
+     * Calls DisableSensor() on all active sensors (i.e. those which have been
+     * enabled but not yet disabled). This should be called from the destructor
+     * of derived classes before tearing down communications to ensure we don't
+     * leave sensors enabled after exiting.
+     */
+    bool DisableActiveSensors();
+
+    /*
+     * Sends all data stored in the calibration file to the context hub.
+     */
+    virtual bool LoadCalibration();
+
+    /*
+     * Prints up to <limit> incoming events. If limit is 0, then continues
+     * indefinitely.
+     */
+    void PrintAllEvents(unsigned int limit);
+
+    /*
+     * Prints up to <sample_limit> incoming sensor samples corresponding to the
+     * given SensorType, ignoring other events. If sample_limit is 0, then
+     * continues indefinitely.
+     */
+    void PrintSensorEvents(SensorType sensor_type, int sample_limit);
+    void PrintSensorEvents(const std::vector<SensorSpec>& sensors,
+        int sample_limit);
+
+  protected:
+    enum class TransportResult {
+        Success,
+        GeneralFailure,
+        Timeout,
+        ParseFailure,
+        Canceled,
+        // Add more specific error reasons as needed
+    };
+
+    // Performs the calibration routine, but does not call SaveCalibration()
+    bool CalibrateSingleSensor(const SensorSpec& sensor);
+
+    /*
+     * Iterates over sensors, invoking the given callback on each element.
+     * Returns true if all callbacks returned true. Exits early on failure.
+     */
+    bool ForEachSensor(const std::vector<SensorSpec>& sensors,
+        std::function<bool(const SensorSpec&)> callback);
+
+    /*
+     * Parses a calibration result event and invokes the appropriate
+     * SetCalibration function with the calibration data.
+     */
+    bool HandleCalibrationResult(const SensorSpec& sensor,
+        const AppToHostEvent &event);
+
+    /*
+     * Same as ReadSensorEvents, but filters on AppToHostEvent instead of
+     * SensorEvent.
+     */
+    TransportResult ReadAppEvents(std::function<bool(const AppToHostEvent&)> callback,
+        int timeout_ms = 0);
+
+    /*
+     * Calls ReadEvent in a loop, handling errors and ignoring events that
+     * didn't originate from a sensor. Valid SensorEvents are passed to the
+     * callback for further processing. The callback should return a boolean
+     * indicating whether to continue (true) or exit the read loop (false).
+     */
+    void ReadSensorEvents(std::function<bool(const SensorEvent&)> callback);
+
+    /*
+     * Sends the given calibration data down to the hub
+     */
+    bool SendCalibrationData(SensorType sensor_type,
+        const std::vector<uint8_t>& cal_data);
+
+    /*
+     * Read an event from the sensor hub. Block until a event is successfully
+     * read, no event traffic is generated for the timeout period, or an error
+     * occurs, such as a CRC check failure.
+     */
+    virtual TransportResult ReadEvent(std::vector<uint8_t>& response,
+        int timeout_ms) = 0;
+    virtual TransportResult WriteEvent(const std::vector<uint8_t>& request) = 0;
+
+    // Implements the firmware loading functionality for the sensor hub. Returns
+    // false if an error occurs while writing the firmware to the device.
+    virtual bool FlashSensorHub(const std::vector<uint8_t>& bytes) = 0;
+
+    // Convenience functions that build on top of the more generic byte-level
+    // interface
+    TransportResult ReadEvent(std::unique_ptr<ReadEventResponse>* response,
+        int timeout_ms = 0);
+    TransportResult WriteEvent(const WriteEventRequest& request);
+
+    // Override these if saving calibration data to persistent storage is
+    // supported on the platform
+    virtual bool SetCalibration(SensorType sensor_type, int32_t data);
+    virtual bool SetCalibration(SensorType sensor_type, float data);
+    virtual bool SetCalibration(SensorType sensor_type, int32_t x,
+        int32_t y, int32_t z);
+    virtual bool SetCalibration(SensorType sensor_type, int32_t x,
+        int32_t y, int32_t z, int32_t w);
+    virtual bool SaveCalibration();
+
+private:
+    std::bitset<static_cast<int>(SensorType::Max_)> sensor_is_active_;
+};
+
+}  // namespace android
+
+#endif  // CONTEXTHUB_H_
diff --git a/util/nanotool/log.cpp b/util/nanotool/log.cpp
new file mode 100644
index 0000000..eec303b
--- /dev/null
+++ b/util/nanotool/log.cpp
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "log.h"
+
+#include <cctype>
+#include <chrono>
+#include <cstdio>
+#include <string>
+
+namespace android {
+
+Log::LogLevel Log::level_;
+Logger* Log::logger_;
+std::chrono::time_point<std::chrono::steady_clock> Log::init_time_;
+
+void Log::Initialize(Logger *logger, LogLevel level) {
+    if (Log::logger_) {
+        Log::Warn("Re-initializing logger");
+    }
+    Log::init_time_ = std::chrono::steady_clock::now();
+    Log::logger_ = logger;
+    Log::SetLevel(level);
+}
+
+void Log::SetLevel(LogLevel level) {
+    Log::level_ = level;
+}
+
+#define LOG_EX_VARARGS(level, format) \
+    do { \
+        va_list arg_list; \
+        va_start(arg_list, format); \
+        Log::LogEx(level, format, arg_list); \
+        va_end(arg_list); \
+    } while (0)
+
+void Log::Error(const char *format, ...) {
+    LOG_EX_VARARGS(LogLevel::Error, format);
+}
+
+void Log::Warn(const char *format, ...) {
+    LOG_EX_VARARGS(LogLevel::Warn, format);
+}
+
+void Log::Info(const char *format, ...) {
+    LOG_EX_VARARGS(LogLevel::Info, format);
+}
+
+void Log::Debug(const char *format, ...) {
+    LOG_EX_VARARGS(LogLevel::Debug, format);
+}
+
+void Log::DebugBuf(std::vector<uint8_t> vec) {
+    Log::DebugBuf(vec.data(), vec.size());
+}
+
+void Log::DebugBuf(const uint8_t *buffer, size_t size) {
+    if (Log::level_ < LogLevel::Debug) {
+        return;
+    }
+
+    char line[32];
+    int offset = 0;
+    char line_chars[32];
+    int offset_chars = 0;
+
+    Log::Debug("Dumping buffer of size %zu bytes", size);
+    for (size_t i = 1; i <= size; ++i) {
+        offset += snprintf(&line[offset], sizeof(line) - offset, "%02x ",
+                           buffer[i - 1]);
+        offset_chars += snprintf(
+            &line_chars[offset_chars], sizeof(line_chars) - offset_chars,
+            "%c", (isprint(buffer[i - 1])) ? buffer[i - 1] : '.');
+        if ((i % 8) == 0) {
+            Log::Debug("  %s\t%s", line, line_chars);
+            offset = 0;
+            offset_chars = 0;
+        } else if ((i % 4) == 0) {
+            offset += snprintf(&line[offset], sizeof(line) - offset, " ");
+        }
+    }
+
+    if (offset > 0) {
+        std::string tabs;
+        while (offset < 28) {
+            tabs += "\t";
+            offset += 8;
+        }
+        Log::Debug("  %s%s%s", line, tabs.c_str(), line_chars);
+    }
+}
+
+char Log::LevelAbbrev(LogLevel level) {
+    switch (level) {
+    case LogLevel::Error:
+        return 'E';
+    case LogLevel::Warn:
+        return 'W';
+    case LogLevel::Info:
+        return 'I';
+    case LogLevel::Debug:
+        return 'D';
+    default:
+        return '?';
+    }
+}
+
+void Log::LogEx(LogLevel level, const char *format, va_list arg_list) {
+    if (Log::level_ < level) {
+        return;
+    }
+
+    std::chrono::duration<float> log_time =
+        (std::chrono::steady_clock::now() - Log::init_time_);
+
+    // Can add colorization here if desired (should be configurable)
+    char prefix[20];
+    snprintf(prefix, sizeof(prefix), "%c %6.03f: ", Log::LevelAbbrev(level),
+             log_time.count());
+
+    Log::logger_->Output(prefix);
+    Log::logger_->Output(format, arg_list);
+    Log::logger_->Output("\n");
+}
+
+void PrintfLogger::Output(const char *str) {
+    printf("%s", str);
+}
+
+void PrintfLogger::Output(const char *format, va_list arg_list) {
+    vprintf(format, arg_list);
+}
+
+}  // namespace android
diff --git a/util/nanotool/log.h b/util/nanotool/log.h
new file mode 100644
index 0000000..2a168f2
--- /dev/null
+++ b/util/nanotool/log.h
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LOG_H_
+#define LOG_H_
+
+#include <stdarg.h>
+
+#include <chrono>
+#include <vector>
+
+namespace android {
+
+/*
+ * Prefer to use these macros instead of calling Log::Error, etc. directly, in
+ * case we want to add tracing of the source file and line number, or compile
+ * out logging completely, etc.
+ */
+#define LOGE(fmt, ...) Log::Error(fmt, ##__VA_ARGS__)
+#define LOGW(fmt, ...) Log::Warn(fmt, ##__VA_ARGS__)
+#define LOGI(fmt, ...) Log::Info(fmt, ##__VA_ARGS__)
+#define LOGD(fmt, ...) Log::Debug(fmt, ##__VA_ARGS__)
+
+#define LOGD_BUF(buf, len) Log::DebugBuf((const uint8_t *) buf, len)
+#define LOGD_VEC(vec) Log::DebugBuf(vec)
+
+// Interface for a log output method
+class Logger {
+  public:
+    virtual ~Logger() {};
+    virtual void Output(const char *str) = 0;
+    virtual void Output(const char *format, va_list arg_list) = 0;
+};
+
+// Singleton used to log messages to an arbitrary output
+class Log {
+  public:
+    enum class LogLevel {
+        // Use with SetLevel to disable logging
+        Disable,
+        Error,
+        Warn,
+        Info,
+        Debug,
+    };
+
+    // Define the logging mechanism and minimum log level that will be printed
+    static void Initialize(Logger *logger, LogLevel level);
+
+    __attribute__((__format__ (printf, 1, 2)))
+    static void Error(const char *format, ...);
+
+    __attribute__((__format__ (printf, 1, 2)))
+    static void Warn(const char *format, ...);
+
+    __attribute__((__format__ (printf, 1, 2)))
+    static void Info(const char *format, ...);
+
+    __attribute__((__format__ (printf, 1, 2)))
+    static void Debug(const char *format, ...);
+
+    static void DebugBuf(std::vector<uint8_t> vec);
+    static void DebugBuf(const uint8_t *buffer, size_t size);
+
+    // Allows for updating the logging level after initialization
+    static void SetLevel(LogLevel level);
+
+  private:
+    static char LevelAbbrev(LogLevel level);
+    static void LogEx(LogLevel level, const char *format, va_list arg_list);
+
+    static Logger* logger_;
+    static LogLevel level_;
+    static std::chrono::time_point<std::chrono::steady_clock> init_time_;
+};
+
+class PrintfLogger : public Logger {
+  public:
+    void Output(const char *str);
+    void Output(const char *format, va_list arg_list);
+};
+
+}  // namespace android
+
+#endif // LOG_H_
diff --git a/util/nanotool/nanomessage.cpp b/util/nanotool/nanomessage.cpp
new file mode 100644
index 0000000..340980f
--- /dev/null
+++ b/util/nanotool/nanomessage.cpp
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "nanomessage.h"
+
+#include <inttypes.h>
+#include <stdio.h>
+
+#include "apptohostevent.h"
+#include "log.h"
+#include "resetreasonevent.h"
+#include "sensorevent.h"
+
+namespace android {
+
+/* HardwareVersionInfo ********************************************************/
+
+bool HardwareVersionInfo::Populate(const std::vector<uint8_t>& buffer) {
+    if (buffer.size() != sizeof(VersionInfo)) {
+        return false;
+    }
+
+    const uint8_t *data = buffer.data();
+    const VersionInfo *source = reinterpret_cast<const VersionInfo *>(data);
+    info = *source;
+    return true;
+}
+
+std::string HardwareVersionInfo::ToString() const {
+    const char format_string[] = "Hardware version info:\n"
+        "    Hardware type: %04x\n"
+        "    Hardware version: %04x\n"
+        "    Bootloader version: %04x\n"
+        "    Operating system version: %04x\n"
+        "    Variant version: %08x\n";
+
+    char buffer[1024];
+    snprintf(buffer, sizeof(buffer), format_string,
+        info.hardware_type,
+        info.hardware_version,
+        info.bootloader_version,
+        info.operating_system_version,
+        info.variant_version);
+    return std::string(buffer);
+}
+
+/* WriteEventResponse *********************************************************/
+
+std::string WriteEventResponse::ToString() const {
+    const char format_string[] = "Write event accepted: %s\n";
+
+    char buffer[128];
+    snprintf(buffer, sizeof(buffer), format_string,
+        response.accepted ? "true" : "false");
+    return std::string(buffer);
+}
+
+bool WriteEventResponse::Populate(const std::vector<uint8_t>& buffer) {
+    if (buffer.size() != sizeof(Response)) {
+        return false;
+    }
+
+    const uint8_t *data = buffer.data();
+    const Response *source = reinterpret_cast<const Response *>(data);
+    response = *source;
+    return true;
+
+}
+
+/* ReadEventRequest ***********************************************************/
+
+std::vector<uint8_t> ReadEventRequest::GetBytes() const {
+    std::vector<uint8_t> buffer(sizeof(Request));
+
+    uint8_t *data = buffer.data();
+    Request *req = reinterpret_cast<Request *>(data);
+    *req = request;
+    return buffer;
+}
+
+std::string ReadEventRequest::ToString() const {
+    const char format_string[] = "Read event at time: %" PRIx64 "\n";
+
+    char buffer[128];
+    snprintf(buffer, sizeof(buffer), format_string,
+        request.boot_time);
+    return std::string(buffer);
+}
+
+/* ReadEventResponse **********************************************************/
+
+std::string ReadEventResponse::ToString() const {
+    char buffer[32];
+    snprintf(buffer, sizeof(buffer), "ReadEventResponse %u\n", GetEventType());
+    return std::string(buffer);
+}
+
+std::unique_ptr<ReadEventResponse> ReadEventResponse::FromBytes(
+        const std::vector<uint8_t>& buffer) {
+    // The first 4 bytes of any event must be the event type - use it to figure
+    // out which class to construct
+    uint32_t event_type = ReadEventResponse::EventTypeFromBuffer(buffer);
+    if (ReadEventResponse::IsSensorEvent(event_type)) {
+        return SensorEvent::FromBytes(buffer);
+    } else if (ReadEventResponse::IsAppToHostEvent(event_type)) {
+        return AppToHostEvent::FromBytes(buffer);
+    } else if (ReadEventResponse::IsResetReasonEvent(event_type)) {
+        return ResetReasonEvent::FromBytes(buffer);
+    } else {
+        LOGW("Received unexpected/unsupported event type %u", event_type);
+        return nullptr;
+    }
+}
+
+bool ReadEventResponse::Populate(const std::vector<uint8_t>& buffer) {
+    if (buffer.size() < sizeof(Event)) {
+        return false;
+    }
+
+    event_data.resize(buffer.size());
+    std::copy(buffer.begin(), buffer.end(), event_data.begin());
+    return true;
+}
+
+bool ReadEventResponse::IsAppToHostEvent() const {
+    return ReadEventResponse::IsAppToHostEvent(GetEventType());
+}
+
+bool ReadEventResponse::IsSensorEvent() const {
+    return ReadEventResponse::IsSensorEvent(GetEventType());
+}
+
+bool ReadEventResponse::IsResetReasonEvent() const {
+    return ReadEventResponse::IsResetReasonEvent(GetEventType());
+}
+
+uint32_t ReadEventResponse::GetEventType() const {
+    return ReadEventResponse::EventTypeFromBuffer(event_data);
+}
+
+bool ReadEventResponse::IsSensorEvent(uint32_t event_type) {
+    return (event_type >= static_cast<uint32_t>(EventType::FirstSensorEvent) &&
+            event_type <= static_cast<uint32_t>(EventType::LastSensorEvent));
+}
+
+bool ReadEventResponse::IsAppToHostEvent(uint32_t event_type) {
+    return (event_type == static_cast<uint32_t>(EventType::AppToHostEvent));
+}
+
+bool ReadEventResponse::IsResetReasonEvent(uint32_t event_type) {
+    return (event_type == static_cast<uint32_t>(EventType::ResetReasonEvent));
+}
+
+uint32_t ReadEventResponse::EventTypeFromBuffer(const std::vector<uint8_t>& buffer) {
+    if (buffer.size() < sizeof(uint32_t)) {
+        LOGW("Invalid/short event of size %zu", buffer.size());
+        return 0;
+    }
+    return *reinterpret_cast<const uint32_t *>(buffer.data());
+}
+
+/* ConfigureSensorRequest *****************************************************/
+
+ConfigureSensorRequest::ConfigureSensorRequest() {
+    config.event_type = static_cast<uint32_t>(EventType::ConfigureSensor);
+}
+
+uint32_t ConfigureSensorRequest::FloatRateToFixedPoint(float rate) {
+    return rate * 1024.0f;
+}
+
+float ConfigureSensorRequest::FixedPointRateToFloat(uint32_t rate) {
+    return rate / 1024.0f;
+}
+
+// TODO(aarossig): Consider writing a template function for this.
+std::vector<uint8_t> ConfigureSensorRequest::GetBytes() const {
+    std::vector<uint8_t> buffer(sizeof(Configuration));
+
+    uint8_t *data = buffer.data();
+    Configuration *configuration = reinterpret_cast<Configuration *>(data);
+    *configuration = config;
+    buffer.insert(buffer.end(), extra_data_.begin(), extra_data_.end());
+
+    return buffer;
+}
+
+void ConfigureSensorRequest::SetAdditionalData(const std::vector<uint8_t>& data) {
+    extra_data_ = data;
+}
+
+std::string ConfigureSensorRequest::ToString() const {
+    const char format_string[] = "Sensor configuration:\n"
+        "    latency: %" PRIx64 "\n"
+        "    rate (fixed point): %08x\n"
+        "    sensor_type: %02x\n"
+        "    command: %02x\n"
+        "    flags: %04x\n";
+
+    char buffer[1024];
+    snprintf(buffer, sizeof(buffer), format_string,
+            config.latency,
+            config.rate,
+            config.sensor_type,
+            config.command,
+            config.flags);
+    return std::string(buffer);
+}
+
+EventType ConfigureSensorRequest::GetEventType() const {
+    return static_cast<EventType>(config.event_type);
+}
+
+}  // namespace android
diff --git a/util/nanotool/nanomessage.h b/util/nanotool/nanomessage.h
new file mode 100644
index 0000000..8495233
--- /dev/null
+++ b/util/nanotool/nanomessage.h
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef NANOMESSAGE_H_
+#define NANOMESSAGE_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "noncopyable.h"
+
+namespace android {
+
+/*
+ * Events types that can be pushed back and forth between the ContextHub and
+ * host software.
+ */
+enum class EventType {
+    FirstSensorEvent = 0x00000200,
+    LastSensorEvent  = 0x000002FF,
+    ConfigureSensor  = 0x00000300,
+    AppToHostEvent   = 0x00000401,
+    ResetReasonEvent = 0x00000403,
+};
+
+/*
+ * An interface for all messages passed to and from the ContextHub.
+ */
+class NanoMessage : public NonCopyable {
+  public:
+    virtual ~NanoMessage() {};
+
+    // Generates a string intended to be printed to a console or saved to logs.
+    // This interface requires that the string be terminated with a newline.
+    virtual std::string ToString() const = 0;
+};
+
+/*
+ * An interface for requests sent to the ContextHub.
+ */
+class NanoRequest : public NanoMessage {
+  public:
+    // Returns a payload of bytes to be packaged into a NanoPacket.
+    virtual std::vector<uint8_t> GetBytes() const = 0;
+};
+
+/*
+ * An interface for responses from the ContextHub.
+ */
+class NanoResponse : public NanoMessage {
+  public:
+    // Populates the fields of the NanoMessage given a NanoPacket. Returns
+    // false if the packet is incomplete or incorrect message.
+    virtual bool Populate(const std::vector<uint8_t>& buffer) = 0;
+};
+
+/*
+ * Version information for a ContextHub.
+ */
+class HardwareVersionInfo : public NanoResponse {
+  public:
+    bool Populate(const std::vector<uint8_t>& buffer) override;
+    std::string ToString() const override;
+
+    struct VersionInfo {
+        uint16_t hardware_type;
+        uint16_t hardware_version;
+        uint16_t bootloader_version;
+        uint16_t operating_system_version;
+        uint32_t variant_version;
+    } __attribute__((packed)) info;
+};
+
+/*
+ * The base event for all event data.
+ */
+struct Event {
+    uint32_t event_type;
+} __attribute__((packed));
+
+/*
+ * A request to write an event to the ContextHub.
+ */
+class WriteEventRequest : public NanoRequest {
+  public:
+    virtual EventType GetEventType() const = 0;
+};
+
+/*
+ * A response to writing an event to the ContextHub.
+ */
+class WriteEventResponse : public NanoResponse {
+  public:
+    std::string ToString() const override;
+    bool Populate(const std::vector<uint8_t>& buffer) override;
+
+    struct Response {
+        bool accepted;
+    } __attribute__((packed)) response;
+};
+
+/*
+ * A response to reading an event from the ContextHub.
+ */
+class ReadEventRequest : public NanoRequest {
+  public:
+    std::vector<uint8_t> GetBytes() const override;
+    std::string ToString() const override;
+
+    struct Request {
+        uint64_t boot_time;
+    } __attribute__((packed)) request;
+};
+
+class ReadEventResponse : public NanoResponse {
+  public:
+    virtual std::string ToString() const override;
+
+    // Construct and populate a concrete ReadEventResponse from the given buffer
+    static std::unique_ptr<ReadEventResponse> FromBytes(
+        const std::vector<uint8_t>& buffer);
+
+    bool Populate(const std::vector<uint8_t>& buffer) override;
+
+    bool IsAppToHostEvent() const;
+    bool IsSensorEvent() const;
+    bool IsResetReasonEvent() const;
+    uint32_t GetEventType() const;
+
+    // Event data associated with this response.
+    std::vector<uint8_t> event_data;
+
+  protected:
+    static uint32_t EventTypeFromBuffer(const std::vector<uint8_t>& buffer);
+    static bool IsAppToHostEvent(uint32_t event_type);
+    static bool IsSensorEvent(uint32_t event_type);
+    static bool IsResetReasonEvent(uint32_t event_type);
+};
+
+/*
+ * An event used to configure a sensor with specific attributes.
+ */
+class ConfigureSensorRequest : public WriteEventRequest {
+  public:
+    enum class CommandType {
+        Disable,
+        Enable,
+        Flush,
+        ConfigData,
+        Calibrate
+    };
+
+    ConfigureSensorRequest();
+
+    static uint32_t FloatRateToFixedPoint(float rate);
+    static float FixedPointRateToFloat(uint32_t rate);
+
+    std::vector<uint8_t> GetBytes() const override;
+    std::string ToString() const override;
+    EventType GetEventType() const override;
+
+    // Appends some data to the configuration request, e.g. for the ConfigData
+    // command
+    void SetAdditionalData(const std::vector<uint8_t>& data);
+
+    struct Configuration : public Event {
+        uint64_t latency;
+        uint32_t rate;
+        uint8_t sensor_type;
+        uint8_t command;
+        uint16_t flags;
+    }  __attribute__((packed)) config = {};
+
+  private:
+    std::vector<uint8_t> extra_data_;
+};
+
+}  // namespace android
+
+#endif  // NANOMESSAGE_H_
diff --git a/util/nanotool/nanopacket.cpp b/util/nanotool/nanopacket.cpp
new file mode 100644
index 0000000..b0b1703
--- /dev/null
+++ b/util/nanotool/nanopacket.cpp
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "nanopacket.h"
+
+namespace android {
+
+constexpr uint8_t kSyncByte(0x31);
+
+// CRC constants.
+constexpr uint32_t kInitialCrc(0xffffffff);
+constexpr uint32_t kCrcTable[] = {
+    0x00000000, 0x04C11DB7, 0x09823B6E, 0x0D4326D9,
+    0x130476DC, 0x17C56B6B, 0x1A864DB2, 0x1E475005,
+    0x2608EDB8, 0x22C9F00F, 0x2F8AD6D6, 0x2B4BCB61,
+    0x350C9B64, 0x31CD86D3, 0x3C8EA00A, 0x384FBDBD
+};
+
+// Computes the CRC of one word.
+uint32_t Crc32Word(uint32_t crc, uint32_t data, int cnt) {
+    crc = crc ^ data;
+
+    for (int i = 0; i < cnt; i++) {
+        crc = (crc << 4) ^ kCrcTable[crc >> 28];
+    }
+
+    return crc;
+}
+
+// Computes the CRC32 of a buffer given a starting CRC.
+uint32_t Crc32(const uint8_t *buffer, int length) {
+    int i;
+    uint32_t crc = kInitialCrc;
+
+    // Word by word crc32
+    for (i = 0; i < (length >> 2); i++) {
+        crc = Crc32Word(crc, ((uint32_t *)buffer)[i], 8);
+    }
+
+    // Zero pad last word if required.
+    if (length & 0x3) {
+        uint32_t word = 0;
+
+        for (i*=4; i<length; i++) {
+            word |= buffer[i] << ((i & 0x3) * 8);
+        }
+
+        crc = Crc32Word(crc, word, 8);
+    }
+
+    return crc;
+}
+
+NanoPacket::NanoPacket(uint32_t sequence_number, PacketReason reason,
+        const std::vector<uint8_t> *data) {
+    Reset();
+    parsing_state_ = ParsingState::Complete;
+    sequence_number_ = sequence_number;
+    reason_ = static_cast<uint32_t>(reason);
+
+    // Resize the buffer to accomodate header, footer and data content.
+    size_t data_size = data ? data->size() : 0;
+    size_t required_buffer_size = 14 + data_size;
+    if (packet_buffer_.size() < required_buffer_size) {
+        packet_buffer_.resize(required_buffer_size);
+    }
+
+    if (packet_content_.size() < required_buffer_size) {
+        packet_content_.resize(required_buffer_size);
+    }
+
+    // Format the header of the packet.
+    packet_buffer_[0] = kSyncByte;
+    packet_buffer_[1] = sequence_number;
+    packet_buffer_[2] = sequence_number >> 8;
+    packet_buffer_[3] = sequence_number >> 16;
+    packet_buffer_[4] = sequence_number >> 24;
+    packet_buffer_[5] = static_cast<uint32_t>(reason);
+    packet_buffer_[6] = static_cast<uint32_t>(reason) >> 8;
+    packet_buffer_[7] = static_cast<uint32_t>(reason) >> 16;
+    packet_buffer_[8] = static_cast<uint32_t>(reason) >> 24;
+    packet_buffer_[9] = data_size;
+
+    // Insert the data content of the packet.
+    if (data) {
+        std::copy(data->begin(), data->end(), packet_buffer_.begin() + 10);
+        std::copy(data->begin(), data->end(), packet_content_.begin());
+    }
+
+    // Format the CRC footer.
+    uint32_t crc = Crc32(packet_buffer_.data(), required_buffer_size - 4);
+    packet_buffer_[data_size + 10] = crc;
+    packet_buffer_[data_size + 11] = crc >> 8;
+    packet_buffer_[data_size + 12] = crc >> 16;
+    packet_buffer_[data_size + 13] = crc >> 24;
+}
+
+NanoPacket::NanoPacket() {
+    Reset();
+}
+
+void NanoPacket::Reset() {
+    packet_buffer_.clear();
+    parsing_state_ = ParsingState::Idle;
+    parsing_progress_ = 0;
+    sequence_number_ = 0;
+    reason_ = 0;
+    packet_content_.clear();
+    crc_ = 0;
+}
+
+bool NanoPacket::ParsingIsComplete() const {
+    return parsing_state_ == ParsingState::Complete;
+}
+
+const std::vector<uint8_t>& NanoPacket::packet_buffer() const {
+    return packet_buffer_;
+}
+
+uint32_t NanoPacket::reason() const {
+    return reason_;
+}
+
+PacketReason NanoPacket::TypedReason() const {
+    return static_cast<PacketReason>(reason_);
+}
+
+const std::vector<uint8_t>& NanoPacket::packet_content() const {
+    return packet_content_;
+}
+
+NanoPacket::ParseResult NanoPacket::Parse(uint8_t *buffer, size_t length,
+        size_t *bytes_parsed) {
+    for (size_t i = 0; i < length; i++) {
+        // Once the state machine is not idle, save all bytes to the current
+        // packet to allow CRC to be computed at the end.
+        if (parsing_state_ != ParsingState::Idle) {
+            packet_buffer_.push_back(buffer[i]);
+        }
+
+        // Proceed through the various states of protocol parsing.
+        if (parsing_state_ == ParsingState::Idle && buffer[i] == kSyncByte) {
+            packet_buffer_.push_back(buffer[i]);
+            parsing_state_ = ParsingState::ParsingSequenceNumber;
+        } else if (parsing_state_ == ParsingState::ParsingSequenceNumber
+                && DeserializeWord(&sequence_number_, buffer[i])) {
+            parsing_state_ = ParsingState::ParsingReason;
+        } else if (parsing_state_ == ParsingState::ParsingReason
+                && DeserializeWord(&reason_, buffer[i])) {
+            parsing_state_ = ParsingState::ParsingLength;
+        } else if (parsing_state_ == ParsingState::ParsingLength) {
+            uint8_t length = buffer[i];
+            if (length > 0) {
+                packet_content_.resize(buffer[i]);
+                parsing_state_ = ParsingState::ParsingContent;
+            } else {
+                parsing_state_ = ParsingState::ParsingCrc;
+            }
+        } else if (parsing_state_ == ParsingState::ParsingContent) {
+            packet_content_[parsing_progress_++] = buffer[i];
+
+            if (parsing_progress_ == packet_content_.size()) {
+                parsing_progress_ = 0;
+                parsing_state_ = ParsingState::ParsingCrc;
+            }
+        } else if (parsing_state_ == ParsingState::ParsingCrc
+                && DeserializeWord(&crc_, buffer[i])) {
+            *bytes_parsed = i + 1;
+            if (ValidateCrc()) {
+                parsing_state_ = ParsingState::Complete;
+                return ParseResult::Success;
+            } else {
+                return ParseResult::CrcMismatch;
+            }
+        }
+    }
+
+    *bytes_parsed = length;
+    return ParseResult::Incomplete;
+}
+
+bool NanoPacket::ValidateCrc() {
+    size_t crc_length = packet_buffer_.size() - 4;
+    uint32_t computed_crc = Crc32(packet_buffer_.data(), crc_length);
+
+    if (computed_crc != crc_) {
+        Reset();
+        return false;
+    }
+
+    return true;
+}
+
+}  // namespace android
diff --git a/util/nanotool/nanopacket.h b/util/nanotool/nanopacket.h
new file mode 100644
index 0000000..c7f02dc
--- /dev/null
+++ b/util/nanotool/nanopacket.h
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef NANOPACKET_H_
+#define NANOPACKET_H_
+
+#include <cstddef>
+#include <cstdint>
+#include <vector>
+
+#include "noncopyable.h"
+
+namespace android {
+
+/*
+ * The various reasons for a NanoPacket to be sent.
+ */
+enum class PacketReason : uint32_t {
+    Acknowledge        = 0x00000000,
+    NAcknowledge       = 0x00000001,
+    NAcknowledgeBusy   = 0x00000002,
+    GetHardwareVersion = 0x00001000,
+    ReadEventRequest   = 0x00001090,
+    WriteEventRequest  = 0x00001091,
+};
+
+/*
+ * A NanoPacket parsing engine. Used to take a stream of bytes and convert them
+ * into an object that can more easily be worked with.
+ */
+class NanoPacket : public NonCopyable {
+  public:
+    /*
+     * The result of parsing a buffer into the packet.
+     */
+    enum class ParseResult {
+        Success,
+        Incomplete,
+        CrcMismatch,
+    };
+
+    // Formats data into NanoPacket format in the provided buffer.
+    NanoPacket(uint32_t sequence_number, PacketReason reason,
+        const std::vector<uint8_t> *data = nullptr);
+
+    // Creates an empty NanoPacket for data to be parsed into.
+    NanoPacket();
+
+    // Resets the parsing engine to the idle state and clears parsed content.
+    void Reset();
+
+    // Parses content from a buffer. Returns true if a packet has been entirely
+    // parsed.
+    ParseResult Parse(uint8_t *buffer, size_t length, size_t *bytes_parsed);
+
+    // Indicated that parsing of the packet has completed.
+    bool ParsingIsComplete() const;
+
+    // The entire content of the message.
+    const std::vector<uint8_t>& packet_buffer() const;
+
+    // Obtains the reason for the packet.
+    uint32_t reason() const;
+
+    // Obtains the reason as a PacketReason.
+    PacketReason TypedReason() const;
+
+    // Obtains the data content of the packet.
+    const std::vector<uint8_t>& packet_content() const;
+
+  private:
+    /*
+     * The current state of the parser.
+     */
+    enum class ParsingState {
+        Idle,
+        ParsingSequenceNumber,
+        ParsingReason,
+        ParsingLength,
+        ParsingContent,
+        ParsingCrc,
+        Complete,
+    };
+
+    // Parsing engine state.
+    std::vector<uint8_t> packet_buffer_;
+    ParsingState parsing_state_;
+    uint32_t parsing_progress_;
+
+    // Parsed protocol fields.
+    uint32_t sequence_number_;
+    uint32_t reason_;
+    std::vector<uint8_t> packet_content_;
+    uint32_t crc_;
+
+    // Validates that the received packet has a CRC that matches a generated
+    // CRC.
+    bool ValidateCrc();
+
+    // Deserializes a little-endian word using the parsing_progress_ member to
+    // maintain state.
+    template<typename T>
+    bool DeserializeWord(T *destination, uint8_t byte);
+};
+
+}  // namespace android
+
+#include "nanopacket_impl.h"
+
+#endif  // NANOPACKET_H_
diff --git a/util/nanotool/nanopacket_impl.h b/util/nanotool/nanopacket_impl.h
new file mode 100644
index 0000000..2a7d9a3
--- /dev/null
+++ b/util/nanotool/nanopacket_impl.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "nanopacket.h"
+
+#include <stdio.h>
+
+namespace android {
+
+template<typename T>
+bool NanoPacket::DeserializeWord(T *destination, uint8_t byte) {
+    *destination |= byte << (8 * parsing_progress_);
+    parsing_progress_++;
+
+    if (parsing_progress_ == sizeof(T)) {
+        parsing_progress_ = 0;
+        return true;
+    }
+
+    return false;
+}
+
+}  // namespace android
diff --git a/util/nanotool/nanotool.cpp b/util/nanotool/nanotool.cpp
new file mode 100644
index 0000000..21b388d
--- /dev/null
+++ b/util/nanotool/nanotool.cpp
@@ -0,0 +1,478 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <getopt.h>
+#include <signal.h>
+
+#include <cstdlib>
+#include <cstring>
+#include <memory>
+#include <sstream>
+#include <tuple>
+#include <vector>
+
+#include "contexthub.h"
+#include "log.h"
+
+#ifdef __ANDROID__
+#include "androidcontexthub.h"
+#else
+#include "cp2130.h"
+#include "usbcontext.h"
+#include "usbcontexthub.h"
+#endif
+
+using namespace android;
+
+enum class NanotoolCommand {
+    Invalid,
+    Disable,
+    DisableAll,
+    Calibrate,
+    Read,
+    Poll,
+    LoadCalibration,
+    Flash,
+};
+
+struct ParsedArgs {
+    NanotoolCommand command = NanotoolCommand::Poll;
+    std::vector<SensorSpec> sensors;
+    int count = 0;
+    bool logging_enabled = false;
+    std::string filename;
+    int device_index = 0;
+};
+
+static NanotoolCommand StrToCommand(const char *command_name) {
+    static const std::vector<std::tuple<std::string, NanotoolCommand>> cmds = {
+        std::make_tuple("disable",     NanotoolCommand::Disable),
+        std::make_tuple("disable_all", NanotoolCommand::DisableAll),
+        std::make_tuple("calibrate",   NanotoolCommand::Calibrate),
+        std::make_tuple("cal",         NanotoolCommand::Calibrate),
+        std::make_tuple("read",        NanotoolCommand::Read),
+        std::make_tuple("poll",        NanotoolCommand::Poll),
+        std::make_tuple("load_cal",    NanotoolCommand::LoadCalibration),
+        std::make_tuple("flash",       NanotoolCommand::Flash),
+    };
+
+    if (!command_name) {
+        return NanotoolCommand::Invalid;
+    }
+
+    for (size_t i = 0; i < cmds.size(); i++) {
+        std::string name;
+        NanotoolCommand cmd;
+
+        std::tie(name, cmd) = cmds[i];
+        if (name.compare(command_name) == 0) {
+            return cmd;
+        }
+    }
+
+    return NanotoolCommand::Invalid;
+}
+
+static void PrintUsage(const char *name) {
+    const char *help_text =
+        "options:\n"
+        "  -x, --cmd          Argument must be one of:\n"
+        "                        disable: send a disable request for one sensor\n"
+        "                        disable_all: send a disable request for all sensors\n"
+        "                        calibrate: disable the sensor, then perform the sensor\n"
+        "                           calibration routine\n"
+        "                        load_cal: send data from calibration file to hub\n"
+        "                        read: output events for the given sensor, or all events\n"
+        "                           if no sensor specified\n"
+        "                        poll (default): enable the sensor, output received\n"
+        "                           events, then disable the sensor before exiting\n"
+        "                        flash: Load a new firmware image to the hub\n"
+        "\n"
+        "  -s, --sensor       Specify sensor type, and parameters for the command.\n"
+        "                     Format is sensor_type[:rate[:latency_ms]][=cal_ref].\n"
+        "                     See below for a complete list sensor types. A rate is\n"
+        "                     required when enabling a sensor, but latency is optional\n"
+        "                     and defaults to 0. Rate can be specified in Hz, or as one\n"
+        "                     of the special values \"onchange\", \"ondemand\", or\n"
+        "                     \"oneshot\".\n"
+        "                     Some sensors require a ground truth value for calibration.\n"
+        "                     Use the cal_ref parameter for this purpose (it's parsed as\n"
+        "                     a float).\n"
+        "                     This argument can be repeated to perform a command on\n"
+        "                     multiple sensors.\n"
+        "\n"
+        "  -c, --count        Number of samples to read before exiting, or set to 0 to\n"
+        "                     read indefinitely (the default behavior)\n"
+        "\n"
+        "  -f, --file\n"
+        "                     Specifies the file to be used with flash.\n"
+        "\n"
+        "  -l, --log          Outputs logs from the sensor hub as they become available.\n"
+        "                     The logs will be printed inline with sensor samples.\n"
+        "                     The default is for log messages to be ignored.\n"
+#ifndef __ANDROID__
+        // This option is only applicable when connecting over USB
+        "\n"
+        "  -i, --index        Selects the device to work with by specifying the index\n"
+        "                     into the device list (default: 0)\n"
+#endif
+        "\n"
+        "  -v, -vv            Output verbose/extra verbose debugging information\n";
+
+    fprintf(stderr, "%s %s\n\n", name, NANOTOOL_VERSION_STR);
+    fprintf(stderr, "Usage: %s [options]\n\n%s\n", name, help_text);
+    fprintf(stderr, "Supported sensors: %s\n\n",
+            ContextHub::ListAllSensorAbbrevNames().c_str());
+    fprintf(stderr, "Examples:\n"
+                    "  %s -s accel:50\n"
+                    "  %s -s accel:50:1000 -s gyro:50:1000\n"
+                    "  %s -s prox:onchange\n"
+                    "  %s -x calibrate -s baro=1000\n",
+            name, name, name, name);
+}
+
+/*
+ * Performs higher-level argument validation beyond just parsing the parameters,
+ * for example check whether a required argument is present when the command is
+ * set to a specific value.
+ */
+static bool ValidateArgs(std::unique_ptr<ParsedArgs>& args, const char *name) {
+    if (!args->sensors.size()
+          && (args->command == NanotoolCommand::Disable
+                || args->command == NanotoolCommand::Calibrate
+                || args->command == NanotoolCommand::Poll)) {
+        fprintf(stderr, "%s: At least 1 sensor must be specified for this "
+                        "command (use -s)\n",
+                name);
+        return false;
+    }
+
+    if (args->command == NanotoolCommand::Flash
+            && args->filename.empty()) {
+        fprintf(stderr, "%s: A filename must be specified for this command "
+                        "(use -f)\n",
+                name);
+        return false;
+    }
+
+    if (args->command == NanotoolCommand::Poll) {
+        for (unsigned int i = 0; i < args->sensors.size(); i++) {
+            if (args->sensors[i].special_rate == SensorSpecialRate::None
+                  && args->sensors[i].rate_hz < 0) {
+                fprintf(stderr, "%s: Sample rate must be specified for sensor "
+                        "%s\n", name,
+                        ContextHub::SensorTypeToAbbrevName(
+                            args->sensors[i].sensor_type).c_str());
+                return false;
+            }
+        }
+    }
+
+    if (args->command == NanotoolCommand::Calibrate) {
+        for (unsigned int i = 0; i < args->sensors.size(); i++) {
+            if (!args->sensors[i].have_cal_ref
+                  && (args->sensors[i].sensor_type == SensorType::Barometer
+                        || args->sensors[i].sensor_type ==
+                             SensorType::AmbientLightSensor)) {
+                fprintf(stderr, "%s: Calibration reference required for sensor "
+                                "%s (for example: -s baro=1000)\n", name,
+                        ContextHub::SensorTypeToAbbrevName(
+                            args->sensors[i].sensor_type).c_str());
+                return false;
+            }
+        }
+    }
+
+    return true;
+}
+
+static bool ParseRate(const std::string& param, SensorSpec& spec) {
+    static const std::vector<std::tuple<std::string, SensorSpecialRate>> rates = {
+        std::make_tuple("ondemand", SensorSpecialRate::OnDemand),
+        std::make_tuple("onchange", SensorSpecialRate::OnChange),
+        std::make_tuple("oneshot",  SensorSpecialRate::OneShot),
+    };
+
+    for (size_t i = 0; i < rates.size(); i++) {
+        std::string name;
+        SensorSpecialRate rate;
+
+        std::tie(name, rate) = rates[i];
+        if (param == name) {
+            spec.special_rate = rate;
+            return true;
+        }
+    }
+
+    spec.rate_hz = std::stof(param);
+    if (spec.rate_hz < 0) {
+        return false;
+    }
+
+    return true;
+}
+
+// Parse a sensor argument in the form of "sensor_name[:rate[:latency]][=cal_ref]"
+// into a SensorSpec, and add it to ParsedArgs.
+static bool ParseSensorArg(std::vector<SensorSpec>& sensors, const char *arg_str,
+        const char *name) {
+    SensorSpec spec;
+    std::string param;
+    std::string pre_cal_ref;
+    std::stringstream full_arg_ss(arg_str);
+    unsigned int index = 0;
+
+    while (std::getline(full_arg_ss, param, '=')) {
+        if (index == 0) {
+            pre_cal_ref = param;
+        } else if (index == 1) {
+            spec.cal_ref = std::stof(param);
+            spec.have_cal_ref = true;
+        } else {
+            fprintf(stderr, "%s: Only one calibration reference may be "
+                            "supplied\n", name);
+            return false;
+        }
+        index++;
+    }
+
+    index = 0;
+    std::stringstream pre_cal_ref_ss(pre_cal_ref);
+    while (std::getline(pre_cal_ref_ss, param, ':')) {
+        if (index == 0) { // Parse sensor type
+            spec.sensor_type = ContextHub::SensorAbbrevNameToType(param);
+            if (spec.sensor_type == SensorType::Invalid_) {
+                fprintf(stderr, "%s: Invalid sensor name '%s'\n",
+                        name, param.c_str());
+                return false;
+            }
+        } else if (index == 1) { // Parse sample rate
+            if (!ParseRate(param, spec)) {
+                fprintf(stderr, "%s: Invalid sample rate %s\n", name,
+                        param.c_str());
+                return false;
+            }
+        } else if (index == 2) { // Parse latency
+            long long latency_ms = std::stoll(param);
+            if (latency_ms < 0) {
+                fprintf(stderr, "%s: Invalid latency %lld\n", name, latency_ms);
+                return false;
+            }
+            spec.latency_ns = static_cast<uint64_t>(latency_ms) * 1000000;
+        } else {
+            fprintf(stderr, "%s: Too many arguments in -s", name);
+            return false;
+        }
+        index++;
+    }
+
+    sensors.push_back(spec);
+    return true;
+}
+
+static std::unique_ptr<ParsedArgs> ParseArgs(int argc, char **argv) {
+    static const struct option long_opts[] = {
+        {"cmd",     required_argument, nullptr, 'x'},
+        {"sensor",  required_argument, nullptr, 's'},
+        {"count",   required_argument, nullptr, 'c'},
+        {"flash",   required_argument, nullptr, 'f'},
+        {"log",     no_argument,       nullptr, 'l'},
+        {"index",   required_argument, nullptr, 'i'},
+    };
+
+    auto args = std::unique_ptr<ParsedArgs>(new ParsedArgs());
+    int index = 0;
+    while (42) {
+        int c = getopt_long(argc, argv, "x:s:c:f:v::li:", long_opts, &index);
+        if (c == -1) {
+            break;
+        }
+
+        switch (c) {
+          case 'x': {
+            args->command = StrToCommand(optarg);
+            if (args->command == NanotoolCommand::Invalid) {
+                fprintf(stderr, "%s: Invalid command '%s'\n", argv[0], optarg);
+                return nullptr;
+            }
+            break;
+          }
+          case 's': {
+            if (!ParseSensorArg(args->sensors, optarg, argv[0])) {
+                return nullptr;
+            }
+            break;
+          }
+          case 'c': {
+            args->count = atoi(optarg);
+            if (args->count < 0) {
+                fprintf(stderr, "%s: Invalid sample count %d\n",
+                        argv[0], args->count);
+                return nullptr;
+            }
+            break;
+          }
+          case 'v': {
+            if (optarg && optarg[0] == 'v') {
+                Log::SetLevel(Log::LogLevel::Debug);
+            } else {
+                Log::SetLevel(Log::LogLevel::Info);
+            }
+            break;
+          }
+          case 'l': {
+            args->logging_enabled = true;
+            break;
+          }
+          case 'f': {
+            if (optarg) {
+                args->filename = std::string(optarg);
+            } else {
+                fprintf(stderr, "File requires a filename\n");
+                return nullptr;
+            }
+            break;
+          }
+          case 'i': {
+            args->device_index = atoi(optarg);
+            if (args->device_index < 0) {
+                fprintf(stderr, "%s: Invalid device index %d\n", argv[0],
+                        args->device_index);
+                return nullptr;
+            }
+            break;
+          }
+          default:
+            return nullptr;
+        }
+    }
+
+    if (!ValidateArgs(args, argv[0])) {
+        return nullptr;
+    }
+    return args;
+}
+
+static std::unique_ptr<ContextHub> GetContextHub(std::unique_ptr<ParsedArgs>& args) {
+#ifdef __ANDROID__
+    (void) args;
+    return std::unique_ptr<AndroidContextHub>(new AndroidContextHub());
+#else
+    return std::unique_ptr<UsbContextHub>(new UsbContextHub(args->device_index));
+#endif
+}
+
+#ifdef __ANDROID__
+static void SignalHandler(int sig) {
+    // Catches a signal and does nothing, to allow any pending syscalls to be
+    // exited with SIGINT and normal cleanup to occur. If SIGINT is sent a
+    // second time, the system will invoke the standard handler.
+    (void) sig;
+}
+
+static void TerminateHandler() {
+    AndroidContextHub::TerminateHandler();
+    std::abort();
+}
+
+static void SetHandlers() {
+    struct sigaction sa;
+    memset(&sa, 0, sizeof(sa));
+    sa.sa_handler = SignalHandler;
+    sigaction(SIGINT, &sa, NULL);
+
+    std::set_terminate(TerminateHandler);
+}
+#endif
+
+int main(int argc, char **argv) {
+    Log::Initialize(new PrintfLogger(), Log::LogLevel::Warn);
+
+    // If no arguments given, print usage without any error messages
+    if (argc == 1) {
+        PrintUsage(argv[0]);
+        return 1;
+    }
+
+    std::unique_ptr<ParsedArgs> args = ParseArgs(argc, argv);
+    if (!args) {
+        PrintUsage(argv[0]);
+        return 1;
+    }
+
+#ifdef __ANDROID__
+    SetHandlers();
+#endif
+
+    std::unique_ptr<ContextHub> hub = GetContextHub(args);
+    if (!hub || !hub->Initialize()) {
+        LOGE("Error initializing ContextHub");
+        return -1;
+    }
+
+    hub->SetLoggingEnabled(args->logging_enabled);
+
+    bool success = true;
+    switch (args->command) {
+      case NanotoolCommand::Disable:
+        success = hub->DisableSensors(args->sensors);
+        break;
+      case NanotoolCommand::DisableAll:
+        success = hub->DisableAllSensors();
+        break;
+      case NanotoolCommand::Read: {
+        if (!args->sensors.size()) {
+            hub->PrintAllEvents(args->count);
+        } else {
+            hub->PrintSensorEvents(args->sensors, args->count);
+        }
+        break;
+      }
+      case NanotoolCommand::Poll: {
+        success = hub->EnableSensors(args->sensors);
+        if (success) {
+            hub->PrintSensorEvents(args->sensors, args->count);
+        }
+        break;
+      }
+      case NanotoolCommand::Calibrate: {
+        hub->DisableSensors(args->sensors);
+        success = hub->CalibrateSensors(args->sensors);
+        break;
+      }
+      case NanotoolCommand::LoadCalibration: {
+        success = hub->LoadCalibration();
+        break;
+      }
+      case NanotoolCommand::Flash: {
+        success = hub->Flash(args->filename);
+        break;
+      }
+      default:
+        LOGE("Command not implemented");
+        return 1;
+    }
+
+    if (!success) {
+        LOGE("Command failed");
+        return -1;
+    } else if (args->command != NanotoolCommand::Read
+                   && args->command != NanotoolCommand::Poll) {
+        printf("Operation completed successfully\n");
+    }
+
+    return 0;
+}
diff --git a/util/nanotool/noncopyable.h b/util/nanotool/noncopyable.h
new file mode 100644
index 0000000..4d8476c
--- /dev/null
+++ b/util/nanotool/noncopyable.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef NONCOPYABLE_H_
+#define NONCOPYABLE_H_
+
+namespace android {
+
+class NonCopyable {
+  public:
+    NonCopyable() = default;
+    NonCopyable(const NonCopyable&) = delete;
+    NonCopyable& operator=(const NonCopyable&) = delete;
+};
+
+}  // namespace android
+
+#endif  // NONCOPYABLE_H_
diff --git a/util/nanotool/resetreasonevent.cpp b/util/nanotool/resetreasonevent.cpp
new file mode 100644
index 0000000..19e7a37
--- /dev/null
+++ b/util/nanotool/resetreasonevent.cpp
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "resetreasonevent.h"
+
+#include "contexthub.h"
+#include "log.h"
+
+namespace android {
+
+/* ResetReasonEvent *************************************************************/
+
+std::unique_ptr<ResetReasonEvent> ResetReasonEvent::FromBytes(
+        const std::vector<uint8_t>& buffer) {
+    auto event = std::unique_ptr<ResetReasonEvent>(new ResetReasonEvent());
+    event->Populate(buffer);
+
+    return event;
+}
+
+uint32_t ResetReasonEvent::GetReason() const {
+    // After the event type header (uint32_t), we should have the reset reason,
+    // which is of type uint32_t
+    if (event_data.size() < (sizeof(uint32_t) + sizeof(uint32_t))) {
+        LOGW("Invalid/short ResetReason event of size %zu", event_data.size());
+        return 0;
+    } else {
+        return *(uint32_t*)reinterpret_cast<const uint32_t*>(
+            event_data.data() + sizeof(uint32_t));
+    }
+}
+
+}  // namespace android
diff --git a/util/nanotool/resetreasonevent.h b/util/nanotool/resetreasonevent.h
new file mode 100644
index 0000000..48c9e67
--- /dev/null
+++ b/util/nanotool/resetreasonevent.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef RESET_REASON_EVENT_H_
+#define RESET_REASON_EVENT_H_
+
+#include "contexthub.h"
+#include "nanomessage.h"
+
+namespace android {
+
+/*
+ * These classes represent events sent with event type EVT_RESET_REASON. The
+ * platform-specific reset reason is sent at each boot of the sensor hub.
+ */
+
+class ResetReasonEvent : public ReadEventResponse {
+  public:
+    /*
+     * Constructs and populates a ResetReasonEvent instance. Returns nullptr if
+     * the packet is malformed. The rest of the methods in this class are not
+     * guaranteed to be safe unless the object is constructed from this
+     * function.
+     */
+    static std::unique_ptr<ResetReasonEvent> FromBytes(
+        const std::vector<uint8_t>& buffer);
+
+    // Returns the 32-bit field that contains the platform-specific reset reason
+    uint32_t GetReason() const;
+};
+
+}  // namespace android
+
+#endif // RESET_REASON_EVENT_H_
diff --git a/util/nanotool/sensorevent.cpp b/util/nanotool/sensorevent.cpp
new file mode 100644
index 0000000..2d64eb1
--- /dev/null
+++ b/util/nanotool/sensorevent.cpp
@@ -0,0 +1,286 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "sensorevent.h"
+
+#include <inttypes.h>
+#include <string.h>
+
+#include "contexthub.h"
+#include "log.h"
+
+namespace android {
+
+constexpr float kCompressedSampleRatio(8.0f * 9.81f / 32768.0f);
+
+/* SensorEvent ****************************************************************/
+
+std::unique_ptr<SensorEvent> SensorEvent::FromBytes(
+        const std::vector<uint8_t>& buffer) {
+    SensorEvent *sensor_event = nullptr;
+
+    SensorType sensor_type = static_cast<SensorType>(
+        ReadEventResponse::EventTypeFromBuffer(buffer) -
+        static_cast<uint32_t>(EventType::FirstSensorEvent));
+
+    switch (sensor_type) {
+      case SensorType::Accel:
+      case SensorType::Gyro:
+      case SensorType::GyroUncal:
+      case SensorType::Magnetometer:
+      case SensorType::MagnetometerUncal:
+      case SensorType::Orientation:
+      case SensorType::Gravity:
+      case SensorType::LinearAccel:
+      case SensorType::RotationVector:
+      case SensorType::GeomagneticRotationVector:
+      case SensorType::GameRotationVector:
+        sensor_event = new TripleAxisSensorEvent();
+        break;
+
+      case SensorType::Barometer:
+      case SensorType::Temperature:
+      case SensorType::AmbientLightSensor:
+      case SensorType::Proximity:
+        sensor_event = new SingleAxisSensorEvent();
+        break;
+
+      // TODO: Activity uses a special struct, it should have its own class
+      case SensorType::Activity:
+      case SensorType::AnyMotion:
+      case SensorType::NoMotion:
+      case SensorType::SignificantMotion:
+      case SensorType::Flat:
+      case SensorType::WindowOrientation:
+      case SensorType::Tilt:
+      case SensorType::Hall:
+      case SensorType::HeartRateECG: // Heart rates not implemented, guessing
+      case SensorType::HeartRatePPG: // data type here...
+      case SensorType::StepCount:
+      case SensorType::StepDetect:
+      case SensorType::Gesture:
+      case SensorType::DoubleTwist:
+      case SensorType::DoubleTap:
+      case SensorType::Vsync:
+          sensor_event = new SingleAxisIntSensorEvent();
+          break;
+
+      case SensorType::CompressedAccel:
+          sensor_event = new CompressedTripleAxisSensorEvent();
+          break;
+
+    default:
+        LOGW("Can't create SensorEvent for unknown/invalid sensor type %d",
+             static_cast<int>(sensor_type));
+    }
+
+    if (sensor_event &&
+        (!sensor_event->Populate(buffer) || !sensor_event->SizeIsValid())) {
+        LOGW("Couldn't populate sensor event, or invalid size");
+        delete sensor_event;
+        sensor_event = nullptr;
+    }
+
+    return std::unique_ptr<SensorEvent>(sensor_event);
+}
+
+SensorType SensorEvent::GetSensorType() const {
+    return static_cast<SensorType>(
+        GetEventType() - static_cast<uint32_t>(EventType::FirstSensorEvent));
+}
+
+/* TimestampedSensorEvent *****************************************************/
+
+uint8_t TimestampedSensorEvent::GetNumSamples() const {
+    // Perform size check, but don't depend on SizeIsValid since it will call us
+    if (event_data.size() < (sizeof(struct SensorEventHeader) +
+                             sizeof(struct SensorFirstSample))) {
+        LOGW("Short/invalid timestamped sensor event; length %zu",
+             event_data.size());
+        return 0;
+    }
+
+    const struct SensorFirstSample *first_sample_header =
+        reinterpret_cast<const struct SensorFirstSample *>(
+            event_data.data() + sizeof(struct SensorEventHeader));
+
+    return first_sample_header->numSamples;
+}
+
+uint64_t TimestampedSensorEvent::GetReferenceTime() const {
+    if (!SizeIsValid()) {
+        return 0;
+    }
+    const struct SensorEventHeader *header =
+        reinterpret_cast<const struct SensorEventHeader *>(event_data.data());
+    return header->reference_time;
+}
+
+uint64_t TimestampedSensorEvent::GetSampleTime(uint8_t index) const {
+    const SensorSampleHeader *sample;
+    uint64_t sample_time = GetReferenceTime();
+
+    // For index 0, the sample time is the reference time. For each subsequent
+    // sample, sum the delta to the previous sample to get the sample time.
+    for (uint8_t i = 1; i <= index; i++) {
+        sample = GetSampleAtIndex(index);
+        sample_time += sample->delta_time;
+    }
+
+    return sample_time;
+}
+
+std::string TimestampedSensorEvent::GetSampleTimeStr(uint8_t index) const {
+    uint64_t sample_time = GetSampleTime(index);
+
+    char buffer[32];
+    snprintf(buffer, sizeof(buffer), "%" PRIu64 ".%06" PRIu64 " ms",
+             sample_time / 1000000, sample_time % 1000000);
+
+    return std::string(buffer);
+}
+
+const SensorSampleHeader *TimestampedSensorEvent::GetSampleAtIndex(
+        uint8_t index) const {
+    if (index >= GetNumSamples()) {
+        LOGW("Requested sample at invalid index %u", index);
+        return nullptr;
+    }
+
+    unsigned int offset = (sizeof(struct SensorEventHeader) +
+        index * GetSampleDataSize());
+    return reinterpret_cast<const struct SensorSampleHeader *>(
+        event_data.data() + offset);
+}
+
+std::string TimestampedSensorEvent::ToString() const {
+    uint8_t num_samples = GetNumSamples();
+    char buffer[64];
+    snprintf(buffer, sizeof(buffer),
+             "Event from sensor %d (%s) with %d sample%s\n",
+             static_cast<int>(GetSensorType()),
+             ContextHub::SensorTypeToAbbrevName(GetSensorType()).c_str(),
+             num_samples, (num_samples != 1) ? "s" : "");
+
+    return std::string(buffer) + StringForAllSamples();
+}
+
+bool TimestampedSensorEvent::SizeIsValid() const {
+    unsigned int min_size = (sizeof(struct SensorEventHeader) +
+        GetNumSamples() * GetSampleDataSize());
+    if (event_data.size() < min_size) {
+        LOGW("Got short sensor event with %zu bytes, expected >= %u",
+             event_data.size(), min_size);
+        return false;
+    }
+
+    return true;
+}
+
+std::string TimestampedSensorEvent::StringForAllSamples() const {
+    std::string str;
+    for (unsigned int i = 0; i < GetNumSamples(); i++) {
+        str += StringForSample(i);
+    }
+    return str;
+}
+
+/* SingleAxisSensorEvent ******************************************************/
+
+std::string SingleAxisSensorEvent::StringForSample(uint8_t index) const {
+    const SingleAxisDataPoint *sample =
+        reinterpret_cast<const SingleAxisDataPoint *>(GetSampleAtIndex(index));
+
+    char buffer[64];
+    snprintf(buffer, sizeof(buffer), "  %f @ %s\n",
+             sample->fdata, GetSampleTimeStr(index).c_str());
+
+    return std::string(buffer);
+}
+
+uint8_t SingleAxisSensorEvent::GetSampleDataSize() const {
+    return sizeof(struct SingleAxisDataPoint);
+}
+
+/* SingleAxisIntSensorEvent ***************************************************/
+
+std::string SingleAxisIntSensorEvent::StringForSample(uint8_t index) const {
+    const SingleAxisDataPoint *sample =
+        reinterpret_cast<const SingleAxisDataPoint *>(GetSampleAtIndex(index));
+
+    char buffer[64];
+    snprintf(buffer, sizeof(buffer), "  %d @ %s\n",
+             sample->idata, GetSampleTimeStr(index).c_str());
+
+    return std::string(buffer);
+}
+
+/* TripleAxisSensorEvent ******************************************************/
+
+std::string TripleAxisSensorEvent::StringForSample(uint8_t index) const {
+    const TripleAxisDataPoint *sample =
+        reinterpret_cast<const TripleAxisDataPoint *>(
+            GetSampleAtIndex(index));
+
+    const struct SensorFirstSample *first_sample =
+        reinterpret_cast<const struct SensorFirstSample *>(
+            event_data.data() + sizeof(struct SensorEventHeader));
+    bool is_bias_sample = first_sample->biasPresent
+        && first_sample->biasSample == index;
+
+    char buffer[128];
+    snprintf(buffer, sizeof(buffer), "  X:%f Y:%f Z:%f @ %s%s\n",
+             sample->x, sample->y, sample->z, GetSampleTimeStr(index).c_str(),
+             is_bias_sample ? " (Bias Sample)" : "");
+
+    return std::string(buffer);
+}
+
+uint8_t TripleAxisSensorEvent::GetSampleDataSize() const {
+    return sizeof(struct TripleAxisDataPoint);
+}
+
+/* CompressedTripleAxisSensorEvent ********************************************/
+
+std::string CompressedTripleAxisSensorEvent::StringForSample(
+        uint8_t index) const {
+    const CompressedTripleAxisDataPoint *sample =
+        reinterpret_cast<const CompressedTripleAxisDataPoint *>(
+            GetSampleAtIndex(index));
+
+    const struct SensorFirstSample *first_sample =
+        reinterpret_cast<const struct SensorFirstSample *>(
+            event_data.data() + sizeof(struct SensorEventHeader));
+    bool is_bias_sample = first_sample->biasPresent
+        && first_sample->biasSample == index;
+
+    float x = sample->ix * kCompressedSampleRatio;
+    float y = sample->iy * kCompressedSampleRatio;
+    float z = sample->iz * kCompressedSampleRatio;
+
+    char buffer[128];
+    snprintf(buffer, sizeof(buffer), "  X:%f Y:%f Z:%f @ %s%s\n",
+             x, y, z, GetSampleTimeStr(index).c_str(),
+             is_bias_sample ? " (Bias Sample)" : "");
+
+    return std::string(buffer);
+}
+
+uint8_t CompressedTripleAxisSensorEvent::GetSampleDataSize() const {
+    return sizeof(CompressedTripleAxisDataPoint);
+}
+
+}  // namespace android
diff --git a/util/nanotool/sensorevent.h b/util/nanotool/sensorevent.h
new file mode 100644
index 0000000..f98a160
--- /dev/null
+++ b/util/nanotool/sensorevent.h
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SENSOREVENT_H_
+#define SENSOREVENT_H_
+
+#include "contexthub.h"
+#include "nanomessage.h"
+
+namespace android {
+
+// Copied from sensors.h in nanohub firmware/inc
+struct SensorFirstSample
+{
+    uint8_t numSamples;
+    uint8_t numFlushes;
+    uint8_t biasCurrent : 1;
+    uint8_t biasPresent : 1;
+    uint8_t biasSample : 6;
+    uint8_t interrupt;
+};
+
+struct SingleAxisDataPoint {
+    union {
+        uint32_t deltaTime; //delta since last sample, for 0th sample this is firstSample
+        struct SensorFirstSample firstSample;
+    };
+    union {
+        float fdata;
+        int32_t idata;
+    };
+} __attribute__((packed));
+
+struct CompressedTripleAxisDataPoint {
+    uint32_t deltaTime;
+    int16_t ix;
+    int16_t iy;
+    int16_t iz;
+} __attribute__((packed));
+
+struct TripleAxisDataPoint {
+    union {
+        uint32_t deltaTime; //delta since last sample, for 0th sample this is firstSample
+        struct SensorFirstSample firstSample;
+    };
+    union {
+        float x;
+        int32_t ix;
+    };
+    union {
+        float y;
+        int32_t iy;
+    };
+    union {
+        float z;
+        int32_t iz;
+    };
+} __attribute__((packed));
+
+/*
+ * Common timestamped sensor event structure is SensorEventHeader followed by
+ * a variable length array of sensor samples, each starting with
+ * SensorSampleHeader.
+ */
+struct SensorEventHeader : public Event {
+    uint64_t reference_time;
+} __attribute__((packed));
+
+struct SensorSampleHeader {
+    union {
+        struct SensorFirstSample first_sample_header;
+        uint32_t delta_time;
+    };
+} __attribute__((packed));
+
+class SensorEvent : public ReadEventResponse {
+  public:
+    /*
+     * Returns a pointer to a ReadEventResponse that will be constructed from
+     * one of the sensor event types and populated from byte stream. If
+     * this function is called, it's assumed that the event type is within the
+     * range [EVT_NO_FIRST_SENSOR_EVENT, EVT_NO_SENSOR_CONFIG_EVENT)
+     */
+    static std::unique_ptr<SensorEvent> FromBytes(
+        const std::vector<uint8_t>& buffer);
+
+    SensorType GetSensorType() const;
+    std::string GetSensorName() const;
+
+    /*
+     * Subclasses should override this function to return the number of samples
+     * contained in the event.
+     */
+    virtual uint8_t GetNumSamples() const = 0;
+
+  protected:
+    virtual bool SizeIsValid() const = 0;
+};
+
+class TimestampedSensorEvent : public SensorEvent {
+  public:
+    uint8_t GetNumSamples() const override;
+    uint64_t GetReferenceTime() const;
+    uint64_t GetSampleTime(uint8_t index) const;
+    std::string GetSampleTimeStr(uint8_t index) const;
+    const SensorSampleHeader *GetSampleAtIndex(uint8_t index) const;
+
+    std::string ToString() const override;
+
+    virtual std::string StringForSample(uint8_t index) const = 0;
+
+  protected:
+    bool SizeIsValid() const override;
+    std::string StringForAllSamples() const;
+
+    /*
+     * Subclasses must implement this to be the size of each data point,
+     * including struct SensorSampleHeader.
+     */
+    virtual uint8_t GetSampleDataSize() const = 0;
+};
+
+class SingleAxisSensorEvent : public TimestampedSensorEvent {
+  public:
+    virtual std::string StringForSample(uint8_t index) const override;
+
+  protected:
+    uint8_t GetSampleDataSize() const override;
+};
+
+// Same as SingleAxisSensorEvent, but data is interpreted as an integer instead
+// of float
+class SingleAxisIntSensorEvent : public SingleAxisSensorEvent {
+  public:
+    std::string StringForSample(uint8_t index) const override;
+};
+
+class TripleAxisSensorEvent : public TimestampedSensorEvent {
+  public:
+    std::string StringForSample(uint8_t index) const override;
+
+  protected:
+    uint8_t GetSampleDataSize() const override;
+};
+
+class CompressedTripleAxisSensorEvent : public TimestampedSensorEvent {
+  public:
+    std::string StringForSample(uint8_t index) const override;
+
+  protected:
+    uint8_t GetSampleDataSize() const override;
+};
+
+}  // namespace android
+
+#endif // SENSOREVENT_H_
diff --git a/util/sensortest/sensortest.cpp b/util/sensortest/sensortest.cpp
index c841a9b..c032ca9 100644
--- a/util/sensortest/sensortest.cpp
+++ b/util/sensortest/sensortest.cpp
@@ -26,6 +26,7 @@
     int listIndex;
     int type;
     int32_t rate;
+    int reportLatency;
     bool receivedEvent;
 };
 
@@ -39,7 +40,7 @@
 
 void showHelp()
 {
-    printf("Usage: sensortest [-h] [-l] [-e <type> <rate_usecs>] [-c]\n");
+    printf("Usage: sensortest [-h] [-l] [-e <type> <rate_usecs>] [-b <type> <rate_usecs> <batch_usecs>] [-c]\n");
 }
 
 void printSensorList()
@@ -93,6 +94,7 @@
 {
     int currArgumentIndex = 1;
     int sensorIndex;
+    int existingSensorConfigIndex;
 
     mNumSensorConfigs = 0;
 
@@ -113,14 +115,61 @@
                 return false;
             }
 
-            mSensorConfigList[(mNumSensorConfigs)++] = {
-                .listIndex = sensorIndex,
-                .type = atoi(argv[currArgumentIndex+1]),
-                .rate = atoi(argv[currArgumentIndex+2]),
-                .receivedEvent = false
-            };
+            existingSensorConfigIndex = findSensorTypeInConfigList(atoi(argv[currArgumentIndex+1]));
+
+            if (existingSensorConfigIndex >= 0) {
+                printf("Replacing previous config for sensor type %d\n", atoi(argv[currArgumentIndex+1]));
+                mSensorConfigList[existingSensorConfigIndex] = {
+                    .listIndex = sensorIndex,
+                    .type = atoi(argv[currArgumentIndex+1]),
+                    .rate = atoi(argv[currArgumentIndex+2]),
+                    .reportLatency = 0,
+                    .receivedEvent = false
+                };
+            } else {
+                mSensorConfigList[(mNumSensorConfigs)++] = {
+                    .listIndex = sensorIndex,
+                    .type = atoi(argv[currArgumentIndex+1]),
+                    .rate = atoi(argv[currArgumentIndex+2]),
+                    .reportLatency = 0,
+                    .receivedEvent = false
+                };
+            }
 
             currArgumentIndex += 3;
+        } else if (!strcmp(argv[currArgumentIndex], "-b")) {
+            if (currArgumentIndex + 3 >= argc) {
+                printf ("Not enough arguments for batch option\n");
+                return false;
+            }
+
+            if ((sensorIndex = findSensorTypeInSensorList(atoi(argv[currArgumentIndex+1]))) < 0) {
+                printf ("No sensor found with type \"%d\"\n", atoi(argv[currArgumentIndex+1]));
+                return false;
+            }
+
+            existingSensorConfigIndex = findSensorTypeInConfigList(atoi(argv[currArgumentIndex+1]));
+
+            if (existingSensorConfigIndex >= 0) {
+                printf("Replacing previous config for sensor type %d\n", atoi(argv[currArgumentIndex+1]));
+                mSensorConfigList[existingSensorConfigIndex] = {
+                    .listIndex = sensorIndex,
+                    .type = atoi(argv[currArgumentIndex+1]),
+                    .rate = atoi(argv[currArgumentIndex+2]),
+                    .reportLatency = atoi(argv[currArgumentIndex+3]),
+                    .receivedEvent = false
+                };
+            } else {
+                mSensorConfigList[(mNumSensorConfigs)++] = {
+                    .listIndex = sensorIndex,
+                    .type = atoi(argv[currArgumentIndex+1]),
+                    .rate = atoi(argv[currArgumentIndex+2]),
+                    .reportLatency = atoi(argv[currArgumentIndex+3]),
+                    .receivedEvent = false
+                };
+            }
+
+            currArgumentIndex += 4;
         } else if (!strcmp(argv[currArgumentIndex], "-c")) {
             mContinuousMode = true;
             currArgumentIndex++;
@@ -164,12 +213,12 @@
     ASensorEventQueue *sensorEventQueue = ASensorManager_createEventQueue(mSensorManager, mLooper, 0, NULL, NULL);
 
     for (int i = 0; i < mNumSensorConfigs; i++) {
-        if (ASensorEventQueue_enableSensor(sensorEventQueue, mSensorList[mSensorConfigList[i].listIndex]) < 0) {
-            printf("Unable to enable sensor %d\n", mSensorConfigList[i].listIndex);
+        if (ASensorEventQueue_registerSensor(sensorEventQueue, mSensorList[mSensorConfigList[i].listIndex],
+                                             mSensorConfigList[i].rate, mSensorConfigList[i].reportLatency) < 0) {
+            printf("Unable to register sensor %d with rate %d and report latency %d\n", mSensorConfigList[i].listIndex,
+                   mSensorConfigList[i].rate, mSensorConfigList[i].reportLatency);
         }
-        if (ASensorEventQueue_setEventRate(sensorEventQueue, mSensorList[mSensorConfigList[i].listIndex], mSensorConfigList[i].rate) < 0) {
-            printf("Invalid rate \"%d\" for sensor %d\n", mSensorConfigList[i].rate, mSensorConfigList[i].listIndex);
-        }
+
     }
 
     while (mContinuousMode || !hasReceivedAllEvents()) {