Use Parser Code for GNSS CHPP->CHRE Conversion am: c825b356a0

Original change: https://googleplex-android-review.googlesource.com/c/platform/system/chre/+/12674979

Change-Id: I6530a35e12472c52cefa3a1a52c1d7077bfc1ebd
diff --git a/Android.mk b/Android.mk
index 6b93aa5..0bf822e 100644
--- a/Android.mk
+++ b/Android.mk
@@ -16,6 +16,14 @@
 
 LOCAL_PATH := $(call my-dir)
 
+# If the daemon is using USF, but USF isn't enabled, disable the daemon.
+# TODO (b/157659611): Remove when USF is available on partner with a prebuilt.
+ifeq ($(CHRE_DAEMON_USES_USF),true)
+  ifneq ($(USF_ENABLED),true)
+    CHRE_DAEMON_ENABLED := false
+  endif
+endif
+
 # Don't build the daemon for targets that don't contain a vendor image as
 # libsdsprpc and libadsprpc are provided by vendor code
 ifeq ($(BUILDING_VENDOR_IMAGE),true)
@@ -56,23 +64,41 @@
     host/common/log_message_parser_base.cc \
     host/common/socket_server.cc \
     host/common/st_hal_lpma_handler.cc \
-    host/msm/daemon/fastrpc_daemon.cc \
-    host/msm/daemon/main.cc \
-    host/msm/daemon/generated/chre_slpi_stub.c \
     platform/shared/host_protocol_common.cc
 
+MSM_DAEMON_SRC_FILES := \
+    host/msm/daemon/fastrpc_daemon.cc \
+    host/msm/daemon/main.cc \
+    host/msm/daemon/generated/chre_slpi_stub.c
+
+USF_DAEMON_SRC_FILES := \
+    host/usf_daemon/usf_daemon.cc \
+    host/usf_daemon/main.cc
+
+USF_DAEMON_CFLAGS := \
+    -DFLATBUFFERS_USF
+
 LOCAL_C_INCLUDES := \
-    external/fastrpc/inc \
     system/chre/external/flatbuffers/include \
     system/chre/host/common/include \
-    system/chre/host/msm/daemon \
     system/chre/platform/shared/include \
-    system/chre/platform/slpi/include \
     system/chre/util/include \
     system/core/base/include \
     system/core/libcutils/include \
     system/core/liblog/include \
-    system/core/libutils/include \
+    system/core/libutils/include
+
+MSM_DAEMON_INCLUDES := \
+    external/fastrpc/inc \
+    system/chre/platform/slpi/include \
+    system/chre/host/msm/daemon
+
+USF_DAEMON_INCLUDES := \
+    system/chre/host/usf_daemon \
+    vendor/google/sensors/usf/core/include \
+    vendor/google/sensors/usf/pal/android/include \
+    vendor/google/sensors/usf/pal/include \
+    vendor/google/sensors/usf/core/fbs
 
 LOCAL_SHARED_LIBRARIES := \
     libjsoncpp \
@@ -84,6 +110,23 @@
     android.hardware.soundtrigger@2.0 \
     libpower
 
+USF_DAEMON_SHARED_LIBRARIES := libusf
+
+ifeq ($(CHRE_DAEMON_USES_USF),true)
+LOCAL_C_INCLUDES += $(USF_DAEMON_INCLUDES)
+LOCAL_SRC_FILES += $(USF_DAEMON_SRC_FILES)
+LOCAL_CFLAGS += $(USF_DAEMON_CFLAGS)
+LOCAL_SHARED_LIBRARIES += $(USF_DAEMON_SHARED_LIBRARIES)
+else
+LOCAL_SRC_FILES += $(MSM_DAEMON_SRC_FILES)
+LOCAL_C_INCLUDES += $(MSM_DAEMON_INCLUDES)
+ifeq ($(CHRE_DAEMON_USE_SDSPRPC),true)
+LOCAL_SHARED_LIBRARIES += libsdsprpc
+else
+LOCAL_SHARED_LIBRARIES += libadsprpc
+endif
+endif
+
 # Enable tokenized logging
 ifeq ($(CHRE_USE_TOKENIZED_LOGGING),true)
 LOCAL_CFLAGS += -DCHRE_USE_TOKENIZED_LOGGING
@@ -102,10 +145,9 @@
 LOCAL_SRC_FILES += $(PIGWEED_TOKENIZER_DIR_RELPATH)/pw_varint/varint.cc
 endif
 
-ifeq ($(CHRE_DAEMON_USE_SDSPRPC),true)
-LOCAL_SHARED_LIBRARIES += libsdsprpc
-else
-LOCAL_SHARED_LIBRARIES += libadsprpc
+ifeq ($(CHRE_DAEMON_LPMA_ENABLED),true)
+LOCAL_SHARED_LIBRARIES += android.hardware.soundtrigger@2.0
+LOCAL_SHARED_LIBRARIES += libpower
 endif
 
 include $(BUILD_EXECUTABLE)
diff --git a/Makefile b/Makefile
index 6c178a7..631a4b3 100644
--- a/Makefile
+++ b/Makefile
@@ -97,6 +97,7 @@
 # implementation of CHRE. Example: this CHRE implementation is never built for
 # google_cm4_nanohub as Nanohub itself is a CHRE implementation.
 include $(CHRE_PREFIX)/build/variant/google_arm64_android.mk
+include $(CHRE_PREFIX)/build/variant/google_armv8a_aoc.mk
 include $(CHRE_PREFIX)/build/variant/google_hexagonv55_slpi-see.mk
 include $(CHRE_PREFIX)/build/variant/google_hexagonv60_slpi.mk
 include $(CHRE_PREFIX)/build/variant/google_hexagonv62_slpi.mk
diff --git a/apps/apps.mk b/apps/apps.mk
index a76dc71..6ab4d6d 100644
--- a/apps/apps.mk
+++ b/apps/apps.mk
@@ -7,7 +7,6 @@
 COMMON_CFLAGS += -Iapps/include
 
 # App makefiles ################################################################
-
 ifeq ($(CHRE_AUDIO_SUPPORT_ENABLED), true)
 include apps/audio_world/audio_world.mk
 endif
diff --git a/apps/sensor_world/sensor_world.cc b/apps/sensor_world/sensor_world.cc
index f1a6687..fd08f5e 100644
--- a/apps/sensor_world/sensor_world.cc
+++ b/apps/sensor_world/sensor_world.cc
@@ -352,6 +352,7 @@
       const auto *ev = static_cast<const chreSensorThreeAxisData *>(eventData);
       const auto header = ev->header;
       const auto *data = ev->readings;
+      const auto accuracy = header.accuracy;
       sampleTime = header.baseTimestamp;
 
       float x = 0, y = 0, z = 0;
@@ -365,9 +366,9 @@
       y /= header.readingCount;
       z /= header.readingCount;
 
-      CLOGI("%s, %d samples: %f %f %f, t=%" PRIu64 " ms",
+      CLOGI("%s, %d samples: %f %f %f, accuracy: %u, t=%" PRIu64 " ms",
             getSensorName(header.sensorHandle), header.readingCount, x, y, z,
-            header.baseTimestamp / kOneMillisecondInNanoseconds);
+            accuracy, header.baseTimestamp / kOneMillisecondInNanoseconds);
 
       if (eventType == CHRE_EVENT_SENSOR_UNCALIBRATED_GYROSCOPE_DATA) {
         CLOGI("UncalGyro time: first %" PRIu64 " last %" PRIu64 " chre %" PRIu64
@@ -395,8 +396,9 @@
       }
       v /= header.readingCount;
 
-      CLOGI("%s, %d samples: %f, t=%" PRIu64 " ms",
+      CLOGI("%s, %d samples: %f, accuracy = %u, t=%" PRIu64 " ms",
             getSensorName(header.sensorHandle), header.readingCount, v,
+            header.accuracy,
             header.baseTimestamp / kOneMillisecondInNanoseconds);
       break;
     }
@@ -407,9 +409,9 @@
       const auto reading = ev->readings[0];
       sampleTime = header.baseTimestamp;
 
-      CLOGI("%s, %d samples: isNear %d, invalid %d",
+      CLOGI("%s, %d samples: isNear %d, invalid %d, accuracy: %u",
             getSensorName(header.sensorHandle), header.readingCount,
-            reading.isNear, reading.invalid);
+            reading.isNear, reading.invalid, header.accuracy);
 
       CLOGI("Prox time: sample %" PRIu64 " chre %" PRIu64 " delta %" PRId64
             "ms",
@@ -446,8 +448,8 @@
       const auto *ev = static_cast<const chreSensorOccurrenceData *>(eventData);
       const auto header = ev->header;
 
-      CLOGI("%s, %d samples", getSensorName(header.sensorHandle),
-            header.readingCount);
+      CLOGI("%s, %d samples, accuracy: %u", getSensorName(header.sensorHandle),
+            header.readingCount, header.accuracy);
       break;
     }
 
diff --git a/build/arch/armv8a.mk b/build/arch/armv8a.mk
new file mode 100644
index 0000000..3073cb9
--- /dev/null
+++ b/build/arch/armv8a.mk
@@ -0,0 +1,36 @@
+#
+# Build targets for an ARMv8 based processor
+#
+
+# The ARMV8A_XX binary path needs to be specified by a
+# higher level makefile
+TARGET_AR = $(ARMV8A_AR)
+TARGET_CC = $(ARMV8A_CC)
+TARGET_LD = $(ARMV8A_LD)
+
+TARGET_CFLAGS += $(DEFINES)
+TARGET_CFLAGS += $(CPPFLAGS)
+COMMON_CXX_CFLAGS += $(CXXFLAGS)
+COMMON_C_CFLAGS += $(CFLAGS)
+
+TARGET_SO_LDFLAGS += $(LDFLAGS)
+TARGET_SO_EARLY_LIBS += $(LDLIBS)
+
+# TODO: Fix ar_client so the following two can be removed.
+TARGET_CFLAGS += -Wno-strict-prototypes
+TARGET_CFLAGS += -Wno-missing-prototypes
+
+# TODO: Fix log_null so this can be removed.
+TARGET_CFLAGS += -Wno-unused-parameter
+
+# CHRE's stack size is 8KB so leave a little room to ensure the stack isn't
+# blown when calling nanoapps since nanoappStart will be invoked with some data
+# on the stack.
+TARGET_CFLAGS += -Wframe-larger-than=7000
+
+# Don't add these flags when building just the static archive as they generate
+# sections that will break static linking.
+ifeq ($(IS_ARCHIVE_ONLY_BUILD),)
+TARGET_CFLAGS += -fPIC
+TARGET_SO_LDFLAGS += -shared
+endif
diff --git a/build/build_template.mk b/build/build_template.mk
index cb2867a..0a956fd 100644
--- a/build/build_template.mk
+++ b/build/build_template.mk
@@ -110,7 +110,11 @@
 $(1)_header: $$($$(1)_HEADER)
 
 .PHONY: $(1)
+ifeq ($(IS_ARCHIVE_ONLY_BUILD),true)
+$(1): $(1)_ar
+else
 $(1): $(1)_ar $(1)_so $(1)_bin $(1)_header
+endif
 
 # If building the runtime, simply add the archive and shared object to the all
 # target. When building CHRE, it is expected that this runtime just be linked
@@ -135,7 +139,7 @@
 #   uint32_t magic;                // "NANO"
 #   uint64_t appId;                // App Id, contains vendor id
 #   uint32_t appVersion;           // Version of the app
-#   uint32_t flags;                // Signed, encrypted
+#   uint32_t flags;                // Signed, encrypted, TCM-capable
 #   uint64_t hwHubType;            // Which hub type is this compiled for
 #   uint8_t targetChreApiMajorVersion; // CHRE API version
 #   uint8_t targetChreApiMinorVersion;
@@ -151,13 +155,20 @@
 # the nanoapp, the version and the app ID. Marshalling this data from the
 # Makefile environment into something like python or even a small C program
 # is an unnecessary step.
+#
+# For the flags field of the struct, the following values are currently defined:
+# Signed                 = 0x00000001
+# Encrypted              = 0x00000002
+# TCM-capable            = 0x00000004
+#
+# The highest order byte is reserved for platform-specific usage.
 
 $$($$(1)_HEADER): $$(OUT)/$$$(1) $$($$$(1)_DIRS)
 	printf "00000000  %.8x " `$(BE_TO_LE_SCRIPT) 0x00000001` > $$@
 	printf "%.8x " `$(BE_TO_LE_SCRIPT) 0x4f4e414e` >> $$@
 	printf "%.16x\n" `$(BE_TO_LE_SCRIPT) $(NANOAPP_ID)` >> $$@
 	printf "00000010  %.8x " `$(BE_TO_LE_SCRIPT) $(NANOAPP_VERSION)` >> $$@
-	printf "%.8x " `$(BE_TO_LE_SCRIPT) 0x00000001` >> $$@
+	printf "%.8x " `$(BE_TO_LE_SCRIPT) $(TARGET_NANOAPP_FLAGS)` >> $$@
 	printf "%.16x\n" `$(BE_TO_LE_SCRIPT) $(13)` >> $$@
 	printf "00000020  %.2x " \
 	    `$(BE_TO_LE_SCRIPT) $(TARGET_CHRE_API_VERSION_MAJOR)` >> $$@
@@ -261,6 +272,9 @@
 
 TARGET_CFLAGS_LOCAL = $(TARGET_CFLAGS)
 TARGET_CFLAGS_LOCAL += -DCHRE_PLATFORM_ID=$(TARGET_PLATFORM_ID)
+
+# Default the nanoapp header flag values to signed if not overidden.
+TARGET_NANOAPP_FLAGS ?= 0x00000001
 $(eval $(call BUILD_TEMPLATE, $(TARGET_NAME), \
                               $(COMMON_CFLAGS) $(TARGET_CFLAGS_LOCAL), \
                               $(TARGET_CC), \
diff --git a/build/nanoapp/app.mk b/build/nanoapp/app.mk
index 25f7791..b153345 100644
--- a/build/nanoapp/app.mk
+++ b/build/nanoapp/app.mk
@@ -112,6 +112,7 @@
 GOOGLE_HEXAGONV66_SLPI-SEE_SRCS += $(DSO_SUPPORT_LIB_SRCS)
 GOOGLE_HEXAGONV66_SLPI-SEE-UIMG_SRCS += $(DSO_SUPPORT_LIB_SRCS)
 GOOGLE_ARM64_ANDROID_SRCS += $(DSO_SUPPORT_LIB_SRCS)
+GOOGLE_AOC_SRCS += $(DSO_SUPPORT_LIB_SRCS)
 GOOGLE_X86_LINUX_SRCS += $(DSO_SUPPORT_LIB_SRCS)
 QCOM_HEXAGONV60_NANOHUB_SRCS += $(APP_SUPPORT_PATH)/qcom_nanohub/app_support.cc
 QCOM_HEXAGONV60_NANOHUB-UIMG_SRCS += $(APP_SUPPORT_PATH)/qcom_nanohub/app_support_uimg.cc
@@ -127,6 +128,7 @@
 
 # Supported variants includes
 include $(CHRE_PREFIX)/build/variant/google_arm64_android.mk
+include $(CHRE_PREFIX)/build/variant/google_armv8a_aoc.mk
 include $(CHRE_PREFIX)/build/variant/google_cm4_nanohub.mk
 include $(CHRE_PREFIX)/build/variant/google_hexagonv55_slpi-see.mk
 include $(CHRE_PREFIX)/build/variant/google_hexagonv60_slpi.mk
diff --git a/build/nanoapp/google_aoc.mk b/build/nanoapp/google_aoc.mk
new file mode 100644
index 0000000..2828d2d
--- /dev/null
+++ b/build/nanoapp/google_aoc.mk
@@ -0,0 +1,31 @@
+#
+# Nanoapp Build Rules for Google CHRE on AoC
+#
+
+################################################################################
+#
+# Google Generic CHRE on AoC Nanoapp Build Template
+#
+# Invoke this to instantiate a set of Nanoapp post processing build targets.
+#
+# TARGET_NAME_nanoapp - The resulting nanoapp output.
+#
+# Argument List:
+#     $1 - TARGET_NAME         - The name of the target being built.
+#
+################################################################################
+
+ifndef GOOGLE_AOC_NANOAPP_BUILD_TEMPLATE
+define GOOGLE_AOC_NANOAPP_BUILD_TEMPLATE
+
+.PHONY: $(1)_nanoapp
+all: $(1)_nanoapp
+
+$(1)_nanoapp: $(1)
+
+endef
+endif
+
+# Template Invocation ##########################################################
+
+$(eval $(call GOOGLE_AOC_NANOAPP_BUILD_TEMPLATE, $(TARGET_NAME)))
diff --git a/build/variant/google_armv8a_aoc.mk b/build/variant/google_armv8a_aoc.mk
new file mode 100644
index 0000000..427aa88
--- /dev/null
+++ b/build/variant/google_armv8a_aoc.mk
@@ -0,0 +1,101 @@
+include $(CHRE_PREFIX)/build/clean_build_template_args.mk
+
+TARGET_NAME = google_armv8a_aoc
+ifneq ($(filter $(TARGET_NAME)% all, $(MAKECMDGOALS)),)
+
+# Environment Checks
+ifeq ($(AOC_TOP_DIR),)
+$(error "The AoC root source directory needs to be exported as the AOC_TOP_DIR \
+         environment variable")
+endif
+
+SUPPORTED_AOC_PLATFORMS_ON_CHRE := fpga_a32 whi_a0_a32
+ifeq ($(AOC_PLATFORM) ,)
+$(error "The platform we're building for needs to be exported as the \
+         AOC_PLATFORM environment variable. Supported platforms are \
+         [${SUPPORTED_AOC_PLATFORMS_ON_CHRE}]")
+endif
+
+ifeq ($(filter $(AOC_PLATFORM),$(SUPPORTED_AOC_PLATFORMS_ON_CHRE)),)
+$(error "Unsupported AoC Platform - $(AOC_PLATFORM) - Supported platforms are \
+        [${SUPPORTED_AOC_PLATFORMS_ON_CHRE}]")
+endif
+
+# The SRC_DIR variable is used in toolchain selection in AoC, add that as
+# a dependency before including the platform toolchain makefile
+ifeq ($(SRC_DIR),)
+SRC_DIR=$(AOC_TOP_DIR)/AOC
+endif
+
+include $(AOC_TOP_DIR)/AOC/build/$(AOC_PLATFORM)/toolchain.mk
+include $(AOC_TOP_DIR)/AOC/targets/aoc.$(AOC_PLATFORM)/local.mk
+
+# Sized based on the buffer allocated in the host daemon (4096 bytes), minus
+# FlatBuffer overhead (max 80 bytes), minus some extra space to make a nice
+# round number and allow for addition of new fields to the FlatBuffer
+TARGET_CFLAGS = -DCHRE_MESSAGE_TO_HOST_MAX_SIZE=4000
+TARGET_CFLAGS += $(AOC_CFLAGS)
+TARGET_CFLAGS += $(FREERTOS_CFLAGS)
+TARGET_CFLAGS += -I$(AOC_TOP_DIR)/AOC/libs/common/basic/include
+TARGET_CFLAGS += -I$(AOC_TOP_DIR)/external/libcxx/include
+
+# libc / libm headers must be included after libcxx headers in case libcxx
+# headers utilize #include_next
+TARGET_CFLAGS += -I$(AOC_TOP_DIR)/AOC/libs/bionic_interface/include
+TARGET_CFLAGS += -I$(AOC_TOP_DIR)/AOC/libs/common/libc/include
+
+# Used to expose libc headers to nanoapps that aren't supported on the given platform
+TARGET_CFLAGS += -I$(CHRE_PREFIX)/platform/shared/include/chre/platform/shared/libc
+
+# add platform specific flags
+ifeq ($(AOC_PLATFORM),fpga_a32)
+TARGET_CFLAGS += $(AOC_FPGA_A32_CFLAGS)
+# We need a function stack size of at least 400 bytes, which might not be
+# the case by default
+TARGET_CFLAGS += -Wframe-larger-than=420
+endif
+ifeq ($(AOC_PLATFORM),whi_a0_a32)
+TARGET_CFLAGS += $(AOC_WHI_A0_A32_CFLAGS)
+# We need a function stack size of at least 400 bytes, which might not be
+# the case by default
+TARGET_CFLAGS += -Wframe-larger-than=420
+endif
+
+TARGET_VARIANT_SRCS += $(AOC_SRCS)
+TARGET_VARIANT_SRCS += $(FREERTOS_SRCS)
+TARGET_VARIANT_SRCS += $(GOOGLE_AOC_SRCS)
+
+TARGET_PLATFORM_ID = 0x476F6F676C000008
+
+# Set platform-based build variables for arch armv8a
+ARMV8A_AR = $(CLANG_PATH)/llvm-ar
+ARMV8A_CC = $(CXX)
+ARMV8A_LD = $(LD)
+
+ifneq ($(IS_NANOAPP_BUILD),)
+ifeq ($(CHRE_NANOAPP_BUILD_ID),)
+# Set to "local" to indicate this was generated locally. Any production build
+# of the nanoapp will specify this ID.
+CHRE_NANOAPP_BUILD_ID=local
+endif
+
+# Provide the CHRE commit hash to make it easy to identify CHRE changes that
+# may be present in a nanoapp. This ID may change even if no nanoapp changes
+# have been made so it has to go in the unstable ID section.
+COMMIT_HASH_COMMAND = git describe --always --long --dirty
+CHRE_COMMIT_HASH = $(shell cd $(CHRE_PREFIX) && $(COMMIT_HASH_COMMAND))
+
+TARGET_CFLAGS += \
+    -DNANOAPP_UNSTABLE_ID=\"nanoapp_unstable_id=$(CHRE_NANOAPP_BUILD_ID)@chre-$(CHRE_COMMIT_HASH)\"
+
+include $(CHRE_PREFIX)/build/nanoapp/google_aoc.mk
+ifeq ($(CHRE_TCM_ENABLED),true)
+TARGET_CFLAGS += -DCHRE_TCM_ENABLED
+# Flags:  Signed | TCM
+TARGET_NANOAPP_FLAGS = 0x00000005
+endif
+endif
+
+include $(CHRE_PREFIX)/build/arch/armv8a.mk
+include $(CHRE_PREFIX)/build/build_template.mk
+endif
diff --git a/chpp/platform/aoc/chpp_init.cc b/chpp/platform/aoc/chpp_init.cc
new file mode 100644
index 0000000..d9d539a
--- /dev/null
+++ b/chpp/platform/aoc/chpp_init.cc
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2020 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 "chpp/platform/chpp_init.h"
+
+#include "chpp/app.h"
+#include "chpp/platform/chpp_uart_link_manager.h"
+#include "chpp/transport.h"
+#include "chre/util/fixed_size_vector.h"
+#include "chre/util/nested_data_ptr.h"
+#include "environment.h"
+#include "uart_map.h"
+
+#include "task.h"
+
+// Forward declaration
+constexpr uint8_t chpp::UartLinkManager::kWifiWakeOutGpioPinNumber;
+constexpr uint8_t chpp::UartLinkManager::kWifiWakeInGpiNumber;
+constexpr uint8_t chpp::UartLinkManager::kGnssWakeOutGpioPinNumber;
+constexpr uint8_t chpp::UartLinkManager::kGnssWakeInGpiNumber;
+constexpr uint8_t chpp::UartLinkManager::kWwanWakeOutGpioPinNumber;
+constexpr uint8_t chpp::UartLinkManager::kWwanWakeInGpiNumber;
+
+using chre::FixedSizeVector;
+using chre::NestedDataPtr;
+
+namespace chpp {
+namespace {
+
+constexpr configSTACK_DEPTH_TYPE kChppTaskStackDepthWords = 0x400;
+
+// Choose an appripriate priority to avoid initializing things too late.
+// TODO: Configure priority in relation to CHRE based on end to end testing.
+constexpr UBaseType_t kChppTaskPriority = tskIDLE_PRIORITY + 2;
+
+// Structures to hold CHPP task related variables
+constexpr size_t kChppLinkTotal = ChppLinkType::CHPP_LINK_TYPE_TOTAL;
+TaskHandle_t gChppTaskHandleList[kChppLinkTotal];
+ChppTransportState gChppTransportStateList[kChppLinkTotal];
+ChppAppState gChppAppStateList[kChppLinkTotal];
+FixedSizeVector<chpp::UartLinkManager, kChppLinkTotal> gManagerList;
+
+struct ChppClientServiceSet getClientServiceSet(ChppLinkType type) {
+  struct ChppClientServiceSet set = {};
+  switch (type) {
+    case ChppLinkType::CHPP_LINK_TYPE_WIFI:
+      set.wifiClient = 1;
+      break;
+    case ChppLinkType::CHPP_LINK_TYPE_GNSS:
+      set.gnssClient = 1;
+      break;
+    case ChppLinkType::CHPP_LINK_TYPE_WWAN:
+      set.wwanClient = 1;
+      break;
+    default:
+      LOGE("Invalid type %d to get client/service set", type);
+      CHRE_ASSERT(false);
+  }
+
+  return set;
+}
+
+// Initializes the CHPP instance and runs the work thread.
+void chppThreadEntry(void *context) {
+  NestedDataPtr<ChppLinkType> type;
+  type.dataPtr = context;
+
+  struct ChppTransportState *transportState =
+      &gChppTransportStateList[type.data];
+  struct ChppAppState *appState = &gChppAppStateList[type.data];
+
+  chppTransportInit(transportState, appState);
+  chppAppInitWithClientServiceSet(appState, transportState,
+                                  getClientServiceSet(type.data));
+
+  chppWorkThreadStart(transportState);
+
+  chppAppDeinit(appState);
+  chppTransportDeinit(transportState);
+
+  vTaskDelete(nullptr);
+  gChppTaskHandleList[type.data] = nullptr;
+}
+
+BaseType_t startChppWorkThread(ChppLinkType type, const char *taskName) {
+  struct ChppTransportState *transportState = &gChppTransportStateList[type];
+  transportState->linkParams.uartLinkManager = &gManagerList[type];
+
+  NestedDataPtr<ChppLinkType> linkType(type);
+  return xTaskCreate(chppThreadEntry, taskName, kChppTaskStackDepthWords,
+                     linkType.dataPtr /* args */, kChppTaskPriority,
+                     &gChppTaskHandleList[type]);
+}
+
+void stopChppWorkThread(ChppLinkType type) {
+  if (gChppTaskHandleList[type] != nullptr) {
+    chppWorkThreadStop(&gChppTransportStateList[type]);
+  }
+}
+
+}  // namespace
+
+void init() {
+  gManagerList.emplace_back(
+      &gChppTransportStateList[ChppLinkType::CHPP_LINK_TYPE_WIFI],
+      Environment::Instance()->UART(UART_MAP::UART_MAP_WIFI),
+      chpp::UartLinkManager::kWifiWakeOutGpioPinNumber,
+      chpp::UartLinkManager::kWifiWakeInGpiNumber);
+
+  gManagerList.emplace_back(
+      &gChppTransportStateList[ChppLinkType::CHPP_LINK_TYPE_GNSS],
+      Environment::Instance()->UART(UART_MAP::UART_MAP_GNSS),
+      chpp::UartLinkManager::kGnssWakeOutGpioPinNumber,
+      chpp::UartLinkManager::kGnssWakeInGpiNumber);
+
+  gManagerList.emplace_back(
+      &gChppTransportStateList[ChppLinkType::CHPP_LINK_TYPE_WWAN],
+      Environment::Instance()->UART(UART_MAP::UART_MAP_MODEM),
+      chpp::UartLinkManager::kWwanWakeOutGpioPinNumber,
+      chpp::UartLinkManager::kWwanWakeInGpiNumber);
+
+  BaseType_t rc =
+      startChppWorkThread(ChppLinkType::CHPP_LINK_TYPE_WIFI, "CHPP WIFI");
+  CHRE_ASSERT(rc == pdPASS);
+
+  rc = startChppWorkThread(ChppLinkType::CHPP_LINK_TYPE_GNSS, "CHPP GNSS");
+  CHRE_ASSERT(rc == pdPASS);
+
+  rc = startChppWorkThread(ChppLinkType::CHPP_LINK_TYPE_WWAN, "CHPP WWAN");
+  CHRE_ASSERT(rc == pdPASS);
+}
+
+void deinit() {
+  stopChppWorkThread(ChppLinkType::CHPP_LINK_TYPE_WIFI);
+  stopChppWorkThread(ChppLinkType::CHPP_LINK_TYPE_GNSS);
+  stopChppWorkThread(ChppLinkType::CHPP_LINK_TYPE_WWAN);
+}
+
+ChppAppState *getChppAppState(ChppLinkType linkType) {
+  CHRE_ASSERT(linkType < ChppLinkType::CHPP_LINK_TYPE_TOTAL);
+  return (linkType < ChppLinkType::CHPP_LINK_TYPE_TOTAL)
+             ? &gChppAppStateList[linkType]
+             : nullptr;
+}
+
+}  // namespace chpp
diff --git a/chpp/platform/aoc/chpp_task_util.cc b/chpp/platform/aoc/chpp_task_util.cc
new file mode 100644
index 0000000..785ae14
--- /dev/null
+++ b/chpp/platform/aoc/chpp_task_util.cc
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2020 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 "chpp/platform/chpp_task_util.h"
+
+#include "chpp/platform/log.h"
+#include "chre/platform/fatal_error.h"
+#include "efw/include/timer.h"
+
+namespace chpp {
+
+TaskUtil::TaskUtil() {
+  mCvSemaphoreHandle = xSemaphoreCreateBinaryStatic(&mSemaphoreBuffer);
+  if (mCvSemaphoreHandle == nullptr) {
+    FATAL_ERROR("failed to create cv semaphore");
+  }
+}
+
+TaskUtil::~TaskUtil() {
+  if (mCvSemaphoreHandle != nullptr) {
+    vSemaphoreDelete(mCvSemaphoreHandle);
+  }
+}
+
+void TaskUtil::suspend(uint64_t suspendTimeNs) {
+  Timer *timer = Timer::Instance();
+  const TickType_t timeoutTicks =
+      static_cast<TickType_t>(timer->NsToTicks(suspendTimeNs));
+
+  auto timeoutCallback = [](void *context) -> bool {
+    SemaphoreHandle_t handle = static_cast<SemaphoreHandle_t>(context);
+    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
+    xSemaphoreGiveFromISR(handle, &xHigherPriorityTaskWoken);
+    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
+    return false;  // one-shot
+  };
+
+  void *timerHandle;
+  int rc = timer->EventAddAtOffset(timeoutTicks, timeoutCallback,
+                                   mCvSemaphoreHandle, &timerHandle);
+  if (rc != 0) {
+    CHPP_LOGE("Failed to set suspend timeout timer");
+  } else {
+    xSemaphoreTake(mCvSemaphoreHandle, portMAX_DELAY /* xBlockTime */);
+  }
+}
+
+}  // namespace chpp
diff --git a/chpp/platform/aoc/chpp_uart_link_manager.cc b/chpp/platform/aoc/chpp_uart_link_manager.cc
new file mode 100644
index 0000000..895a747
--- /dev/null
+++ b/chpp/platform/aoc/chpp_uart_link_manager.cc
@@ -0,0 +1,262 @@
+/*
+ * Copyright (C) 2020 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 "chpp/platform/chpp_uart_link_manager.h"
+
+#include "aoc.h"
+#include "chpp/macros.h"
+#include "chpp/platform/log.h"
+#include "chre/platform/fatal_error.h"
+#include "chre/platform/system_time.h"
+#include "efw/include/interrupt_controller.h"
+#include "efw/include/processor.h"
+#include "efw/include/timer.h"
+#include "ipc-regions.h"
+
+// Define as 0 to disable wake handshaking
+// TODO: Enable once ready
+#define WAKE_HANDSHAKE_ENABLE 0
+
+using chre::SystemTime;
+
+namespace chpp {
+
+namespace {
+
+void onUartRxInterrupt(void *context) {
+  UartLinkManager *manager = static_cast<UartLinkManager *>(context);
+  manager->getUart()->DisableRxInterrupt();
+  manager->pullRxSamples();
+  chppWorkThreadSignalFromLink(&manager->getTransportContext()->linkParams,
+                               CHPP_TRANSPORT_SIGNAL_LINK_RX_PROCESS);
+  if (!manager->isRxBufferFull()) {
+    manager->getUart()->EnableRxInterrupt();
+  }
+}
+
+//! This is the interrupt to use when handling requests from the remote
+//! to start a transaction.
+void onTransactionRequestInterrupt(void *context, uint32_t /* interrupt */) {
+  UartLinkManager *manager = static_cast<UartLinkManager *>(context);
+  manager->getWakeInGpi()->SetTriggerFunction(GPIAoC::GPI_DISABLE);
+  manager->getWakeInGpi()->ClearInterrupt();
+  manager->setTransactionPending();
+  chppWorkThreadSignalFromLink(&manager->getTransportContext()->linkParams,
+                               CHPP_TRANSPORT_SIGNAL_LINK_WAKE_IN_IRQ);
+}
+
+#if WAKE_HANDSHAKE_ENABLE
+//! This is the interrupt to use when handling signal handshaking during
+//! an on-going transaction.
+void onTransactionHandshakeInterrupt(void *context, uint32_t /* interrupt */) {
+  UartLinkManager *manager = static_cast<UartLinkManager *>(context);
+  manager->getWakeInGpi()->SetTriggerFunction(GPIAoC::GPI_DISABLE);
+  manager->getWakeInGpi()->ClearInterrupt();
+
+  BaseType_t xHigherPriorityTaskWoken = 0;
+  xTaskNotifyFromISR(manager->getTaskHandle(),
+                     CHPP_TRANSPORT_SIGNAL_LINK_HANDSHAKE_IRQ, eSetBits,
+                     &xHigherPriorityTaskWoken);
+  portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
+}
+#endif  // WAKE_HANDSHAKE_ENABLE
+
+}  // anonymous namespace
+
+UartLinkManager::UartLinkManager(struct ChppTransportState *context, UART *uart,
+                                 uint8_t wakeOutPinNumber,
+                                 uint8_t wakeInGpiNumber)
+    : mTransportContext(context),
+      mUart(uart),
+      mWakeOutGpio(wakeOutPinNumber, IP_LOCK_GPIO),
+      mWakeInGpi(wakeInGpiNumber) {
+  mWakeOutGpio.SetDirection(GPIO::DIRECTION::OUTPUT);
+  mWakeOutGpio.Clear();
+}
+
+void UartLinkManager::init(TaskHandle_t handle) {
+  mUart->RegisterRxCallback(onUartRxInterrupt, this);
+  mUart->EnableRxInterrupt();
+
+  mTaskHandle = handle;
+  mWakeInGpi.SetInterruptHandler(onTransactionRequestInterrupt, this);
+  mWakeInGpi.SetTriggerFunction(GPIAoC::GPI_RISING_EDGE);
+  InterruptController::Instance()->InterruptEnable(
+      IRQ_GPI0 + mWakeInGpi.GetGpiNumber(), Processor::Instance()->CoreID(),
+      true /* enable */);
+}
+
+void UartLinkManager::deinit() {
+  clearTxPacket();
+  mWakeOutGpio.Clear();
+
+  mUart->DisableRxInterrupt();
+
+  InterruptController::Instance()->InterruptEnable(
+      IRQ_GPI0 + mWakeInGpi.GetGpiNumber(), Processor::Instance()->CoreID(),
+      false /* enable */);
+  mWakeInGpi.SetTriggerFunction(GPIAoC::GPI_DISABLE);
+}
+
+bool UartLinkManager::prepareTxPacket(uint8_t *buf, size_t len) {
+  bool success = !hasTxPacket();
+  if (!success) {
+    CHPP_LOGE("Cannot prepare packet while one pending");
+  } else {
+    mCurrentBuffer = buf;
+    mCurrentBufferLen = len;
+    setTransactionPending();
+  }
+  return success;
+}
+
+bool UartLinkManager::waitForHandshakeIrq(uint64_t timeoutNs) {
+#if WAKE_HANDSHAKE_ENABLE
+  bool success = false;
+  Timer *timer = Timer::Instance();
+  const TickType_t timeoutTicks =
+      static_cast<TickType_t>(timer->NsToTicks(timeoutNs));
+
+  // Treat as if the IRQ occurred in the timeout case. We check if the timer
+  // was successfully cancelled to determine if we timed out or not.
+  auto timeoutCallback = [](void *context) -> bool {
+    onTransactionHandshakeInterrupt(context, 0);
+    return false;  // one-shot
+  };
+
+  void *timerHandle;
+  int rc = timer->EventAddAtOffset(timeoutTicks, timeoutCallback, this,
+                                   &timerHandle);
+  if (rc != 0) {
+    CHPP_LOGE("Failed to set handshake timeout timer");
+  } else {
+    uint32_t signal = 0;
+    while ((signal & CHPP_TRANSPORT_SIGNAL_LINK_HANDSHAKE_IRQ) == 0) {
+      xTaskNotifyWait(
+          0 /* ulBitsToClearOnEntry */,
+          CHPP_TRANSPORT_SIGNAL_LINK_HANDSHAKE_IRQ /* ulBitsToClearOnExit */,
+          &signal, portMAX_DELAY /* xTicksToWait */);
+    }
+
+    // If we cleared the notification while another one is in progress,
+    // re-notify the task so it doesn't get lost.
+    if ((signal & ~(CHPP_TRANSPORT_SIGNAL_LINK_HANDSHAKE_IRQ)) != 0) {
+      xTaskNotify(mTaskHandle, 0, eNoAction);
+    }
+
+    // EventRemove() will not return 0 if the timer already fired.
+    success = (timer->EventRemove(timerHandle) == 0);
+  }
+
+  return success;
+#else
+  return true;
+#endif  // WAKE_HANDSHAKE_ENABLE
+}
+
+bool UartLinkManager::startTransaction() {
+  bool success = true;
+  mWakeInGpi.SetTriggerFunction(GPIAoC::GPI_DISABLE);
+
+  // Check if a transaction is pending (either from an IRQ or from a pending
+  // TX packet) before starting one. This check is required to avoid attempting
+  // to start a transaction when it has already been handled (e.g. an IRQ has
+  // signalled the transport layer, but it already had a packet to send and
+  // handled them together).
+  if (mTransactionPending.load()) {
+    mWakeOutGpio.Set(true /* set */);
+    uint64_t pulseEndTimeNs =
+        SystemTime::getMonotonicTime().toRawNanoseconds() + kPulseTimeNs;
+
+#if WAKE_HANDSHAKE_ENABLE
+    mWakeInGpi.SetInterruptHandler(onTransactionHandshakeInterrupt, this);
+    mWakeInGpi.SetTriggerFunction(GPIAoC::GPI_LEVEL_ACTIVE_HIGH);
+#endif  // WAKE_HANDSHAKE_ENABLE
+
+    if (waitForHandshakeIrq(kStartTimeoutNs)) {
+      if (hasTxPacket()) {
+        int bytesTransmitted = mUart->Tx(mCurrentBuffer, mCurrentBufferLen);
+
+        // Deassert wake_out as soon as data is transmitted, per specifications.
+        mWakeOutGpio.Clear();
+
+        if (static_cast<size_t>(bytesTransmitted) != mCurrentBufferLen) {
+          CHPP_LOGE("Failed to transmit data");
+          success = false;
+        }
+
+      } else {
+        uint64_t now = SystemTime::getMonotonicTime().toRawNanoseconds();
+        if (now < pulseEndTimeNs) {
+          mTaskUtil.suspend(pulseEndTimeNs - now);
+        }
+
+        mWakeOutGpio.Clear();
+      }
+
+#if WAKE_HANDSHAKE_ENABLE
+      mWakeInGpi.SetTriggerFunction(GPIAoC::GPI_LEVEL_ACTIVE_LOW);
+      if (!waitForHandshakeIrq(kEndTimeoutNs)) {
+        CHPP_LOGE("Wake handshaking end timed out");
+        success = false;
+      }
+#endif  // WAKE_HANDSHAKE_ENABLE
+    } else {
+      CHPP_LOGE("Wake handshaking start timed out");
+      success = false;
+      mWakeOutGpio.Clear();
+    }
+
+    mTransactionPending = false;
+  }
+
+  // Re-enable the interrupt to handle transaction requests.
+  mWakeInGpi.SetTriggerFunction(GPIAoC::GPI_DISABLE);
+  mWakeInGpi.SetInterruptHandler(onTransactionRequestInterrupt, this);
+  // TODO: Handle potential cases where we miss a rising edge
+  mWakeInGpi.SetTriggerFunction(GPIAoC::GPI_RISING_EDGE);
+
+  // Remove the TX packet to allow subsequent transmission.
+  clearTxPacket();
+
+  return success;
+}
+
+void UartLinkManager::pullRxSamples() {
+  int ch;
+  while ((ch = mUart->GetChar()) != EOF && mRxBufIndex < kRxBufSize) {
+    mRxBuf[mRxBufIndex++] = static_cast<uint8_t>(ch);
+  }
+}
+
+void UartLinkManager::processRxSamples() {
+  mUart->DisableRxInterrupt();
+  pullRxSamples();
+
+  chppRxDataCb(mTransportContext, const_cast<uint8_t *>(mRxBuf), mRxBufIndex);
+  mRxBufIndex = 0;
+  mUart->EnableRxInterrupt();
+}
+
+bool UartLinkManager::hasTxPacket() const {
+  return mCurrentBuffer != nullptr && mUart != nullptr;
+}
+
+void UartLinkManager::clearTxPacket() {
+  mCurrentBuffer = nullptr;
+}
+
+}  // namespace chpp
diff --git a/chpp/platform/aoc/condition_variable.cc b/chpp/platform/aoc/condition_variable.cc
new file mode 100644
index 0000000..e4d9aca
--- /dev/null
+++ b/chpp/platform/aoc/condition_variable.cc
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2020 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 "chpp/platform/platform_condition_variable.h"
+
+#include "chpp/platform/log.h"
+#include "chre/platform/fatal_error.h"
+#include "efw/include/interrupt_controller.h"
+#include "efw/include/timer.h"
+
+namespace {
+
+bool wait(struct ChppConditionVariable *cv, struct ChppMutex *mutex) {
+  chppMutexUnlock(mutex);
+  BaseType_t rc = xSemaphoreTake(cv->semaphoreHandle, portMAX_DELAY);
+  chppMutexLock(mutex);
+
+  return (rc == pdTRUE);
+}
+
+bool timeoutCallback(void *context) {
+  auto *cv = static_cast<struct ChppConditionVariable *>(context);
+  chppPlatformConditionVariableSignal(cv);
+  return false;  // one-shot
+}
+
+}  // anonymous namespace
+
+void chppPlatformConditionVariableInit(struct ChppConditionVariable *cv) {
+  cv->semaphoreHandle = xSemaphoreCreateBinaryStatic(&cv->staticSemaphore);
+  if (cv->semaphoreHandle == NULL) {
+    FATAL_ERROR("Failed to create cv semaphore");
+  }
+}
+
+void chppPlatformConditionVariableDeinit(struct ChppConditionVariable *cv) {
+  if (cv->semaphoreHandle != NULL) {
+    vSemaphoreDelete(cv->semaphoreHandle);
+  }
+}
+
+bool chppPlatformConditionVariableWait(struct ChppConditionVariable *cv,
+                                       struct ChppMutex *mutex) {
+  return wait(cv, mutex);
+}
+
+bool chppPlatformConditionVariableTimedWait(struct ChppConditionVariable *cv,
+                                            struct ChppMutex *mutex,
+                                            uint64_t timeoutNs) {
+  bool success = false;
+  Timer *timer = Timer::Instance();
+  const TickType_t timeoutTicks =
+      static_cast<TickType_t>(timer->NsToTicks(timeoutNs));
+
+  void *timerHandle;
+  int rc =
+      timer->EventAddAtOffset(timeoutTicks, timeoutCallback, cv, &timerHandle);
+  if (rc != 0) {
+    CHPP_LOGE("Failed to set cv timeout timer");
+  } else {
+    success = wait(cv, mutex) && (timer->EventRemove(timerHandle) == 0);
+  }
+
+  return success;
+}
+
+void chppPlatformConditionVariableSignal(struct ChppConditionVariable *cv) {
+  if (InterruptController::Instance()->IsInterruptContext()) {
+    BaseType_t xHigherPriorityTaskWoken = 0;
+    xSemaphoreGiveFromISR(cv->semaphoreHandle, &xHigherPriorityTaskWoken);
+    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
+  } else {
+    xSemaphoreGive(cv->semaphoreHandle);
+  }
+}
diff --git a/chpp/platform/aoc/include/chpp/platform/chpp_init.h b/chpp/platform/aoc/include/chpp/platform/chpp_init.h
new file mode 100644
index 0000000..35aef3a
--- /dev/null
+++ b/chpp/platform/aoc/include/chpp/platform/chpp_init.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2020 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 CHPP_PLATFORM_INIT_H_
+#define CHPP_PLATFORM_INIT_H_
+
+#include "chpp/app.h"
+
+namespace chpp {
+
+/**
+ * Initializes all CHPP instance and threads.
+ */
+void init();
+
+/**
+ * Deinitializes all CHPP instance and threads.
+ */
+void deinit();
+
+/**
+ * @return The ChppAppState associated with the provided link type.
+ */
+ChppAppState *getChppAppState(ChppLinkType linkType);
+
+}  // namespace chpp
+
+#endif  // CHPP_PLATFORM_INIT_H_
diff --git a/chpp/platform/aoc/include/chpp/platform/chpp_task_util.h b/chpp/platform/aoc/include/chpp/platform/chpp_task_util.h
new file mode 100644
index 0000000..98db803
--- /dev/null
+++ b/chpp/platform/aoc/include/chpp/platform/chpp_task_util.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2020 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 CHPP_PLATFORM_TASK_UTIL_H_
+#define CHPP_PLATFORM_TASK_UTIL_H_
+
+#include "FreeRTOS.h"
+#include "semphr.h"
+
+namespace chpp {
+
+/**
+ * A utility class for task related operations.
+ */
+class TaskUtil {
+ public:
+  TaskUtil();
+  ~TaskUtil();
+
+  /**
+   * Suspends the task for a specified period of time.
+   *
+   * @param suspendTimeNs The time in nanoseconds to suspend.
+   */
+  void suspend(uint64_t suspendTimeNs);
+
+ private:
+  //! The semaphore handle
+  SemaphoreHandle_t mCvSemaphoreHandle;
+  //! Buffer used to store state used by the semaphore.
+  StaticSemaphore_t mSemaphoreBuffer;
+};
+
+}  // namespace chpp
+
+#endif  // CHPP_PLATFORM_TASK_UTIL_H_
diff --git a/chpp/platform/aoc/include/chpp/platform/chpp_uart_link_manager.h b/chpp/platform/aoc/include/chpp/platform/chpp_uart_link_manager.h
new file mode 100644
index 0000000..38f1c61
--- /dev/null
+++ b/chpp/platform/aoc/include/chpp/platform/chpp_uart_link_manager.h
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2020 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 CHPP_PLATFORM_UART_LINK_MANAGER_H_
+#define CHPP_PLATFORM_UART_LINK_MANAGER_H_
+
+#include "FreeRTOS.h"
+#include "chpp/condition_variable.h"
+#include "chpp/link.h"
+#include "chpp/mutex.h"
+#include "chpp/platform/chpp_task_util.h"
+#include "chpp/transport.h"
+#include "chre/platform/atomic.h"
+#include "chre/util/non_copyable.h"
+#include "chre/util/time.h"
+#include "gpi_aoc.h"
+#include "gpio_aoc.h"
+#include "uart.h"
+
+namespace chpp {
+
+/**
+ * A class to manage a CHPP link to a remote CHPP endpoint using UART as the
+ * physical layer. Each physical link must instantiate its own instance of
+ * the UartLinkManager.
+ *
+ * This class implements the wake handshaking protocol as follows. Each endpoint
+ * is expected to have a wake_in and wake_out GPIO and a bidirectional UART
+ * link.
+ *
+ * 1) A transaction begins by a request to transmit a packet from either
+ * end of the link. If the local endpoint has data to transmit, it must notify
+ * the remote endpoint by asserting its wake_out GPIO. Consequently, a request
+ * from the remote endpoint is driven by an IRQ from the wake_in GPIO.
+ *
+ * 2) If a packet is pending, it can be transmitted once the remote end has
+ * asserted its wake_out GPIO. Otherwise, the wake_out GPIO pulse should be
+ * asserted for at least the duration to satisfy the pulse width requirements of
+ * the specification.
+ *
+ * 3) The wake_out GPIO can be deasserted once the transmission is complete (if
+ * any). No transaction can begin until the current one has completed, and both
+ * GPIOs have been deasserted.
+ */
+class UartLinkManager : public chre::NonCopyable {
+ public:
+  /**
+   * @param context The context pointer of the CHPP transport.
+   * @param uart The pointer to the UART instance.
+   * @param wakeOutPinNumber The pin number of the wake_out GPIO.
+   * @param wakeInGpiNumber The GPI number of the wake_in GPIO.
+   */
+  UartLinkManager(struct ChppTransportState *context, UART *uart,
+                  uint8_t wakeOutPinNumber, uint8_t wakeInGpiNumber);
+
+  /**
+   * This method must be called before invoking the rest of the public methods
+   * in this class.
+   *
+   * @param handle The task handle that this manager will run in.
+   */
+  void init(TaskHandle_t handle);
+
+  /**
+   * Resets the state and disables the UartLinkManager. init() must be called
+   * after invoking this method to use this class again.
+   */
+  void deinit();
+
+  /**
+   * @param buf The non-null pointer to the buffer.
+   * @param len The length of the above buffer.
+   *
+   * @return true if the packet was successfully prepared to be transmitted in
+   * the next transaction.
+   */
+  bool prepareTxPacket(uint8_t *buf, size_t len);
+
+  /**
+   * Starts a transaction to transmit any pending packets (if any, via a
+   * previous call to prepareTxPacket()), and handles the wake handshaking
+   * protocol.
+   *
+   * @return true if the transaction succeeded.
+   */
+  bool startTransaction();
+
+  /**
+   * Pulls RX data from the UART peripheral.
+   */
+  void pullRxSamples();
+
+  /**
+   * Same as pullRxSamples() but also sends the data to the CHPP transport.
+   */
+  void processRxSamples();
+
+  struct ChppTransportState *getTransportContext() const {
+    return mTransportContext;
+  }
+
+  UART *getUart() const {
+    return mUart;
+  }
+
+  GPIAoC *getWakeInGpi() {
+    return &mWakeInGpi;
+  }
+
+  void setTransactionPending() {
+    mTransactionPending = true;
+  }
+
+  TaskHandle_t getTaskHandle() const {
+    return mTaskHandle;
+  }
+
+  /**
+   * Waits for a wake handshaking IRQ to trigger, with a specified timeout.
+   *
+   * @param timeoutNs The timeout in nanoseconds.
+   *
+   * @return false if timed out
+   */
+  bool waitForHandshakeIrq(uint64_t timeoutNs);
+
+  bool isRxBufferFull() const {
+    return mRxBufIndex == kRxBufSize;
+  }
+
+  /**
+   * GPIO pin numbers to be used in the argument of UartLinkManager, which
+   * defines the physical pin used for the wake_out GPIO.
+   */
+  static constexpr uint8_t kWifiWakeOutGpioPinNumber = 43;
+  static constexpr uint8_t kGnssWakeOutGpioPinNumber = 86;
+  static constexpr uint8_t kWwanWakeOutGpioPinNumber = 84;
+
+  /**
+   * GPI numbers to be used in the argument of UartLinkManager, which defines
+   * the GPI used to generate the wake_in interrupts.
+   */
+  static constexpr uint8_t kWifiWakeInGpiNumber = 46;
+  static constexpr uint8_t kGnssWakeInGpiNumber = 44;
+  static constexpr uint8_t kWwanWakeInGpiNumber = 42;
+
+ private:
+  TaskHandle_t mTaskHandle = nullptr;
+
+  struct ChppTransportState *mTransportContext = nullptr;
+
+  UART *mUart = nullptr;
+
+  GPIOAoC mWakeOutGpio;
+
+  GPIAoC mWakeInGpi;
+
+  //! The pointer to the currently pending TX packet.
+  uint8_t *mCurrentBuffer = nullptr;
+  size_t mCurrentBufferLen = 0;
+
+  //! The temporary buffer to store RX data.
+  //! NOTE: Access to these variables must be done when the RX interrupt is
+  //! disabled.
+  static constexpr size_t kRxBufSize = CHPP_PLATFORM_LINK_RX_MTU_BYTES;
+  volatile uint8_t mRxBuf[kRxBufSize];
+  volatile size_t mRxBufIndex = 0;
+
+  //! The timeout for waiting on the remote to indicate transaction readiness
+  //! (t_start per specifications).
+  static constexpr uint64_t kStartTimeoutNs =
+      100 * chre::kOneMillisecondInNanoseconds;
+
+  //! The timeout for waiting on the remote to indicate transaction ended
+  //! (t_end per specifications).
+  static constexpr uint64_t kEndTimeoutNs = chre::kOneSecondInNanoseconds;
+
+  //! The minimum amount of time to assert the wake_out GPIO (t_pulse per
+  //! specifications).
+  static constexpr uint64_t kPulseTimeNs =
+      100 * chre::kOneMicrosecondInNanoseconds;
+
+  chre::AtomicBool mTransactionPending{false};
+
+  TaskUtil mTaskUtil;
+
+  /**
+   * @return if a TX packet is pending transmission.
+   */
+  bool hasTxPacket() const;
+
+  /**
+   * Clears a pending TX packet.
+   */
+  void clearTxPacket();
+};
+
+}  // namespace chpp
+
+#endif  // CHPP_PLATFORM_UART_LINK_MANAGER_H_
diff --git a/chpp/platform/aoc/include/chpp/platform/log.h b/chpp/platform/aoc/include/chpp/platform/log.h
new file mode 100644
index 0000000..7228fd8
--- /dev/null
+++ b/chpp/platform/aoc/include/chpp/platform/log.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2020 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 CHPP_LOG_H_
+#define CHPP_LOG_H_
+
+#include <chre.h>
+#include <inttypes.h>
+
+#include "FreeRTOS.h"
+#include "task.h"
+
+#ifndef PRIuSIZE
+#define PRIuSIZE "zu"
+#endif
+
+#define CHPP_AOC_LOG(level, fmt, ...)                                    \
+  chreLog(level, "%s: " fmt, pcTaskGetName(xTaskGetCurrentTaskHandle()), \
+          ##__VA_ARGS__)
+
+#define CHPP_LOGE(fmt, ...) CHPP_AOC_LOG(CHRE_LOG_ERROR, fmt, ##__VA_ARGS__)
+#define CHPP_LOGW(fmt, ...) CHPP_AOC_LOG(CHRE_LOG_WARN, fmt, ##__VA_ARGS__)
+#define CHPP_LOGI(fmt, ...) CHPP_AOC_LOG(CHRE_LOG_INFO, fmt, ##__VA_ARGS__)
+#define CHPP_LOGD(fmt, ...) CHPP_AOC_LOG(CHRE_LOG_DEBUG, fmt, ##__VA_ARGS__)
+
+#define CHPP_LOG_OOM(fmt, ...) \
+  CHPP_AOC_LOG(CHRE_LOG_ERROR, "(OOM) " fmt, ##__VA_ARGS__)
+
+#endif  // CHPP_LOG_H_
diff --git a/chpp/platform/aoc/include/chpp/platform/platform_condition_variable.h b/chpp/platform/aoc/include/chpp/platform/platform_condition_variable.h
new file mode 100644
index 0000000..79e4ccf
--- /dev/null
+++ b/chpp/platform/aoc/include/chpp/platform/platform_condition_variable.h
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2020 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 CHPP_PLATFORM_CONDITION_VARIABLE_H_
+#define CHPP_PLATFORM_CONDITION_VARIABLE_H_
+
+#include <stdbool.h>
+
+#include "FreeRTOS.h"
+#include "chpp/mutex.h"
+#include "semphr.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct ChppConditionVariable {
+  SemaphoreHandle_t semaphoreHandle;
+  StaticSemaphore_t staticSemaphore;
+};
+
+/**
+ * Platform implementation of chppConditionVariableInit().
+ */
+void chppPlatformConditionVariableInit(struct ChppConditionVariable *cv);
+
+/**
+ * Platform implementation of chppConditionVariableDeinit().
+ */
+void chppPlatformConditionVariableDeinit(struct ChppConditionVariable *cv);
+
+/**
+ * Platform implementation of chppConditionVariable[Timed]Wait().
+ */
+bool chppPlatformConditionVariableWait(struct ChppConditionVariable *cv,
+                                       struct ChppMutex *mutex);
+bool chppPlatformConditionVariableTimedWait(struct ChppConditionVariable *cv,
+                                            struct ChppMutex *mutex,
+                                            uint64_t timeoutNs);
+
+/**
+ * Platform implementation of chppConditionVariableSignal().
+ */
+void chppPlatformConditionVariableSignal(struct ChppConditionVariable *cv);
+
+static inline void chppConditionVariableInit(struct ChppConditionVariable *cv) {
+  chppPlatformConditionVariableInit(cv);
+}
+
+static inline void chppConditionVariableDeinit(
+    struct ChppConditionVariable *cv) {
+  chppPlatformConditionVariableDeinit(cv);
+}
+
+static inline bool chppConditionVariableWait(struct ChppConditionVariable *cv,
+                                             struct ChppMutex *mutex) {
+  return chppPlatformConditionVariableWait(cv, mutex);
+}
+
+static inline bool chppConditionVariableTimedWait(
+    struct ChppConditionVariable *cv, struct ChppMutex *mutex,
+    uint64_t timeoutNs) {
+  return chppPlatformConditionVariableTimedWait(cv, mutex, timeoutNs);
+}
+
+static inline void chppConditionVariableSignal(
+    struct ChppConditionVariable *cv) {
+  chppPlatformConditionVariableSignal(cv);
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  // CHPP_PLATFORM_CONDITION_VARIABLE_H_
diff --git a/chpp/platform/aoc/include/chpp/platform/platform_link.h b/chpp/platform/aoc/include/chpp/platform/platform_link.h
new file mode 100644
index 0000000..e3b607e
--- /dev/null
+++ b/chpp/platform/aoc/include/chpp/platform/platform_link.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2020 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 CHPP_PLATFORM_LINK_H_
+#define CHPP_PLATFORM_LINK_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+//! A signal to use when there is RX data to process at the UART.
+#define CHPP_TRANSPORT_SIGNAL_LINK_RX_PROCESS UINT32_C(1 << 16)
+
+//! A signal to use when there is an interrupt from the wake_in GPIO.
+#define CHPP_TRANSPORT_SIGNAL_LINK_WAKE_IN_IRQ UINT32_C(1 << 17)
+
+//! A signal to use when waiting for the handshaking wake_in GPIO IRQs.
+//! This signal is reserved for use internally in the chpp::UartLinkManager.
+#define CHPP_TRANSPORT_SIGNAL_LINK_HANDSHAKE_IRQ UINT32_C(1 << 18)
+
+#define CHPP_PLATFORM_LINK_TX_MTU_BYTES 1280
+#define CHPP_PLATFORM_LINK_RX_MTU_BYTES 1280
+
+#define CHPP_PLATFORM_TRANSPORT_TIMEOUT_MS 1000
+
+enum ChppLinkType {
+  CHPP_LINK_TYPE_WIFI = 0,
+  CHPP_LINK_TYPE_GNSS,
+  CHPP_LINK_TYPE_WWAN,
+  CHPP_LINK_TYPE_TOTAL,
+};
+
+struct ChppPlatformLinkParameters {
+  //! Must be of type chpp::UartLinkManager
+  void *uartLinkManager;
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  // CHPP_PLATFORM_LINK_H_
diff --git a/chpp/platform/aoc/include/chpp/platform/platform_mutex.h b/chpp/platform/aoc/include/chpp/platform/platform_mutex.h
new file mode 100644
index 0000000..c745478
--- /dev/null
+++ b/chpp/platform/aoc/include/chpp/platform/platform_mutex.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2020 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 CHPP_PLATFORM_MUTEX_H_
+#define CHPP_PLATFORM_MUTEX_H_
+
+#include "chpp/platform/log.h"
+#include "FreeRTOS.h"
+#include "semphr.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Encapsulates a mutex handle and its associated semaphore structure.
+ * The static semaphore is used to avoid heap allocations.
+ */
+struct ChppMutex {
+  SemaphoreHandle_t semaphoreHandle;
+  StaticSemaphore_t staticSemaphore;
+};
+
+static inline void chppMutexInit(struct ChppMutex *mutex) {
+  mutex->semaphoreHandle = xSemaphoreCreateMutexStatic(&mutex->staticSemaphore);
+}
+
+static inline void chppMutexDeinit(struct ChppMutex *mutex) {
+  if (mutex->semaphoreHandle) {
+    vSemaphoreDelete(mutex->semaphoreHandle);
+  }
+}
+
+static inline void chppMutexLock(struct ChppMutex *mutex) {
+  TickType_t blockForever = portMAX_DELAY;
+  if (pdTRUE != xSemaphoreTake(mutex->semaphoreHandle, blockForever)) {
+    // TODO: Use CHPP_ASSERT
+    CHPP_LOGE("Failed to lock mutex");
+  }
+}
+
+static inline void chppMutexUnlock(struct ChppMutex *mutex) {
+  if (pdTRUE != xSemaphoreGive(mutex->semaphoreHandle)) {
+    CHPP_LOGE("Failed to properly unlock mutex!");
+  }
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  // CHPP_PLATFORM_MUTEX_H_
diff --git a/chpp/platform/aoc/include/chpp/platform/platform_notifier.h b/chpp/platform/aoc/include/chpp/platform/platform_notifier.h
new file mode 100644
index 0000000..47364c4
--- /dev/null
+++ b/chpp/platform/aoc/include/chpp/platform/platform_notifier.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2020 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 CHPP_PLATFORM_SYNC_H_
+#define CHPP_PLATFORM_SYNC_H_
+
+#include "FreeRTOS.h"
+#include "task.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct ChppNotifier {
+  //! The task that will be notified.
+  TaskHandle_t task;
+};
+
+/**
+ * Platform implementation of chppNotifierInit()
+ */
+void chppPlatformNotifierInit(struct ChppNotifier *notifier);
+
+/**
+ * Platform implementation of chppNotifierDeinit()
+ */
+void chppPlatformNotifierDeinit(struct ChppNotifier *notifier);
+
+/**
+ * Platform implementation of chppNotifierWait()
+ */
+uint32_t chppPlatformNotifierWait(struct ChppNotifier *notifier);
+
+/**
+ * Platform implementation of chppNotifierSignal()
+ */
+void chppPlatformNotifierSignal(struct ChppNotifier *notifier, uint32_t signal);
+
+static inline void chppNotifierInit(struct ChppNotifier *notifier) {
+  chppPlatformNotifierInit(notifier);
+}
+
+static inline void chppNotifierDeinit(struct ChppNotifier *notifier) {
+  chppPlatformNotifierDeinit(notifier);
+}
+
+static inline uint32_t chppNotifierWait(struct ChppNotifier *notifier) {
+  return chppPlatformNotifierWait(notifier);
+}
+
+static inline void chppNotifierSignal(struct ChppNotifier *notifier,
+                                      uint32_t signal) {
+  chppPlatformNotifierSignal(notifier, signal);
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  // CHPP_PLATFORM_SYNC_H_
diff --git a/chpp/platform/aoc/include/chpp/platform/platform_time.h b/chpp/platform/aoc/include/chpp/platform/platform_time.h
new file mode 100644
index 0000000..a9c17a1
--- /dev/null
+++ b/chpp/platform/aoc/include/chpp/platform/platform_time.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2020 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 CHPP_PLATFORM_TIME_H_
+#define CHPP_PLATFORM_TIME_H_
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Platform implementation of chppGetCurrentTimeNs().
+ */
+uint64_t chppPlatformGetCurrentTimeNs();
+
+static inline uint64_t chppGetCurrentTimeNs(void) {
+  return chppPlatformGetCurrentTimeNs();
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  // CHPP_PLATFORM_TIME_H_
diff --git a/chpp/platform/aoc/link.cc b/chpp/platform/aoc/link.cc
new file mode 100644
index 0000000..76f3f86
--- /dev/null
+++ b/chpp/platform/aoc/link.cc
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2020 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 "chpp/link.h"
+
+#include "chpp/macros.h"
+#include "chpp/platform/chpp_uart_link_manager.h"
+#include "chpp/transport.h"
+
+using chpp::UartLinkManager;
+
+void chppPlatformLinkInit(struct ChppPlatformLinkParameters *params) {
+  UartLinkManager *manager =
+      static_cast<UartLinkManager *>(params->uartLinkManager);
+  manager->init(xTaskGetCurrentTaskHandle());
+}
+
+void chppPlatformLinkDeinit(struct ChppPlatformLinkParameters *params) {
+  UartLinkManager *manager =
+      static_cast<UartLinkManager *>(params->uartLinkManager);
+  manager->deinit();
+}
+
+enum ChppLinkErrorCode chppPlatformLinkSend(
+    struct ChppPlatformLinkParameters *params, uint8_t *buf, size_t len) {
+  bool success = false;
+
+  UartLinkManager *manager =
+      static_cast<UartLinkManager *>(params->uartLinkManager);
+  if (manager->prepareTxPacket(buf, len)) {
+    // TODO: Consider some other cases where asynchronous reporting
+    // is necessary, e.g. delayed transaction when waiting for a
+    // timeout.
+    success = manager->startTransaction();
+  }
+
+  return success ? CHPP_LINK_ERROR_NONE_SENT : CHPP_LINK_ERROR_UNSPECIFIED;
+}
+
+void chppPlatformLinkDoWork(struct ChppPlatformLinkParameters *params,
+                            uint32_t signal) {
+  UartLinkManager *manager =
+      static_cast<UartLinkManager *>(params->uartLinkManager);
+
+  if (signal & CHPP_TRANSPORT_SIGNAL_LINK_WAKE_IN_IRQ) {
+    // This indicates that the remote end wants to start a transaction.
+    manager->startTransaction();
+  }
+  if (signal & CHPP_TRANSPORT_SIGNAL_LINK_RX_PROCESS) {
+    manager->processRxSamples();
+  }
+}
+
+void chppPlatformLinkReset(struct ChppPlatformLinkParameters *params) {
+  UartLinkManager *manager =
+      static_cast<UartLinkManager *>(params->uartLinkManager);
+  manager->deinit();
+  manager->init(xTaskGetCurrentTaskHandle());
+}
diff --git a/chpp/platform/aoc/memory.cc b/chpp/platform/aoc/memory.cc
new file mode 100644
index 0000000..eff5855
--- /dev/null
+++ b/chpp/platform/aoc/memory.cc
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2020 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 "chpp/memory.h"
+#include "chpp/macros.h"
+
+#include "chre/platform/memory.h"
+#include "string.h"
+
+void *chppMalloc(const size_t size) {
+  return chre::memoryAlloc(size);
+}
+
+void chppFree(void *ptr) {
+  chre::memoryFree(ptr);
+}
+
+void *chppRealloc(void *oldPtr, const size_t newSize, const size_t oldSize) {
+  if (newSize == 0) {
+    chppFree(oldPtr);
+    return nullptr;
+  }
+  if (newSize == oldSize) {
+    return oldPtr;
+  }
+
+  void *ptr = chppMalloc(newSize);
+  if (ptr != nullptr) {
+    memcpy(ptr, oldPtr, newSize > oldSize ? oldSize : newSize);
+    chppFree(oldPtr);
+  }
+
+  return ptr;
+}
diff --git a/chpp/platform/aoc/notifier.cc b/chpp/platform/aoc/notifier.cc
new file mode 100644
index 0000000..3a7eac4
--- /dev/null
+++ b/chpp/platform/aoc/notifier.cc
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2020 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 <stdbool.h>
+
+#include "chpp/macros.h"
+#include "chpp/memory.h"
+#include "chpp/notifier.h"
+#include "efw/include/interrupt_controller.h"
+
+void chppPlatformNotifierInit(struct ChppNotifier *notifier) {
+  notifier->task = xTaskGetCurrentTaskHandle();
+  xTaskNotifyStateClear(notifier->task);
+}
+
+void chppPlatformNotifierDeinit(struct ChppNotifier *notifier) {}
+
+uint32_t chppPlatformNotifierWait(struct ChppNotifier *notifier) {
+  uint32_t signal;
+  xTaskNotifyWait(0 /* ulBitsToClearOnEntry */,
+                  UINT32_MAX /* ulBitsToClearOnExit */, &signal,
+                  portMAX_DELAY /* xTicksToWait */);
+  return signal;
+}
+
+void chppPlatformNotifierSignal(struct ChppNotifier *notifier,
+                                uint32_t signal) {
+  if (InterruptController::Instance()->IsInterruptContext()) {
+    BaseType_t xHigherPriorityTaskWoken = 0;
+    xTaskNotifyFromISR(notifier->task, signal, eSetBits,
+                       &xHigherPriorityTaskWoken);
+    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
+  } else {
+    xTaskNotify(notifier->task, signal, eSetBits);
+  }
+}
diff --git a/chpp/platform/aoc/time.cc b/chpp/platform/aoc/time.cc
new file mode 100644
index 0000000..51ec0d9
--- /dev/null
+++ b/chpp/platform/aoc/time.cc
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2020 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 "chpp/platform/platform_time.h"
+
+#include "chre/platform/system_time.h"
+
+uint64_t chppPlatformGetCurrentTimeNs() {
+  return chre::SystemTime::getMonotonicTime().toRawNanoseconds();
+}
diff --git a/chre_daemon.rc b/chre_daemon.rc
index 120da10..544d3fe 100644
--- a/chre_daemon.rc
+++ b/chre_daemon.rc
@@ -17,7 +17,7 @@
 service vendor.chre /vendor/bin/chre
     class late_start
     user context_hub
-    group wakelock context_hub
+    group wakelock context_hub system
     capabilities BLOCK_SUSPEND
     socket chre seqpacket 0660 root context_hub
     shutdown critical
diff --git a/core/include/chre/core/event_loop_common.h b/core/include/chre/core/event_loop_common.h
index 03765f0..ea1cdbc 100644
--- a/core/include/chre/core/event_loop_common.h
+++ b/core/include/chre/core/event_loop_common.h
@@ -53,6 +53,7 @@
   SettingChangeEvent,
   GnssLocationReportEvent,
   GnssMeasurementReportEvent,
+  TimerSyncRequest,
 };
 
 //! The function signature of a system callback mirrors the CHRE event free
diff --git a/external/external.mk b/external/external.mk
index 4a04a13..c95b43e 100644
--- a/external/external.mk
+++ b/external/external.mk
@@ -29,7 +29,7 @@
 #
 # Kiss FFT
 #
-
+ifeq ($(CHRE_AUDIO_SUPPORT_ENABLED), true)
 # Common Compiler Flags ########################################################
 
 # Include paths.
@@ -42,3 +42,4 @@
 
 COMMON_SRCS += external/kiss_fft/kiss_fft.c
 COMMON_SRCS += external/kiss_fft/kiss_fftr.c
+endif
diff --git a/external/kiss_fft/_kiss_fft_guts.h b/external/kiss_fft/_kiss_fft_guts.h
index e83771f..6aa102d 100644
--- a/external/kiss_fft/_kiss_fft_guts.h
+++ b/external/kiss_fft/_kiss_fft_guts.h
@@ -55,9 +55,13 @@
 #define SAMP_MIN -SAMP_MAX
 
 #if defined(CHECK_OVERFLOW)
+#ifdef CHRE_KISS_FFT_CAN_USE_STDIO
 #  define CHECK_OVERFLOW_OP(a,op,b)  \
 	if ( (SAMPPROD)(a) op (SAMPPROD)(b) > SAMP_MAX || (SAMPPROD)(a) op (SAMPPROD)(b) < SAMP_MIN ) { \
 		fprintf(stderr,"WARNING:overflow @ " __FILE__ "(%d): (%d " #op" %d) = %ld\n",__LINE__,(a),(b),(SAMPPROD)(a) op (SAMPPROD)(b) );  }
+#else
+#define CHECK_OVERFLOW(a, op, b) {}
+#endif
 #endif
 
 
@@ -145,10 +149,13 @@
 	}while(0)
 
 
+#ifdef CHRE_KISS_FFT_CAN_USE_STDIO
 /* a debugging function */
 #define pcpx(c)\
     fprintf(stderr,"%g + %gi\n",(double)((c)->r),(double)((c)->i) )
-
+#else
+#define pcpx(c) {}
+#endif
 
 #ifdef KISS_FFT_USE_ALLOCA
 // define this to allow use of alloca instead of malloc for temporary buffers
diff --git a/external/kiss_fft/kiss_fft.c b/external/kiss_fft/kiss_fft.c
index 7f439c2..c1ea7e3 100644
--- a/external/kiss_fft/kiss_fft.c
+++ b/external/kiss_fft/kiss_fft.c
@@ -18,6 +18,14 @@
  fixed or floating point complex numbers.  It also delares the kf_ internal functions.
  */
 
+// CHRE modifications begin
+#if defined(__clang__)
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wcast-align"
+#pragma clang diagnostic ignored "-Wbad-function-cast"
+#endif
+// CHRE modifications end
+
 static void kf_bfly2(
         kiss_fft_cpx * Fout,
         const size_t fstride,
@@ -406,3 +414,9 @@
     }
     return n;
 }
+
+// CHRE modifications begin
+#if defined(__clang__)
+#pragma clang diagnostic pop
+#endif
+// CHRE modifications end
diff --git a/external/kiss_fft/kiss_fftr.c b/external/kiss_fft/kiss_fftr.c
index f31f23e..0dad8a2 100644
--- a/external/kiss_fft/kiss_fftr.c
+++ b/external/kiss_fft/kiss_fftr.c
@@ -15,6 +15,14 @@
 #include "kiss_fftr.h"
 #include "_kiss_fft_guts.h"
 
+// CHRE modifications begin
+#if defined(__clang__)
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wcast-align"
+#pragma clang diagnostic ignored "-Wbad-function-cast"
+#endif
+// CHRE modifications end
+
 struct kiss_fftr_state{
     kiss_fft_cfg substate;
     kiss_fft_cpx * tmpbuf;
@@ -31,7 +39,9 @@
     size_t subsize, memneeded;
 
     if (nfft & 1) {
+#if CHRE_KISS_FFT_CAN_USE_STDIO
         fprintf(stderr,"Real FFT optimization must be even.\n");
+#endif
         return NULL;
     }
     nfft >>= 1;
@@ -71,8 +81,12 @@
     kiss_fft_cpx fpnk,fpk,f1k,f2k,tw,tdc;
 
     if ( st->substate->inverse) {
+#if CHRE_KISS_FFT_CAN_USE_STDIO
         fprintf(stderr,"kiss fft usage error: improper alloc\n");
         exit(1);
+#else
+        return;
+#endif
     }
 
     ncfft = st->substate->nfft;
@@ -126,8 +140,12 @@
     int k, ncfft;
 
     if (st->substate->inverse == 0) {
+#if CHRE_KISS_FFT_CAN_USE_STDIO
         fprintf (stderr, "kiss fft usage error: improper alloc\n");
         exit (1);
+#else
+        return;
+#endif
     }
 
     ncfft = st->substate->nfft;
@@ -157,3 +175,9 @@
     }
     kiss_fft (st->substate, st->tmpbuf, (kiss_fft_cpx *) timedata);
 }
+
+// CHRE modifications begin
+#if defined(__clang__)
+#pragma clang diagnostic pop
+#endif
+// CHRE modifications end
diff --git a/host/common/audio_stress_test/audio_stress_test.cc b/host/common/audio_stress_test/audio_stress_test.cc
index f824786..0073718 100644
--- a/host/common/audio_stress_test/audio_stress_test.cc
+++ b/host/common/audio_stress_test/audio_stress_test.cc
@@ -15,6 +15,7 @@
  */
 
 #include "chre/util/nanoapp/app_id.h"
+#include "chre/util/system/napp_header_utils.h"
 #include "chre_host/host_protocol_host.h"
 #include "chre_host/log.h"
 #include "chre_host/socket_client.h"
@@ -92,12 +93,15 @@
     return;
   }
 
+  // All loaded nanoapps must be signed currently.
+  uint32_t appFlags = CHRE_NAPP_HEADER_SIGNED;
+
   // Perform loading with 1 fragment for simplicity
   FlatBufferBuilder builder(size + 128);
   FragmentedLoadTransaction transaction =
       FragmentedLoadTransaction(1 /* transactionId */, appId, appVersion,
-                                0x01000000 /* targetApiVersion */, buffer,
-                                buffer.size() /* fragmentSize */);
+                                appFlags, 0x01000000 /* targetApiVersion */,
+                                buffer, buffer.size() /* fragmentSize */);
   HostProtocolHost::encodeFragmentedLoadNanoappRequest(
       builder, transaction.getNextRequest());
 
diff --git a/host/common/fragmented_load_transaction.cc b/host/common/fragmented_load_transaction.cc
index cf1f637..c3c26d3 100644
--- a/host/common/fragmented_load_transaction.cc
+++ b/host/common/fragmented_load_transaction.cc
@@ -48,8 +48,8 @@
 
 FragmentedLoadTransaction::FragmentedLoadTransaction(
     uint32_t transactionId, uint64_t appId, uint32_t appVersion,
-    uint32_t targetApiVersion, const std::vector<uint8_t> &appBinary,
-    size_t fragmentSize) {
+    uint32_t appFlags, uint32_t targetApiVersion,
+    const std::vector<uint8_t> &appBinary, size_t fragmentSize) {
   mTransactionId = transactionId;
 
   // Start with fragmentId at 1 since 0 is used to indicate
@@ -59,8 +59,9 @@
   do {
     if (fragmentId == 1) {
       mFragmentRequests.emplace_back(
-          fragmentId++, transactionId, appId, appVersion, targetApiVersion,
-          appBinary.size(), getSubVector(appBinary, byteIndex, fragmentSize));
+          fragmentId++, transactionId, appId, appVersion, appFlags,
+          targetApiVersion, appBinary.size(),
+          getSubVector(appBinary, byteIndex, fragmentSize));
     } else {
       mFragmentRequests.emplace_back(
           fragmentId++, transactionId,
diff --git a/host/common/host_protocol_host.cc b/host/common/host_protocol_host.cc
index 21904f5..365a237 100644
--- a/host/common/host_protocol_host.cc
+++ b/host/common/host_protocol_host.cc
@@ -103,8 +103,8 @@
     const FragmentedLoadRequest &request) {
   encodeLoadNanoappRequestForBinary(
       builder, request.transactionId, request.appId, request.appVersion,
-      request.targetApiVersion, request.binary, request.fragmentId,
-      request.appTotalSizeBytes);
+      request.appFlags, request.targetApiVersion, request.binary,
+      request.fragmentId, request.appTotalSizeBytes);
 }
 
 void HostProtocolHost::encodeNanoappListRequest(FlatBufferBuilder &builder) {
@@ -170,13 +170,13 @@
 
 void HostProtocolHost::encodeLoadNanoappRequestForBinary(
     FlatBufferBuilder &builder, uint32_t transactionId, uint64_t appId,
-    uint32_t appVersion, uint32_t targetApiVersion,
+    uint32_t appVersion, uint32_t appFlags, uint32_t targetApiVersion,
     const std::vector<uint8_t> &nanoappBinary, uint32_t fragmentId,
     size_t appTotalSizeBytes) {
   auto appBinary = builder.CreateVector(nanoappBinary);
   auto request = fbs::CreateLoadNanoappRequest(
       builder, transactionId, appId, appVersion, targetApiVersion, appBinary,
-      fragmentId, appTotalSizeBytes);
+      fragmentId, appTotalSizeBytes, 0 /* app_binary_file_name */, appFlags);
   finalize(builder, fbs::ChreMessage::LoadNanoappRequest, request.Union());
 }
 
diff --git a/host/common/include/chre_host/daemon_base.h b/host/common/include/chre_host/daemon_base.h
index 7423849..469f9a8 100644
--- a/host/common/include/chre_host/daemon_base.h
+++ b/host/common/include/chre_host/daemon_base.h
@@ -66,7 +66,8 @@
    * @param message A buffer containing the message
    * @param messageLen size of the message buffer in bytes
    */
-  virtual void onMessageReceived(unsigned char *message, size_t messageLen) = 0;
+  virtual void onMessageReceived(const unsigned char *message,
+                                 size_t messageLen) = 0;
 
   /**
    * Function to query if a graceful shutdown of CHRE was requested
@@ -77,6 +78,16 @@
     return mChreShutdownRequested;
   }
 
+  /**
+   * Loads the supplied file into the provided buffer.
+   *
+   * @param filename The name of the file to load.
+   * @param buffer The buffer to load into.
+   * @return true if successful, false otherwise.
+   */
+  static bool readFileContents(const char *filename,
+                               std::vector<uint8_t> *buffer);
+
  protected:
   //! The host ID to use when preloading nanoapps. This is used before the
   //! server is started and is sufficiently high enough so as to not collide
@@ -112,8 +123,9 @@
    * @param name The filename of the nanoapp to load.
    * @param transactionId The transaction ID to use when loading the app.
    */
-  void loadPreloadedNanoapp(const std::string &directory,
-                            const std::string &name, uint32_t transactionId);
+  virtual void loadPreloadedNanoapp(const std::string &directory,
+                                    const std::string &name,
+                                    uint32_t transactionId);
 
   /**
    * Sends a preloaded nanoapp filename / metadata to CHRE.
@@ -165,20 +177,11 @@
                              bool logOnError);
 
   /**
-   * Loads the supplied file into the provided buffer.
-   *
-   * @param filename The name of the file to load.
-   * @param buffer The buffer to load into.
-   * @return true if successful, false otherwise.
-   */
-  bool readFileContents(const char *filename, std::vector<uint8_t> *buffer);
-
-  /**
    * Handles a message that is directed towards the daemon.
    *
    * @param message The message sent to the daemon.
    */
-  void handleDaemonMessage(const uint8_t *message);
+  virtual void handleDaemonMessage(const uint8_t *message);
 
  private:
   //! Set to true when we request a graceful shutdown of CHRE
diff --git a/host/common/include/chre_host/fragmented_load_transaction.h b/host/common/include/chre_host/fragmented_load_transaction.h
index 98a4e56..42ce478 100644
--- a/host/common/include/chre_host/fragmented_load_transaction.h
+++ b/host/common/include/chre_host/fragmented_load_transaction.h
@@ -34,22 +34,25 @@
   uint32_t transactionId;
   uint64_t appId;
   uint32_t appVersion;
+  uint32_t appFlags;
   uint32_t targetApiVersion;
   size_t appTotalSizeBytes;
   std::vector<uint8_t> binary;
 
   FragmentedLoadRequest(size_t fragmentId, uint32_t transactionId,
                         const std::vector<uint8_t> &binary)
-      : FragmentedLoadRequest(fragmentId, transactionId, 0, 0, 0, 0, binary) {}
+      : FragmentedLoadRequest(fragmentId, transactionId, 0, 0, 0, 0, 0,
+                              binary) {}
 
   FragmentedLoadRequest(size_t fragmentId, uint32_t transactionId,
-                        uint64_t appId, uint32_t appVersion,
+                        uint64_t appId, uint32_t appVersion, uint32_t appFlags,
                         uint32_t targetApiVersion, size_t appTotalSizeBytes,
                         const std::vector<uint8_t> &binary)
       : fragmentId(fragmentId),
         transactionId(transactionId),
         appId(appId),
         appVersion(appVersion),
+        appFlags(appFlags),
         targetApiVersion(targetApiVersion),
         appTotalSizeBytes(appTotalSizeBytes),
         binary(binary) {}
@@ -69,12 +72,14 @@
    * @param transactionId the unique ID of the unfragmented load transaction
    * @param appId the unique ID of the nanoapp
    * @param appVersion the version of the nanoapp
+   * @param appFlags the flags specified by the nanoapp to be loaded.
    * @param targetApiVersion the API version this nanoapp is targeted for
    * @param appBinary the nanoapp binary data
    * @param fragmentSize the size of each fragment in bytes
    */
   FragmentedLoadTransaction(uint32_t transactionId, uint64_t appId,
-                            uint32_t appVersion, uint32_t targetApiVersion,
+                            uint32_t appVersion, uint32_t appFlags,
+                            uint32_t targetApiVersion,
                             const std::vector<uint8_t> &appBinary,
                             size_t fragmentSize = kDefaultFragmentSize);
 
diff --git a/host/common/include/chre_host/generated/host_messages_generated.h b/host/common/include/chre_host/generated/host_messages_generated.h
index 4e280b6..7eb9317 100644
--- a/host/common/include/chre_host/generated/host_messages_generated.h
+++ b/host/common/include/chre_host/generated/host_messages_generated.h
@@ -1174,13 +1174,15 @@
   uint32_t fragment_id;
   uint32_t total_app_size;
   std::vector<int8_t> app_binary_file_name;
+  uint32_t app_flags;
   LoadNanoappRequestT()
       : transaction_id(0),
         app_id(0),
         app_version(0),
         target_api_version(0),
         fragment_id(0),
-        total_app_size(0) {
+        total_app_size(0),
+        app_flags(0) {
   }
 };
 
@@ -1233,7 +1235,8 @@
     VT_APP_BINARY = 12,
     VT_FRAGMENT_ID = 14,
     VT_TOTAL_APP_SIZE = 16,
-    VT_APP_BINARY_FILE_NAME = 18
+    VT_APP_BINARY_FILE_NAME = 18,
+    VT_APP_FLAGS = 20
   };
   uint32_t transaction_id() const {
     return GetField<uint32_t>(VT_TRANSACTION_ID, 0);
@@ -1289,6 +1292,14 @@
   flatbuffers::Vector<int8_t> *mutable_app_binary_file_name() {
     return GetPointer<flatbuffers::Vector<int8_t> *>(VT_APP_BINARY_FILE_NAME);
   }
+  /// The nanoapp flag values from the nanoapp header defined in
+  /// build/build_template.mk. Refer to that file for more details.
+  uint32_t app_flags() const {
+    return GetField<uint32_t>(VT_APP_FLAGS, 0);
+  }
+  bool mutate_app_flags(uint32_t _app_flags) {
+    return SetField<uint32_t>(VT_APP_FLAGS, _app_flags, 0);
+  }
   bool Verify(flatbuffers::Verifier &verifier) const {
     return VerifyTableStart(verifier) &&
            VerifyField<uint32_t>(verifier, VT_TRANSACTION_ID) &&
@@ -1301,6 +1312,7 @@
            VerifyField<uint32_t>(verifier, VT_TOTAL_APP_SIZE) &&
            VerifyOffset(verifier, VT_APP_BINARY_FILE_NAME) &&
            verifier.VerifyVector(app_binary_file_name()) &&
+           VerifyField<uint32_t>(verifier, VT_APP_FLAGS) &&
            verifier.EndTable();
   }
   LoadNanoappRequestT *UnPack(const flatbuffers::resolver_function_t *_resolver = nullptr) const;
@@ -1336,6 +1348,9 @@
   void add_app_binary_file_name(flatbuffers::Offset<flatbuffers::Vector<int8_t>> app_binary_file_name) {
     fbb_.AddOffset(LoadNanoappRequest::VT_APP_BINARY_FILE_NAME, app_binary_file_name);
   }
+  void add_app_flags(uint32_t app_flags) {
+    fbb_.AddElement<uint32_t>(LoadNanoappRequest::VT_APP_FLAGS, app_flags, 0);
+  }
   explicit LoadNanoappRequestBuilder(flatbuffers::FlatBufferBuilder &_fbb)
         : fbb_(_fbb) {
     start_ = fbb_.StartTable();
@@ -1358,9 +1373,11 @@
     flatbuffers::Offset<flatbuffers::Vector<uint8_t>> app_binary = 0,
     uint32_t fragment_id = 0,
     uint32_t total_app_size = 0,
-    flatbuffers::Offset<flatbuffers::Vector<int8_t>> app_binary_file_name = 0) {
+    flatbuffers::Offset<flatbuffers::Vector<int8_t>> app_binary_file_name = 0,
+    uint32_t app_flags = 0) {
   LoadNanoappRequestBuilder builder_(_fbb);
   builder_.add_app_id(app_id);
+  builder_.add_app_flags(app_flags);
   builder_.add_app_binary_file_name(app_binary_file_name);
   builder_.add_total_app_size(total_app_size);
   builder_.add_fragment_id(fragment_id);
@@ -1380,7 +1397,8 @@
     const std::vector<uint8_t> *app_binary = nullptr,
     uint32_t fragment_id = 0,
     uint32_t total_app_size = 0,
-    const std::vector<int8_t> *app_binary_file_name = nullptr) {
+    const std::vector<int8_t> *app_binary_file_name = nullptr,
+    uint32_t app_flags = 0) {
   auto app_binary__ = app_binary ? _fbb.CreateVector<uint8_t>(*app_binary) : 0;
   auto app_binary_file_name__ = app_binary_file_name ? _fbb.CreateVector<int8_t>(*app_binary_file_name) : 0;
   return chre::fbs::CreateLoadNanoappRequest(
@@ -1392,7 +1410,8 @@
       app_binary__,
       fragment_id,
       total_app_size,
-      app_binary_file_name__);
+      app_binary_file_name__,
+      app_flags);
 }
 
 flatbuffers::Offset<LoadNanoappRequest> CreateLoadNanoappRequest(flatbuffers::FlatBufferBuilder &_fbb, const LoadNanoappRequestT *_o, const flatbuffers::rehasher_function_t *_rehasher = nullptr);
@@ -2640,6 +2659,7 @@
   { auto _e = fragment_id(); _o->fragment_id = _e; }
   { auto _e = total_app_size(); _o->total_app_size = _e; }
   { auto _e = app_binary_file_name(); if (_e) { _o->app_binary_file_name.resize(_e->size()); for (flatbuffers::uoffset_t _i = 0; _i < _e->size(); _i++) { _o->app_binary_file_name[_i] = _e->Get(_i); } } }
+  { auto _e = app_flags(); _o->app_flags = _e; }
 }
 
 inline flatbuffers::Offset<LoadNanoappRequest> LoadNanoappRequest::Pack(flatbuffers::FlatBufferBuilder &_fbb, const LoadNanoappRequestT* _o, const flatbuffers::rehasher_function_t *_rehasher) {
@@ -2658,6 +2678,7 @@
   auto _fragment_id = _o->fragment_id;
   auto _total_app_size = _o->total_app_size;
   auto _app_binary_file_name = _o->app_binary_file_name.size() ? _fbb.CreateVector(_o->app_binary_file_name) : 0;
+  auto _app_flags = _o->app_flags;
   return chre::fbs::CreateLoadNanoappRequest(
       _fbb,
       _transaction_id,
@@ -2667,7 +2688,8 @@
       _app_binary,
       _fragment_id,
       _total_app_size,
-      _app_binary_file_name);
+      _app_binary_file_name,
+      _app_flags);
 }
 
 inline LoadNanoappResponseT *LoadNanoappResponse::UnPack(const flatbuffers::resolver_function_t *_resolver) const {
diff --git a/host/common/include/chre_host/host_protocol_host.h b/host/common/include/chre_host/host_protocol_host.h
index ba04c14..107cb58 100644
--- a/host/common/include/chre_host/host_protocol_host.h
+++ b/host/common/include/chre_host/host_protocol_host.h
@@ -196,9 +196,9 @@
    */
   static void encodeLoadNanoappRequestForBinary(
       flatbuffers::FlatBufferBuilder &builder, uint32_t transactionId,
-      uint64_t appId, uint32_t appVersion, uint32_t targetApiVersion,
-      const std::vector<uint8_t> &nanoappBinary, uint32_t fragmentId,
-      size_t appTotalSizeBytes);
+      uint64_t appId, uint32_t appVersion, uint32_t appFlags,
+      uint32_t targetApiVersion, const std::vector<uint8_t> &nanoappBinary,
+      uint32_t fragmentId, size_t appTotalSizeBytes);
 
   /**
    * Encodes a message requesting to load a nanoapp specified by the included
diff --git a/host/common/include/chre_host/log_message_parser_base.h b/host/common/include/chre_host/log_message_parser_base.h
index e949ce3..932c196 100644
--- a/host/common/include/chre_host/log_message_parser_base.h
+++ b/host/common/include/chre_host/log_message_parser_base.h
@@ -46,8 +46,7 @@
     return true;
   };
 
-  virtual void log(const uint8_t * /* logBuffer */,
-                   size_t /* logBufferSize */) {}
+  virtual void log(const uint8_t *logBuffer, size_t logBufferSize);
 
   /**
    * With verbose logging enabled (via enableVerbose()), dump a
@@ -71,6 +70,21 @@
 
   void emitLogMessage(uint8_t level, uint64_t timestampNanos,
                       const char *logMessage);
+
+ private:
+  enum LogLevel : uint8_t {
+    ERROR = 1,
+    WARNING = 2,
+    INFO = 3,
+    DEBUG = 4,
+  };
+
+  //! See host_messages.fbs for the definition of this struct.
+  struct LogMessage {
+    enum LogLevel logLevel;
+    uint64_t timestampNanos;
+    char logMessage[];
+  } __attribute__((packed));
 };
 
 }  // namespace chre
diff --git a/host/common/include/chre_host/tokenized_log_message_parser.h b/host/common/include/chre_host/tokenized_log_message_parser.h
index d82140d..7677b38 100644
--- a/host/common/include/chre_host/tokenized_log_message_parser.h
+++ b/host/common/include/chre_host/tokenized_log_message_parser.h
@@ -18,6 +18,7 @@
 #define CHRE_TOKENIZED_LOG_MESSAGE_PARSER_H_
 
 #include <memory>
+#include "chre_host/daemon_base.h"
 #include "chre_host/log_message_parser_base.h"
 
 #include "pw_tokenizer/detokenize.h"
@@ -25,12 +26,14 @@
 using pw::tokenizer::DetokenizedString;
 using pw::tokenizer::Detokenizer;
 
+namespace android {
 namespace chre {
 
 class ChreTokenizedLogMessageParser : public ChreLogMessageParserBase {
  public:
   virtual bool init() override final {
     mDetokenizer = logDetokenizerInit();
+    return mDetokenizer != nullptr;
   }
 
   virtual void log(const uint8_t *logBuffer,
@@ -55,7 +58,7 @@
     constexpr const char kLogDatabaseFilePath[] =
         "/vendor/etc/chre/libchre_log_database.bin";
     std::vector<uint8_t> tokenData;
-    if (readFileContents(kLogDatabaseFilePath, &tokenData)) {
+    if (ChreDaemonBase::readFileContents(kLogDatabaseFilePath, &tokenData)) {
       pw::tokenizer::TokenDatabase database =
           pw::tokenizer::TokenDatabase::Create(tokenData);
       if (database.ok()) {
@@ -71,7 +74,7 @@
 
   // Log messages are routed through ashLog if tokenized logging
   // is disabled, so only parse tokenized log messages here.
-  void parseAndEmitTokenizedLogMessages(unsigned char *message,
+  void parseAndEmitTokenizedLogMessages(const uint8_t *message,
                                         unsigned int messageLen,
                                         const Detokenizer *detokenizer) {
     if (detokenizer != nullptr) {
@@ -80,24 +83,16 @@
       // logs (b/148873804)
       constexpr size_t kLogMessageHeaderSize =
           1 /*logLevel*/ + sizeof(uint64_t) /*timestamp*/;
-
-      const fbs::MessageContainer *container =
-          fbs::GetMessageContainer(message);
-      const auto *logMessage =
-          static_cast<const fbs::LogMessage *>(container->message());
-
-      const flatbuffers::Vector<int8_t> &logData = *logMessage->buffer();
-      const uint8_t *log = reinterpret_cast<const uint8_t *>(logData.data());
-      uint8_t level = *log;
-      ++log;
+      uint8_t level = *message;
+      ++message;
 
       uint64_t timestampNanos;
-      memcpy(&timestampNanos, log, sizeof(uint64_t));
+      memcpy(&timestampNanos, message, sizeof(uint64_t));
       timestampNanos = le64toh(timestampNanos);
-      log += sizeof(uint64_t);
+      message += sizeof(uint64_t);
 
       DetokenizedString detokenizedLog =
-          detokenizer->Detokenize(log, messageLen - kLogMessageHeaderSize);
+          detokenizer->Detokenize(message, messageLen - kLogMessageHeaderSize);
       std::string decodedLog = detokenizedLog.BestStringWithErrors();
       emitLogMessage(level, timestampNanos, decodedLog.c_str());
     } else {
@@ -107,5 +102,6 @@
 };
 
 }  // namespace chre
+}  // namespace android
 
 #endif  // CHRE_TOKENIZED_LOG_MESSAGE_PARSER_H_
\ No newline at end of file
diff --git a/host/common/log_message_parser_base.cc b/host/common/log_message_parser_base.cc
index 86d70d5..3de5002 100644
--- a/host/common/log_message_parser_base.cc
+++ b/host/common/log_message_parser_base.cc
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include <endian.h>
 #include <cstdio>
 
 #include "chre_host/log_message_parser_base.h"
@@ -27,14 +28,6 @@
 #else
 constexpr bool kVerboseLoggingEnabled = false;
 #endif
-
-enum ChreLogLevel : uint8_t {
-  LOG_LEVEL_ERROR = 0,
-  LOG_LEVEL_WARN = 1,
-  LOG_LEVEL_INFO = 2,
-  LOG_LEVEL_DEBUG = 3
-};
-
 }  // anonymous namespace
 
 ChreLogMessageParserBase::ChreLogMessageParserBase()
@@ -85,19 +78,33 @@
 android_LogPriority ChreLogMessageParserBase::chreLogLevelToAndroidLogPriority(
     uint8_t level) {
   switch (level) {
-    case LOG_LEVEL_ERROR:
+    case LogLevel::ERROR:
       return ANDROID_LOG_ERROR;
-    case LOG_LEVEL_WARN:
+    case LogLevel::WARNING:
       return ANDROID_LOG_WARN;
-    case LOG_LEVEL_INFO:
+    case LogLevel::INFO:
       return ANDROID_LOG_INFO;
-    case LOG_LEVEL_DEBUG:
+    case LogLevel::DEBUG:
       return ANDROID_LOG_DEBUG;
     default:
       return ANDROID_LOG_SILENT;
   }
 }
 
+void ChreLogMessageParserBase::log(const uint8_t *logBuffer,
+                                   size_t logBufferSize) {
+  size_t bufferIndex = 0;
+  while (bufferIndex < logBufferSize) {
+    const LogMessage *message =
+        reinterpret_cast<const LogMessage *>(&logBuffer[bufferIndex]);
+    emitLogMessage(message->logLevel, le64toh(message->timestampNanos),
+                   message->logMessage);
+    bufferIndex += sizeof(LogMessage) +
+                   strnlen(message->logMessage, logBufferSize - bufferIndex) +
+                   1;
+  }
+}
+
 void ChreLogMessageParserBase::emitLogMessage(uint8_t level,
                                               uint64_t timestampNanos,
                                               const char *logMessage) {
diff --git a/host/common/test/chre_test_client.cc b/host/common/test/chre_test_client.cc
index d651cb8..8e74657 100644
--- a/host/common/test/chre_test_client.cc
+++ b/host/common/test/chre_test_client.cc
@@ -15,6 +15,7 @@
  */
 
 #include "chre/util/nanoapp/app_id.h"
+#include "chre/util/system/napp_header_utils.h"
 #include "chre_host/host_protocol_host.h"
 #include "chre_host/log.h"
 #include "chre_host/socket_client.h"
@@ -37,7 +38,7 @@
  *
  * Usage:
  *  chre_test_client load <nanoapp-id> <nanoapp-path> \
- *      [app-version] [api-version]
+ *      [app-version] [api-version] [tcm-capable]
  *  chre_test_client unload <nanoapp-id>
  */
 
@@ -168,7 +169,7 @@
 
 void sendLoadNanoappRequest(SocketClient &client, const char *filename,
                             uint64_t appId, uint32_t appVersion,
-                            uint32_t apiVersion) {
+                            uint32_t apiVersion, bool tcmApp) {
   std::ifstream file(filename, std::ios::binary | std::ios::ate);
   if (!file) {
     LOGE("Couldn't open file '%s': %s", filename, strerror(errno));
@@ -183,10 +184,16 @@
     return;
   }
 
+  // All loaded nanoapps must be signed currently.
+  uint32_t appFlags = CHRE_NAPP_HEADER_SIGNED;
+  if (tcmApp) {
+    appFlags |= CHRE_NAPP_HEADER_TCM_CAPABLE;
+  }
+
   // Perform loading with 1 fragment for simplicity
   FlatBufferBuilder builder(size + 128);
   FragmentedLoadTransaction transaction = FragmentedLoadTransaction(
-      1 /* transactionId */, appId, appVersion, apiVersion, buffer,
+      1 /* transactionId */, appId, appVersion, appFlags, apiVersion, buffer,
       buffer.size() /* fragmentSize */);
   HostProtocolHost::encodeFragmentedLoadNanoappRequest(
       builder, transaction.getNextRequest());
@@ -237,6 +244,7 @@
   const std::string path{argi < argc ? argv[argi++] : ""};
   const std::string appVerStr{argi < argc ? argv[argi++] : ""};
   const std::string apiVerStr{argi < argc ? argv[argi++] : ""};
+  const std::string tcmCapStr{argi < argc ? argv[argi++] : ""};
 
   SocketClient client;
   sp<SocketCallbacks> callbacks = new SocketCallbacks();
@@ -252,7 +260,8 @@
     sendMessageToNanoapp(client);
     sendLoadNanoappRequest(client, "/data/activity.so",
                            0x476f6f676c00100b /* appId */, 0 /* appVersion */,
-                           0x01000000 /* targetApiVersion */);
+                           0x01000000 /* targetApiVersion */,
+                           false /* tcmCapable */);
     sendUnloadNanoappRequest(client, 0x476f6f676c00100b /* appId */);
 
     LOGI("Sleeping, waiting on responses");
@@ -261,6 +270,7 @@
     uint64_t id = 0;
     uint32_t appVersion = kDefaultAppVersion;
     uint32_t apiVersion = kDefaultApiVersion;
+    bool tcmApp = false;
 
     if (idstr.empty() || path.empty()) {
       LOGE("Arguments not provided!");
@@ -274,7 +284,11 @@
     if (!apiVerStr.empty()) {
       std::istringstream(apiVerStr) >> std::setbase(0) >> apiVersion;
     }
-    sendLoadNanoappRequest(client, path.c_str(), id, appVersion, apiVersion);
+    if (!tcmCapStr.empty()) {
+      std::istringstream(tcmCapStr) >> tcmApp;
+    }
+    sendLoadNanoappRequest(client, path.c_str(), id, appVersion, apiVersion,
+                           tcmApp);
   } else if (cmd == "unload") {
     uint64_t id = 0;
 
diff --git a/host/common/test/power_test/chre_power_test_client.cc b/host/common/test/power_test/chre_power_test_client.cc
index c317dbb..1c0737e 100644
--- a/host/common/test/power_test/chre_power_test_client.cc
+++ b/host/common/test/power_test/chre_power_test_client.cc
@@ -32,6 +32,7 @@
 #include <vector>
 
 #include "chre/util/nanoapp/app_id.h"
+#include "chre/util/system/napp_header_utils.h"
 #include "chre/version.h"
 #include "chre_host/host_protocol_host.h"
 #include "chre_host/log.h"
@@ -281,7 +282,7 @@
 
 bool sendLoadNanoappRequest(SocketClient &client, const char *filename,
                             uint64_t appId, uint32_t appVersion,
-                            uint32_t apiVersion) {
+                            uint32_t apiVersion, bool tcmApp) {
   std::ifstream file(filename, std::ios::binary | std::ios::ate);
   if (!file) {
     LOGE("Couldn't open file '%s': %s", filename, strerror(errno));
@@ -297,10 +298,16 @@
     return false;
   }
 
+  // All loaded nanoapps must be signed currently.
+  uint32_t appFlags = CHRE_NAPP_HEADER_SIGNED;
+  if (tcmApp) {
+    appFlags |= CHRE_NAPP_HEADER_TCM_CAPABLE;
+  }
+
   // Perform loading with 1 fragment for simplicity
   FlatBufferBuilder builder(size + 128);
   FragmentedLoadTransaction transaction = FragmentedLoadTransaction(
-      1 /* transactionId */, appId, appVersion, apiVersion, buffer,
+      1 /* transactionId */, appId, appVersion, appFlags, apiVersion, buffer,
       buffer.size() /* fragmentSize */);
   HostProtocolHost::encodeFragmentedLoadNanoappRequest(
       builder, transaction.getNextRequest());
@@ -318,9 +325,9 @@
 
 bool loadNanoapp(SocketClient &client, sp<SocketCallbacks> callbacks,
                  const char *filename, uint64_t appId, uint32_t appVersion,
-                 uint32_t apiVersion) {
-  if (!sendLoadNanoappRequest(client, filename, appId, appVersion,
-                              apiVersion)) {
+                 uint32_t apiVersion, bool tcmApp) {
+  if (!sendLoadNanoappRequest(client, filename, appId, appVersion, apiVersion,
+                              tcmApp)) {
     return false;
   }
   auto status = kReadyCond.wait_for(kReadyCondLock, kTimeout);
@@ -384,11 +391,12 @@
   return true;
 }
 
+bool isTcmArgSpecified(std::vector<string> &args) {
+  return !args.empty() && args[0] == "tcm";
+}
+
 inline uint64_t getId(std::vector<string> &args) {
-  if (!args.empty() && args[0] == "tcm") {
-    return kPowerTestTcmAppId;
-  }
-  return kPowerTestAppId;
+  return isTcmArgSpecified(args) ? kPowerTestTcmAppId : kPowerTestAppId;
 }
 
 /**
@@ -761,7 +769,7 @@
     }
     case Command::kLoad: {
       success = loadNanoapp(client, callbacks, getPath(args), getId(args),
-                            kAppVersion, kApiVersion);
+                            kAppVersion, kApiVersion, isTcmArgSpecified(args));
       break;
     }
     default: {
diff --git a/host/hal_generic/common/generic_context_hub_base.h b/host/hal_generic/common/generic_context_hub_base.h
index 81514e5..6f0f47f 100644
--- a/host/hal_generic/common/generic_context_hub_base.h
+++ b/host/hal_generic/common/generic_context_hub_base.h
@@ -239,7 +239,7 @@
       uint32_t targetApiVersion = (appBinary.targetChreApiMajorVersion << 24) |
                                   (appBinary.targetChreApiMinorVersion << 16);
       mPendingLoadTransaction = FragmentedLoadTransaction(
-          transactionId, appBinary.appId, appBinary.appVersion,
+          transactionId, appBinary.appId, appBinary.appVersion, appBinary.flags,
           targetApiVersion, appBinary.customBinary, kLoadFragmentSizeBytes);
 
       result =
diff --git a/host/msm/daemon/fastrpc_daemon.cc b/host/msm/daemon/fastrpc_daemon.cc
index 5ba4ccc..7ffe5c0 100644
--- a/host/msm/daemon/fastrpc_daemon.cc
+++ b/host/msm/daemon/fastrpc_daemon.cc
@@ -147,7 +147,13 @@
   }
 
   if (messageType == fbs::ChreMessage::LogMessage) {
-    mLogger.log(messageBuffer, messageLen);
+    std::unique_ptr<fbs::MessageContainerT> container =
+        fbs::UnPackMessageContainer(messageBuffer);
+    const auto *logMessage = container->message.AsLogMessage();
+    const std::vector<int8_t> &logData = logMessage->buffer;
+
+    mLogger.log(reinterpret_cast<const uint8_t *>(logData.data()),
+                logData.size());
   } else if (messageType == fbs::ChreMessage::TimeSyncRequest) {
     sendTimeSync(true /* logOnError */);
   } else if (messageType == fbs::ChreMessage::LowPowerMicAccessRequest) {
diff --git a/host/usf_daemon/main.cc b/host/usf_daemon/main.cc
new file mode 100644
index 0000000..0911d1f
--- /dev/null
+++ b/host/usf_daemon/main.cc
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2020 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 "usf_daemon.h"
+
+#include "chre_host/log.h"
+
+int main() {
+  LOGI("CHRE Starting!");
+
+  android::chre::UsfChreDaemon daemon;
+
+  if (!daemon.init()) {
+    LOGE("Failed to initialize the CHRE daemon..");
+  } else {
+    daemon.run();
+  }
+
+  return 0;
+}
diff --git a/host/usf_daemon/usf_daemon.cc b/host/usf_daemon/usf_daemon.cc
new file mode 100644
index 0000000..e5b21c6
--- /dev/null
+++ b/host/usf_daemon/usf_daemon.cc
@@ -0,0 +1,377 @@
+/*
+ * Copyright (C) 2020 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 "usf_daemon.h"
+
+#include <unistd.h>
+#include <chrono>
+#include <fstream>
+#include <thread>
+
+#include "usf/usf_android_time.h"
+#include "usf/usf_client.h"
+
+// Aliased for consistency with the way these symbols are referenced in
+// CHRE-side code
+namespace fbs = ::chre::fbs;
+
+namespace android {
+namespace chre {
+
+namespace {
+
+using ReffedTransportClientPtr = refcount::reffed_ptr<usf::UsfTransportClient>;
+
+constexpr uint8_t kConnectRetries = 5;
+constexpr int kRetryDelayMilliSeconds = 100;
+
+bool connectToUsfWithRetry(ReffedTransportClientPtr &client) {
+  // client has already been nullptr-checked
+  uint8_t retryCount = 0;
+  bool success = false;
+  usf::UsfErr err;
+
+  while (retryCount < kConnectRetries) {
+    if ((err = client->Connect()) == usf::kErrNone) {
+      success = true;
+      break;
+    }
+    ++retryCount;
+    std::this_thread::sleep_for(
+        std::chrono::milliseconds(kRetryDelayMilliSeconds));
+  }
+
+  if (success) {
+    LOGD("Connected to USF Transport server (retry count: %u)", retryCount + 1);
+  }
+
+  return success;
+}
+
+}  // namespace
+
+bool UsfChreDaemon::init() {
+  constexpr size_t kMaxTimeSyncRetries = 5;
+  constexpr useconds_t kTimeSyncRetryDelayUs = 50000;  // 50 ms
+  bool success = false;
+  usf::UsfErr err;
+
+  if ((err = usf::UsfClientMgr::Init()) != usf::kErrNone) {
+    LOGE("Failed to initialize usf client mgr: (err %d)",
+         static_cast<int>(err));
+  } else if ((err = usf::UsfTransportClient::Create(
+                  &mConnection.transportClient)) != usf::kErrNone) {
+    LOGE("Failed to create a USF transport client: (err %d)",
+         static_cast<int>(err));
+  } else if (mConnection.transportClient == nullptr) {
+    LOGE("Init Failed: Created a NULL transport client!");
+  } else if (!connectToUsfWithRetry(mConnection.transportClient)) {
+    LOGE("Failed to  connect to USF transport server on %u retries",
+         kConnectRetries);
+  } else if ((err = usf::UsfTransportMgr::SetMsgHandler(
+                  usf::UsfMsgType_CHRE, UsfChreDaemon::usfMessageHandler,
+                  this)) != usf::kErrNone) {
+    LOGE("Failed to set Usf Message Handler");
+  } else if (!sendTimeSyncWithRetry(kMaxTimeSyncRetries, kTimeSyncRetryDelayUs,
+                                    true /* logOnError */)) {
+    LOGE("Failed to send initial time sync message");
+  } else {
+    mLogger = getLogMessageParser();
+    loadPreloadedNanoapps();
+    success = true;
+    LOGD("CHRE daemon initialized successfully");
+  }
+
+  return success;
+}
+
+void UsfChreDaemon::deinit() {
+  setShutdownRequested(true);
+  usf::UsfClientMgr::Deinit();
+}
+
+void UsfChreDaemon::run() {
+  constexpr char kChreSocketName[] = "chre";
+  auto serverCb = [&](uint16_t clientId, void *data, size_t len) {
+    sendMessageToChre(clientId, data, len);
+  };
+
+  mSocketServer.run(kChreSocketName, true /* allowSocketCreation */, serverCb);
+}
+
+usf::UsfErr UsfChreDaemon::sendUsfMessage(const uint8_t *data, size_t dataLen) {
+  usf::UsfTxMsg msg;
+
+  msg.SetMsgType(usf::UsfMsgType_CHRE);
+  msg.SetData(data, dataLen);
+
+  return mConnection.transportClient->SendMsg(&msg);
+}
+
+bool UsfChreDaemon::sendMessageToChre(uint16_t clientId, void *data,
+                                      size_t length) {
+  bool success = false;
+  usf::UsfErr err;
+
+  if (!HostProtocolHost::mutateHostClientId(data, length, clientId)) {
+    LOGE("Couldn't set host client ID in message container!");
+  } else {
+    LOGV("Delivering message from host (size %zu)", length);
+    if ((err = sendUsfMessage(static_cast<const uint8_t *>(data), length)) !=
+        usf::kErrNone) {
+      LOGE("Failed to deliver message from host to CHRE: %d",
+           static_cast<int>(err));
+    } else {
+      success = true;
+    }
+  }
+
+  return success;
+}
+
+// TODO(karthikmb): Consider moving platform independent parts of this
+// function to the base class, revisit when implementing the daemon for
+// another platform.
+void UsfChreDaemon::onMessageReceived(const unsigned char *messageBuffer,
+                                      size_t messageLen) {
+  mLogger.dump(messageBuffer, messageLen);
+
+  uint16_t hostClientId;
+  fbs::ChreMessage messageType;
+  if (!HostProtocolHost::extractHostClientIdAndType(
+          messageBuffer, messageLen, &hostClientId, &messageType)) {
+    LOGW(
+        "Failed to extract host client ID from message - sending "
+        "broadcast");
+    hostClientId = ::chre::kHostClientIdUnspecified;
+  }
+
+  if (messageType == fbs::ChreMessage::LogMessage) {
+    std::unique_ptr<fbs::MessageContainerT> container =
+        fbs::UnPackMessageContainer(messageBuffer);
+    const auto *logMessage = container->message.AsLogMessage();
+    const std::vector<int8_t> &logData = logMessage->buffer;
+
+    mLogger.log(reinterpret_cast<const uint8_t *>(logData.data()),
+                logData.size());
+  } else if (messageType == fbs::ChreMessage::TimeSyncRequest) {
+    sendTimeSync(true /* logOnError */);
+  } else if (messageType == fbs::ChreMessage::LowPowerMicAccessRequest) {
+    LOGD("LpmaRequest unsupported");
+  } else if (messageType == fbs::ChreMessage::LowPowerMicAccessRelease) {
+    LOGD("LpmaRelease unsupported");
+  } else if (hostClientId == kHostClientIdDaemon) {
+    handleDaemonMessage(messageBuffer);
+  } else if (hostClientId == ::chre::kHostClientIdUnspecified) {
+    mSocketServer.sendToAllClients(messageBuffer,
+                                   static_cast<size_t>(messageLen));
+  } else {
+    mSocketServer.sendToClientById(
+        messageBuffer, static_cast<size_t>(messageLen), hostClientId);
+  }
+}
+
+void UsfChreDaemon::handleDaemonMessage(const uint8_t *message) {
+  std::unique_ptr<fbs::MessageContainerT> container =
+      fbs::UnPackMessageContainer(message);
+  if (container->message.type != fbs::ChreMessage::LoadNanoappResponse) {
+    LOGE("Invalid message from CHRE directed to daemon");
+  } else {
+    const auto *response = container->message.AsLoadNanoappResponse();
+    std::unique_lock<std::mutex> lock(mPreloadedNanoappsMutex);
+
+    if (!mPreloadedNanoappPending) {
+      LOGE("Received nanoapp load response with no pending load");
+    } else if (mPreloadedNanoappPendingTransactionId !=
+               response->transaction_id) {
+      LOGE("Received nanoapp load response with invalid transaction id");
+    } else {
+      mPreloadedNanoappPending = false;
+    }
+
+    mPreloadedNanoappsCond.notify_all();
+  }
+}
+
+void UsfChreDaemon::loadPreloadedNanoapp(const std::string &directory,
+                                         const std::string &name,
+                                         uint32_t transactionId) {
+  std::vector<uint8_t> headerBuffer;
+  std::vector<uint8_t> nanoappBuffer;
+
+  std::string headerFilename = directory + "/" + name + ".napp_header";
+  std::string nanoappFilename = directory + "/" + name + ".so";
+
+  if (readFileContents(headerFilename, headerBuffer) &&
+      readFileContents(nanoappFilename, nanoappBuffer) &&
+      !loadNanoapp(headerBuffer, nanoappBuffer, transactionId)) {
+    LOGE("Failed to load nanoapp: '%s'", name.c_str());
+  }
+}
+
+bool UsfChreDaemon::readFileContents(const std::string &filename,
+                                     std::vector<uint8_t> &buffer) {
+  bool success = false;
+  std::ifstream file(filename.c_str(), std::ios::binary | std::ios::ate);
+  if (!file) {
+    LOGE("Couldn't open file '%s': %d (%s)", filename.c_str(), errno,
+         strerror(errno));
+  } else {
+    ssize_t size = file.tellg();
+    file.seekg(0, std::ios::beg);
+
+    buffer.resize(size);
+    if (!file.read(reinterpret_cast<char *>(buffer.data()), size)) {
+      LOGE("Couldn't read from file '%s': %d (%s)", filename.c_str(), errno,
+           strerror(errno));
+    } else {
+      success = true;
+    }
+  }
+
+  return success;
+}
+
+bool UsfChreDaemon::loadNanoapp(const std::vector<uint8_t> &header,
+                                const std::vector<uint8_t> &nanoapp,
+                                uint32_t transactionId) {
+  // This struct comes from build/build_template.mk and must not be modified.
+  // Refer to that file for more details.
+  struct NanoAppBinaryHeader {
+    uint32_t headerVersion;
+    uint32_t magic;
+    uint64_t appId;
+    uint32_t appVersion;
+    uint32_t flags;
+    uint64_t hwHubType;
+    uint8_t targetChreApiMajorVersion;
+    uint8_t targetChreApiMinorVersion;
+    uint8_t reserved[6];
+  } __attribute__((packed));
+
+  bool success = false;
+  if (header.size() != sizeof(NanoAppBinaryHeader)) {
+    LOGE("Header size mismatch");
+  } else {
+    // The header blob contains the struct above.
+    const auto *appHeader =
+        reinterpret_cast<const NanoAppBinaryHeader *>(header.data());
+
+    // Build the target API version from major and minor.
+    uint32_t targetApiVersion = (appHeader->targetChreApiMajorVersion << 24) |
+                                (appHeader->targetChreApiMinorVersion << 16);
+
+    success = sendFragmentedNanoappLoad(
+        appHeader->appId, appHeader->appVersion, appHeader->flags,
+        targetApiVersion, nanoapp.data(), nanoapp.size(), transactionId);
+  }
+
+  return success;
+}
+
+bool UsfChreDaemon::sendFragmentAndWaitOnResponse(
+    uint32_t transactionId, flatbuffers::FlatBufferBuilder &builder) {
+  bool success = true;
+  std::unique_lock<std::mutex> lock(mPreloadedNanoappsMutex);
+
+  mPreloadedNanoappPendingTransactionId = transactionId;
+  mPreloadedNanoappPending = sendMessageToChre(
+      kHostClientIdDaemon, builder.GetBufferPointer(), builder.GetSize());
+  if (!mPreloadedNanoappPending) {
+    LOGE("Failed to send nanoapp fragment");
+    success = false;
+  } else {
+    // 60s timeout for fragments. Set high for busy first bootups where the
+    // PALs can block CHRE initialization while other subsystems come up.
+    std::chrono::seconds timeout(60);
+    bool signaled = mPreloadedNanoappsCond.wait_for(
+        lock, timeout, [this] { return !mPreloadedNanoappPending; });
+
+    if (!signaled) {
+      LOGE("Nanoapp fragment load timed out");
+      success = false;
+    }
+  }
+  return success;
+}
+
+bool UsfChreDaemon::sendFragmentedNanoappLoad(
+    uint64_t appId, uint32_t appVersion, uint32_t appFlags,
+    uint32_t appTargetApiVersion, const uint8_t *appBinary, size_t appSize,
+    uint32_t transactionId) {
+  // TODO: This is currently limited by the USF Message size, revisit
+  // and increase this when the USF message size increases.
+  constexpr size_t kFragmentSize = 512;
+  std::vector<uint8_t> binary(appSize);
+  std::copy(appBinary, appBinary + appSize, binary.begin());
+
+  FragmentedLoadTransaction transaction(transactionId, appId, appVersion,
+                                        appFlags, appTargetApiVersion, binary,
+                                        kFragmentSize);
+
+  bool success = true;
+
+  while (success && !transaction.isComplete()) {
+    // Pad the builder to avoid allocation churn.
+    const auto &fragment = transaction.getNextRequest();
+    flatbuffers::FlatBufferBuilder builder(fragment.binary.size() + 128);
+    HostProtocolHost::encodeFragmentedLoadNanoappRequest(builder, fragment);
+    success = sendFragmentAndWaitOnResponse(transactionId, builder);
+  }
+
+  return success;
+}
+
+int64_t UsfChreDaemon::getTimeOffset(bool *success) {
+  int64_t offset = 0;
+  uint64_t androidTimeNs;
+  uint64_t sensorCoreTimeNs;
+  usf::UsfErr err =
+      usf::UsfGetAndroidAndSensorCoreTime(&androidTimeNs, &sensorCoreTimeNs);
+  if (err != usf::kErrNone) {
+    LOGE("Get Android and sensor core time failed.");
+  } else {
+    offset = static_cast<int64_t>(androidTimeNs) -
+             static_cast<int64_t>(sensorCoreTimeNs);
+  }
+  *success = (err == usf::kErrNone);
+  return offset;
+}
+
+ChreLogMessageParserBase UsfChreDaemon::getLogMessageParser() {
+#ifdef CHRE_USE_TOKENIZED_LOGGING
+  return ChreTokenizedLogMessageParser();
+#else
+  return ChreLogMessageParserBase();
+#endif
+}
+
+usf::UsfErr UsfChreDaemon::usfMessageHandler(
+    usf::UsfTransport * /* transport */, const usf::UsfMsg *usfMsg, void *arg) {
+  usf::UsfErr err = usf::kErrInvalid;
+  auto *daemon = static_cast<UsfChreDaemon *>(arg);
+  if ((daemon != nullptr) && (usfMsg != nullptr)) {
+    const uint8_t *msgData = usfMsg->data()->data();
+    size_t msgLen = usfMsg->data()->size();
+    daemon->onMessageReceived(msgData, msgLen);
+  } else {
+    LOGE("Error Handling Usf Message");
+  }
+  return err;
+}
+
+}  // namespace chre
+}  // namespace android
diff --git a/host/usf_daemon/usf_daemon.h b/host/usf_daemon/usf_daemon.h
new file mode 100644
index 0000000..194d719
--- /dev/null
+++ b/host/usf_daemon/usf_daemon.h
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2020 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 CHRE_USF_DAEMON_H_
+#define CHRE_USF_DAEMON_H_
+
+#include "chre_host/daemon_base.h"
+#include "chre_host/socket_server.h"
+#ifdef CHRE_USE_TOKENIZED_LOGGING
+#include "chre_host/tokenized_log_message_parser.h"
+#else
+#include "chre_host/log_message_parser_base.h"
+#endif
+
+#include "usf/usf_transport_client.h"
+
+#include <utils/SystemClock.h>
+
+// Disable verbose logging
+// TODO: use property_get_bool to make verbose logging runtime configurable
+// #define LOG_NDEBUG 0
+
+namespace android {
+namespace chre {
+
+class UsfChreDaemon : public ChreDaemonBase {
+ public:
+  struct UsfServerConnection {
+    refcount::reffed_ptr<usf::UsfTransportClient> transportClient;
+    usf::UsfTransport *transport;
+    usf::UsfServerHandle serverHandle;
+  };
+
+  ~UsfChreDaemon() {
+    deinit();
+  }
+
+  /**
+   * Initializes the CHRE daemon, and creates a USF transport for
+   * communicating with CHRE
+   *
+   * @return true on successful init
+   */
+  bool init();
+
+  /**
+   * Starts a socket server receive loop for inbound messages
+   */
+  void run();
+
+  bool sendMessageToChre(uint16_t clientId, void *data, size_t length);
+
+  void onMessageReceived(const unsigned char *messageBuffer, size_t messageLen);
+
+  UsfServerConnection &getMutableServerConnection() {
+    return mConnection;
+  }
+
+ protected:
+  void loadPreloadedNanoapp(const std::string &directory,
+                            const std::string &name,
+                            uint32_t transactionId) override;
+
+  void handleDaemonMessage(const uint8_t *message) override;
+
+ private:
+  SocketServer mSocketServer;
+  ChreLogMessageParserBase mLogger;
+  UsfServerConnection mConnection;
+
+  //! The mutex used to guard state between the nanoapp messaging thread
+  //! and loading preloaded nanoapps.
+  std::mutex mPreloadedNanoappsMutex;
+
+  //! The condition variable used to wait for a nanoapp to finish loading.
+  std::condition_variable mPreloadedNanoappsCond;
+
+  //! Set to true when a preloaded nanoapp is pending load.
+  bool mPreloadedNanoappPending;
+
+  //! Set to the expected transaction ID for loading a nanoapp.
+  uint32_t mPreloadedNanoappPendingTransactionId;
+
+  /**
+   * Perform a graceful shutdown of the daemon
+   */
+  void deinit();
+
+  /**
+   * Platform specific getTimeOffset for the WHI Daemon
+   *
+   * @return clock drift offset in nanoseconds
+   */
+  int64_t getTimeOffset(bool *success);
+
+  /**
+   * Get the Log Message Parser configured for this platform
+   *
+   * @return An instance of a log message parser
+   */
+  ChreLogMessageParserBase getLogMessageParser();
+
+  /**
+   * Loads the supplied file into the provided buffer.
+   *
+   * @param filename The name of the file to load.
+   * @param buffer The buffer to load into.
+   * @return true if successful, false otherwise.
+   */
+  bool readFileContents(const std::string &filename,
+                        std::vector<uint8_t> &buffer);
+
+  /**
+   * Sends a preloaded nanoapp to CHRE.
+   *
+   * @param header The nanoapp header binary blob.
+   * @param nanoapp The nanoapp binary blob.
+   * @param transactionId The transaction ID to use when loading the app.
+   * @return true if succssful, false otherwise.
+   */
+  bool loadNanoapp(const std::vector<uint8_t> &header,
+                   const std::vector<uint8_t> &nanoapp, uint32_t transactionId);
+
+  /**
+   * Loads a nanoapp using fragments.
+   *
+   * @param appId The ID of the nanoapp to load.
+   * @param appVersion The version of the nanoapp to load.
+   * @param appFlags The flags specified by the nanoapp to be loaded.
+   * @param appTargetApiVersion The version of the CHRE API that the app
+   * targets.
+   * @param appBinary The application binary code.
+   * @param appSize The size of the appBinary.
+   * @param transactionId The transaction ID to use when loading.
+   * @return true if successful, false otherwise.
+   */
+  bool sendFragmentedNanoappLoad(uint64_t appId, uint32_t appVersion,
+                                 uint32_t appFlags,
+                                 uint32_t appTargetApiVersion,
+                                 const uint8_t *appBinary, size_t appSize,
+                                 uint32_t transactionId);
+
+  static usf::UsfErr usfMessageHandler(usf::UsfTransport *transport,
+                                       const usf::UsfMsg *usfMsg, void *arg);
+
+  usf::UsfErr sendUsfMessage(const uint8_t *data, size_t dataLen);
+
+  bool sendFragmentAndWaitOnResponse(uint32_t transactionId,
+                                     flatbuffers::FlatBufferBuilder &builder);
+};
+
+}  // namespace chre
+}  // namespace android
+
+#endif  // CHRE_USF_DAEMON_H_
diff --git a/platform/aoc/audio_controller.cc b/platform/aoc/audio_controller.cc
new file mode 100644
index 0000000..2986ad3
--- /dev/null
+++ b/platform/aoc/audio_controller.cc
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2020 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 "chre/platform/aoc/audio_controller.h"
+#include "chre/core/event_loop_manager.h"
+#include "chre/platform/assert.h"
+
+#include "aoc/ff1/audio/include/controller_audio_input.h"  // For command IDs
+#include "aoc/include/processor_aoc.h"
+#include "efw/include/environment_ipc.h"
+#include "efw/include/ring_buffer_ipc.h"
+
+namespace chre {
+
+namespace {
+
+// Helper function to get the IPC Array parameter for the conroller
+// constructor. The function exists because of a current limitation in
+// the EFW Controller class constructor which requires an IPC array
+// variable to be passed in.
+inline IPCommunication **GetIpcArray() {
+  static IPCommunication *arr =
+      EnvironmentIpc::Instance()->IpcByID(IPC_CHANNEL_CMD_CHRE_AUDIO_NOTIF_ID);
+  return &arr;
+}
+
+}  // namespace
+
+AudioController::AudioController()
+    : Controller(
+          "CHRE_CTR" /* name */, EFWObject::Root(),
+          tskIDLE_PRIORITY + 5 /* priority */, 1024 /* stack size in words */,
+          GetIpcArray() /* ipc array */, 1 /*ipc descriptor array size */,
+          5 /* max outstanding ipc notifications */,
+          10 /* max total notifications */, 1 /* num IPC ring buffers */, 10),
+      mControllerIpcAoc("CHRE_CTR_IPC" /* name */, EFWObject::Root(),
+                        tskIDLE_PRIORITY + 5 /* priority */,
+                        ProcessorAoC::FF1 /* remote core */,
+                        EnvironmentIpc::Instance()->IpcByID(
+                            IPC_CHANNEL_CMD_CHRE_AUDIO_INPUT_ID),
+                        1 /* notifDepth */, 1 /* NumIpcRingBuffers */),
+      mPipeIpcReceiverAoc(
+          "CHRE_PIPE" /* name */,
+          EnvironmentIpc::Instance()->IpcByID(IPC_CHANNEL_DTA_CHRE_AUDIO_ID),
+          ProcessorAoC::A32 /* Primary core */, EFWObject::Root()) {
+  LOGV("MainController created");
+
+  TaskSpawn();
+}
+
+void AudioController::SetUp() {
+  Controller::SetUp();
+  mControllerIpcAoc.Start();
+}
+
+void AudioController::SetUpRemoteCore() {
+  if (!mDspCoreInitDone) {
+    int rc = -1;
+    struct CMD_HDR cmd;
+    // See controller_audio_input.h in EFW for command ID definitions.
+    uint16_t cmdId = ControllerAudioInput::CMD_AUDIO_INPUT_CHRE_SETUP_ID;
+    if ((rc = mControllerIpcAoc.CmdRelay(&cmd, cmdId, sizeof(CMD_HDR),
+                                         portMAX_DELAY)) != 0) {
+      LOGE("Failed to send setup cmd to DSP rc: %d", rc);
+    }
+  }
+}
+
+void AudioController::TearDown() {
+  Controller::TearDown();
+
+  mFilter.Stop();
+  mFilter.RingUnbind(AudioFilter::kRingIndex);
+}
+
+bool AudioController::CmdProcessor(struct CMD_HDR *cmd) {
+  switch (cmd->id) {
+    case ControllerAudioInput::CMD_AUDIO_INPUT_CHRE_F1_CTR_READY_ID:
+      cmd->reply = OnDspReady();
+      break;
+
+    default:
+      LOGD("Unknown cmd for CHRE controller");
+      cmd->reply = -1;
+      break;
+  }
+
+  return true;
+}
+
+int AudioController::OnDspReady() {
+  int rc = -1;
+
+  mDspCoreInitDone = true;
+
+  if ((rc = mFilter.InputBind(AudioFilter::kPipeIndex, &mPipeIpcReceiverAoc)) !=
+      0) {
+    LOGE("Failed to bind pipe to filter input");
+  } else if ((rc = mFilter.RingBind(
+                  AudioFilter::kRingIndex,
+                  ring_buffer_ipc_[AudioFilter::kRingIndex])) != 0) {
+    LOGE("Failed to bind ipc ring buffer to filter");
+  } else if ((rc = mFilter.Start()) != 0) {
+    LOGE("Failed to start audio filter");
+  } else {
+    EventLoopManagerSingleton::get()
+        ->getAudioRequestManager()
+        .handleAudioAvailability(0 /*handle*/, true /*available*/);
+  }
+
+  return rc;
+}
+
+const chreAudioSource *AudioController::GetSource(uint32_t /*handle*/) const {
+  return &kAudioSource;
+}
+
+void AudioController::SetEnabled(uint32_t /*handle*/, bool enabled) {
+  mSourceStatus.enabled = enabled;
+}
+
+bool AudioController::UpdateStream(uint32_t /*handle*/, bool start) {
+  bool success = false;
+  // TODO: handle and other params
+  if (mDspCoreInitDone && mSourceStatus.enabled) {
+    if (mRequestStarted != start) {
+      mRequestStarted = start;
+      CMD_HDR cmd;
+      cmd.reply = -1;  // negative value to test
+      // see controller_audio_input.h in EFW for command ID definitions.
+      uint16_t cmdId =
+          start ? ControllerAudioInput::CMD_AUDIO_INPUT_MIC_CHRE_START_ID
+                : ControllerAudioInput::CMD_AUDIO_INPUT_MIC_CHRE_STOP_ID;
+
+      int rc = mControllerIpcAoc.CmdRelay(&cmd, cmdId, sizeof(CMD_HDR),
+                                          portMAX_DELAY);
+      LOGV("request audio send: %d", rc);
+      success = (rc == 0);
+    } else {
+      // streaming already in the correct state for this handle
+      success = true;
+    }
+  }
+
+  return success;
+}
+
+bool AudioController::RequestAudio(uint32_t handle) {
+  return UpdateStream(handle, true /*start*/);
+}
+
+bool AudioController::ReleaseAudio(uint32_t handle) {
+  return UpdateStream(handle, false /*start*/);
+}
+
+void AudioController::OnBufferReleased() {
+  mFilter.Signal(AudioFilter::kBufferReleasedSignalIndex);
+}
+
+}  // namespace chre
diff --git a/platform/aoc/audio_filter.cc b/platform/aoc/audio_filter.cc
new file mode 100644
index 0000000..ac66e6a
--- /dev/null
+++ b/platform/aoc/audio_filter.cc
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2020 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 "chre/platform/aoc/audio_filter.h"
+#include "chre/core/event_loop_manager.h"
+#include "chre/platform/aoc/memory.h"
+#include "chre/platform/assert.h"
+#include "chre/platform/log.h"
+#include "chre/platform/system_time.h"
+#include "chre/util/time.h"
+#include "efw/include/ring_buffer_ipc.h"
+
+namespace chre {
+
+AudioFilter::AudioFilter()
+    : Filter("CHRE_FLT" /* name */, EFWObject::Root() /* parent */,
+             tskIDLE_PRIORITY + 5 /* priority */, 512 /* stack size words */,
+             1 /* command depth */, 1 /* Num Filter inputs */,
+             1 /* Num Filter Outputs */, 1 /* Num Ring Buffers */) {
+  LOGV("Filter Created");
+
+  TaskSpawn();
+}
+
+int AudioFilter::StartCallback() {
+  mRingBufferHandle = ring_[kRingIndex]->ReaderRegister();
+  CHRE_ASSERT(mRingBufferHandle != nullptr);
+
+  // TODO (b/168330113): The sample buffer is a fairly large chunk of
+  // contiguous memory that may be prone to allocation failures - reserve
+  // this memory statically.
+  forceDramAccess();
+  mSampleBufferDram =
+      static_cast<int16_t *>(memoryAllocDram(sizeof(mSampleBufferSram)));
+  CHRE_ASSERT(mSampleBufferDram != nullptr);
+
+  return 0;
+}
+
+bool AudioFilter::StopCallback() {
+  ring_[kRingIndex]->ReaderUnregister(mRingBufferHandle);
+  mRingBufferHandle = nullptr;
+
+  forceDramAccess();
+  memoryFreeDram(mSampleBufferDram);
+  mSampleBufferDram = nullptr;
+
+  return true;
+}
+
+bool AudioFilter::SignalProcessor(int index) {
+  switch (index) {
+    case kBufferReleasedSignalIndex:
+      OnBufferReleased();
+      break;
+    default:
+      break;
+  }
+  return true;
+}
+
+void AudioFilter::InputProcessor(int /*pin*/, void *message, size_t size) {
+  CHRE_ASSERT(message != nullptr);
+
+  // TODO resync and synchronization, check metadata format etc.
+  auto *metadata = static_cast<struct AudioInputMetadata *>(message);
+
+  if (metadata->need_resync) {
+    LOGW("Resync request received, logic not implemented yet");
+  }
+
+  constexpr size_t kBytesPerAocSample = sizeof(uint32_t);
+  constexpr size_t kNumSamplesAoc = 160;
+  constexpr size_t kRingSize = kNumSamplesAoc * kBytesPerAocSample *
+                               1;  // 1 channel of u32 samples per 10 ms
+  uint32_t buffer[kNumSamplesAoc];
+  uint32_t nBytes =
+      ring_[kRingIndex]->Read(mRingBufferHandle, &buffer, kRingSize);
+  CHRE_ASSERT_LOG(nBytes != 0, "Got data pipe notif, but no data in ring");
+  size_t nSamples = nBytes / kBytesPerAocSample;
+
+  if (mSampleCount == 0) {
+    // TODO: Get a better estimate of the timestamp of the first sample in
+    // the frame. Since the pipe notification arrives every 10ms, we could
+    // subtract 10ms from now(), and maybe even keep track of the embedded
+    // timestamp of the first sample of the current frame, and the last
+    // sample of the previous frame.
+    mDataEvent.timestamp = SystemTime::getMonotonicTime().toRawNanoseconds();
+  }
+
+  // TODO: For the initial implementation/testing, we assume that the
+  // frame might not fit to a T in our current buffer, but that is the
+  // expectation. With the current logic, we could end up dropping a frame,
+  // revisit this to make sure we capture the exact number of samples,
+  // while continuing to buffer samples either via a ping-pong scheme, or
+  // a small extra scratch space.
+  if ((nSamples + mSampleCount) > kSamplesPer2Sec) {
+    if (!mDramBufferInUse) {
+      forceDramAccess();
+      memcpy(mSampleBufferDram, mSampleBufferSram, sizeof(mSampleBufferSram));
+      mDataEvent.sampleCount = mSampleCount;
+      mDataEvent.samplesS16 = mSampleBufferDram;
+      mDramBufferInUse = true;
+
+      EventLoopManagerSingleton::get()
+          ->getAudioRequestManager()
+          .handleAudioDataEvent(&mDataEvent);
+    } else {
+      LOGW("SRAM audio buffer full while DRAM audio buffer not released!");
+    }
+    mSampleCount = 0;
+  }
+  for (size_t i = 0; i < nSamples; ++i, ++mSampleCount) {
+    // The zeroth byte of the received sample is a timestamp, which we
+    // strip out.
+    // TODO: Verify that there's no scaling on the remaining data, in
+    // which case casting without descaling won't be the right thing to
+    // do.
+    mSampleBufferSram[mSampleCount] =
+        static_cast<int16_t>((buffer[i] >> 8) & 0xffff);
+  }
+}
+
+void AudioFilter::OnBufferReleased() {
+  mDramBufferInUse = false;
+}
+
+}  // namespace chre
diff --git a/platform/aoc/chre_api_re.cc b/platform/aoc/chre_api_re.cc
new file mode 100644
index 0000000..00fa668
--- /dev/null
+++ b/platform/aoc/chre_api_re.cc
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2020 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 <cinttypes>
+
+#include "chre/platform/log.h"
+#include "chre/util/macros.h"
+#include "chre_api/chre/re.h"
+
+void chreLog(enum chreLogLevel level, const char *formatStr, ...) {
+  va_list args;
+  va_start(args, formatStr);
+
+  // TODO(karthikmb): Remove this once log buffering is implemented.
+  char logBuf[500];
+  va_list vsnargs;
+  va_copy(vsnargs, args);
+  vsnprintf(logBuf, sizeof(logBuf), formatStr, args);
+  va_end(vsnargs);
+  printf("CHRE: %s\n", logBuf);
+
+  chre::vaLog(level, formatStr, args);
+  va_end(args);
+}
diff --git a/platform/aoc/dram_vote_client.cc b/platform/aoc/dram_vote_client.cc
new file mode 100644
index 0000000..0977560
--- /dev/null
+++ b/platform/aoc/dram_vote_client.cc
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2020 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 "chre/platform/shared/dram_vote_client.h"
+
+#include "chre/platform/assert.h"
+
+#include "sysmem.h"
+
+namespace chre {
+
+void DramVoteClient::issueDramVote(bool enabled) {
+  if (mLastDramVote != enabled) {
+    int rc;
+    if (enabled) {
+      rc = SysMem::Instance()->MemoryRequest(SysMem::MIF, true);
+    } else {
+      rc = SysMem::Instance()->MemoryRelease(SysMem::MIF);
+    }
+
+    CHRE_ASSERT_LOG(rc == 0,
+                    "Unable to change DRAM access to %d with error code %d",
+                    enabled, rc);
+  }
+}
+
+}  // namespace chre
diff --git a/platform/aoc/fatal_error.cc b/platform/aoc/fatal_error.cc
new file mode 100644
index 0000000..ae3903d
--- /dev/null
+++ b/platform/aoc/fatal_error.cc
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2020 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 "chre/target_platform/fatal_error.h"
+
+#include "chre/platform/shared/platform_log.h"
+
+namespace chre {
+
+void preFatalError() {
+  // TODO: stubbed out, Implement this.
+}
+
+}  // namespace chre
diff --git a/platform/aoc/host_link.cc b/platform/aoc/host_link.cc
new file mode 100644
index 0000000..2c0d638
--- /dev/null
+++ b/platform/aoc/host_link.cc
@@ -0,0 +1,541 @@
+/*
+ * Copyright (C) 2020 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 "chre/platform/host_link.h"
+
+#include "chre/core/event_loop_manager.h"
+#include "chre/core/host_comms_manager.h"
+#include "chre/core/settings.h"
+#include "chre/platform/aoc/system_time.h"
+#include "chre/platform/shared/host_protocol_chre.h"
+#include "chre/platform/shared/nanoapp_load_manager.h"
+#include "chre/util/fixed_size_blocking_queue.h"
+#include "chre/util/flatbuffers/helpers.h"
+#include "chre_api/chre/version.h"
+
+namespace chre {
+
+Nanoseconds HostLinkBase::mLastTimeSyncRequestNanos(0);
+
+namespace {
+
+//! Used to pass the client ID through the user data pointer in deferCallback
+union HostClientIdCallbackData {
+  uint16_t hostClientId;
+  void *ptr;
+};
+static_assert(sizeof(uint16_t) <= sizeof(void *),
+              "Pointer must at least fit a u16 for passing the host client ID");
+
+struct LoadNanoappCallbackData {
+  uint64_t appId;
+  uint32_t transactionId;
+  uint16_t hostClientId;
+  UniquePtr<Nanoapp> nanoapp;
+  uint32_t fragmentId;
+};
+
+struct UnloadNanoappCallbackData {
+  uint64_t appId;
+  uint32_t transactionId;
+  uint16_t hostClientId;
+  bool allowSystemNanoappUnload;
+};
+
+inline HostCommsManager &getHostCommsManager() {
+  return EventLoopManagerSingleton::get()->getHostCommsManager();
+}
+
+void setTimeSyncRequestTimer(Nanoseconds delay) {
+  static TimerHandle sHandle;
+  static bool sHandleInitialized;
+
+  if (sHandleInitialized) {
+    EventLoopManagerSingleton::get()->cancelDelayedCallback(sHandle);
+  }
+
+  auto callback = [](uint16_t /* eventType */, void * /* data */) {
+    HostLinkBase::sendTimeSyncRequest();
+  };
+  sHandle = EventLoopManagerSingleton::get()->setDelayedCallback(
+      SystemCallbackType::TimerSyncRequest, nullptr /* data */, callback,
+      delay);
+  sHandleInitialized = true;
+}
+
+bool getSettingFromFbs(fbs::Setting setting, Setting *chreSetting) {
+  bool success = true;
+  switch (setting) {
+    case fbs::Setting::LOCATION:
+      *chreSetting = Setting::LOCATION;
+      break;
+    default:
+      LOGE("Unknown setting %" PRIu8, setting);
+      success = false;
+  }
+
+  return success;
+}
+
+bool getSettingStateFromFbs(fbs::SettingState state,
+                            SettingState *chreSettingState) {
+  bool success = true;
+  switch (state) {
+    case fbs::SettingState::DISABLED:
+      *chreSettingState = SettingState::DISABLED;
+      break;
+    case fbs::SettingState::ENABLED:
+      *chreSettingState = SettingState::ENABLED;
+      break;
+    default:
+      LOGE("Unknown state %" PRIu8, state);
+      success = false;
+  }
+
+  return success;
+}
+
+void sendDebugDumpData(uint16_t hostClientId, const char *debugStr,
+                       size_t debugStrSize) {
+  constexpr size_t kFixedSizePortion = 52;
+  ChreFlatBufferBuilder builder(kFixedSizePortion + debugStrSize);
+  HostProtocolChre::encodeDebugDumpData(builder, hostClientId, debugStr,
+                                        debugStrSize);
+
+  getHostCommsManager().send(builder.GetBufferPointer(), builder.GetSize());
+}
+
+void sendDebugDumpResponse(uint16_t hostClientId, bool success,
+                           uint32_t dataCount) {
+  constexpr size_t kFixedSizePortion = 52;
+  ChreFlatBufferBuilder builder(kFixedSizePortion);
+  HostProtocolChre::encodeDebugDumpResponse(builder, hostClientId, success,
+                                            dataCount);
+  getHostCommsManager().send(builder.GetBufferPointer(), builder.GetSize());
+}
+
+void constructNanoappListCallback(uint16_t /*eventType*/, void *deferCbData) {
+  HostClientIdCallbackData clientIdCbData;
+  clientIdCbData.ptr = deferCbData;
+
+  struct NanoappListData {
+    ChreFlatBufferBuilder *builder;
+    DynamicVector<NanoappListEntryOffset> nanoappEntries;
+  };
+  NanoappListData cbData;
+
+  EventLoop &eventLoop = EventLoopManagerSingleton::get()->getEventLoop();
+  size_t expectedNanoappCount = eventLoop.getNanoappCount();
+  if (!cbData.nanoappEntries.reserve(expectedNanoappCount)) {
+    LOG_OOM();
+  } else {
+    constexpr size_t kFixedOverhead = 48;
+    constexpr size_t kPerNanoappSize = 32;
+    size_t initialBufferSize =
+        (kFixedOverhead + expectedNanoappCount * kPerNanoappSize);
+
+    ChreFlatBufferBuilder builder(initialBufferSize);
+    cbData.builder = &builder;
+
+    auto nanoappAdderCallback = [](const Nanoapp *nanoapp, void *data) {
+      auto *listData = static_cast<NanoappListData *>(data);
+      HostProtocolChre::addNanoappListEntry(
+          *(listData->builder), listData->nanoappEntries, nanoapp->getAppId(),
+          nanoapp->getAppVersion(), true /*enabled*/,
+          nanoapp->isSystemNanoapp());
+    };
+    eventLoop.forEachNanoapp(nanoappAdderCallback, &cbData);
+    HostProtocolChre::finishNanoappListResponse(builder, cbData.nanoappEntries,
+                                                clientIdCbData.hostClientId);
+    getHostCommsManager().send(builder.GetBufferPointer(), builder.GetSize());
+  }
+}
+
+void sendFragmentResponse(uint16_t hostClientId, uint32_t transactionId,
+                          uint32_t fragmentId, bool success) {
+  constexpr size_t kInitialBufferSize = 52;
+  ChreFlatBufferBuilder builder(kInitialBufferSize);
+  HostProtocolChre::encodeLoadNanoappResponse(
+      builder, hostClientId, transactionId, success, fragmentId);
+
+  if (!getHostCommsManager().send(builder.GetBufferPointer(),
+                                  builder.GetSize())) {
+    LOGE(
+        "Failed to send fragment response for HostClientID: %x FragmentID: %x"
+        "transactionID: 0x%x",
+        hostClientId, fragmentId, transactionId);
+  }
+}
+
+void finishLoadingNanoappCallback(uint16_t /*eventType*/, void *data) {
+  UniquePtr<LoadNanoappCallbackData> cbData(
+      static_cast<LoadNanoappCallbackData *>(data));
+  constexpr size_t kInitialBufferSize = 48;
+  ChreFlatBufferBuilder builder(kInitialBufferSize);
+
+  CHRE_ASSERT(cbData != nullptr);
+  LOGD("Finished loading nanoapp cb on fragment ID: %u", cbData->fragmentId);
+
+  EventLoop &eventLoop = EventLoopManagerSingleton::get()->getEventLoop();
+  bool success =
+      cbData->nanoapp->isLoaded() && eventLoop.startNanoapp(cbData->nanoapp);
+
+  sendFragmentResponse(cbData->hostClientId, cbData->transactionId,
+                       cbData->fragmentId, success);
+}
+
+void handleUnloadNanoappCallback(uint16_t /*eventType*/, void *data) {
+  auto *cbData = static_cast<UnloadNanoappCallbackData *>(data);
+  bool success = false;
+  uint32_t instanceId;
+  EventLoop &eventLoop = EventLoopManagerSingleton::get()->getEventLoop();
+  if (!eventLoop.findNanoappInstanceIdByAppId(cbData->appId, &instanceId)) {
+    LOGE("Couldn't unload app ID 0x%016" PRIx64 ": not found", cbData->appId);
+  } else {
+    success =
+        eventLoop.unloadNanoapp(instanceId, cbData->allowSystemNanoappUnload);
+  }
+
+  constexpr size_t kInitialBufferSize = 52;
+  ChreFlatBufferBuilder builder(kInitialBufferSize);
+  HostProtocolChre::encodeUnloadNanoappResponse(builder, cbData->hostClientId,
+                                                cbData->transactionId, success);
+
+  if (!getHostCommsManager().send(builder.GetBufferPointer(),
+                                  builder.GetSize())) {
+    LOGE("Failed to send unload response to host: %x transactionID: 0x%x",
+         cbData->hostClientId, cbData->transactionId);
+  }
+
+  memoryFree(data);
+}
+
+}  // anonymous namespace
+
+void sendDebugDumpResultToHost(uint16_t hostClientId, const char *debugStr,
+                               size_t debugStrSize, bool complete,
+                               uint32_t dataCount) {
+  if (debugStrSize > 0) {
+    sendDebugDumpData(hostClientId, debugStr, debugStrSize);
+  }
+
+  if (complete) {
+    sendDebugDumpResponse(hostClientId, true /*success*/, dataCount);
+  }
+}
+
+void HostLink::flushMessagesSentByNanoapp(uint64_t /* appId */) {
+  // TODO: Implement this once USF supports this (b/149318001)
+}
+
+bool HostLink::sendMessage(const MessageToHost *message) {
+  constexpr size_t kFixedReserveSize = 80;
+  ChreFlatBufferBuilder builder(message->message.size() + kFixedReserveSize);
+  HostProtocolChre::encodeNanoappMessage(
+      builder, message->appId, message->toHostData.messageType,
+      message->toHostData.hostEndpoint, message->message.data(),
+      message->message.size());
+
+  bool success =
+      getHostCommsManager().send(builder.GetBufferPointer(), builder.GetSize());
+
+  getHostCommsManager().onMessageToHostComplete(message);
+
+  return success;
+}
+
+HostLinkBase::HostLinkBase() {
+  usf::UsfErr err = usf::UsfTransportMgr::SetMsgHandler(
+      usf::UsfMsgType::UsfMsgType_CHRE, handleUsfMessage, nullptr);
+  if (err == usf::kErrNone) {
+    err = usf::UsfWorkMgr::CreateWorker(&mWorker);
+  }
+
+  if (err != usf::kErrNone) {
+    FATAL_ERROR("Failed to initialize CHRE transport");
+  }
+}
+
+HostLinkBase::~HostLinkBase() {
+  mWorker->Stop();
+  mWorker.reset();
+}
+
+void HostLinkBase::init(usf::UsfTransport *transport) {
+  if (mTransportHandle == nullptr) {
+    if (transport == nullptr) {
+      FATAL_ERROR("Null transport at init, cannot send out messages!");
+    }
+    mTransportHandle = transport;
+  }
+}
+
+bool HostLinkBase::send(uint8_t *data, size_t dataLen) {
+  usf::UsfErr err = usf::kErrFailed;
+
+  if (mTransportHandle != nullptr) {
+    usf::UsfTxMsg msg;
+    msg.SetMsgType(usf::UsfMsgType_CHRE);
+    msg.SetData(data, dataLen);
+    err = mTransportHandle->SendMsg(&msg);
+  }
+
+  return (usf::kErrNone == err);
+}
+
+usf::UsfErr HostLinkBase::handleUsfMessage(usf::UsfTransport *transport,
+                                           const usf::UsfMsg *msg,
+                                           void * /* arg */) {
+  usf::UsfErr rc = usf::kErrFailed;
+  getHostCommsManager().init(transport);
+
+  if (msg == nullptr) {
+    rc = usf::kErrInvalid;
+  } else {
+    usf::UsfMsgType msgType = msg->msg_type();
+
+    if (msgType == usf::UsfMsgType_CHRE) {
+      auto *dataVector = msg->data();
+      if (dataVector != nullptr) {
+        size_t dataLen = dataVector->size();
+        struct MessageData {
+          uint64_t dataLen;
+          void *data;
+        };
+
+        // handleUsfMessage isn't single-threaded. Make a copy and process
+        // on a worker thread to ensure CHRE only processes incoming messages
+        // on a single thread. CHRE's thread isn't used since several messages
+        // can be processed without posting to it.
+        //
+        // TODO(b/163592230): Improve USF messaging interface to remove extra
+        // data copies and keep work single-threaded.
+        MessageData data;
+        data.dataLen = dataLen;
+        data.data = memoryAlloc(dataLen);
+        if (data.data == nullptr) {
+          LOG_OOM();
+          rc = usf::kErrAllocation;
+        } else {
+          memcpy(data.data, dataVector->data(), dataLen);
+
+          auto callback = [](void *data) {
+            MessageData *dataInfo = static_cast<MessageData *>(data);
+            HostProtocolChre::decodeMessageFromHost(dataInfo->data,
+                                                    dataInfo->dataLen);
+            memoryFree(dataInfo->data);
+            // USF owns the passed in data pointer and will free it after the
+            // callback returns.
+          };
+          rc = getHostCommsManager().getWorker()->Enqueue(callback, &data,
+                                                          sizeof(data));
+        }
+      }
+    } else {
+      rc = usf::kErrInvalid;
+    }
+  }
+
+  // Opportunistically send a time sync message (1 hour period threshold)
+  constexpr Seconds kOpportunisticTimeSyncPeriod = Seconds(60 * 60 * 1);
+  if (SystemTime::getMonotonicTime() >
+      mLastTimeSyncRequestNanos + kOpportunisticTimeSyncPeriod) {
+    sendTimeSyncRequest();
+  }
+
+  return rc;
+}
+
+void HostLinkBase::sendLogMessage(const uint8_t *logMessage,
+                                  size_t logMessageSize) {
+  constexpr size_t kInitialSize = 128;
+  ChreFlatBufferBuilder builder(logMessageSize + kInitialSize);
+  HostProtocolChre::encodeLogMessages(builder, logMessage, logMessageSize);
+
+  getHostCommsManager().send(builder.GetBufferPointer(), builder.GetSize());
+}
+
+void HostLinkBase::sendTimeSyncRequest() {
+  constexpr size_t kInitialSize = 52;
+  ChreFlatBufferBuilder builder(kInitialSize);
+  HostProtocolChre::encodeTimeSyncRequest(builder);
+
+  getHostCommsManager().send(builder.GetBufferPointer(), builder.GetSize());
+
+  mLastTimeSyncRequestNanos = SystemTime::getMonotonicTime();
+}
+
+void HostMessageHandlers::handleDebugDumpRequest(uint16_t hostClientId) {
+  chre::EventLoopManagerSingleton::get()
+      ->getDebugDumpManager()
+      .onDebugDumpRequested(hostClientId);
+}
+
+void HostMessageHandlers::handleHubInfoRequest(uint16_t hostClientId) {
+  constexpr size_t kInitialBufferSize = 192;
+
+  constexpr char kHubName[] = "CHRE on AoC";
+  constexpr char kVendor[] = "Google";
+  constexpr char kToolchain[] =
+      "Clang " STRINGIFY(__clang_major__) "." STRINGIFY(
+          __clang_minor__) "." STRINGIFY(__clang_patchlevel__) ")";
+  constexpr uint32_t kLegacyPlatformVersion = 0;
+  constexpr uint32_t kLegacyToolchainVersion =
+      ((__clang_major__ & 0xFF) << 24) | ((__clang_minor__ & 0xFF) << 16) |
+      (__clang_patchlevel__ & 0xFFFF);
+  constexpr float kPeakMips = 800;
+  // TODO: The following need to be updated when we've measured the
+  // indicated power levels on the AoC
+  constexpr float kStoppedPower = 0;
+  constexpr float kSleepPower = 0;
+  constexpr float kPeakPower = 0;
+
+  ChreFlatBufferBuilder builder(kInitialBufferSize);
+  HostProtocolChre::encodeHubInfoResponse(
+      builder, kHubName, kVendor, kToolchain, kLegacyPlatformVersion,
+      kLegacyToolchainVersion, kPeakMips, kStoppedPower, kSleepPower,
+      kPeakPower, CHRE_MESSAGE_TO_HOST_MAX_SIZE, chreGetPlatformId(),
+      chreGetVersion(), hostClientId);
+
+  if (!getHostCommsManager().send(builder.GetBufferPointer(),
+                                  builder.GetSize())) {
+    LOGE("Failed to send Hub Info Response message");
+  }
+}
+
+void HostMessageHandlers::handleLoadNanoappRequest(
+    uint16_t hostClientId, uint32_t transactionId, uint64_t appId,
+    uint32_t appVersion, uint32_t appFlags, uint32_t targetApiVersion,
+    const void *buffer, size_t bufferLen, const char *appFileName,
+    uint32_t fragmentId, size_t appBinaryLen) {
+  bool success = true;
+  static NanoappLoadManager sLoadManager;
+
+  if (fragmentId == 0 || fragmentId == 1) {
+    size_t totalAppBinaryLen = (fragmentId == 0) ? bufferLen : appBinaryLen;
+    LOGD("Load nanoapp request for app ID 0x%016" PRIx64 " ver 0x%" PRIx32
+         " flags 0x%" PRIx32 " target API 0x%08" PRIx32
+         " size %zu (txnId %" PRIu32 " client %" PRIu16 ")",
+         appId, appVersion, appFlags, targetApiVersion, totalAppBinaryLen,
+         transactionId, hostClientId);
+
+    if (sLoadManager.hasPendingLoadTransaction()) {
+      FragmentedLoadInfo info = sLoadManager.getTransactionInfo();
+      sendFragmentResponse(info.hostClientId, info.transactionId,
+                           0 /* fragmentId */, false /* success */);
+      sLoadManager.markFailure();
+    }
+
+    success =
+        sLoadManager.prepareForLoad(hostClientId, transactionId, appId,
+                                    appVersion, appFlags, totalAppBinaryLen);
+  }
+
+  if (success) {
+    success = sLoadManager.copyNanoappFragment(
+        hostClientId, transactionId, (fragmentId == 0) ? 1 : fragmentId, buffer,
+        bufferLen);
+  } else {
+    LOGE("Failed to prepare for load");
+  }
+
+  if (sLoadManager.isLoadComplete()) {
+    LOGD("Load manager load complete...");
+    auto cbData = MakeUnique<LoadNanoappCallbackData>();
+    if (cbData.isNull()) {
+      LOG_OOM();
+    } else {
+      cbData->transactionId = transactionId;
+      cbData->hostClientId = hostClientId;
+      cbData->appId = appId;
+      cbData->fragmentId = fragmentId;
+      cbData->nanoapp = sLoadManager.releaseNanoapp();
+
+      // Note that if this fails, we'll generate the error response in
+      // the normal deferred callback
+      EventLoopManagerSingleton::get()->deferCallback(
+          SystemCallbackType::FinishLoadingNanoapp, cbData.release(),
+          finishLoadingNanoappCallback);
+    }
+  } else {
+    // send a response for this fragment
+    sendFragmentResponse(hostClientId, transactionId, fragmentId, success);
+  }
+}
+
+void HostMessageHandlers::handleNanoappListRequest(uint16_t hostClientId) {
+  LOGD("Nanoapp list request from client ID %" PRIu16, hostClientId);
+  HostClientIdCallbackData cbData = {};
+  cbData.hostClientId = hostClientId;
+  EventLoopManagerSingleton::get()->deferCallback(
+      SystemCallbackType::NanoappListResponse, cbData.ptr,
+      constructNanoappListCallback);
+}
+
+void HostMessageHandlers::handleNanoappMessage(uint64_t appId,
+                                               uint32_t messageType,
+                                               uint16_t hostEndpoint,
+                                               const void *messageData,
+                                               size_t messageDataLen) {
+  LOGD("Parsed nanoapp message from host: app ID 0x%016" PRIx64
+       " endpoint 0x%" PRIx16 " msgType %" PRIu32 " payload size %zu",
+       appId, hostEndpoint, messageType, messageDataLen);
+
+  getHostCommsManager().sendMessageToNanoappFromHost(
+      appId, messageType, hostEndpoint, messageData, messageDataLen);
+}
+
+void HostMessageHandlers::handleSettingChangeMessage(fbs::Setting setting,
+                                                     fbs::SettingState state) {
+  Setting chreSetting;
+  SettingState chreSettingState;
+  if (getSettingFromFbs(setting, &chreSetting) &&
+      getSettingStateFromFbs(state, &chreSettingState)) {
+    postSettingChange(chreSetting, chreSettingState);
+  }
+}
+
+void HostMessageHandlers::handleTimeSyncMessage(int64_t offset) {
+  setEstimatedHostTimeOffset(offset);
+
+  // Schedule a time sync request since offset may drift
+  constexpr Seconds kClockDriftTimeSyncPeriod =
+      Seconds(60 * 60 * 6);  // 6 hours
+  setTimeSyncRequestTimer(kClockDriftTimeSyncPeriod);
+}
+
+void HostMessageHandlers::handleUnloadNanoappRequest(
+    uint16_t hostClientId, uint32_t transactionId, uint64_t appId,
+    bool allowSystemNanoappUnload) {
+  LOGD("Unload nanoapp request (txnID %" PRIu32 ") for appId 0x%016" PRIx64
+       " system %d",
+       transactionId, appId, allowSystemNanoappUnload);
+  auto *cbData = memoryAlloc<UnloadNanoappCallbackData>();
+  if (cbData == nullptr) {
+    LOG_OOM();
+  } else {
+    cbData->appId = appId;
+    cbData->transactionId = transactionId;
+    cbData->hostClientId = hostClientId;
+    cbData->allowSystemNanoappUnload = allowSystemNanoappUnload;
+
+    EventLoopManagerSingleton::get()->deferCallback(
+        SystemCallbackType::HandleUnloadNanoapp, cbData,
+        handleUnloadNanoappCallback);
+  }
+}
+
+}  // namespace chre
diff --git a/platform/aoc/include/chre/platform/aoc/audio_controller.h b/platform/aoc/include/chre/platform/aoc/audio_controller.h
new file mode 100644
index 0000000..76d19e3
--- /dev/null
+++ b/platform/aoc/include/chre/platform/aoc/audio_controller.h
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2020 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 CHRE_PLATFORM_AOC_AUDIO_CONTROLLER_H_
+#define CHRE_PLATFORM_AOC_AUDIO_CONTROLLER_H_
+
+#include "chre/platform/log.h"
+#include "chre/platform/system_time.h"
+#include "chre/target_platform/init.h"
+#include "chre/util/time.h"
+#include "chre_api/chre/audio.h"
+
+#include "aoc/common/include/controller_ipc_aoc.h"
+#include "aoc/common/include/pipe_ipc_receiver_aoc.h"
+
+#include "efw/include/controller.h"
+
+#include "chre/platform/aoc/audio_filter.h"
+
+namespace chre {
+
+/**
+ * This class implements an audio controller, which sets up a filter object
+ * for buffering audio (see audio_filter.h), and manages both local and
+ * remote core commands to route audio data from the microphone to CHRE.
+ * To ensure proper timing of operation, commands are processed in a request-
+ * response pattern. The controller handles messages inbound from the remote
+ * core directly, via a PipeIPCReceiver object that it instantiates and binds
+ * to, while delegating outbound messages to a ControllerIPC object
+ * that it also instantiates. The controller handles all CHRE platform audio
+ * calls, relaying and collecting relevant information and data from the
+ * audio filter.
+ *
+ * Limitations:
+ *  - Only a single microphone channel is currently supported
+ */
+class AudioController : public Controller {
+ public:
+  static constexpr size_t kMaxAocMicrophones = 1;
+
+  AudioController();
+
+  /**
+   * Calls the base class SetUp method, initializes audio source data,
+   * and initializes cross core communication.
+   */
+  void SetUp() override;
+
+  /**
+   * Sends a command to the remote core indicating that the A32 Audio
+   * Controller is fully up and ready to process data. This prompts
+   * the DSP to setup its own CHRE Audio Controller.
+   */
+  void SetUpRemoteCore();
+
+  /**
+   * Calls the base class TearDown method, and unbinds the shared ring
+   * buffer from the audio filter, and shuts the filter down.
+   */
+  void TearDown() override;
+
+  /**
+   * Handles commands inbound to the filter. This method is only
+   * invoked if the IPC channel for this controller and its cross
+   * core peer has been configured and setup correctly, else all
+   * messages are dropped by default by the worker thread.
+   *
+   * @return We always return true from this function to not trigger
+   * assertions in the underlying CmdThread, regardless of whether
+   * we received a valid command or not. On receiving a command not
+   * recognized by the controller, we simply set the command reply to
+   * an error value (-1).
+   */
+  bool CmdProcessor(struct CMD_HDR *cmd) override;
+
+  /**
+   * Get audio source information
+   *
+   * @return Pointer to chreAudioSource
+   */
+  const chreAudioSource *GetSource(uint32_t handle) const;
+
+  /**
+   * Enables audio requests on a handle
+   *
+   * @param handle Handle to be enabled
+   *
+   * @param enabled Handle is enabled if true, disabled otherwise
+   */
+  void SetEnabled(uint32_t handle, bool enabled);
+
+  /**
+   * Request audio data from given handle/source.
+   *
+   * @param handle Handle to an audio source to request data from.
+   *
+   * @return true on success
+   */
+  bool RequestAudio(uint32_t handle);
+
+  /**
+   * Cancel/Release audio data events from given handle.
+   *
+   * @param handle Handle to audio source to cancel data from
+   *
+   * @return true on success
+   */
+  bool ReleaseAudio(uint32_t handle);
+
+  /**
+   * Called by the CHRE release audio data event callback, this function
+   * lets the filter know that the current audio buffer was processed
+   * by all clients.
+   */
+  void OnBufferReleased();
+
+ private:
+  static constexpr uint64_t kSupportedDurationNs =
+      Seconds(AudioFilter::kSupportedDurationSeconds).toRawNanoseconds();
+  static constexpr const char *kMicrophoneName = "MIC_0";
+
+  bool mDspCoreInitDone = false;
+  bool mRequestStarted = false;
+
+  const chreAudioSource kAudioSource = {
+      .name = kMicrophoneName,
+      .sampleRate = AudioFilter::kSupportedSampleRate,
+      .minBufferDuration = kSupportedDurationNs,
+      .maxBufferDuration = kSupportedDurationNs,
+      .format = CHRE_AUDIO_DATA_FORMAT_16_BIT_SIGNED_PCM};
+
+  chreAudioSourceStatus mSourceStatus = {.enabled = false, .suspended = false};
+
+  AudioFilter mFilter;
+
+  // The peer Controller IPC to the CHRE controller on the DSP,
+  // this class handles outbound messages from the audio controller.
+  ControllerIpcAoC mControllerIpcAoc;
+
+  // The peer Pipe Receiver to the CHRE pipe instance on the DSP,
+  // this class receives IPC pipe notifications sent by the said
+  // remote pipe.
+  PipeIpcReceiverAoC mPipeIpcReceiverAoc;
+
+  /**
+   * Invoked by the command processor when it gets a 'Ready'
+   * message from the DSP. Binds the Pipe object to the audio filter's
+   * input, and binds the ipc ring buffer to the audio filter.
+   *
+   * @return 0 on success, -1 otherwise.
+   */
+  int OnDspReady();
+
+  /**
+   * Checks the current streaming state of a handle/channel, sends
+   * a command to the remote core to start/stop streaming if they
+   * don't match, and updates the current state on the handle.
+   * Note that there's an ~50ms latency for a new 'start' on a handle.
+   *
+   * @param start Start streaming if enabled, stop otherwise.
+   *
+   * @return true on success.
+   *
+   * TODO: Handle the 'handle' parameter when multiple channels are supported,
+   * and update the state tracking logic accordingly.
+   */
+  bool UpdateStream(uint32_t handle, bool start);
+};
+
+}  // namespace chre
+
+#endif  // CHRE_PLATFORM_AOC_AUDIO_CONTROLLER_H_
diff --git a/platform/aoc/include/chre/platform/aoc/audio_filter.h b/platform/aoc/include/chre/platform/aoc/audio_filter.h
new file mode 100644
index 0000000..260e6fa
--- /dev/null
+++ b/platform/aoc/include/chre/platform/aoc/audio_filter.h
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2020 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 CHRE_PLATFORM_AOC_AUDIO_FILTER_H_
+#define CHRE_PLATFORM_AOC_AUDIO_FILTER_H_
+
+#include "chre_api/chre/audio.h"
+#include "efw/include/filter.h"
+#include "filters/audio/common/include/audio_metadata.h"
+
+namespace chre {
+
+/**
+ * This class implements an audio filter, whose primary purpose is to
+ * read audio samples from a shared ring buffer, process it, and route
+ * it to registered audio clients. 'Filter', in this context, means a
+ * data processing unit (audio data itself is raw, with no actual filtering
+ * done on it). The audio filter is fully controlled by an instance of an
+ * audio controller object (see audio_controller.h). The interaction occurs
+ * as follows:
+ *  - The controller instantiates a filter, and upon notification from the
+ *    remote core that a ring buffer is available, binds it to the filter.
+ *  - The controller also binds an IPC Pipe to the filter's input. The pipe's
+ *    primary (and only) purpose is to notify the filter that there's fresh
+ *    data in the ring buffer.
+ *  - The filter then reads the ring buffer, does some processing (strip
+ *    timestamps, format data, etc.), and sends said data out to clients via
+ *    the CHRE AudioRequestManager.
+ *  The filter instantiates a worker thread, which is woken up by any one of
+ *  the following two mechanisms (outside of the automatic watchdog pet wakeup):
+ *  - 'Signal': A signal is an internal (to the core) command, sent by the main
+ *  audio controller, to notify the filter of local (to the core) conditions.
+ *  For example, CHRE called a free data event, which the controller receives,
+ *  and notifies the filter. Signals are processed by overloading the
+ *  SignalProcessor method of the filter base class.
+ *  - 'Input': An input is a cross core notification that the filter registers
+ *  to, via the pipe that's bound to it. On a pipe notification, the thread
+ *  wakes up to read/process data, which is done by overloading the
+ *  InputProcessor method of the filter base class.
+ *
+ *  TODO:
+ *  1 - Audio samples are currently collected and processed in SRAM, need to
+ *      copy out to DRAM before passing to nanoapps, to free up the 2-sec
+ *      buffer to resume collecting samples.
+ *  2 - Only a single channel (of the 4 available) is currently supported
+ *      (blocked by 1)..
+ *  3 - The Input processor drops audio samples while the current buffer
+ *      is being serviced by clients (blocked by 1)
+ */
+
+class AudioFilter : public Filter {
+ public:
+  // Defines the index of a signal sent by the controller
+  // on receiving CHRE's freeAudioDataEvent.
+  static constexpr size_t kBufferReleasedSignalIndex = 0;
+
+  // Defines the index at which the audio controller's IPC pipe binds to
+  // the filter input. Note that this is dictated by the 'Num Inputs'
+  // parameter to the filter constructor.
+  static constexpr size_t kPipeIndex = 0;
+
+  // Defines the index at which the audio controller's IPC ring buffer
+  // binds to the filter's ring buffer array. Note that this is dictated
+  // by the 'Num Ring Buffers' parameter to the filter constructor.
+  static constexpr size_t kRingIndex = 0;
+
+  static constexpr uint32_t kSupportedSampleRate = 16000;
+  static constexpr uint32_t kSupportedDurationSeconds = 2;
+
+  AudioFilter();
+
+  /**
+   * Callback that's called at the start of  the 'Start' method,
+   * and before the worker thread has been instantiated.
+   *
+   * @return 0 on success, -1 otherwise.
+   */
+  int StartCallback() override final;
+
+  /**
+   * Callback that gets called at the end of the 'Stop' method,
+   * and after the worker thread has been shutdown.
+   *
+   * @return true on success
+   */
+  bool StopCallback() override final;
+
+  /**
+   * Method that handles commands that are internal to the core,
+   * typically sent by the audio controller. The argument to the
+   * function is predicated by the 'Command Depth' argument to the
+   * constructor. If the filter is expected to process a new signal,
+   * then the constructor argument needs to be incremented as well,
+   * as any index exceeding this is automatically dropped.
+   *
+   * @param index Signal ID (less than command depth) sent by the
+   * controller
+   *
+   * @return true on valid command and processing success
+   */
+  bool SignalProcessor(int index) override final;
+
+  /**
+   * Method that handles cross core pipe notifications, if one was bound
+   * to the filter's input. The 'pin' argument to the function is predicated
+   * by the 'Num Filter Inputs' argument to the constructor. If the filter is
+   * expected to process a new input (via a new pipe being bound to its
+   * input), then the constructor argument needs to be incremented as well,
+   * as any index exceeding this is automatically dropped. The pipe index
+   * depends on the order that InputBind was called. No lookup is provided,
+   * and the application is expected to keep track of this (maybe via an
+   * external enum).
+   *
+   * @param pin Index of the input pipe that was bound to the filter.
+   *
+   * @param message Input pipe notification to be processed. This can be
+   * a nullptr - which just means that the pipe wanted to notify a certain
+   * condition, but had no metadata to provide.
+   *
+   * @param size Input message size. Can be zero, if the pipe had no
+   * metadata to send.
+   */
+  void InputProcessor(int pin, void *message, size_t size) override final;
+
+  /**
+   * Callback that's invoked on CHRE's release audio data event.
+   *
+   * TODO: This needs to be refactored when we store audio samples in DRAM.
+   */
+  void OnBufferReleased();
+
+ private:
+  static constexpr size_t kSamplesPer2Sec =
+      kSupportedSampleRate * kSupportedDurationSeconds;
+
+  int16_t mSampleBufferSram[kSamplesPer2Sec];
+  int16_t *mSampleBufferDram = nullptr;
+  void *mRingBufferHandle = nullptr;
+  size_t mSampleCount = 0;
+  bool mDramBufferInUse = false;
+
+  struct chreAudioDataEvent mDataEvent = {
+      .version = CHRE_AUDIO_DATA_EVENT_VERSION,
+      .reserved = {0, 0, 0},
+      .handle = 0,
+      .timestamp = 0,
+      .sampleRate = kSupportedSampleRate,
+      .sampleCount = 0,
+      .format = CHRE_AUDIO_DATA_FORMAT_16_BIT_SIGNED_PCM,
+      .samplesS16 = nullptr};
+};
+
+}  // namespace chre
+
+#endif  // CHRE_PLATFORM_AOC_AUDIO_FILTER_H_
diff --git a/platform/aoc/include/chre/platform/aoc/memory.h b/platform/aoc/include/chre/platform/aoc/memory.h
new file mode 100644
index 0000000..38b6928
--- /dev/null
+++ b/platform/aoc/include/chre/platform/aoc/memory.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2020 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 CHRE_PLATFORM_AOC_MEMORY_H_
+#define CHRE_PLATFORM_AOC_MEMORY_H_
+
+#include <cstddef>
+
+#include "chre/platform/shared/memory.h"
+
+namespace chre {
+
+/**
+ * Memory allocation specifically using the DRAM heap. The semantics are the
+ * same as malloc.
+ */
+void *memoryAllocDram(size_t size);
+
+/**
+ * Memory free from memory allocated using the DRAM heap. The semantics are the
+ * same as free.
+ */
+void memoryFreeDram(void *pointer);
+
+}  // namespace chre
+
+#endif  // CHRE_PLATFORM_AOC_MEMORY_H_
diff --git a/platform/aoc/include/chre/platform/aoc/system_time.h b/platform/aoc/include/chre/platform/aoc/system_time.h
new file mode 100644
index 0000000..26c4fca
--- /dev/null
+++ b/platform/aoc/include/chre/platform/aoc/system_time.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2019 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 CHRE_PLATFORM_AOC_SYSTEM_TIME_H_
+#define CHRE_PLATFORM_AOC_SYSTEM_TIME_H_
+
+#include <cstdint>
+
+namespace chre {
+
+/**
+ * Sets the estimated offset between the host and AoC clock.
+ *
+ * @param offset The current estimated offset in nanoseconds.
+ */
+void setEstimatedHostTimeOffset(int64_t offset);
+
+}  // namespace chre
+
+#endif  // CHRE_PLATFORM_AOC_SYSTEM_TIME_H_
diff --git a/platform/aoc/include/chre/target_platform/assert.h b/platform/aoc/include/chre/target_platform/assert.h
new file mode 100644
index 0000000..aec4e33
--- /dev/null
+++ b/platform/aoc/include/chre/target_platform/assert.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2020 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 CHRE_PLATFORM_AOC_ASSERT_H_
+#define CHRE_PLATFORM_AOC_ASSERT_H_
+
+#include "chre/platform/fatal_error.h"
+
+#define CHRE_ASSERT(condition)                                            \
+  do {                                                                    \
+    if (!(condition)) {                                                   \
+      FATAL_ERROR("Assertion failure at %s:%d", CHRE_FILENAME, __LINE__); \
+    }                                                                     \
+  } while (0)
+
+#endif  // CHRE_PLATFORM_AOC_ASSERT_H_
diff --git a/platform/aoc/include/chre/target_platform/fatal_error.h b/platform/aoc/include/chre/target_platform/fatal_error.h
new file mode 100644
index 0000000..8a5c5c8
--- /dev/null
+++ b/platform/aoc/include/chre/target_platform/fatal_error.h
@@ -0,0 +1,32 @@
+/**
+ * Copyright 2019 Google LLC. All Rights Reserved
+ *
+ * @file fatal_error.h
+ *
+ * @brief Fatal Error Handling
+ */
+
+#ifndef CHRE_PLATFORM_AOC_FATAL_ERROR_H_
+#define CHRE_PLATFORM_AOC_FATAL_ERROR_H_
+
+#include <cstdlib>
+
+#define FATAL_ERROR_QUIT() \
+  do {                     \
+    chre::preFatalError(); \
+    abort();               \
+  } while (0)
+
+namespace chre {
+
+/**
+ * Do preparation for an impending fatal error including flushing logs.
+ *
+ * It must not be possible for FATAL_ERROR() to be called by this function or
+ * any of its callees.
+ */
+void preFatalError();
+
+}  // namespace chre
+
+#endif  // CHRE_PLATFORM_AOC_FATAL_ERROR_H_
diff --git a/platform/aoc/include/chre/target_platform/host_link_base.h b/platform/aoc/include/chre/target_platform/host_link_base.h
new file mode 100644
index 0000000..638866e
--- /dev/null
+++ b/platform/aoc/include/chre/target_platform/host_link_base.h
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2020 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 CHRE_PLATFORM_AOC_HOST_LINK_BASE_H_
+#define CHRE_PLATFORM_AOC_HOST_LINK_BASE_H_
+
+#include "chre/util/time.h"
+
+#include "usf/error.h"
+#include "usf/usf_transport.h"
+#include "usf/usf_work.h"
+
+namespace chre {
+
+/**
+ * Helper function to send debug dump result to host.
+ */
+void sendDebugDumpResultToHost(uint16_t hostClientId, const char *debugStr,
+                               size_t debugStrSize, bool complete,
+                               uint32_t dataCount);
+
+/**
+ * This class implements a basic wrapper for sending/receiving messages over USF
+ * transports. It sets up a callback into the USF transport layer for receiving
+ * messages on instantiation. The first message that we receive from the USF
+ * transport layer includes a pointer to a 'transport' abstraction, that can be
+ * used to send messages out of CHRE.
+ *
+ * TODO: Revisit this implementation after the USF feature requests in
+ * b/149318001 and b/149317051 are fulfilled
+ */
+class HostLinkBase {
+ public:
+  HostLinkBase();
+  ~HostLinkBase();
+
+  /**
+   * Initializes the host link with the transport provided when the CHRE daemon
+   * sends its first message to AoC. If called multiple times, only the first
+   * transport is used.
+   *
+   * TODO (b/149317051) Remove this once USF exposes an API to acquire a
+   * transport for communicating with the AP.
+   */
+  void init(usf::UsfTransport *transport);
+
+  /**
+   * Sends a message to the CHRE daemon.
+   *
+   * @param data data to be sent to the daemon
+   * @param dataLen length of the data being sent
+   * @return true if the data was successfully queued for sending
+   */
+  bool send(uint8_t *data, size_t dataLen);
+
+  refcount::reffed_ptr<usf::UsfWorker> &getWorker() {
+    return mWorker;
+  }
+
+  /**
+   * The USF external message handler function for CHRE, that
+   * is invoked on CHRE inbound messages from the AP
+   *
+   * @param transport A pointer to a transport that is used to send messages
+   * out of CHRE to the AP
+   * @param msg flatbuffer encoded message of type UsfMsgType_CHRE
+   * @param arg yields the message length when cast to size_t
+   * @return usf::kErrNone if success, an error code indicating the failure
+   * otherwise
+   */
+  static usf::UsfErr handleUsfMessage(usf::UsfTransport *transport,
+                                      const usf::UsfMsg *msg, void *arg);
+
+  /**
+   * Sends a request to the host for a time sync message.
+   */
+  static void sendTimeSyncRequest();
+
+  /**
+   * Enqueues a log message to be sent to the host.
+   *
+   * @param logMessage Pointer to a buffer that has the log message. Note that
+   * the message might be encoded
+   *
+   * @param logMessageSize length of the log message buffer
+   */
+  void sendLogMessage(const uint8_t *logMessage, size_t logMessageSize);
+
+ private:
+  //! The last time a time sync request message has been sent.
+  static Nanoseconds mLastTimeSyncRequestNanos;
+
+  //! Transport handle provided when the CHRE daemon sends its first message
+  //! to AoC.
+  usf::UsfTransport *mTransportHandle = nullptr;
+
+  //! Worker thread used to ensure single-threaded communication with CHRE.
+  refcount::reffed_ptr<usf::UsfWorker> mWorker;
+};
+
+}  // namespace chre
+
+#endif  // CHRE_PLATFORM_AOC_HOST_LINK_BASE_H_
diff --git a/platform/aoc/include/chre/target_platform/log.h b/platform/aoc/include/chre/target_platform/log.h
new file mode 100644
index 0000000..f2fb3d5
--- /dev/null
+++ b/platform/aoc/include/chre/target_platform/log.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2020 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 CHRE_PLATFORM_AOC_LOG_H_
+#define CHRE_PLATFORM_AOC_LOG_H_
+
+#include <chre.h>
+#include <stdio.h>
+#include "efw_log.h"
+
+#ifndef __FILENAME__
+#ifdef __BASE_FILE__
+#define __FILENAME__ __BASE_FILE__
+#else
+#define __FILENAME__ __FILE__
+#endif  // __BASE_FILE__
+#endif  // __FILE_NAME__
+
+#ifndef CHRE_DL_VERBOSE
+#define CHRE_DL_VERBOSE false
+#endif  // CHRE_DL_VERBOSE
+
+// TODO(b/149317051): The printf in the below macro is needed until CHRE can log
+// to the AP before the daemon has connected to AoC.
+#define CHRE_AOC_LOG(level, fmt, ...)                               \
+  do {                                                              \
+    CHRE_LOG_PREAMBLE                                               \
+    chre::log(level, fmt, ##__VA_ARGS__);                           \
+    DBG_MSG_RAW(255, "CHRE:%s:%d\t" fmt "", __FILENAME__, __LINE__, \
+                ##__VA_ARGS__);                                     \
+    CHRE_LOG_EPILOGUE                                               \
+  } while (0)
+
+#define LOGE(fmt, ...) CHRE_AOC_LOG(CHRE_LOG_ERROR, fmt, ##__VA_ARGS__)
+#define LOGW(fmt, ...) CHRE_AOC_LOG(CHRE_LOG_WARN, fmt, ##__VA_ARGS__)
+#define LOGI(fmt, ...) CHRE_AOC_LOG(CHRE_LOG_INFO, fmt, ##__VA_ARGS__)
+#define LOGD(fmt, ...) CHRE_AOC_LOG(CHRE_LOG_DEBUG, fmt, ##__VA_ARGS__)
+
+#if CHRE_DL_VERBOSE
+#define LOGV(fmt, ...) LOGD(fmt, ##__VA_ARGS__)
+#else
+#define LOGV(fmt, ...)
+#endif  // CHRE_DL_VERBOSE
+
+namespace chre {
+
+void log(enum chreLogLevel level, const char *formatStr, ...);
+void vaLog(enum chreLogLevel level, const char *format, va_list args);
+
+}  // namespace chre
+
+#endif  // CHRE_PLATFORM_AOC_LOG_H_
diff --git a/platform/aoc/include/chre/target_platform/platform_audio_base.h b/platform/aoc/include/chre/target_platform/platform_audio_base.h
new file mode 100644
index 0000000..d42d2c8
--- /dev/null
+++ b/platform/aoc/include/chre/target_platform/platform_audio_base.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2020 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 CHRE_PLATFORM_AOC_PLATFORM_AUDIO_BASE_H_
+#define CHRE_PLATFORM_AOC_PLATFORM_AUDIO_BASE_H_
+
+#include <cstring>
+
+#include "chre/platform/aoc/audio_controller.h"
+#include "chre/platform/platform_audio.h"
+
+namespace chre {
+
+/**
+ * The base PlatformAudio class for AoC to inject platform
+ * specific functionality from. Instantiates an Audio Controller object,
+ * which handles communications, data buffering, and audio data event
+ * dispatch for CHRE.
+ */
+class PlatformAudioBase {
+ protected:
+  AudioController mAudioController;
+};
+
+}  // namespace chre
+
+#endif  // CHRE_PLATFORM_AOC_PLATFORM_AUDIO_BASE_H_
diff --git a/platform/aoc/include/chre/target_platform/platform_log_base.h b/platform/aoc/include/chre/target_platform/platform_log_base.h
new file mode 100644
index 0000000..1d5657a
--- /dev/null
+++ b/platform/aoc/include/chre/target_platform/platform_log_base.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2020 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 CHRE_PLATFORM_AOC_PLATFORM_LOG_BASE_H_
+#define CHRE_PLATFORM_AOC_PLATFORM_LOG_BASE_H_
+
+namespace chre {
+
+class PlatformLogBase {
+  // TODO: Stubbed out, implement this
+  // Can we EFWObject::PrintSet(printf) here instead of in the application?
+};
+
+}  // namespace chre
+
+#endif  // CHRE_PLATFORM_AOC_PLATFORM_LOG_BASE_H_
diff --git a/platform/aoc/include/chre/target_platform/power_control_manager_base.h b/platform/aoc/include/chre/target_platform/power_control_manager_base.h
new file mode 100644
index 0000000..fdd9d46
--- /dev/null
+++ b/platform/aoc/include/chre/target_platform/power_control_manager_base.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2020 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 CHRE_PLATFORM_AOC_POWER_CONTROL_MANAGER_BASE_H
+#define CHRE_PLATFORM_AOC_POWER_CONTROL_MANAGER_BASE_H
+
+#include "chre/platform/atomic.h"
+
+namespace chre {
+
+class PowerControlManagerBase {
+ public:
+  PowerControlManagerBase();
+
+  /**
+   * Updates internal wake/suspend flag and pushes awake/sleep notification
+   * to nanoapps that are listening for it.
+   *
+   * @param awake true if host is awake, otherwise suspended.
+   */
+  void onHostWakeSuspendEvent(bool awake);
+
+ protected:
+  //! Set to true if the host is awake, false if suspended.
+  AtomicBool mHostIsAwake;
+};
+
+}  // namespace chre
+
+#endif  // CHRE_PLATFORM_AOC_POWER_CONTROL_MANAGER_BASE_H
diff --git a/platform/aoc/include/chre/target_platform/system_timer_base.h b/platform/aoc/include/chre/target_platform/system_timer_base.h
new file mode 100644
index 0000000..94469c0
--- /dev/null
+++ b/platform/aoc/include/chre/target_platform/system_timer_base.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2020 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 CHRE_PLATFORM_AOC_SYSTEM_TIMER_BASE_H_
+#define CHRE_PLATFORM_AOC_SYSTEM_TIMER_BASE_H_
+
+#include <cstdint>
+
+#include "chre/platform/assert.h"
+#include "chre/util/time_impl.h"
+
+#include "efw/include/timer.h"
+
+#include "FreeRTOS.h"
+#include "task.h"
+
+namespace chre {
+
+/**
+ * The AOC platform base class for the SystemTimer. The AOC implementation uses
+ * a EFW timer.
+ */
+class SystemTimerBase {
+ protected:
+  //! The timer handle that is generated by Timer::EventAdd.
+  void *mTimerHandle;
+
+  // TODO(b/168645313): Share the same dispatch thread amongst all timer
+  // instances.
+  //! A FreeRTOS Thread to dispatch timer callbacks
+  TaskHandle_t mTimerCbDispatchThreadHandle = nullptr;
+
+  //! Stack size (in words) of the timer callback dispatch thread
+  static constexpr uint32_t kStackDepthWords = configMINIMAL_STACK_SIZE;
+
+  //! Task stack associated with this timer.
+  StackType_t mTaskStack[kStackDepthWords];
+
+  //! FreeRTOS struct to hold the TCB of the timer dispatch thread
+  StaticTask_t mTaskBuffer;
+
+  //! Tracks whether the timer has been initialized correctly.
+  bool mInitialized = false;
+
+  //! A static method that is invoked by the underlying EFW timer.
+  static bool systemTimerNotifyCallback(void *context);
+
+  //! This function implements the timer callback dispatch thread.
+  // It blocks until it's woken up by the underlying system timer's ISR,
+  // then executes the CHRE timer callback from the dispatch thread context.
+  static void timerCallbackDispatch(void *context);
+};
+
+static_assert(configSUPPORT_STATIC_ALLOCATION == 1,
+              "Static task allocation must be supported!");
+
+}  // namespace chre
+
+#endif  // CHRE_PLATFORM_AOC_SYSTEM_TIMER_BASE_H_
diff --git a/platform/aoc/log.cc b/platform/aoc/log.cc
new file mode 100644
index 0000000..689cef1
--- /dev/null
+++ b/platform/aoc/log.cc
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2020 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 "chre/target_platform/log.h"
+
+#include "chre/core/event_loop_manager.h"
+#include "chre/platform/system_time.h"
+
+#include <endian.h>
+
+namespace chre {
+namespace {
+constexpr size_t kChreLogBufferSize = CHRE_MESSAGE_TO_HOST_MAX_SIZE;
+char logBuffer[kChreLogBufferSize];
+}  // namespace
+
+void log(enum chreLogLevel level, const char *formatStr, ...) {
+  va_list args;
+  va_start(args, formatStr);
+  vaLog(level, formatStr, args);
+  va_end(args);
+}
+
+// TODO: b/146164384 - We will need to batch logs rather than send them
+// one at a time to avoid waking the AP.
+void vaLog(enum chreLogLevel level, const char *format, va_list args) {
+  auto &hostCommsMgr =
+      chre::EventLoopManagerSingleton::get()->getHostCommsManager();
+
+  // See host_messages.fbs for the log message format.
+  size_t logBufIndex = 0;
+  logBuffer[logBufIndex] = level + 1;
+  logBufIndex++;
+
+  uint64_t currentTimeNs = SystemTime::getMonotonicTime().toRawNanoseconds();
+  memcpy(&logBuffer[logBufIndex], &currentTimeNs, sizeof(uint64_t));
+  logBufIndex += sizeof(uint64_t);
+
+  int msgLen = vsnprintf(&logBuffer[logBufIndex],
+                         kChreLogBufferSize - logBufIndex, format, args);
+  if (msgLen >= 0) {
+    // msgLen doesn't include the terminating null char.
+    logBufIndex += msgLen + 1;
+
+    hostCommsMgr.sendLogMessage(reinterpret_cast<uint8_t *>(logBuffer),
+                                logBufIndex);
+  }
+}
+
+}  // namespace chre
diff --git a/platform/aoc/memory.cc b/platform/aoc/memory.cc
new file mode 100644
index 0000000..92d3fa9
--- /dev/null
+++ b/platform/aoc/memory.cc
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2020 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 "chre/platform/aoc/memory.h"
+#include "chre/platform/memory.h"
+#include "chre/platform/mutex.h"
+#include "chre/platform/shared/dram_vote_client.h"
+#include "chre/platform/shared/pal_system_api.h"
+
+#include "basic.h"
+#include "heap.h"
+
+#include <cstdlib>
+
+//! Beginning of the CHRE SRAM heap.
+extern uintptr_t __heap_chre_beg__;
+//! End of the CHRE SRAM heap.
+extern uintptr_t __heap_chre_end__;
+//! Beginning of the CHRE DRAM heap.
+extern uintptr_t __heap_chre_dram_beg__;
+//! End of the CHRE DRAM heap.
+extern uintptr_t __heap_chre_dram_end__;
+
+namespace chre {
+namespace {
+
+//! Handle to the SRAM heap.
+void *gSramHeap = nullptr;
+//! Mutex used to synchronize access to the SRAM heap.
+Mutex gSramMutex;
+//! Handle to the DRAM heap.
+void *gDramHeap = nullptr;
+//! Mutex used to synchronize access to the DRAM heap.
+Mutex gDramMutex;
+
+enum ChreHeap { DRAM, SRAM };
+
+//! Helper functions passed to EFW so it can lock when heap allocations / frees
+//! are performed.
+inline void LockMutex(void *mutex) {
+  static_cast<Mutex *>(mutex)->lock();
+}
+inline void UnlockMutex(void *mutex) {
+  static_cast<Mutex *>(mutex)->unlock();
+}
+
+void *GetSramHeap() {
+  if (gSramHeap == nullptr) {
+    auto heap_base = &__heap_chre_beg__;
+    auto heap_size = PTR_OFFSET_GET(&__heap_chre_end__, heap_base);
+
+    gSramHeap = HeapInit(heap_base, heap_size);
+    CHRE_ASSERT(gSramHeap != nullptr);
+    if (gSramHeap == nullptr) {
+      return nullptr;
+    } else {
+      HeapLocking(gSramHeap, &gSramMutex, LockMutex, UnlockMutex);
+    }
+  }
+  return gSramHeap;
+}
+
+void *GetDramHeap() {
+  if (gDramHeap == nullptr) {
+    auto heap_base = &__heap_chre_dram_beg__;
+    auto heap_size = PTR_OFFSET_GET(&__heap_chre_dram_end__, heap_base);
+
+    gDramHeap = HeapInit(heap_base, heap_size);
+    CHRE_ASSERT(gDramHeap != nullptr);
+    if (gDramHeap == nullptr) {
+      return nullptr;
+    } else {
+      HeapLocking(gDramHeap, &gDramMutex, LockMutex, UnlockMutex);
+    }
+  }
+  return gDramHeap;
+}
+
+void *GetHeap(ChreHeap heap) {
+  if (heap == ChreHeap::SRAM) {
+    return GetSramHeap();
+  } else if (heap == ChreHeap::DRAM) {
+    return GetDramHeap();
+  } else {
+    CHRE_ASSERT(false);
+  }
+  return nullptr;
+}
+
+bool IsInHeap(ChreHeap heap, const void *pointer) {
+  void *handle = GetHeap(heap);
+  bool found = false;
+  if (handle != nullptr) {
+    const struct HEAP_STATS *stats = HeapStats(handle);
+    const uintptr_t heapBase = reinterpret_cast<uintptr_t>(stats->base);
+    const uintptr_t castPointer = reinterpret_cast<uintptr_t>(pointer);
+    found = castPointer >= heapBase && castPointer < (heapBase + stats->len);
+  }
+
+  return found;
+}
+
+}  // namespace
+
+void *memoryAlloc(size_t size) {
+  void *ptr = nullptr;
+  void *handle = GetSramHeap();
+  if (handle != nullptr) {
+    ptr = HeapMalloc(handle, size);
+    // Fall back to DRAM memory when SRAM memory is exhausted. Must exclude size
+    // 0 as clients may not explicitly free memory of size 0, which may
+    // mistakenly leave DRAM accessible
+    if (ptr == nullptr && size != 0) {
+      // Increment DRAM vote count to prevent system from powering down DRAM
+      // while DRAM memory is in use.
+      DramVoteClientSingleton::get()->incrementDramVoteCount();
+      ptr = memoryAllocDram(size);
+
+      // DRAM allocation failed too.
+      if (ptr == nullptr) {
+        DramVoteClientSingleton::get()->decrementDramVoteCount();
+      }
+    }
+  }
+
+  return ptr;
+}
+
+void *memoryAllocAligned(size_t alignment, size_t size) {
+  void *ptr = nullptr;
+  void *handle = GetSramHeap();
+
+  if (handle != nullptr) {
+    ptr = HeapAlignedAlloc(handle, alignment, size);
+    // This method is currently only used for nanoapp loading so don't fall back
+    // to DRAM or there can be power implications.
+  }
+
+  return ptr;
+}
+
+void *memoryAllocDramAligned(size_t alignment, size_t size) {
+  CHRE_ASSERT_LOG(DramVoteClientSingleton::get()->isDramVoteActive(),
+                  "DRAM allocation when not accessible");
+
+  void *ptr = nullptr;
+  void *handle = GetDramHeap();
+
+  if (handle != nullptr) {
+    ptr = HeapAlignedAlloc(handle, alignment, size);
+  }
+
+  return ptr;
+}
+
+void *memoryAllocDram(size_t size) {
+  CHRE_ASSERT_LOG(DramVoteClientSingleton::get()->isDramVoteActive(),
+                  "DRAM allocation when not accessible");
+
+  void *ptr = nullptr;
+  void *handle = GetDramHeap();
+  if (handle != nullptr) {
+    ptr = HeapMalloc(handle, size);
+  }
+
+  return ptr;
+}
+
+void memoryFree(void *pointer) {
+  if (pointer != nullptr) {
+    if (IsInHeap(ChreHeap::SRAM, pointer)) {
+      HeapFree(GetHeap(ChreHeap::SRAM), pointer);
+    } else {
+      memoryFreeDram(pointer);
+      DramVoteClientSingleton::get()->decrementDramVoteCount();
+    }
+  }
+}
+
+void memoryFreeDram(void *pointer) {
+  CHRE_ASSERT_LOG(DramVoteClientSingleton::get()->isDramVoteActive(),
+                  "DRAM freed when not accessible");
+
+  if (pointer != nullptr) {
+    bool inDramHeap = IsInHeap(ChreHeap::DRAM, pointer);
+    CHRE_ASSERT(inDramHeap);
+    if (inDramHeap) {
+      HeapFree(GetDramHeap(), pointer);
+    }
+  }
+}
+
+void forceDramAccess() {
+  DramVoteClientSingleton::get()->voteDramAccess(true /* enabled */);
+}
+
+void removeDramAccessVote() {
+  DramVoteClientSingleton::get()->voteDramAccess(false /* enabled */);
+}
+
+void *palSystemApiMemoryAlloc(size_t size) {
+  return memoryAlloc(size);
+}
+
+void palSystemApiMemoryFree(void *pointer) {
+  memoryFree(pointer);
+}
+
+}  // namespace chre
diff --git a/platform/aoc/platform_audio.cc b/platform/aoc/platform_audio.cc
new file mode 100644
index 0000000..0019697
--- /dev/null
+++ b/platform/aoc/platform_audio.cc
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2020 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 "chre/platform/platform_audio.h"
+#include <cinttypes>
+#include "chre/core/event_loop_manager.h"
+#include "chre/platform/assert.h"
+#include "chre/platform/log.h"
+#include "chre/platform/system_time.h"
+
+namespace chre {
+
+PlatformAudio::PlatformAudio() {}
+
+PlatformAudio::~PlatformAudio() {}
+
+void PlatformAudio::init() {
+  mAudioController.Start();
+  mAudioController.SetUpRemoteCore();
+}
+
+size_t PlatformAudio::getSourceCount() {
+  return AudioController::kMaxAocMicrophones;
+}
+
+bool PlatformAudio::getAudioSource(uint32_t handle,
+                                   struct chreAudioSource *source) const {
+  bool success = false;
+  if ((handle < AudioController::kMaxAocMicrophones) && (source != nullptr)) {
+    memcpy(source, mAudioController.GetSource(handle), sizeof(chreAudioSource));
+    success = true;
+  }
+
+  return success;
+}
+
+void PlatformAudio::setHandleEnabled(uint32_t handle, bool enabled) {
+  mAudioController.SetEnabled(handle, enabled);
+}
+
+void PlatformAudio::cancelAudioDataEventRequest(uint32_t handle) {
+  mAudioController.ReleaseAudio(handle);
+}
+
+bool PlatformAudio::requestAudioDataEvent(uint32_t handle, uint32_t numSamples,
+                                          Nanoseconds eventDelay) {
+  // TODO: Event delay
+  bool success = mAudioController.RequestAudio(handle);
+  LOGV("Request audio data for hdl %u, numSamples %u @ %" PRIu64
+       ", success: %d",
+       handle, numSamples, SystemTime::getMonotonicTime().toRawNanoseconds(),
+       success);
+
+  return success;
+}
+
+void PlatformAudio::releaseAudioDataEvent(
+    struct chreAudioDataEvent * /*event*/) {
+  mAudioController.OnBufferReleased();
+}
+
+}  // namespace chre
diff --git a/platform/aoc/platform_cache_management.cc b/platform/aoc/platform_cache_management.cc
new file mode 100644
index 0000000..b375d16
--- /dev/null
+++ b/platform/aoc/platform_cache_management.cc
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2020 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 "chre/target_platform/platform_cache_management.h"
+#include "core/arm/generic/include/cache_management.h"
+#include "core/arm/generic/include/memory_barrier.h"
+
+namespace chre {
+
+namespace {
+
+void invalidateInstructionCache() {
+  INSTRUCTION_CACHE_INVALIDATE();
+  MB();
+}
+
+void cleanAndInvalidateDataCache() {
+  CACHE_SET_WAY_FLUSH_AND_INVALIDATE_ALL();
+  MB();
+}
+
+}  // anonymous namespace
+
+void wipeSystemCaches() {
+  cleanAndInvalidateDataCache();
+  invalidateInstructionCache();
+}
+
+}  // namespace chre
diff --git a/platform/aoc/platform_pal.cc b/platform/aoc/platform_pal.cc
new file mode 100644
index 0000000..515a3e5
--- /dev/null
+++ b/platform/aoc/platform_pal.cc
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2020 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 "chre/platform/shared/platform_pal.h"
+
+namespace chre {
+
+void PlatformPal::prePalApiCall() const {}
+
+}  // namespace chre
diff --git a/platform/aoc/power_control_manager.cc b/platform/aoc/power_control_manager.cc
new file mode 100644
index 0000000..8e57651
--- /dev/null
+++ b/platform/aoc/power_control_manager.cc
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2020 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 "chre/platform/power_control_manager.h"
+
+#include "chre/core/event_loop_manager.h"
+#include "chre/platform/shared/memory.h"
+
+namespace chre {
+
+PowerControlManagerBase::PowerControlManagerBase() : mHostIsAwake(true) {}
+
+void PowerControlManagerBase::onHostWakeSuspendEvent(bool awake) {
+  if (mHostIsAwake != awake) {
+    mHostIsAwake = awake;
+
+    if (!awake) {
+      EventLoopManagerSingleton::get()
+          ->getHostCommsManager()
+          .resetBlameForNanoappHostWakeup();
+    }
+
+    EventLoopManagerSingleton::get()->getEventLoop().postEventOrDie(
+        awake ? CHRE_EVENT_HOST_AWAKE : CHRE_EVENT_HOST_ASLEEP,
+        nullptr /* eventData */, nullptr /* freeCallback */);
+  }
+}
+
+void PowerControlManager::postEventLoopProcess(size_t numPendingEvents) {
+  if (numPendingEvents == 0) {
+    removeDramAccessVote();
+  }
+}
+
+bool PowerControlManager::hostIsAwake() {
+  return mHostIsAwake;
+}
+
+}  // namespace chre
diff --git a/platform/aoc/system_time.cc b/platform/aoc/system_time.cc
new file mode 100644
index 0000000..ded9a63
--- /dev/null
+++ b/platform/aoc/system_time.cc
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2020 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 "chre/platform/system_time.h"
+#include "chre/platform/assert.h"
+#include "chre/platform/log.h"
+
+#include "efw/include/timer.h"
+
+namespace chre {
+
+namespace {
+
+int64_t gEstimatedHostTimeOffset = 0;
+
+}
+
+Nanoseconds SystemTime::getMonotonicTime() {
+  return Nanoseconds(Timer::Instance()->TimestampNanoseconds());
+}
+
+int64_t SystemTime::getEstimatedHostTimeOffset() {
+  return gEstimatedHostTimeOffset;
+}
+
+void setEstimatedHostTimeOffset(int64_t offset) {
+  gEstimatedHostTimeOffset = offset;
+}
+
+}  // namespace chre
diff --git a/platform/aoc/system_timer.cc b/platform/aoc/system_timer.cc
new file mode 100644
index 0000000..4650f8c
--- /dev/null
+++ b/platform/aoc/system_timer.cc
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2020 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 "chre/platform/system_timer.h"
+#include "chre/platform/assert.h"
+#include "chre/platform/log.h"
+#include "chre/util/time.h"
+
+#include "efw/include/timer.h"
+
+namespace {
+
+// The timer dispatch thread is notifed from a
+// timer interrupt context, and there are context checks in the FreeRTOS code
+// (i.e. the xxGive and xxGiveFromISR are not interchangeable). Since there
+// are no interrupts in a simulated platform, we end up with two different
+// notification mechanisms that accomplish the same purpose.
+void wakeupDispatchThreadFromIsr(TaskHandle_t &handle) {
+  BaseType_t xHigherPriorityTaskWoken = pdFALSE;
+  vTaskNotifyGiveFromISR(handle, &xHigherPriorityTaskWoken);
+  portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
+}
+
+}  // namespace
+
+namespace chre {
+
+bool SystemTimerBase::systemTimerNotifyCallback(void *context) {
+  SystemTimer *pTimer = static_cast<SystemTimer *>(context);
+  if (pTimer != nullptr) {
+    wakeupDispatchThreadFromIsr(pTimer->mTimerCbDispatchThreadHandle);
+  }
+
+  // The EFW timer callback is setup in such a way that returning 'true'
+  // here reschedules the timer, while returning 'false' does not.
+  // Since we're interested in a one-shot timer, we return 'false' here.
+  return false;
+}
+
+void SystemTimerBase::timerCallbackDispatch(void *context) {
+  SystemTimer *pTimer = static_cast<SystemTimer *>(context);
+  if (pTimer == nullptr) {
+    FATAL_ERROR("Null System Timer");
+  }
+
+  while (true) {
+    ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
+    // Obtain pointer to callback before checking its value to avoid race
+    // with cancel().
+    SystemTimerCallback *callback = pTimer->mCallback;
+    if (callback != nullptr) {
+      pTimer->mTimerHandle = nullptr;
+      callback(pTimer->mData);
+    }
+  }
+}
+
+SystemTimer::SystemTimer() {}
+
+SystemTimer::~SystemTimer() {
+  // cancel an existing timer if any
+  cancel();
+  // Delete the timer dispatch thread if it was created
+  if (mTimerCbDispatchThreadHandle != nullptr) {
+    vTaskDelete(mTimerCbDispatchThreadHandle);
+    mTimerCbDispatchThreadHandle = nullptr;
+  }
+}
+
+bool SystemTimer::init() {
+  // TODO(b/168526254): Balance this priority with other CHRE tasks.
+  constexpr UBaseType_t kTaskPriority = tskIDLE_PRIORITY + 15;
+  const char *const kTaskName = "ChreTimerCB";
+
+  mTimerCbDispatchThreadHandle =
+      xTaskCreateStatic(timerCallbackDispatch, kTaskName, kStackDepthWords,
+                        this, kTaskPriority, mTaskStack, &mTaskBuffer);
+  if (mTimerCbDispatchThreadHandle != nullptr) {
+    mInitialized = true;
+  } else {
+    LOGE("Failed to create Timer Dispatch Thread");
+  }
+
+  return mInitialized;
+}
+
+bool SystemTimer::set(SystemTimerCallback *callback, void *data,
+                      Nanoseconds delay) {
+  bool success = false;
+
+  if (mInitialized) {
+    // Cancel the existing timer to avoid a race where the old timer expires
+    // when setting up the new timer.
+    cancel();
+
+    mCallback = callback;
+    mData = data;
+
+    Timer *timer = Timer::Instance();
+    int rc =
+        timer->EventAddAtOffset(timer->NsToTicks(delay.toRawNanoseconds()),
+                                systemTimerNotifyCallback, this, &mTimerHandle);
+
+    if (rc != 0) {
+      LOGE("Failed to set timer");
+    } else {
+      success = true;
+    }
+  }
+
+  return success;
+}
+
+bool SystemTimer::cancel() {
+  int rc = -1;
+  if (mTimerHandle != nullptr) {
+    rc = Timer::Instance()->EventRemove(mTimerHandle);
+    mTimerHandle = nullptr;
+    mCallback = nullptr;
+  }
+
+  return (rc == 0) ? true : false;
+}
+
+bool SystemTimer::isActive() {
+  return mTimerHandle != nullptr;
+}
+
+}  // namespace chre
diff --git a/platform/freertos/context.cc b/platform/freertos/context.cc
new file mode 100644
index 0000000..8ee7448
--- /dev/null
+++ b/platform/freertos/context.cc
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2019 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 "chre/platform/context.h"
+#include "chre/target_platform/init.h"
+
+#include "FreeRTOS.h"
+#include "task.h"
+
+namespace chre {
+
+bool inEventLoopThread() {
+  TaskHandle_t evtLoopTaskHandle = xTaskGetHandle(freertos::getChreTaskName());
+  TaskHandle_t currentTaskHandle = xTaskGetCurrentTaskHandle();
+
+  return (evtLoopTaskHandle == currentTaskHandle);
+}
+
+}  // namespace chre
diff --git a/platform/freertos/include/chre/target_platform/atomic_base.h b/platform/freertos/include/chre/target_platform/atomic_base.h
new file mode 100644
index 0000000..582d0ae
--- /dev/null
+++ b/platform/freertos/include/chre/target_platform/atomic_base.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2019 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 CHRE_PLATFORM_FREERTOS_ATOMIC_BASE_H_
+#define CHRE_PLATFORM_FREERTOS_ATOMIC_BASE_H_
+
+#include <atomic>
+
+namespace chre {
+
+template <typename AtomicType>
+class AtomicBase {
+ protected:
+  std::atomic<AtomicType> mAtomic;
+};
+
+typedef AtomicBase<bool> AtomicBoolBase;
+typedef AtomicBase<uint32_t> AtomicUint32Base;
+
+}  // namespace chre
+
+#endif  // CHRE_PLATFORM_FREERTOS_ATOMIC_BASE_H_
diff --git a/platform/freertos/include/chre/target_platform/atomic_base_impl.h b/platform/freertos/include/chre/target_platform/atomic_base_impl.h
new file mode 100644
index 0000000..d44a197
--- /dev/null
+++ b/platform/freertos/include/chre/target_platform/atomic_base_impl.h
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2019 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 CHRE_PLATFORM_FREERTOS_ATOMIC_BASE_IMPL_H_
+#define CHRE_PLATFORM_FREERTOS_ATOMIC_BASE_IMPL_H_
+
+#include "chre/platform/atomic.h"
+
+namespace chre {
+
+inline AtomicBool::AtomicBool(bool startingValue) {
+  std::atomic_init(&mAtomic, startingValue);
+}
+
+inline bool AtomicBool::operator=(bool desired) {
+  mAtomic = desired;
+  return desired;
+}
+
+inline bool AtomicBool::load() const {
+  return mAtomic.load();
+}
+
+inline void AtomicBool::store(bool desired) {
+  mAtomic.store(desired);
+}
+
+inline bool AtomicBool::exchange(bool desired) {
+  return mAtomic.exchange(desired);
+}
+
+inline AtomicUint32::AtomicUint32(uint32_t startingValue) {
+  std::atomic_init(&mAtomic, startingValue);
+}
+
+inline uint32_t AtomicUint32::operator=(uint32_t desired) {
+  mAtomic = desired;
+  return desired;
+}
+
+inline uint32_t AtomicUint32::load() const {
+  return mAtomic.load();
+}
+
+inline void AtomicUint32::store(uint32_t desired) {
+  mAtomic.store(desired);
+}
+
+inline uint32_t AtomicUint32::exchange(uint32_t desired) {
+  return mAtomic.exchange(desired);
+}
+
+inline uint32_t AtomicUint32::fetch_add(uint32_t arg) {
+  return mAtomic.fetch_add(arg);
+}
+
+inline uint32_t AtomicUint32::fetch_increment() {
+  return mAtomic.fetch_add(1);
+}
+
+inline uint32_t AtomicUint32::fetch_sub(uint32_t arg) {
+  return mAtomic.fetch_sub(arg);
+}
+
+inline uint32_t AtomicUint32::fetch_decrement() {
+  return mAtomic.fetch_sub(1);
+}
+
+}  // namespace chre
+
+#endif  // CHRE_PLATFORM_FREERTOS_ATOMIC_BASE_IMPL_H_
diff --git a/platform/freertos/include/chre/target_platform/condition_variable_base.h b/platform/freertos/include/chre/target_platform/condition_variable_base.h
new file mode 100644
index 0000000..d290c62
--- /dev/null
+++ b/platform/freertos/include/chre/target_platform/condition_variable_base.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2019 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 CHRE_PLATFORM_FREERTOS_CONDITION_VARIABLE_BASE_H_
+#define CHRE_PLATFORM_FREERTOS_CONDITION_VARIABLE_BASE_H_
+
+#include "chre/platform/mutex.h"
+#include "chre/platform/system_timer.h"
+#include "chre/target_platform/fatal_error.h"
+
+#include "FreeRTOS.h"
+#include "semphr.h"
+
+namespace chre {
+
+/**
+ * @brief FreeRTOS implementation of the condition variable
+ *        using FreeRTOS primitives
+ *
+ * The following base class implements the CHRE ConditionVariable
+ * interface for FreeRTOS. Some points to be noted:
+ * - We have the benefit of not needing to support notify_all(),
+ *   as we only provide the notify_one() abstraction to common code.
+ * - We don't have the requirement where more than 1 thread can wait
+ *   on a given condition variable in parallel  (which kind of goes against the
+ *   CV name, but really we were looking for something that let us implement
+ *   @ref FixedSizeBlockingQueue while keeping familiarity to the C++11 thread
+ *   support library
+ * - Currently, there's exactly one user of the condition variable in the common
+ *   code, and that's the FixedSizeBlockingQueue used by the EventLoop. Hence,
+ *   we can codify assumptions that are well supported by the current code into
+ *   the API. One example of this would be ensuring the caller can handle
+ *   spurious wakeups (which is fine for CHRE) as the worst side effect of this
+ *   is an extra iteration of the while-empty-queue loop.
+ */
+class ConditionVariableBase {
+ public:
+  /**
+   * While in an interrupt context, unblock one thread that is waiting on this
+   * condition variable.
+   */
+  void notify_one_from_isr();
+
+ protected:
+  // Since, per CHRE specification, only one thread is ever
+  // going to be blocked on this condition variable, all we
+  // need is a Binary Semaphore. Note that:
+  // - while std::condition_variable allows for multiple threads
+  //   to wait on the same condition variable object
+  //   concurrently, the CHRE platform implementation is only required
+  //   to allow for a single waiting thread.
+  // - The calling code has to allow for spurious wakeups
+  SemaphoreHandle_t mCvSemaphoreHandle;
+
+  //! Buffer used to store state used by the semaphore.
+  StaticSemaphore_t mSemaphoreBuffer;
+
+  //! The timer used for timed condition variable wait.
+  SystemTimer mTimeoutTimer;
+
+  //! Set to true when the timeout timer is initialized.
+  bool mTimerInitialized = false;
+
+  //! Set to true if the timeout timer timed out.
+  bool mTimedOut = false;
+};
+
+}  // namespace chre
+
+#endif  // CHRE_PLATFORM_FREERTOS_CONDITION_VARIABLE_BASE_H_
diff --git a/platform/freertos/include/chre/target_platform/condition_variable_impl.h b/platform/freertos/include/chre/target_platform/condition_variable_impl.h
new file mode 100644
index 0000000..83dadae
--- /dev/null
+++ b/platform/freertos/include/chre/target_platform/condition_variable_impl.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2019 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 CHRE_PLATFORM_FREERTOS_CONDITION_VARIABLE_IMPL_H_
+#define CHRE_PLATFORM_FREERTOS_CONDITION_VARIABLE_IMPL_H_
+
+#include "chre/platform/condition_variable.h"
+
+namespace chre {
+
+inline ConditionVariable::ConditionVariable() {
+  mCvSemaphoreHandle = xSemaphoreCreateBinaryStatic(&mSemaphoreBuffer);
+  if (mCvSemaphoreHandle == NULL) {
+    FATAL_ERROR("failed to create cv semaphore");
+  }
+}
+
+inline ConditionVariable::~ConditionVariable() {
+  if (mCvSemaphoreHandle != NULL) {
+    vSemaphoreDelete(mCvSemaphoreHandle);
+  }
+}
+
+inline void ConditionVariable::notify_one() {
+  xSemaphoreGive(mCvSemaphoreHandle);
+}
+
+inline void ConditionVariable::wait(Mutex &mutex) {
+  mutex.unlock();
+  xSemaphoreTake(mCvSemaphoreHandle, portMAX_DELAY /* xBlockTime */);
+  mutex.lock();
+}
+
+inline bool ConditionVariable::wait_for(Mutex &mutex, Nanoseconds timeout) {
+  if (!mTimerInitialized) {
+    if (!mTimeoutTimer.init()) {
+      FATAL_ERROR("Failed to initialize condition variable timer");
+    } else {
+      mTimerInitialized = true;
+    }
+  }
+
+  // Reset semaphore in case notify_one was invoked twice previously. This can
+  // happen if the timer expires at the same time as the wait condition is met.
+  xQueueReset(mCvSemaphoreHandle);
+  mTimedOut = false;
+
+  auto callback = [](void *data) {
+    auto cbData = static_cast<ConditionVariable *>(data);
+    cbData->mTimedOut = true;
+    cbData->notify_one();
+  };
+  if (!mTimeoutTimer.set(callback, this, timeout)) {
+    LOGE("Failed to set condition variable timer");
+  }
+
+  wait(mutex);
+  if (mTimeoutTimer.isActive()) {
+    if (!mTimeoutTimer.cancel()) {
+      LOGD("Failed to cancel condition variable timer");
+    }
+  }
+  return !mTimedOut;
+}
+
+inline void ConditionVariableBase::notify_one_from_isr() {
+  BaseType_t xHigherPriorityTaskWoken = pdFALSE;
+  xSemaphoreGiveFromISR(mCvSemaphoreHandle, &xHigherPriorityTaskWoken);
+  portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
+}
+
+}  // namespace chre
+
+#endif  // CHRE_PLATFORM_FREERTOS_CONDITION_VARIABLE_IMPL_H_
diff --git a/platform/freertos/include/chre/target_platform/init.h b/platform/freertos/include/chre/target_platform/init.h
new file mode 100644
index 0000000..5eb90c9
--- /dev/null
+++ b/platform/freertos/include/chre/target_platform/init.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2020 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 CHRE_PLATFORM_FREERTOS_INIT_H_
+#define CHRE_PLATFORM_FREERTOS_INIT_H_
+
+#include "FreeRTOS.h"
+
+namespace chre {
+namespace freertos {
+
+/**
+ * This init function spawns a (non-privileged) FreeRTOS task that
+ * initializes the CHRE core, loads any static nanoapps, and starts
+ * the CHRE event loop.
+ * The task attribute constants are located in the corresponding init.cc
+ * source file, in case they need to be altered. The defaults chosen are:
+ * - Task Stack Depth: 8K Words
+ * - Task Priority: 1 (idle_task + 1)
+ *
+ * @return pdPASS on success, a FreeRTOS error code otherwise.
+ */
+BaseType_t init();
+
+/**
+ * Delete the task spawned in the init function
+ */
+void deinit();
+
+const char *getChreTaskName();
+
+}  // namespace freertos
+}  // namespace chre
+
+#endif  // CHRE_PLATFORM_FREERTOS_INIT_H_
diff --git a/platform/freertos/include/chre/target_platform/mutex_base.h b/platform/freertos/include/chre/target_platform/mutex_base.h
new file mode 100644
index 0000000..d26170a
--- /dev/null
+++ b/platform/freertos/include/chre/target_platform/mutex_base.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2019 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 CHRE_PLATFORM_FREERTOS_MUTEX_BASE_H_
+#define CHRE_PLATFORM_FREERTOS_MUTEX_BASE_H_
+
+#include "chre/platform/assert.h"
+#include "chre/platform/log.h"
+
+#include "FreeRTOS.h"
+#include "semphr.h"
+
+namespace chre {
+
+/**
+ * The FreeRTOS implementation of MutexBase
+ */
+class MutexBase {
+ protected:
+  SemaphoreHandle_t mSemaphoreHandle;
+
+  /**
+   * Initialize the mutex handle using a static semaphore
+   * to avoid heap allocations
+   */
+  void initStaticMutex() {
+    mSemaphoreHandle = xSemaphoreCreateMutexStatic(&mStaticSemaphore);
+    if (mSemaphoreHandle == NULL) {
+      FATAL_ERROR("Failed to initialize mutex");
+    }
+  }
+
+ private:
+  StaticSemaphore_t mStaticSemaphore;
+};
+
+}  // namespace chre
+
+#endif  // CHRE_PLATFORM_FREERTOS_MUTEX_BASE_H_
diff --git a/platform/freertos/include/chre/target_platform/mutex_base_impl.h b/platform/freertos/include/chre/target_platform/mutex_base_impl.h
new file mode 100644
index 0000000..23e957d
--- /dev/null
+++ b/platform/freertos/include/chre/target_platform/mutex_base_impl.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2019 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 CHRE_PLATFORM_FREERTOS_MUTEX_BASE_IMPL_H_
+#define CHRE_PLATFORM_FREERTOS_MUTEX_BASE_IMPL_H_
+
+#include "chre/platform/mutex.h"
+
+namespace chre {
+
+inline Mutex::Mutex() {
+  initStaticMutex();
+}
+
+inline Mutex::~Mutex() {
+  if (mSemaphoreHandle) {
+    vSemaphoreDelete(mSemaphoreHandle);
+  }
+}
+
+inline void Mutex::lock() {
+  TickType_t blockForever = portMAX_DELAY;
+  if (pdTRUE != xSemaphoreTake(mSemaphoreHandle, blockForever)) {
+    LOGE("Failed to lock mutex");
+  }
+}
+
+inline bool Mutex::try_lock() {
+  TickType_t doNotBlock = static_cast<TickType_t>(0);
+  BaseType_t rv = xSemaphoreTake(mSemaphoreHandle, doNotBlock);
+
+  return (rv == pdTRUE) ? true : false;
+}
+
+inline void Mutex::unlock() {
+  if (pdTRUE != xSemaphoreGive(mSemaphoreHandle)) {
+    LOGE("Failed to properly unlock mutex!");
+  }
+}
+
+}  // namespace chre
+
+#endif  // CHRE_PLATFORM_FREERTOS_MUTEX_BASE_IMPL_H_
diff --git a/platform/freertos/include/chre/target_platform/platform_debug_dump_manager_base.h b/platform/freertos/include/chre/target_platform/platform_debug_dump_manager_base.h
new file mode 100644
index 0000000..5c2399c
--- /dev/null
+++ b/platform/freertos/include/chre/target_platform/platform_debug_dump_manager_base.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2020 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 CHRE_PLATFORM_FREERTOS_PLATFORM_DEBUG_DUMP_MANAGER_BASE_H_
+#define CHRE_PLATFORM_FREERTOS_PLATFORM_DEBUG_DUMP_MANAGER_BASE_H_
+
+#include <cstdbool>
+#include <cstddef>
+#include <cstdint>
+
+namespace chre {
+
+/**
+ * FreeRTOS-specific debug dump functionality.
+ */
+class PlatformDebugDumpManagerBase {
+ public:
+  /**
+   * To be called when receiving a debug dump request from host.
+   *
+   * @param hostClientId The host client ID that requested the debug dump.
+   */
+  void onDebugDumpRequested(uint16_t hostClientId);
+
+  /**
+   * @see PlatformDebugDumpManager::sendDebugDump
+   */
+  void sendDebugDumpResult(const char *debugStr, size_t debugStrSize,
+                           bool complete);
+
+ protected:
+  //! Host client ID that triggered the debug dump process.
+  uint16_t mHostClientId = 0;
+
+  //! Number of times sendDebugDumpToHost called with debugStrSize > 0.
+  uint32_t mDataCount = 0;
+
+  //! Whenther the last debug dump session has been marked complete.
+  bool mComplete = true;
+
+  static constexpr size_t kDebugDumpStrMaxSize = CHRE_MESSAGE_TO_HOST_MAX_SIZE;
+};
+
+}  // namespace chre
+
+#endif  // CHRE_PLATFORM_SLPI_PLATFORM_DEBUG_DUMP_MANAGER_BASE_H_
diff --git a/platform/freertos/include/chre/target_platform/platform_nanoapp_base.h b/platform/freertos/include/chre/target_platform/platform_nanoapp_base.h
new file mode 100644
index 0000000..42541e0
--- /dev/null
+++ b/platform/freertos/include/chre/target_platform/platform_nanoapp_base.h
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2019 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 CHRE_PLATFORM_FREERTOS_PLATFORM_NANOAPP_BASE_H_
+#define CHRE_PLATFORM_FREERTOS_PLATFORM_NANOAPP_BASE_H_
+
+#include "chre/platform/shared/memory.h"
+#include "chre/platform/shared/nanoapp_support_lib_dso.h"
+
+namespace chre {
+
+/**
+ * FREERTOS-specific nanoapp functionality.
+ */
+class PlatformNanoappBase {
+ public:
+  /**
+   * Associate this Nanoapp instance with a nanoapp that is statically built
+   * into the CHRE binary with the given app info structure.
+   */
+  void loadStatic(const struct chreNslNanoappInfo *appInfo);
+
+  /**
+   * @return true if the app's binary data is resident in memory or if the app's
+   *         filename is saved, i.e. all binary fragments are loaded through
+   *         copyNanoappFragment, loadFromFile/loadStatic() was successful
+   */
+  bool isLoaded() const;
+
+  /**
+   * @return true if this app is loaded into TCM.
+   */
+  bool isTcmApp() const;
+
+  /**
+   * Sets app info that will be used later when the app is loaded into the
+   * system.
+   *
+   * @param appId The unique app identifier associated with this binary
+   * @param appVersion An application-defined version number
+   * @param appFilename The filename of the app that should be loaded from disk
+   *
+   * @return true if the info was successfully stored
+   */
+  bool setAppInfo(uint64_t appId, uint32_t appVersion, const char *appFilename);
+
+  /**
+   * Reserves buffer space for a nanoapp's binary. This method should be called
+   * before copyNanoappFragment is called.
+   *
+   * @param appId The unique app identifier associated with this binary
+   * @param appVersion An application-defined version number
+   * @param appFlags The flags provided by the app being loaded
+   * @param appBinaryLen Size of appBinary, in bytes
+   *
+   * @return true if the allocation was successful, false otherwise
+   */
+  bool reserveBuffer(uint64_t appId, uint32_t appVersion, uint32_t appFlags,
+                     size_t appBinaryLen);
+
+  /**
+   * Copies the (possibly fragmented) application binary data into the allocated
+   * buffer, and updates the pointer to the next address to write into. The
+   * application may be invalid - full checking and initialization happens just
+   * before invoking start() nanoapp entry point.
+   *
+   * @param buffer The pointer to the buffer
+   * @param bufferSize The size of the buffer in bytes
+   *
+   * @return true if the reserved buffer did not overflow, false otherwise
+   */
+  bool copyNanoappFragment(const void *buffer, size_t bufferSize);
+
+ protected:
+  //! The app ID we received in the metadata alongside the nanoapp binary. This
+  //! is also included in (and checked against) mAppInfo.
+  uint64_t mExpectedAppId;
+
+  //! The application-defined version number we received in the metadata
+  //! alongside the nanoapp binary. This is also included in (and checked
+  //! against) mAppInfo.
+  uint32_t mExpectedAppVersion = 0;
+
+  //! Whether the nanoapp is expected to be loaded into TCM.
+  bool mExpectedTcmCapable = false;
+
+  //! Whether this nanoapp is loaded into TCM.
+  bool mIsTcmNanoapp = false;
+
+  //! Buffer containing the complete DSO binary - only populated if
+  //! copyNanoappFragment() was used to load this nanoapp
+  void *mAppBinary = nullptr;
+  size_t mAppBinaryLen = 0;
+
+  //! Null-terminated ASCII string containing the file name that contains the
+  //! app binary to be loaded. This is used over mAppBinary to load the nanoapp
+  //! if set.
+  char *mAppFilename = nullptr;
+
+  //! The dynamic shared object (DSO) handle returned by dlopenbuf()
+  void *mDsoHandle = nullptr;
+
+  //! Pointer to the app info structure within this nanoapp
+  const struct chreNslNanoappInfo *mAppInfo = nullptr;
+
+  //! Pointer containing the unstable ID section for this nanoapp
+  const char *mAppUnstableId = nullptr;
+
+  //! Set to true if this app is built into the CHRE binary, and was loaded via
+  //! loadStatic(). In this case, the member variables above are not valid or
+  //! applicable.
+  bool mIsStatic = false;
+
+  //! The number of bytes of the binary that has been loaded so far.
+  size_t mBytesLoaded = 0;
+
+  /**
+   * Loads the nanoapp symbols from the currently loaded binary and verifies
+   * they match the expected information the nanoapp should have.
+   *
+   * @return true if the app info structure passed validation.
+   */
+  bool verifyNanoappInfo();
+
+  /**
+   * Calls through to openNanoappFromBuffer or openNanoappFromFile, depending on
+   * how this nanoapp was loaded.
+   */
+  bool openNanoapp();
+
+  /**
+   * Releases the DSO handle if it was active, by calling dlclose(). This will
+   * result in execution of any unload handlers in the nanoapp.
+   */
+  void closeNanoapp();
+
+  /**
+   * Retrieves the nanoapp's version string. This is intended to be a human
+   * readable version string to aid in debugging. This must always return a
+   * valid string so if none is available it is recommended to return
+   * "<undefined>" or similar.
+   *
+   * @param length The length of the returned version string
+   * @return A char array containing the version string for this nanoapp.
+   */
+  const char *getAppVersionString(size_t *length) const;
+
+  /** If this app needs to access DRAM to function, enables DRAM access. */
+  inline void enableDramAccessIfRequired() const {
+    if (!isTcmApp()) {
+      forceDramAccess();
+    }
+  }
+};
+
+}  // namespace chre
+
+#endif  // CHRE_PLATFORM_FREERTOS_PLATFORM_NANOAPP_BASE_H_
diff --git a/platform/freertos/include/chre/target_platform/static_nanoapp_init.h b/platform/freertos/include/chre/target_platform/static_nanoapp_init.h
new file mode 100644
index 0000000..75d3531
--- /dev/null
+++ b/platform/freertos/include/chre/target_platform/static_nanoapp_init.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2019 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 CHRE_PLATFORM_FREERTOS_STATIC_NANOAPP_INIT_H_
+#define CHRE_PLATFORM_FREERTOS_STATIC_NANOAPP_INIT_H_
+
+#include "chre/core/nanoapp.h"
+#include "chre/platform/fatal_error.h"
+#include "chre/util/unique_ptr.h"
+
+#define CHRE_STATIC_NANOAPP_INIT(appName, appId_, appVersion_)               \
+  namespace chre {                                                           \
+                                                                             \
+  UniquePtr<Nanoapp> initializeStaticNanoapp##appName() {                    \
+    UniquePtr<Nanoapp> nanoapp = MakeUnique<Nanoapp>();                      \
+    static struct chreNslNanoappInfo appInfo;                                \
+    appInfo.magic = CHRE_NSL_NANOAPP_INFO_MAGIC;                             \
+    appInfo.structMinorVersion = CHRE_NSL_NANOAPP_INFO_STRUCT_MINOR_VERSION; \
+    appInfo.targetApiVersion = CHRE_API_VERSION;                             \
+    appInfo.vendor = "Google"; /* TODO: make this configurable */            \
+    appInfo.name = #appName;                                                 \
+    appInfo.isSystemNanoapp = true;                                          \
+    appInfo.isTcmNanoapp = false;                                            \
+    appInfo.appId = appId_;                                                  \
+    appInfo.appVersion = appVersion_;                                        \
+    appInfo.entryPoints.start = nanoappStart;                                \
+    appInfo.entryPoints.handleEvent = nanoappHandleEvent;                    \
+    appInfo.entryPoints.end = nanoappEnd;                                    \
+    appInfo.appVersionString = "<undefined>";                                \
+    if (nanoapp.isNull()) {                                                  \
+      FATAL_ERROR("Failed to allocate nanoapp " #appName);                   \
+    } else {                                                                 \
+      nanoapp->loadStatic(&appInfo);                                         \
+    }                                                                        \
+                                                                             \
+    return nanoapp;                                                          \
+  }                                                                          \
+  } /* namespace chre */
+
+#endif  // CHRE_PLATFORM_FREERTOS_NANOAPP_INIT_H_
diff --git a/platform/freertos/init.cc b/platform/freertos/init.cc
new file mode 100644
index 0000000..c1cd445
--- /dev/null
+++ b/platform/freertos/init.cc
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2020 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 "chre/core/init.h"
+
+#include "chpp/platform/chpp_init.h"
+#include "chre/core/event_loop_manager.h"
+#include "chre/core/static_nanoapps.h"
+#include "chre/platform/shared/dram_vote_client.h"
+#include "chre/target_platform/init.h"
+
+#include "task.h"
+
+namespace chre {
+namespace freertos {
+namespace {
+
+constexpr configSTACK_DEPTH_TYPE kChreTaskStackDepthWords = 0x800;
+
+constexpr UBaseType_t kChreTaskPriority = tskIDLE_PRIORITY + 1;
+
+TaskHandle_t gChreTaskHandle;
+
+// This function is intended to be the task action function for FreeRTOS.
+// It Initializes CHRE, runs the event loop, and only exits if it receives
+// a message to shutdown. Note that depending on the hardware platform this
+// runs on, CHRE might create additional threads, which are cleaned up when
+// CHRE exits.
+void chreThreadEntry(void *context) {
+  DramVoteClientSingleton::init();
+
+  chre::init();
+  chre::EventLoopManagerSingleton::get()->lateInit();
+  chre::loadStaticNanoapps();
+
+  chre::EventLoopManagerSingleton::get()->getEventLoop().run();
+
+  // we only get here if the CHRE EventLoop exited
+  chre::deinit();
+
+  DramVoteClientSingleton::deinit();
+
+  vTaskDelete(nullptr);
+  gChreTaskHandle = nullptr;
+}
+
+}  // namespace
+
+BaseType_t init() {
+  BaseType_t rc = xTaskCreate(chreThreadEntry, freertos::getChreTaskName(),
+                              kChreTaskStackDepthWords, nullptr /* args */,
+                              kChreTaskPriority, &gChreTaskHandle);
+  CHRE_ASSERT(rc == pdPASS);
+
+  chpp::init();
+
+  return rc;
+}
+
+void deinit() {
+  // On a deinit call, we just stop the CHRE event loop. This causes the 'run'
+  // method in the task function exit, and move on to handle task cleanup
+  if (gChreTaskHandle != nullptr) {
+    chre::EventLoopManagerSingleton::get()->getEventLoop().stop();
+  }
+
+  chpp::deinit();
+}
+
+const char *getChreTaskName() {
+  static constexpr char kChreTaskName[] = "CHRE";
+  return kChreTaskName;
+}
+
+}  // namespace freertos
+}  // namespace chre
diff --git a/platform/freertos/memory.cc b/platform/freertos/memory.cc
new file mode 100644
index 0000000..ada6702
--- /dev/null
+++ b/platform/freertos/memory.cc
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2019 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 "chre/platform/memory.h"
+#include "chre/platform/shared/pal_system_api.h"
+
+#include <cstdlib>
+
+namespace chre {
+
+void *memoryAlloc(size_t size) {
+  return malloc(size);
+}
+
+void *palSystemApiMemoryAlloc(size_t size) {
+  return malloc(size);
+}
+
+void memoryFree(void *pointer) {
+  free(pointer);
+}
+
+void palSystemApiMemoryFree(void *pointer) {
+  free(pointer);
+}
+
+}  // namespace chre
diff --git a/platform/freertos/memory_manager.cc b/platform/freertos/memory_manager.cc
new file mode 100644
index 0000000..d3e5b10
--- /dev/null
+++ b/platform/freertos/memory_manager.cc
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2019 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 "chre/platform/memory_manager.h"
+#include "chre/platform/shared/memory.h"
+#include "chre/util/memory.h"
+
+namespace chre {
+
+void *MemoryManager::doAlloc(Nanoapp *app, uint32_t bytes) {
+  if (app->isTcmApp()) {
+    return chre::memoryAlloc(bytes);
+  } else {
+    return chre::memoryAllocDram(bytes);
+  }
+}
+
+void MemoryManager::doFree(Nanoapp *app, void *ptr) {
+  if (app->isTcmApp()) {
+    chre::memoryFree(ptr);
+  } else {
+    chre::memoryFreeDram(ptr);
+  }
+}
+
+}  // namespace chre
diff --git a/platform/freertos/platform_debug_dump_manager.cc b/platform/freertos/platform_debug_dump_manager.cc
new file mode 100644
index 0000000..056817e
--- /dev/null
+++ b/platform/freertos/platform_debug_dump_manager.cc
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2020 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 "chre/platform/platform_debug_dump_manager.h"
+
+#include "chre/core/event_loop_manager.h"
+#include "chre/platform/log.h"
+#include "chre/target_platform/host_link_base.h"
+#include "chre/target_platform/platform_debug_dump_manager_base.h"
+
+#include <cstring>
+
+namespace chre {
+
+void PlatformDebugDumpManager::sendDebugDump(const char *debugStr,
+                                             bool complete) {
+  // DDM is guaranteed to call complete=true at the end of a debug dump session.
+  // However, sendDebugDumpResult may not get called with complete=true.
+  // Therefore, mDataCount has to be reset here instead of in
+  // sendDebugDumpResult(), to properly start the next debug dump session.
+  if (mComplete) {
+    mDataCount = 0;
+  }
+  mComplete = complete;
+
+  sendDebugDumpResult(debugStr, strlen(debugStr), complete);
+}
+
+void PlatformDebugDumpManagerBase::onDebugDumpRequested(uint16_t hostClientId) {
+  mHostClientId = hostClientId;
+
+  EventLoopManagerSingleton::get()->getDebugDumpManager().trigger();
+}
+
+void PlatformDebugDumpManagerBase::sendDebugDumpResult(const char *debugStr,
+                                                       size_t debugStrSize,
+                                                       bool complete) {
+  if (debugStrSize > 0) {
+    mDataCount++;
+  }
+  sendDebugDumpResultToHost(mHostClientId, debugStr, debugStrSize, complete,
+                            mDataCount);
+}
+
+}  // namespace chre
diff --git a/platform/freertos/platform_nanoapp.cc b/platform/freertos/platform_nanoapp.cc
new file mode 100644
index 0000000..d2de853
--- /dev/null
+++ b/platform/freertos/platform_nanoapp.cc
@@ -0,0 +1,301 @@
+/*
+ * Copyright (C) 2019 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 "chre/platform/platform_nanoapp.h"
+
+#include <dlfcn.h>
+#include <cinttypes>
+
+#include "chre/platform/assert.h"
+#include "chre/platform/log.h"
+#include "chre/platform/shared/authentication.h"
+#include "chre/platform/shared/nanoapp_dso_util.h"
+#include "chre/platform/shared/nanoapp_loader.h"
+#include "chre/util/macros.h"
+#include "chre/util/system/napp_header_utils.h"
+#include "chre_api/chre/version.h"
+
+namespace chre {
+namespace {
+
+const char kDefaultAppVersionString[] = "<undefined>";
+size_t kDefaultAppVersionStringSize = ARRAY_SIZE(kDefaultAppVersionString);
+
+}  // namespace
+
+PlatformNanoapp::~PlatformNanoapp() {
+  closeNanoapp();
+
+  if (mAppBinary != nullptr) {
+    forceDramAccess();
+    memoryFreeDram(mAppBinary);
+  }
+}
+
+bool PlatformNanoapp::start() {
+  //! Always force DRAM access when starting since nanoapps are loaded via DRAM.
+  forceDramAccess();
+
+  bool success = false;
+  if (!openNanoapp()) {
+    LOGE("Failed to open nanoapp");
+  } else if (mAppInfo == nullptr) {
+    LOGE("Null app info!");
+  } else {
+    success = mAppInfo->entryPoints.start();
+  }
+
+  return success;
+}
+
+void PlatformNanoapp::handleEvent(uint32_t senderInstanceId, uint16_t eventType,
+                                  const void *eventData) {
+  enableDramAccessIfRequired();
+  mAppInfo->entryPoints.handleEvent(senderInstanceId, eventType, eventData);
+}
+
+void PlatformNanoapp::end() {
+  enableDramAccessIfRequired();
+  mAppInfo->entryPoints.end();
+  closeNanoapp();
+}
+
+uint64_t PlatformNanoapp::getAppId() const {
+  // TODO (karthikmb/stange): Ideally, we should store the metadata as
+  // variables in TCM, to avoid bumping into DRAM for basic queries.
+  enableDramAccessIfRequired();
+  return (mAppInfo != nullptr) ? mAppInfo->appId : mExpectedAppId;
+}
+
+uint32_t PlatformNanoapp::getAppVersion() const {
+  enableDramAccessIfRequired();
+  return (mAppInfo != nullptr) ? mAppInfo->appVersion : mExpectedAppVersion;
+}
+
+const char *PlatformNanoapp::getAppName() const {
+  enableDramAccessIfRequired();
+  return (mAppInfo != nullptr) ? mAppInfo->name : "Unknown";
+}
+
+uint32_t PlatformNanoapp::getTargetApiVersion() const {
+  enableDramAccessIfRequired();
+  return (mAppInfo != nullptr) ? mAppInfo->targetApiVersion : 0;
+}
+
+bool PlatformNanoapp::isSystemNanoapp() const {
+  enableDramAccessIfRequired();
+  return (mAppInfo != nullptr && mAppInfo->isSystemNanoapp);
+}
+
+void PlatformNanoapp::logStateToBuffer(DebugDumpWrapper &debugDump) const {
+  if (mAppInfo != nullptr) {
+    size_t versionLen = 0;
+    const char *version = getAppVersionString(&versionLen);
+    debugDump.print("%s (%s) @ build: %.*s", mAppInfo->name, mAppInfo->vendor,
+                    versionLen, version);
+  }
+}
+
+const char *PlatformNanoappBase::getAppVersionString(size_t *length) const {
+  const char *versionString = kDefaultAppVersionString;
+  *length = kDefaultAppVersionStringSize;
+
+  size_t endOffset = 0;
+  if (mAppUnstableId != nullptr) {
+    size_t appVersionStringLength = strlen(mAppUnstableId);
+
+    //! The unstable ID is expected to be in the format of
+    //! <descriptor>=<build ID>@<more descriptors>. Use this expected layout
+    //! knowledge to parse the string and only return the build ID portion that
+    //! should be printed.
+    size_t startOffset = SIZE_MAX;
+    for (size_t i = 0; i < appVersionStringLength; i++) {
+      size_t offset = i + 1;
+      if (startOffset == SIZE_MAX && mAppUnstableId[i] == '=' &&
+          offset < appVersionStringLength) {
+        startOffset = offset;
+      }
+
+      if (mAppUnstableId[i] == '@') {
+        endOffset = i;
+        break;
+      }
+    }
+
+    if (startOffset < endOffset) {
+      versionString = &mAppUnstableId[startOffset];
+      *length = endOffset - startOffset;
+    }
+  }
+
+  return versionString;
+}
+
+bool PlatformNanoappBase::isLoaded() const {
+  return (mIsStatic ||
+          (mAppBinary != nullptr && mBytesLoaded == mAppBinaryLen) ||
+          mDsoHandle != nullptr || mAppFilename != nullptr);
+}
+
+bool PlatformNanoappBase::isTcmApp() const {
+  return mIsTcmNanoapp;
+}
+
+void PlatformNanoappBase::loadStatic(const struct chreNslNanoappInfo *appInfo) {
+  CHRE_ASSERT(!isLoaded());
+  mIsStatic = true;
+  mAppInfo = appInfo;
+}
+
+bool PlatformNanoappBase::reserveBuffer(uint64_t appId, uint32_t appVersion,
+                                        uint32_t appFlags,
+                                        size_t appBinaryLen) {
+  CHRE_ASSERT(!isLoaded());
+
+  forceDramAccess();
+
+  bool success = false;
+  mAppBinary = memoryAllocDram(appBinaryLen);
+
+  bool isSigned = IS_BIT_SET(appFlags, CHRE_NAPP_HEADER_SIGNED);
+  if (!isSigned) {
+    LOGE("Unable to load unsigned nanoapps");
+  } else if (mAppBinary == nullptr) {
+    LOG_OOM();
+  } else {
+    bool tcmCapable = IS_BIT_SET(appFlags, CHRE_NAPP_HEADER_TCM_CAPABLE);
+    mExpectedAppId = appId;
+    mExpectedAppVersion = appVersion;
+    mExpectedTcmCapable = tcmCapable;
+    mAppBinaryLen = appBinaryLen;
+    success = true;
+  }
+
+  return success;
+}
+
+bool PlatformNanoappBase::copyNanoappFragment(const void *buffer,
+                                              size_t bufferLen) {
+  CHRE_ASSERT(!isLoaded());
+
+  forceDramAccess();
+
+  bool success = true;
+
+  if ((mBytesLoaded + bufferLen) > mAppBinaryLen) {
+    LOGE("Overflow: cannot load %zu bytes to %zu/%zu nanoapp binary buffer",
+         bufferLen, mBytesLoaded, mAppBinaryLen);
+    success = false;
+  } else {
+    uint8_t *binaryBuffer = static_cast<uint8_t *>(mAppBinary) + mBytesLoaded;
+    memcpy(binaryBuffer, buffer, bufferLen);
+    mBytesLoaded += bufferLen;
+  }
+
+  return success;
+}
+
+bool PlatformNanoappBase::verifyNanoappInfo() {
+  bool success = false;
+
+  if (mDsoHandle == nullptr) {
+    LOGE("No nanoapp info to verify");
+  } else {
+    mAppInfo = static_cast<const struct chreNslNanoappInfo *>(
+        dlsym(mDsoHandle, CHRE_NSL_DSO_NANOAPP_INFO_SYMBOL_NAME));
+    mAppUnstableId = static_cast<const char *>(
+        dlsym(mDsoHandle, CHRE_NSL_DSO_NANOAPP_UNSTABLE_ID_SYMBOL_NAME));
+    if (mAppInfo == nullptr) {
+      LOGE("Failed to find app info symbol");
+    } else if (mAppUnstableId == nullptr) {
+      LOGE("Failed to find unstable ID symbol");
+    } else {
+      success = validateAppInfo(mExpectedAppId, mExpectedAppVersion, mAppInfo);
+      if (success && mAppInfo->isTcmNanoapp != mExpectedTcmCapable) {
+        success = false;
+        LOGE("Expected TCM nanoapp %d found %d", mExpectedTcmCapable,
+             mAppInfo->isTcmNanoapp);
+      }
+
+      if (!success) {
+        mAppInfo = nullptr;
+      } else {
+        LOGI("Successfully loaded nanoapp: %s (0x%016" PRIx64
+             ") version 0x%" PRIx32 " uimg %d system %d",
+             mAppInfo->name, mAppInfo->appId, mAppInfo->appVersion,
+             mAppInfo->isTcmNanoapp, mAppInfo->isSystemNanoapp);
+      }
+    }
+  }
+  return success;
+}
+
+bool PlatformNanoappBase::openNanoapp() {
+  bool success = false;
+  if (mIsStatic) {
+    success = true;
+  } else if (mAppBinary != nullptr) {
+    //! The true start of the binary will be after the authentication header.
+    //! Use the returned value from authenticateBinary to ensure dlopenbuf has
+    //! the starting address to a valid ELF.
+    void *binaryStart = mAppBinary;
+
+    // TODO(158770259): Enforce this and make the log message and error once all
+    // nanoapp binaries are signed.
+    if (!authenticateBinary(mAppBinary, &binaryStart)) {
+      LOGV("Unable to authenticate 0x%" PRIx32 " not stopping loading for now",
+           mExpectedAppId);
+    }
+    if (mDsoHandle == nullptr) {
+      mDsoHandle = dlopenbuf(binaryStart, mExpectedTcmCapable);
+      success = verifyNanoappInfo();
+    } else {
+      LOGE("Trying to reopen an existing buffer");
+    }
+  }
+
+  if (!success) {
+    closeNanoapp();
+  }
+
+  if (mAppBinary != nullptr) {
+    memoryFreeDram(mAppBinary);
+    mAppBinary = nullptr;
+  }
+
+  // Save this flag locally since it may be referenced while the system is in
+  // TCM-only mode.
+  if (mAppInfo != nullptr) {
+    mIsTcmNanoapp = mAppInfo->isTcmNanoapp;
+  }
+
+  return success;
+}
+
+void PlatformNanoappBase::closeNanoapp() {
+  if (mDsoHandle != nullptr) {
+    // Force DRAM access since dl* functions are only safe to call with DRAM
+    // available.
+    forceDramAccess();
+    mAppInfo = nullptr;
+    if (dlclose(mDsoHandle) != 0) {
+      LOGE("dlclose failed");
+    }
+    mDsoHandle = nullptr;
+  }
+}
+
+}  // namespace chre
diff --git a/platform/platform.mk b/platform/platform.mk
index e170fa8..19467b7 100644
--- a/platform/platform.mk
+++ b/platform/platform.mk
@@ -26,6 +26,9 @@
 SLPI_CFLAGS += -I$(SLPI_PREFIX)/platform/inc/a1std
 SLPI_CFLAGS += -I$(SLPI_PREFIX)/platform/inc/stddef
 SLPI_CFLAGS += -I$(SLPI_PREFIX)/platform/rtld/inc
+SLPI_CFLAGS += -I$(SLPI_PREFIX)/ssc/goog/api
+SLPI_CFLAGS += -I$(SLPI_PREFIX)/ssc/inc
+SLPI_CFLAGS += -I$(SLPI_PREFIX)/ssc/inc/internal
 
 SLPI_CFLAGS += -Iplatform/shared/include
 SLPI_CFLAGS += -Iplatform/slpi/include
@@ -33,6 +36,10 @@
 # We use FlatBuffers in the SLPI platform layer
 SLPI_CFLAGS += $(FLATBUFFERS_CFLAGS)
 
+# Needed to define __SIZEOF_ATTR_THREAD in sns_osa_thread.h, included in
+# sns_memmgr.h.
+SLPI_CFLAGS += -DSSC_TARGET_HEXAGON
+
 ifneq ($(CHRE_ENABLE_ACCEL_CAL), false)
 SLPI_CFLAGS += -DCHRE_ENABLE_ACCEL_CAL
 endif
@@ -49,19 +56,12 @@
 SLPI_SEE_CFLAGS += -I$(SLPI_PREFIX)/qmimsgs/common/api
 SLPI_SEE_CFLAGS += -I$(SLPI_PREFIX)/ssc_api/pb
 SLPI_SEE_CFLAGS += -I$(SLPI_PREFIX)/ssc/framework/cm/inc
-SLPI_SEE_CFLAGS += -I$(SLPI_PREFIX)/ssc/goog/api
-SLPI_SEE_CFLAGS += -I$(SLPI_PREFIX)/ssc/inc
-SLPI_SEE_CFLAGS += -I$(SLPI_PREFIX)/ssc/inc/internal
 SLPI_SEE_CFLAGS += -I$(SLPI_PREFIX)/ssc/inc/utils/nanopb
 
 SLPI_SEE_CFLAGS += -Iplatform/slpi/see/include
 
 SLPI_SEE_CFLAGS += -DCHRE_SLPI_SEE
 
-# Needed to define __SIZEOF_ATTR_THREAD in sns_osa_thread.h, included in
-# sns_memmgr.h.
-SLPI_SEE_CFLAGS += -DSSC_TARGET_HEXAGON
-
 # Defined in slpi_proc/ssc/build/ssc.scons
 SLPI_SEE_CFLAGS += -DPB_FIELD_16BIT
 
@@ -153,6 +153,140 @@
 SLPI_SEE_SRCS += platform/slpi/see/island_vote_client.cc
 endif
 
+# FreeRTOS-specific Source Files ###############################################
+
+FREERTOS_SRCS += platform/freertos/context.cc
+FREERTOS_SRCS += platform/freertos/init.cc
+FREERTOS_SRCS += platform/freertos/memory_manager.cc
+FREERTOS_SRCS += platform/freertos/platform_debug_dump_manager.cc
+FREERTOS_SRCS += platform/freertos/platform_nanoapp.cc
+
+# Optional FreeRTOS-specific Source Files ######################################
+
+# memory.cc is only needed if the given platform doesn't have its own memory
+# allocation setup.
+FREERTOS_OPTIONAL_SRCS += platform/freertos/memory.cc
+
+# FreeRTOS-specific Compiler Flags #############################################
+FREERTOS_CFLAGS += -I$(AOC_TOP_DIR)/external/FreeRTOS/FreeRTOS/Source/include
+FREERTOS_CFLAGS += -Iplatform/freertos/include
+FREERTOS_CFLAGS += -Iplatform/shared/include
+
+# AoC-specific Source Files ####################################################
+
+AOC_SRCS += chpp/transport.c
+AOC_SRCS += chpp/app.c
+AOC_SRCS += chpp/services.c
+AOC_SRCS += chpp/services/discovery.c
+AOC_SRCS += chpp/services/loopback.c
+AOC_SRCS += chpp/services/nonhandle.c
+AOC_SRCS += chpp/clients.c
+AOC_SRCS += chpp/clients/discovery.c
+AOC_SRCS += chpp/clients/loopback.c
+AOC_SRCS += chpp/platform/pal_api.c
+AOC_SRCS += chpp/platform/aoc/condition_variable.cc
+AOC_SRCS += chpp/platform/aoc/link.cc
+AOC_SRCS += chpp/platform/aoc/chpp_init.cc
+AOC_SRCS += chpp/platform/aoc/chpp_task_util.cc
+AOC_SRCS += chpp/platform/aoc/chpp_uart_link_manager.cc
+AOC_SRCS += chpp/platform/aoc/memory.cc
+AOC_SRCS += chpp/platform/aoc/notifier.cc
+AOC_SRCS += chpp/platform/aoc/time.cc
+AOC_SRCS += platform/aoc/audio_controller.cc
+AOC_SRCS += platform/aoc/audio_filter.cc
+AOC_SRCS += platform/aoc/chre_api_re.cc
+AOC_SRCS += platform/aoc/dram_vote_client.cc
+AOC_SRCS += platform/aoc/fatal_error.cc
+AOC_SRCS += platform/aoc/host_link.cc
+AOC_SRCS += platform/aoc/log.cc
+AOC_SRCS += platform/aoc/memory.cc
+AOC_SRCS += platform/aoc/power_control_manager.cc
+AOC_SRCS += platform/aoc/platform_audio.cc
+AOC_SRCS += platform/aoc/platform_cache_management.cc
+AOC_SRCS += platform/aoc/platform_pal.cc
+AOC_SRCS += platform/aoc/system_time.cc
+AOC_SRCS += platform/aoc/system_timer.cc
+AOC_SRCS += platform/shared/chre_api_audio.cc
+AOC_SRCS += platform/shared/chre_api_core.cc
+AOC_SRCS += platform/shared/chre_api_gnss.cc
+AOC_SRCS += platform/shared/chre_api_re.cc
+AOC_SRCS += platform/shared/chre_api_sensor.cc
+AOC_SRCS += platform/shared/chre_api_version.cc
+AOC_SRCS += platform/shared/chre_api_wifi.cc
+AOC_SRCS += platform/shared/chre_api_wwan.cc
+AOC_SRCS += platform/shared/dlfcn.cc
+AOC_SRCS += platform/shared/dram_vote_client.cc
+AOC_SRCS += platform/shared/host_protocol_chre.cc
+AOC_SRCS += platform/shared/host_protocol_common.cc
+AOC_SRCS += platform/shared/memory_manager.cc
+AOC_SRCS += platform/shared/nanoapp_loader.cc
+AOC_SRCS += platform/shared/nanoapp_load_manager.cc
+AOC_SRCS += platform/shared/pal_system_api.cc
+AOC_SRCS += platform/shared/pal_sensor_stub.cc
+AOC_SRCS += platform/shared/system_time.cc
+AOC_SRCS += platform/shared/nanoapp/nanoapp_dso_util.cc
+AOC_SRCS += platform/usf/platform_sensor.cc
+AOC_SRCS += platform/usf/platform_sensor_manager.cc
+AOC_SRCS += platform/usf/platform_sensor_type_helpers.cc
+AOC_SRCS += platform/usf/usf_helper.cc
+
+# Optional GNSS support.
+ifeq ($(CHRE_GNSS_SUPPORT_ENABLED), true)
+AOC_SRCS += platform/shared/platform_gnss.cc
+AOC_SRCS += chpp/clients/gnss.c
+AOC_CFLAGS += -DCHPP_CLIENT_ENABLED_CHRE_GNSS
+endif
+
+# Optional Wi-Fi support.
+ifeq ($(CHRE_WIFI_SUPPORT_ENABLED), true)
+AOC_SRCS += platform/shared/platform_wifi.cc
+AOC_SRCS += chpp/clients/wifi.c
+AOC_CFLAGS += -DCHPP_CLIENT_ENABLED_CHRE_WIFI
+endif
+
+# Optional WWAN support.
+ifeq ($(CHRE_WWAN_SUPPORT_ENABLED), true)
+AOC_SRCS += platform/shared/platform_wwan.cc
+AOC_SRCS += chpp/clients/wwan.c
+AOC_CFLAGS += -DCHPP_CLIENT_ENABLED_CHRE_WWAN
+endif
+
+# AoC-specific Compiler Flags ##################################################
+AOC_CFLAGS += -Iplatform/aoc/include
+AOC_CFLAGS += -Iplatform/usf/include
+AOC_CFLAGS += -I$(AOC_AUTOGEN_DIR)/regions
+AOC_CFLAGS += -I$(AOC_TOP_DIR)/AOC
+AOC_CFLAGS += -I$(AOC_TOP_DIR)/AOC/apps
+AOC_CFLAGS += -I$(AOC_TOP_DIR)/AOC/core/arm/generic/include
+AOC_CFLAGS += -I$(AOC_TOP_DIR)/AOC/drivers/gpio/aoc/include
+AOC_CFLAGS += -I$(AOC_TOP_DIR)/AOC/drivers/gpio/base/include
+AOC_CFLAGS += -I$(AOC_TOP_DIR)/AOC/drivers/gpi/aoc/include
+AOC_CFLAGS += -I$(AOC_TOP_DIR)/AOC/drivers/gpi/base/include
+AOC_CFLAGS += -I$(AOC_TOP_DIR)/AOC/drivers/processor
+AOC_CFLAGS += -I$(AOC_TOP_DIR)/AOC/drivers/processor/aoc/include
+AOC_CFLAGS += -I$(AOC_TOP_DIR)/AOC/drivers/sys_mem/base/include
+AOC_CFLAGS += -I$(AOC_TOP_DIR)/AOC/efw/include
+AOC_CFLAGS += -I$(AOC_TOP_DIR)/AOC/filters/audio/common/include
+AOC_CFLAGS += -I$(AOC_TOP_DIR)/AOC/libs/common/heap/common/include
+AOC_CFLAGS += -I$(AOC_TOP_DIR)/AOC/os/common/include
+AOC_CFLAGS += -I$(AOC_TOP_DIR)/AOC/platform/common/include
+AOC_CFLAGS += -I$(AOC_TOP_DIR)/AOC/product/$(AOC_PRODUCT_FAMILY)/include
+AOC_CFLAGS += -I$(AOC_TOP_DIR)/usf/core/include
+AOC_CFLAGS += -I$(AOC_TOP_DIR)/usf/core/fbs
+AOC_CFLAGS += -I$(AOC_TOP_DIR)/usf/pal/include
+AOC_CFLAGS += -I$(AOC_TOP_DIR)/usf/pal/efw/include
+
+AOC_CFLAGS += -Ichpp/include
+AOC_CFLAGS += -Ichpp/platform/aoc/include
+AOC_CFLAGS += -DCHPP_CLIENT_ENABLED_DISCOVERY
+
+# We use FlatBuffers in the AOC platform layer
+AOC_CFLAGS += $(FLATBUFFERS_CFLAGS)
+
+# Ensure USF uses its own flatbuffers header. This is needed while USF migrates
+# away from CHRE's header.
+AOC_CFLAGS += -DFLATBUFFERS_USF
+
 # Simulator-specific Compiler Flags ############################################
 
 SIM_CFLAGS += -Iplatform/shared/include
diff --git a/platform/shared/dlfcn.cc b/platform/shared/dlfcn.cc
new file mode 100644
index 0000000..630b73c
--- /dev/null
+++ b/platform/shared/dlfcn.cc
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2020 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 <dlfcn.h>
+
+#include "chre/platform/assert.h"
+#include "chre/platform/shared/nanoapp_loader.h"
+#include "chre/util/unique_ptr.h"
+
+void *dlopenbuf(void *elfBinary, bool mapIntoTcm) {
+  return chre::NanoappLoader::create(elfBinary, mapIntoTcm);
+}
+
+void *dlsym(void *handle, const char *symbol) {
+  LOGV("Attempting to find %s", symbol);
+
+  void *resolvedSymbol = nullptr;
+  if (handle == RTLD_NEXT) {
+    resolvedSymbol = chre::NanoappLoader::findExportedSymbol(symbol);
+  } else if (handle != nullptr) {
+    auto *loader = reinterpret_cast<chre::NanoappLoader *>(handle);
+    resolvedSymbol = loader->findSymbolByName(symbol);
+  }
+
+  if (resolvedSymbol == nullptr) {
+    LOGE("dlsym unable to resolve %s", symbol);
+  } else {
+    LOGV("Found symbol at %p", resolvedSymbol);
+  }
+
+  return resolvedSymbol;
+}
+
+int dlclose(void *handle) {
+  int rv = -1;
+
+  if (handle != nullptr) {
+    chre::NanoappLoader::destroy(static_cast<chre::NanoappLoader *>(handle));
+    rv = 0;
+  }
+
+  return rv;
+}
+
+const char *dlerror() {
+  return "Shared library load failed";
+}
diff --git a/platform/shared/dram_vote_client.cc b/platform/shared/dram_vote_client.cc
new file mode 100644
index 0000000..9a15120
--- /dev/null
+++ b/platform/shared/dram_vote_client.cc
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2020 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 "chre/platform/shared/dram_vote_client.h"
+
+#include <cinttypes>
+
+#include "chre/platform/assert.h"
+#include "chre/platform/fatal_error.h"
+#include "chre/platform/log.h"
+#include "chre/platform/system_time.h"
+
+namespace chre {
+
+void DramVoteClient::voteDramAccess(bool enabled) {
+  LockGuard<Mutex> lock(mMutex);
+  mLastDramRequest = enabled;
+
+  bool needDram = (enabled || mDramVoteCount > 0);
+  if (needDram != mLastDramVote) {
+    issueDramVote(needDram);
+    mLastDramVote = needDram;
+  }
+}
+
+void DramVoteClient::incrementDramVoteCount() {
+  LockGuard<Mutex> lock(mMutex);
+
+  if (mDramVoteCount++ == 0) {
+    mVoteCountStart = Milliseconds(SystemTime::getMonotonicTime());
+    LOGW("DRAM vote count begins");
+
+    if (!mLastDramVote) {
+      // Do not call voteDramAccess() directly as it will override
+      // mLastDramRequest.
+      issueDramVote(true /* enabled */);
+      mLastDramVote = true;
+    }
+  } else {
+    checkDramDuration();
+  }
+}
+
+void DramVoteClient::decrementDramVoteCount() {
+  LockGuard<Mutex> lock(mMutex);
+  CHRE_ASSERT_LOG(mDramVoteCount > 0,
+                  "Tried to decrement DRAM vote count when it's 0");
+
+  if (--mDramVoteCount == 0) {
+    LOGW("DRAM vote count ends: %" PRIu64 " ms", checkDramDuration());
+
+    // There's no DRAM activity now, remove CHRE's DRAM access vote.
+    if (!mLastDramRequest) {
+      issueDramVote(false /* enabled */);
+      mLastDramVote = false;
+    }
+  }
+}
+
+Milliseconds DramVoteClient::checkDramDuration() const {
+  Milliseconds duration(0);
+  if (mDramVoteCount > 0) {
+    duration = Milliseconds(SystemTime::getMonotonicTime()) - mVoteCountStart;
+  }
+
+  // DRAM memory fallback only intends to handle a surge of DRAM memory
+  // requests. If there's a prolonged period of memory fallback, this might
+  // indicate a memory leak or inadequate SRAM heap size.
+  if (duration > kMaxDramDuration) {
+    FATAL_ERROR("Forced into DRAM for %" PRIu64 " msec", duration);
+  }
+  return duration;
+}
+
+//! Explicitly instantiate the DramVoteClient singleton to reduce code size.
+template class Singleton<DramVoteClient>;
+
+}  // namespace chre
diff --git a/platform/shared/host_protocol_chre.cc b/platform/shared/host_protocol_chre.cc
index 7aa751e..9e04a1a 100644
--- a/platform/shared/host_protocol_chre.cc
+++ b/platform/shared/host_protocol_chre.cc
@@ -81,9 +81,10 @@
             getStringFromByteVector(request->app_binary_file_name());
         HostMessageHandlers::handleLoadNanoappRequest(
             hostClientId, request->transaction_id(), request->app_id(),
-            request->app_version(), request->target_api_version(),
-            appBinary->data(), appBinary->size(), appBinaryFilename,
-            request->fragment_id(), request->total_app_size());
+            request->app_version(), request->app_flags(),
+            request->target_api_version(), appBinary->data(), appBinary->size(),
+            appBinaryFilename, request->fragment_id(),
+            request->total_app_size());
         break;
       }
 
diff --git a/platform/shared/idl/host_messages.fbs b/platform/shared/idl/host_messages.fbs
index a34836c..c6f0451 100644
--- a/platform/shared/idl/host_messages.fbs
+++ b/platform/shared/idl/host_messages.fbs
@@ -139,6 +139,10 @@
   /// Null-terminated ASCII string containing the file name that contains the
   /// app binary to be loaded.
   app_binary_file_name:[byte];
+
+  /// The nanoapp flag values from the nanoapp header defined in
+  /// build/build_template.mk. Refer to that file for more details.
+  app_flags:uint;
 }
 
 table LoadNanoappResponse {
diff --git a/platform/shared/include/chre/platform/shared/authentication.h b/platform/shared/include/chre/platform/shared/authentication.h
new file mode 100644
index 0000000..6409b47
--- /dev/null
+++ b/platform/shared/include/chre/platform/shared/authentication.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2020 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 CHRE_PLATFORM_SHARED_AUTHENTICATION_H_
+#define CHRE_PLATFORM_SHARED_AUTHENTICATION_H_
+
+namespace chre {
+
+/**
+ * Authenticates the signature of the provided binary. If not provided
+ * elsewhere by the platform, this method must ensure that nanoapps are signed
+ * appropriately and no corruption has occurred to the binary prior to being
+ * loaded. If this method succeeds, CHRE will assume the binary has the same
+ * execution privileges as the core framework itself.
+ *
+ * @param binary Pointer to the binary that should be authenticated.
+ * @param realBinaryStart A non-null pointer that, if this method succeeds, must
+ *     be filled with the starting address of the raw binary after any headers
+ *     used by the authentication code. This will be passed to the dynamic
+ *     loader which will assume the starting address is a valid ELF binary.
+ * @return True if the binary passed authentication.
+ */
+bool authenticateBinary(void *binary, void **realBinaryStart);
+
+}  // namespace chre
+
+#endif  // CHRE_PLATFORM_SHARED_AUTHENTICATION_H_
diff --git a/platform/shared/include/chre/platform/shared/dram_vote_client.h b/platform/shared/include/chre/platform/shared/dram_vote_client.h
new file mode 100644
index 0000000..00476a4
--- /dev/null
+++ b/platform/shared/include/chre/platform/shared/dram_vote_client.h
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2020 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 CHRE_PLATFORM_SHARED_VOTE_CLIENT_H_
+#define CHRE_PLATFORM_SHARED_VOTE_CLIENT_H_
+
+#include "chre/platform/mutex.h"
+#include "chre/util/lock_guard.h"
+#include "chre/util/singleton.h"
+#include "chre/util/time.h"
+
+namespace chre {
+
+/**
+ * Class used to manage voting for access to DRAM for platforms that require
+ * voting for DRAM memory to be accessible.
+ */
+class DramVoteClient : public NonCopyable {
+ public:
+  /**
+   * @return true if CHRE currently has a DRAM vote in place.
+   */
+  bool isDramVoteActive() {
+    LockGuard<Mutex> lock(mMutex);
+    return mLastDramVote;
+  }
+
+  /**
+   * Makes a DRAM access request. An actual vote to the memory manager may not
+   * be cast depending on the current mode and mDramVoteCount.
+   *
+   * @param enabled Whether to request DRAM access.
+   */
+  void voteDramAccess(bool enabled);
+
+  /**
+   * Increment the DRAM vote count when a client needs to perform some DRAM
+   * activity. A DRAM vote is issued when the count increments from 0.
+   */
+  void incrementDramVoteCount();
+
+  /**
+   * Decrement the DRAM vote count when a client finishes some activity that has
+   * to be performed in DRAM. A DRAM vote may be removed when the count
+   * decrements to 0, depending on if explicit DRAM votes have been issued from
+   * voteDramAccess.
+   */
+  void decrementDramVoteCount();
+
+ private:
+  //! The maximum allowed duration to be voted into DRAM by
+  //! incrementDramVoteCount before a FATAL_ERROR is triggered.
+  static constexpr Seconds kMaxDramDuration = Seconds(300);
+
+  //! Last DRAM request made through voteDramAccess().
+  bool mLastDramRequest = false;
+
+  //! Last DRAM vote cast to the system.
+  bool mLastDramVote = false;
+
+  //! The system time mDramVoteCount increments from 0.
+  Milliseconds mVoteCountStart = Milliseconds(0);
+
+  //! The count of DRAM activities.
+  uint32_t mDramVoteCount = 0;
+
+  //! Used to protect access to member variables from other threads.
+  Mutex mMutex;
+
+  /**
+   * Issue a vote to the underlying system. This must be implemented by each
+   * platform to communicate with the right system.
+   *
+   * @param enabled Whether DRAM should be accessible.
+   */
+  void issueDramVote(bool enabled);
+
+  /**
+   * Check how long the system has been voted into DRAM due to
+   * incrementDramVoteCount. If longer than kMaxDramDuration, trigger a crash.
+   *
+   * @return the duration in milliseconds since the system has been voted into
+   *         big image due to incrementDramVoteCount.
+   */
+  Milliseconds checkDramDuration() const;
+};
+
+//! Provides an alias to the DramVoteClient singleton
+typedef Singleton<DramVoteClient> DramVoteClientSingleton;
+
+extern template class Singleton<DramVoteClient>;
+
+}  // namespace chre
+
+#endif  // CHRE_PLATFORM_SHARED_VOTE_CLIENT_H_
diff --git a/platform/shared/include/chre/platform/shared/generated/host_messages_generated.h b/platform/shared/include/chre/platform/shared/generated/host_messages_generated.h
index 15a9284..8eeb753 100644
--- a/platform/shared/include/chre/platform/shared/generated/host_messages_generated.h
+++ b/platform/shared/include/chre/platform/shared/generated/host_messages_generated.h
@@ -839,7 +839,8 @@
     VT_APP_BINARY = 12,
     VT_FRAGMENT_ID = 14,
     VT_TOTAL_APP_SIZE = 16,
-    VT_APP_BINARY_FILE_NAME = 18
+    VT_APP_BINARY_FILE_NAME = 18,
+    VT_APP_FLAGS = 20
   };
   uint32_t transaction_id() const {
     return GetField<uint32_t>(VT_TRANSACTION_ID, 0);
@@ -871,6 +872,11 @@
   const flatbuffers::Vector<int8_t> *app_binary_file_name() const {
     return GetPointer<const flatbuffers::Vector<int8_t> *>(VT_APP_BINARY_FILE_NAME);
   }
+  /// The nanoapp flag values from the nanoapp header defined in
+  /// build/build_template.mk. Refer to that file for more details.
+  uint32_t app_flags() const {
+    return GetField<uint32_t>(VT_APP_FLAGS, 0);
+  }
   bool Verify(flatbuffers::Verifier &verifier) const {
     return VerifyTableStart(verifier) &&
            VerifyField<uint32_t>(verifier, VT_TRANSACTION_ID) &&
@@ -883,6 +889,7 @@
            VerifyField<uint32_t>(verifier, VT_TOTAL_APP_SIZE) &&
            VerifyOffset(verifier, VT_APP_BINARY_FILE_NAME) &&
            verifier.VerifyVector(app_binary_file_name()) &&
+           VerifyField<uint32_t>(verifier, VT_APP_FLAGS) &&
            verifier.EndTable();
   }
 };
@@ -915,6 +922,9 @@
   void add_app_binary_file_name(flatbuffers::Offset<flatbuffers::Vector<int8_t>> app_binary_file_name) {
     fbb_.AddOffset(LoadNanoappRequest::VT_APP_BINARY_FILE_NAME, app_binary_file_name);
   }
+  void add_app_flags(uint32_t app_flags) {
+    fbb_.AddElement<uint32_t>(LoadNanoappRequest::VT_APP_FLAGS, app_flags, 0);
+  }
   explicit LoadNanoappRequestBuilder(flatbuffers::FlatBufferBuilder &_fbb)
         : fbb_(_fbb) {
     start_ = fbb_.StartTable();
@@ -937,9 +947,11 @@
     flatbuffers::Offset<flatbuffers::Vector<uint8_t>> app_binary = 0,
     uint32_t fragment_id = 0,
     uint32_t total_app_size = 0,
-    flatbuffers::Offset<flatbuffers::Vector<int8_t>> app_binary_file_name = 0) {
+    flatbuffers::Offset<flatbuffers::Vector<int8_t>> app_binary_file_name = 0,
+    uint32_t app_flags = 0) {
   LoadNanoappRequestBuilder builder_(_fbb);
   builder_.add_app_id(app_id);
+  builder_.add_app_flags(app_flags);
   builder_.add_app_binary_file_name(app_binary_file_name);
   builder_.add_total_app_size(total_app_size);
   builder_.add_fragment_id(fragment_id);
@@ -959,7 +971,8 @@
     const std::vector<uint8_t> *app_binary = nullptr,
     uint32_t fragment_id = 0,
     uint32_t total_app_size = 0,
-    const std::vector<int8_t> *app_binary_file_name = nullptr) {
+    const std::vector<int8_t> *app_binary_file_name = nullptr,
+    uint32_t app_flags = 0) {
   auto app_binary__ = app_binary ? _fbb.CreateVector<uint8_t>(*app_binary) : 0;
   auto app_binary_file_name__ = app_binary_file_name ? _fbb.CreateVector<int8_t>(*app_binary_file_name) : 0;
   return chre::fbs::CreateLoadNanoappRequest(
@@ -971,7 +984,8 @@
       app_binary__,
       fragment_id,
       total_app_size,
-      app_binary_file_name__);
+      app_binary_file_name__,
+      app_flags);
 }
 
 struct LoadNanoappResponse FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table {
diff --git a/platform/shared/include/chre/platform/shared/host_protocol_chre.h b/platform/shared/include/chre/platform/shared/host_protocol_chre.h
index 8ee6f80..0b3deea 100644
--- a/platform/shared/include/chre/platform/shared/host_protocol_chre.h
+++ b/platform/shared/include/chre/platform/shared/host_protocol_chre.h
@@ -59,9 +59,9 @@
 
   static void handleLoadNanoappRequest(
       uint16_t hostClientId, uint32_t transactionId, uint64_t appId,
-      uint32_t appVersion, uint32_t targetApiVersion, const void *buffer,
-      size_t bufferLen, const char *appFileName, uint32_t fragmentId,
-      size_t appBinaryLen);
+      uint32_t appVersion, uint32_t appFlags, uint32_t targetApiVersion,
+      const void *buffer, size_t bufferLen, const char *appFileName,
+      uint32_t fragmentId, size_t appBinaryLen);
 
   static void handleUnloadNanoappRequest(uint16_t hostClientId,
                                          uint32_t transactionId, uint64_t appId,
diff --git a/platform/shared/include/chre/platform/shared/libc/dlfcn.h b/platform/shared/include/chre/platform/shared/libc/dlfcn.h
new file mode 100644
index 0000000..9758378
--- /dev/null
+++ b/platform/shared/include/chre/platform/shared/libc/dlfcn.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2020 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 CHRE_PLATFORM_SHARED_DLFCN_H_
+#define CHRE_PLATFORM_SHARED_DLFCN_H_
+
+//! This file is intended to be used when a platform doesn't provide a dlfcn.h
+//! implementation so that dynamic loading support can be provided to nanoapps.
+
+#include <cstdlib>
+
+//! Indicates that the dlsym call is attempting to lookup the provided symbol
+//! in another library (CHRE).
+#ifndef RTLD_NEXT
+#define RTLD_NEXT ((void *)-1L)
+#endif  // RTLD_NEXT
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * This function parses, verifies, and loads a buffer containing an ELF
+ * file, and returns an opaque handle for symbol lookup via dlsym.
+ * Note that there is no requirement to pass in a 'buffer size' argument,
+ * since all the necessary information is gathered from the headers
+ * embedded in the elf file itself. The buffer is expected to contain the
+ * entire elf file in memory (cannot be a FILE pointer, for example).
+ *
+ * @param elfBinary is a buffer containing the elf file
+ * @param mapIntoTcm Indicates whether the elfBinary should be mapped into
+ *     tightly coupled memory.
+ */
+void *dlopenbuf(void *elfBinary, bool mapIntoTcm);
+
+/**
+ * Returns a (function) pointer to the symbol named by the input argument.
+ *
+ * @return pointer to the symbol if it was found, nullptr if not found or
+ * the handle was invalid
+ */
+void *dlsym(void *handle, const char *symbol);
+
+/**
+ * Invalidate the handle used for symbol lookup, and free up any
+ * allocated memory.
+ *
+ * @return 0 on success
+ */
+int dlclose(void *handle);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  // CHRE_PLATFORM_SHARED_DLFCN_H_
diff --git a/platform/shared/include/chre/platform/shared/loader_util.h b/platform/shared/include/chre/platform/shared/loader_util.h
new file mode 100644
index 0000000..8e33428
--- /dev/null
+++ b/platform/shared/include/chre/platform/shared/loader_util.h
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2020 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 CHRE_PLATFORM_SHARED_LOADER_UTIL_H_
+#define CHRE_PLATFORM_SHARED_LOADER_UTIL_H_
+
+// The below macros allow switching the ELF symbol type between 32/64-bit
+// depending on what the chipset supports.
+#ifndef ELFW
+#ifndef __WORDSIZE
+// Until we can get a hold of wordsize.h, we need to define it here.
+// Only 32-bit architectures currently supported.
+#ifndef __aarch64__
+#define __WORDSIZE 32
+#else
+#error "Only 32-bit architectures currently supported"
+#endif
+#endif
+// https://refspecs.linuxbase.org/elf/gabi4+/ch4.reloc.html
+#define ELF32_R_SYM(info) ((info) >> 8)
+#define ELF32_R_TYPE(info) ((unsigned char)(info))
+#define ELF32_R_INFO(sym, type) (((sym) << 8) + (unsigned char)(type))
+#define __ELF_NATIVE_CLASS __WORDSIZE
+#define ELFW(type) _ELFW(__ELF_NATIVE_CLASS, type)
+#define _ELFW(bits, type) __ELFW(bits, type)
+#define __ELFW(bits, type) ELF##bits##_##type
+#endif
+#define ELFW_R_TYPE(x) ELFW(R_TYPE)(x)
+#define ELFW_R_SYM(x) ELFW(R_SYM)(x)
+
+struct ExportedData {
+  void *data;
+  const char *dataName;
+};
+
+// The below is copied from bionic/libc/kernel/uapi/linux/elf.h
+// to avoid pulling those deps into the build.
+#if defined(__LP64__)
+#define ElfW(type) Elf64_##type
+#else
+#define ElfW(type) Elf32_##type
+#endif
+
+#define EM_ARM 40
+#define EI_MAG0 0
+#define EI_MAG1 1
+#define EI_MAG2 2
+#define EI_MAG3 3
+#define EI_CLASS 4
+#define EI_DATA 5
+#define EI_VERSION 6
+#define EI_OSABI 7
+#define EI_PAD 8
+#define ELFMAG0 0x7f
+#define ELFMAG1 'E'
+#define ELFMAG2 'L'
+#define ELFMAG3 'F'
+#define ELFMAG "\177ELF"
+#define SELFMAG 4
+#define ELFCLASSNONE 0
+#define ELFCLASS32 1
+#define ELFCLASS64 2
+#define ELFCLASSNUM 3
+#define ELFDATANONE 0
+#define ELFDATA2LSB 1
+#define ELFDATA2MSB 2
+#define EV_NONE 0
+#define EV_CURRENT 1
+#define EV_NUM 2
+#define ELFOSABI_NONE 0
+#define ELFOSABI_LINUX 3
+#define PT_NULL 0
+#define PT_LOAD 1
+#define PT_DYNAMIC 2
+#define PT_INTERP 3
+#define PT_NOTE 4
+#define PT_SHLIB 5
+#define PT_PHDR 6
+#define PT_TLS 7
+#define PT_LOOS 0x60000000
+#define PT_HIOS 0x6fffffff
+#define PT_LOPROC 0x70000000
+#define PT_HIPROC 0x7fffffff
+#define PT_GNU_EH_FRAME 0x6474e550
+#define PT_GNU_STACK (PT_LOOS + 0x474e551)
+#define PN_XNUM 0xffff
+#define ET_NONE 0
+#define ET_REL 1
+#define ET_EXEC 2
+#define ET_DYN 3
+#define ET_CORE 4
+#define ET_LOPROC 0xff00
+#define ET_HIPROC 0xffff
+#define DT_NULL 0
+#define DT_NEEDED 1
+#define DT_PLTRELSZ 2
+#define DT_PLTGOT 3
+#define DT_HASH 4
+#define DT_STRTAB 5
+#define DT_SYMTAB 6
+#define DT_RELA 7
+#define DT_RELASZ 8
+#define DT_RELAENT 9
+#define DT_STRSZ 10
+#define DT_SYMENT 11
+#define DT_INIT 12
+#define DT_FINI 13
+#define DT_SONAME 14
+#define DT_RPATH 15
+#define DT_SYMBOLIC 16
+#define DT_REL 17
+#define DT_RELSZ 18
+#define DT_RELENT 19
+#define DT_PLTREL 20
+#define DT_DEBUG 21
+#define DT_TEXTREL 22
+#define DT_JMPREL 23
+#define DT_ENCODING 32
+
+typedef __signed__ char __s8;
+typedef unsigned char __u8;
+typedef __signed__ short __s16;
+typedef unsigned short __u16;
+typedef __signed__ int __s32;
+typedef unsigned int __u32;
+typedef __signed__ long __s64;
+typedef unsigned long __u64;
+
+typedef __u32 Elf32_Addr;
+typedef __u16 Elf32_Half;
+typedef __u32 Elf32_Off;
+typedef __s32 Elf32_Sword;
+typedef __u32 Elf32_Word;
+
+#define EI_NIDENT 16
+typedef struct elf32_hdr {
+  unsigned char e_ident[EI_NIDENT];
+  Elf32_Half e_type;
+  Elf32_Half e_machine;
+  Elf32_Word e_version;
+  Elf32_Addr e_entry;
+  Elf32_Off e_phoff;
+  Elf32_Off e_shoff;
+  Elf32_Word e_flags;
+  Elf32_Half e_ehsize;
+  Elf32_Half e_phentsize;
+  Elf32_Half e_phnum;
+  Elf32_Half e_shentsize;
+  Elf32_Half e_shnum;
+  Elf32_Half e_shstrndx;
+} Elf32_Ehdr;
+
+typedef struct dynamic {
+  Elf32_Sword d_tag;
+  union {
+    Elf32_Sword d_val;
+    Elf32_Addr d_ptr;
+  } d_un;
+} Elf32_Dyn;
+
+typedef struct elf32_phdr {
+  Elf32_Word p_type;
+  Elf32_Off p_offset;
+  Elf32_Addr p_vaddr;
+  Elf32_Addr p_paddr;
+  Elf32_Word p_filesz;
+  Elf32_Word p_memsz;
+  Elf32_Word p_flags;
+  Elf32_Word p_align;
+} Elf32_Phdr;
+
+typedef struct elf32_shdr {
+  Elf32_Word sh_name;
+  Elf32_Word sh_type;
+  Elf32_Word sh_flags;
+  Elf32_Addr sh_addr;
+  Elf32_Off sh_offset;
+  Elf32_Word sh_size;
+  Elf32_Word sh_link;
+  Elf32_Word sh_info;
+  Elf32_Word sh_addralign;
+  Elf32_Word sh_entsize;
+} Elf32_Shdr;
+
+typedef struct elf32_rela {
+  Elf32_Addr r_offset;
+  Elf32_Word r_info;
+  Elf32_Sword r_addend;
+} Elf32_Rela;
+
+typedef struct elf32_rel {
+  Elf32_Addr r_offset;
+  Elf32_Word r_info;
+} Elf32_Rel;
+
+typedef struct elf32_sym {
+  Elf32_Word st_name;
+  Elf32_Addr st_value;
+  Elf32_Word st_size;
+  unsigned char st_info;
+  unsigned char st_other;
+  Elf32_Half st_shndx;
+} Elf32_Sym;
+
+// The following defines are copied from bionic's elf_arm.h header
+// at bionic/libc/kernel/uapi/linux/elf.h
+// Only the relocation types currently supported are copied.
+#define R_ARM_NONE 0
+#define R_ARM_ABS32 2
+#define R_ARM_COPY 20
+#define R_ARM_GLOB_DAT 21
+#define R_ARM_JUMP_SLOT 22
+#define R_ARM_RELATIVE 23
+
+// The following (legal values for segment flags) are copied from
+// bionic's elf.h
+/* http://www.sco.com/developers/gabi/latest/ch5.pheader.html */
+#define PF_X 0x1
+#define PF_W 0x2
+#define PF_R 0x4
+#define PF_MASKOS 0x0ff00000
+#define PF_MASKPROC 0xf0000000
+
+#endif  // CHRE_PLATFORM_SHARED_LOADER_UTIL_H_
diff --git a/platform/shared/include/chre/platform/shared/memory.h b/platform/shared/include/chre/platform/shared/memory.h
new file mode 100644
index 0000000..46a7721
--- /dev/null
+++ b/platform/shared/include/chre/platform/shared/memory.h
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2020 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 CHRE_PLATFORM_SHARED_MEMORY_H_
+#define CHRE_PLATFORM_SHARED_MEMORY_H_
+
+#include <cstddef>
+#include <type_traits>
+
+namespace chre {
+
+/**
+ * Aligned memory allocation using the lowest power heap available. The
+ * semantics are the same as malloc.
+ */
+void *memoryAllocAligned(size_t alignment, size_t size);
+
+/**
+ * Aligned memory allocation specifically using the DRAM heap. The semantics are
+ * the same as malloc.
+ *
+ * If DRAM or another large memory capacity region is not available, this will
+ * allocate from the same memory region as memoryAlloc.
+ */
+void *memoryAllocDramAligned(size_t alignment, size_t size);
+
+/**
+ * Memory allocation specifically using the DRAM heap. The semantics are the
+ * same as malloc.
+ *
+ * If DRAM or another large memory capacity region is not available, this will
+ * allocate from the same memory region as memoryAlloc.
+ */
+void *memoryAllocDram(size_t size);
+
+/**
+ * Memory free from memory allocated using the DRAM heap. The semantics are the
+ * same as free.
+ *
+ * If DRAM or another large memory capacity region is not available, this will
+ * free from the same memory region as memoryFree.
+ */
+void memoryFreeDram(void *pointer);
+
+/**
+ * Ensures memory allocated through memoryAllocDram can be utilized. If memory
+ * allocated through memoryAllocDram is always available, this method can be a
+ * no-op.
+ *
+ * This must be support being invoked from multiple threads.
+ */
+void forceDramAccess();
+
+/**
+ * Removes CHRE's vote to keep DRAM accessible. This must only be called when
+ * CHRE is idle.
+ */
+void removeDramAccessVote();
+
+/**
+ * Allocates memory in DRAM for an object of size T and constructs the object in
+ * the newly allocated object by forwarding the provided parameters.
+ */
+template <typename T, typename... Args>
+inline T *memoryAllocDram(Args &&... args) {
+  auto *storage = static_cast<T *>(memoryAllocDram(sizeof(T)));
+  if (storage != nullptr) {
+    new (storage) T(std::forward<Args>(args)...);
+  }
+
+  return storage;
+}
+
+}  // namespace chre
+
+#endif  // CHRE_PLATFORM_SHARED_MEMORY_H_
diff --git a/platform/shared/include/chre/platform/shared/nanoapp_load_manager.h b/platform/shared/include/chre/platform/shared/nanoapp_load_manager.h
index f8b17a5..c8ae589 100644
--- a/platform/shared/include/chre/platform/shared/nanoapp_load_manager.h
+++ b/platform/shared/include/chre/platform/shared/nanoapp_load_manager.h
@@ -52,12 +52,13 @@
    * @param transactionId the ID of the transaction
    * @param appId the ID of the app to load
    * @param appVersion the version of the app to load
+   * @param appFlags the flags provided by the app being loaded
    * @param totalBinaryLen the total nanoapp binary length
    *
    * @return true if the preparation was successful, false otherwise
    */
   bool prepareForLoad(uint16_t hostClientId, uint32_t transactionId,
-                      uint64_t appId, uint32_t appVersion,
+                      uint64_t appId, uint32_t appVersion, uint32_t appFlags,
                       size_t totalBinaryLen);
 
   /**
diff --git a/platform/shared/include/chre/platform/shared/nanoapp_loader.h b/platform/shared/include/chre/platform/shared/nanoapp_loader.h
new file mode 100644
index 0000000..8185f4c
--- /dev/null
+++ b/platform/shared/include/chre/platform/shared/nanoapp_loader.h
@@ -0,0 +1,349 @@
+/*
+ * Copyright (C) 2020 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 CHRE_PLATFORM_SHARED_NANOAPP_LOADER_H_
+#define CHRE_PLATFORM_SHARED_NANOAPP_LOADER_H_
+
+#include <cinttypes>
+#include <cstdlib>
+
+#include "chre/platform/shared/loader_util.h"
+
+#include "chre/util/dynamic_vector.h"
+#include "chre/util/nested_data_ptr.h"
+
+namespace chre {
+
+/**
+ * Provides dynamic loading support for nanoapps on FreeRTOS-based platforms.
+ * At a high level, this class is responsible for mapping the provided binary
+ * into CHRE's address space, relocating and resolving symbols, and initializing
+ * and freeing static data.
+ */
+class NanoappLoader {
+ public:
+  NanoappLoader() = delete;
+
+  explicit NanoappLoader(void *elfInput, bool mapIntoTcm) {
+    mBinary.dataPtr = elfInput;
+    mIsTcmBinary = mapIntoTcm;
+  }
+
+  /**
+   * Factory method to create a NanoappLoader Instance after loading
+   * the buffer containing the ELF binary.
+   *
+   * @param elfInput Buffer containing the elf file
+   * @param mapIntoTcm Indicates whether the elfBinary should be mapped into
+   *     tightly coupled memory.
+   * @return Class instance on successful load and verification,
+   *     nullptr otherwise.
+   */
+  static void *create(void *elfInput, bool mapIntoTcm);
+
+  /**
+   * Closes and destroys the NanoappLoader instance.
+   *
+   * @param loader A non-null pointer to the loader that must be destroyed.
+   */
+  static void destroy(NanoappLoader *loader);
+
+  /**
+   * Attempts to locate the exported symbol specified by the given function
+   * name.
+   *
+   * @param name A null-terminated char array that is the name of the function
+   *     to be found.
+   * @return The address of the function. nullptr if not found.
+   */
+  static void *findExportedSymbol(const char *name);
+
+  /**
+   * Method for pointer lookup by symbol name. Only function pointers
+   * are currently supported.
+   *
+   * @return function pointer on successful lookup, nullptr otherwise
+   */
+  void *findSymbolByName(const char *name);
+
+  /**
+   * Registers a function provided through atexit during static initialization
+   * that should be called prior to unloading a nanoapp.
+   *
+   * @param function Function that should be invoked prior to unloading a
+   *     nanoapp.
+   */
+  void registerAtexitFunction(void (*function)(void));
+
+ private:
+  /**
+   * Opens the ELF binary. This maps the binary into memory, resolves symbols,
+   * and invokes any static initializers.
+   *
+   * @return true if all required opening steps were completed.
+   */
+  bool open();
+
+  /**
+   * Closes the loader, freeing any state associated with the loaded ELF binary
+   * and unmapping it from memory. Prior to unmapping from memory, any static
+   * termination functions will be invoked.
+   */
+  void close();
+
+  using DynamicHeader = ElfW(Dyn);
+  using ElfAddr = ElfW(Addr);
+  using ElfHeader = ElfW(Ehdr);
+  using ElfRel = ElfW(Rel);  // Relocation table entry,
+                             // in section of type SHT_REL
+  using ElfRela = ElfW(Rela);
+  using ElfSym = ElfW(Sym);
+  using ElfWord = ElfW(Word);
+  using ProgramHeader = ElfW(Phdr);
+  using SectionHeader = ElfW(Shdr);
+
+  //! Name of various segments in the ELF that need to be looked up
+  static constexpr const char *kSymTableName = ".symtab";
+  static constexpr const char *kStrTableName = ".strtab";
+  static constexpr const char *kInitArrayName = ".init_array";
+  static constexpr const char *kFiniArrayName = ".fini_array";
+  // For now, assume all segments are 4K aligned.
+  // TODO(karthikmb/stange): See about reducing this.
+  static constexpr size_t kBinaryAlignment = 4096;
+
+  //! Pointer to the table of all the section names.
+  char *mSectionNamesPtr = nullptr;
+  //! Pointer to the table of symbol names of defined symbols.
+  char *mStringTablePtr = nullptr;
+  //! Pointer to the table of symbol information for defined symbols.
+  uint8_t *mSymbolTablePtr = nullptr;
+  //! Pointer to the array of section header entries.
+  SectionHeader *mSectionHeadersPtr;
+  //! Number of SectionHeaders pointed to by mSectionHeadersPtr.
+  size_t mNumSectionHeaders = 0;
+  //! Size of the data pointed to by mSymbolTablePtr.
+  size_t mSymbolTableSize = 0;
+
+  // TODO(stange): Store this elsewhere since the location will be invalid after
+  // loading is complete.
+  //! The ELF that is being mapped into the system. This pointer will be invalid
+  //! after open returns.
+  NestedDataPtr<uintptr_t> mBinary = {0};
+  //! The starting location of the memory that has been mapped into the system.
+  NestedDataPtr<uintptr_t> mMapping = {0};
+  //! The difference between where the first load segment was mapped into
+  //! virtual memory and what the virtual load offset was of that segment.
+  ElfAddr mLoadBias = 0;
+  //! Dynamic vector containing functions that should be invoked prior to
+  //! unloading this nanoapp. Note that functions are stored in the order they
+  //! were added and should be called in reverse.
+  DynamicVector<void (*)(void)> mAtexitFunctions;
+  //! Whether this loader instance is managing a TCM nanoapp binary.
+  bool mIsTcmBinary;
+
+  /**
+   * Invokes all functions registered via atexit during static initialization.
+   */
+  void callAtexitFunctions();
+
+  /**
+   * Invokes all initialization functions in .init_array segment.
+   *
+   * @return true if static initialization succeeded.
+   */
+  bool callInitArray();
+
+  /**
+   * Invokes all termination functions in the .fini_array segment.
+   */
+  void callTerminatorArray();
+
+  /**
+   * Allocates memory for all load segments that need to be mapped into virtual
+   * memory and copies the load segments into the newly allocated memory.
+   *
+   * @return true if the memory for mapping was allocated and the load segments
+   *     were formatted correctly.
+   */
+  bool createMappings();
+
+  /**
+   * Copies various sections and headers from the ELF while verifying that they
+   * match the ELF format specficiation.
+   *
+   * @return true if all data was copied and verified.
+   */
+  bool copyAndVerifyHeaders();
+
+  /**
+   * Resolves all relocated symbols located in the DT_REL table.
+   *
+   * @return true if all relocated symbols were resolved.
+   */
+  bool fixRelocations();
+
+  /**
+   * Resolves entries in the Global Offset Table (GOT) to facility the ELF's
+   * compiled using position independent code (PIC).
+   *
+   * @return true if all symbols were resolved.
+   */
+  bool resolveGot();
+
+  /**
+   * Verifies the ELF header has correct values based on the ELF spec.
+   *
+   * @return true if the header passed verification.
+   */
+  bool verifyElfHeader();
+
+  /**
+   * Verifies basic information about program headers.
+   *
+   * @return true if the headers passed verification.
+   */
+  bool verifyProgramHeaders();
+
+  /**
+   * Verifies basic information about section headers.
+   *
+   * @return true if the headers passed verification.
+   */
+  bool verifySectionHeaders();
+
+  /**
+   * Retrieves the symbol name of data located at the given position in the
+   * symbol table.
+   *
+   * @param posInSymbolTable The position in the symbol table where information
+   *     about the symbol can be found.
+   * @return The symbol's name or nullptr if not found.
+   */
+  const char *getDataName(size_t posInSymbolTable);
+
+  /**
+   * Retrieves the name of the section header located at the given offset in the
+   * section name table.
+   *
+   * @param headerOffset The offset in the section names table where the header
+   *     is located.
+   * @return The section's name or the empty string if the offset is 0.
+   */
+  const char *getSectionHeaderName(size_t headerOffset);
+
+  /**
+   * Rounds the given address down to the closest alignment boundary.
+   *
+   * @param virtualAddr The address to be rounded.
+   * @return An address that is a multiple of the platform's alignment and is
+   *     less than or equal to virtualAddr.
+   */
+  uintptr_t roundDownToAlign(uintptr_t virtualAddr);
+
+  /**
+   * Frees any data that was allocated as part of loading the ELF into memory.
+   */
+  void freeAllocatedData();
+
+  /**
+   * Ensures the BSS section is properly mapped into memory. If there is a
+   * difference between the size of the BSS section in the ELF binary and the
+   * size it needs to be in memory, the rest of the section is zeroed out.
+   *
+   * @param header The ProgramHeader of the BSS section that is being mapped in.
+   */
+  void mapBss(const ProgramHeader *header);
+
+  /**
+   * Resolves the address of an undefined symbol located at the given position
+   * in the symbol table. This symbol must be defined and exposed by the given
+   * platform in order for it to be resolved successfully.
+   *
+   * @param posInSymbolTable The position of the undefined symbol in the symbol
+   *     table.
+   * @return The address of the resolved symbol. nullptr if not found.
+   */
+  void *resolveData(size_t posInSymbolTable);
+
+  /**
+   * @return The address for the dynamic segment. nullptr if not found.
+   */
+  DynamicHeader *getDynamicHeader();
+
+  /**
+   * @return The address of the first read-only segment. nullptr if not found.
+   */
+  ProgramHeader *getFirstRoSegHeader();
+
+  /**
+   * Retrieves the section header with the given name.
+   *
+   * @param headerName The name of the section header to find.
+   * @return The address of the section. nullptr if not found.
+   */
+  SectionHeader *getSectionHeader(const char *headerName);
+
+  /**
+   * @return The ELF header for the binary being loaded. nullptr if it doesn't
+   *    exist or no binary is being loaded.
+   */
+  ElfHeader *getElfHeader();
+
+  /**
+   * @return The array of program headers for the binary being loaded. nullptr
+   *    if it doesn't exist or no binary is being loaded.
+   */
+  ProgramHeader *getProgramHeaderArray();
+
+  /**
+   * @return The size of the array of program headers for the binary being
+   *    loaded. 0 if it doesn't exist or no binary is being loaded.
+   */
+  size_t getProgramHeaderArraySize();
+
+  /**
+   * @return An array of characters containing the symbol names for dynamic
+   *    symbols inside the binary being loaded. nullptr if it doesn't exist or
+   *    no binary is being loaded.
+   */
+  char *getDynamicStringTable();
+
+  /**
+   * @return An array of dynamic symbol information for the binary being loaded.
+   *     nullptr if it doesn't exist or no binary is being loaded.
+   */
+  uint8_t *getDynamicSymbolTable();
+
+  /**
+   * @return The size of the array of dynamic symbol information for the binary
+   *     being loaded. 0 if it doesn't exist or no binary is being loaded.
+   */
+  size_t getDynamicSymbolTableSize();
+
+  /**
+   * Returns the first entry in the dynamic header that has a tag that matches
+   * the given field.
+   *
+   * @param dyn The dynamic header for the binary.
+   * @param field The field to be searched for.
+   * @return The value found at the entry. 0 if the entry isn't found.
+   */
+  static ElfWord getDynEntry(DynamicHeader *dyn, int field);
+};
+
+}  // namespace chre
+
+#endif  // CHRE_PLATFORM_SHARED_NANOAPP_LOADER_H_
diff --git a/platform/shared/include/chre/platform/shared/nanoapp_support_lib_dso.h b/platform/shared/include/chre/platform/shared/nanoapp_support_lib_dso.h
index 239206e..abce1bb 100644
--- a/platform/shared/include/chre/platform/shared/nanoapp_support_lib_dso.h
+++ b/platform/shared/include/chre/platform/shared/nanoapp_support_lib_dso.h
@@ -46,6 +46,10 @@
 //! The symbol name expected from the nanoapp's definition of its info struct
 #define CHRE_NSL_DSO_NANOAPP_INFO_SYMBOL_NAME "_chreNslDsoNanoappInfo"
 
+//! The symbol name expected from the nanoapp's definition of its unstable ID
+//! char array
+#define CHRE_NSL_DSO_NANOAPP_UNSTABLE_ID_SYMBOL_NAME "_chreNanoappUnstableId"
+
 //! Maximum length of vendor and name strings
 #define CHRE_NSL_DSO_NANOAPP_STRING_MAX_LEN (32)
 
diff --git a/platform/shared/include/chre/target_platform/platform_cache_management.h b/platform/shared/include/chre/target_platform/platform_cache_management.h
new file mode 100644
index 0000000..123de17
--- /dev/null
+++ b/platform/shared/include/chre/target_platform/platform_cache_management.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2020 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 CHRE_PLATFORM_SHARED_CACHE_MANAGEMENT_H_
+#define CHRE_PLATFORM_SHARED_CACHE_MANAGEMENT_H_
+
+namespace chre {
+
+/**
+ * Invalidates and/or cleans the system instruction and data caches.
+ *
+ * When nanoapps in CHRE are loaded dynamically, the data and
+ * executable segments are parsed and relocated into memory. This operation
+ * can cause cached instructions and data to be invalid when we start executing
+ * the newly loaded nanoapp's instructions, and could possibly lead to an
+ * exception. To support custom dynamic loading implementations (for example,
+ * when it's not part of the underlying OS/system), the platform needs to
+ * implement (or provide an empty stub for) the following method to invalidate
+ * and/or clean the system data and instruction caches.
+ */
+void wipeSystemCaches();
+
+}  // namespace chre
+
+#endif  // CHRE_PLATFORM_SHARED_CACHE_MANAGEMENT_H_
diff --git a/platform/shared/nanoapp/nanoapp_support_lib_dso.cc b/platform/shared/nanoapp/nanoapp_support_lib_dso.cc
index 51cba03..48131ea 100644
--- a/platform/shared/nanoapp/nanoapp_support_lib_dso.cc
+++ b/platform/shared/nanoapp/nanoapp_support_lib_dso.cc
@@ -31,7 +31,7 @@
 
 namespace {
 
-#ifdef CHRE_SLPI_UIMG_ENABLED
+#if defined(CHRE_SLPI_UIMG_ENABLED) || defined(CHRE_TCM_ENABLED)
 constexpr int kIsTcmNanoapp = 1;
 #else
 constexpr int kIsTcmNanoapp = 0;
@@ -75,6 +75,15 @@
 
 }  // anonymous namespace
 
+//! Additional symbol used to determine the given unstable ID that was provided
+//! when building this nanoapp, if any. The symbol is placed in its own section
+//! so it be stripped to determine if the nanoapp changed compared to a previous
+//! version.
+#ifdef NANOAPP_UNSTABLE_ID
+DLL_EXPORT extern "C" const char _chreNanoappUnstableId[]
+    __attribute__((section(".unstable_id"))) = NANOAPP_UNSTABLE_ID;
+#endif  // NANOAPP_UNSTABLE_ID
+
 DLL_EXPORT extern "C" const struct chreNslNanoappInfo _chreNslDsoNanoappInfo = {
     /* magic */ CHRE_NSL_NANOAPP_INFO_MAGIC,
     /* structMinorVersion */ CHRE_NSL_NANOAPP_INFO_STRUCT_MINOR_VERSION,
diff --git a/platform/shared/nanoapp_load_manager.cc b/platform/shared/nanoapp_load_manager.cc
index 03e2b0a..d16b822 100644
--- a/platform/shared/nanoapp_load_manager.cc
+++ b/platform/shared/nanoapp_load_manager.cc
@@ -20,13 +20,14 @@
 
 bool NanoappLoadManager::prepareForLoad(uint16_t hostClientId,
                                         uint32_t transactionId, uint64_t appId,
-                                        uint32_t appVersion,
+                                        uint32_t appVersion, uint32_t appFlags,
                                         size_t totalBinaryLen) {
   if (hasPendingLoadTransaction()) {
     LOGW(
         "Pending load transaction already exists. Overriding previous"
         " transaction.");
   }
+
   mCurrentLoadInfo.hostClientId = hostClientId;
   mCurrentLoadInfo.transactionId = transactionId;
   mCurrentLoadInfo.nextFragmentId = 1;
@@ -36,7 +37,8 @@
   if (mNanoapp.isNull()) {
     LOG_OOM();
   } else {
-    success = mNanoapp->reserveBuffer(appId, appVersion, totalBinaryLen);
+    success =
+        mNanoapp->reserveBuffer(appId, appVersion, appFlags, totalBinaryLen);
   }
 
   if (!success) {
diff --git a/platform/shared/nanoapp_loader.cc b/platform/shared/nanoapp_loader.cc
new file mode 100644
index 0000000..cd31614
--- /dev/null
+++ b/platform/shared/nanoapp_loader.cc
@@ -0,0 +1,862 @@
+/*
+ * Copyright (C) 2020 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 <dlfcn.h>
+#include <cctype>
+#include <cmath>
+#include <cstring>
+
+#include "chre/platform/shared/nanoapp_loader.h"
+
+#include "ash.h"
+#include "chre.h"
+#include "chre/platform/assert.h"
+#include "chre/platform/shared/debug_dump.h"
+#include "chre/platform/shared/memory.h"
+#include "chre/target_platform/platform_cache_management.h"
+#include "chre/util/dynamic_vector.h"
+#include "chre/util/macros.h"
+
+#ifndef CHRE_LOADER_ARCH
+#define CHRE_LOADER_ARCH EM_ARM
+#endif  // CHRE_LOADER_ARCH
+
+namespace chre {
+namespace {
+
+using ElfHeader = ElfW(Ehdr);
+using ProgramHeader = ElfW(Phdr);
+
+struct ExportedData {
+  void *data;
+  const char *dataName;
+};
+
+//! If non-null, a nanoapp is currently being loaded. This allows certain C
+//! functions to access the nanoapp if called during static init.
+NanoappLoader *gCurrentlyLoadingNanoapp = nullptr;
+//! Indicates whether a failure occurred during static initialization.
+bool gStaticInitFailure = false;
+
+// The new operator is used by singleton.h which causes the delete operator to
+// be undefined in nanoapp binaries even though it's unused. Define this in case
+// a nanoapp actually tries to use the operator.
+void deleteOverride(void *ptr) {
+  FATAL_ERROR("Nanoapp tried to free %p through delete operator", ptr);
+}
+
+// atexit is used to register functions that must be called when a binary is
+// removed from the system.
+int atexitOverride(void (*function)(void)) {
+  LOGV("atexit invoked with %p", function);
+  if (gCurrentlyLoadingNanoapp == nullptr) {
+    CHRE_ASSERT_LOG(false,
+                    "atexit is only supported during static initialization.");
+    return -1;
+  }
+
+  gCurrentlyLoadingNanoapp->registerAtexitFunction(function);
+  return 0;
+}
+
+// The following functions from the cmath header need to be overridden, since
+// they're overloaded functions, and we need to specify explicit types of the
+// template for the compiler.
+double frexpOverride(double value, int *exp) {
+  return frexp(value, exp);
+}
+
+double sinOverride(double rad) {
+  return sin(rad);
+}
+
+double asinOverride(double val) {
+  return asin(val);
+}
+
+double cosOverride(double rad) {
+  return cos(rad);
+}
+
+float sqrtOverride(float val) {
+  return sqrt(val);
+}
+
+// This function is required to be exposed to nanoapps to handle errors from
+// invoking virtual functions.
+void __cxa_pure_virtual(void) {
+  chreAbort(CHRE_ERROR /* abortCode */);
+}
+
+#define ADD_EXPORTED_SYMBOL(function_name, function_string) \
+  { reinterpret_cast<void *>(function_name), function_string }
+#define ADD_EXPORTED_C_SYMBOL(function_name) \
+  ADD_EXPORTED_SYMBOL(function_name, STRINGIFY(function_name))
+
+// TODO(karthikmb/stange): While this array was hand-coded for simple
+// "hello-world" prototyping, the list of exported symbols must be
+// generated to minimize runtime errors and build breaks.
+const ExportedData gExportedData[] = {
+    /* libmath overrrides and symbols */
+    ADD_EXPORTED_SYMBOL(asinOverride, "asin"),
+    ADD_EXPORTED_SYMBOL(cosOverride, "cos"),
+    ADD_EXPORTED_SYMBOL(frexpOverride, "frexp"),
+    ADD_EXPORTED_SYMBOL(sinOverride, "sin"),
+    ADD_EXPORTED_SYMBOL(sqrtOverride, "sqrt"),
+    ADD_EXPORTED_C_SYMBOL(atan2f),
+    ADD_EXPORTED_C_SYMBOL(expf),
+    ADD_EXPORTED_C_SYMBOL(fmodf),
+    ADD_EXPORTED_C_SYMBOL(sqrtf),
+    ADD_EXPORTED_C_SYMBOL(tanhf),
+    /* libc overrides and symbols */
+    ADD_EXPORTED_C_SYMBOL(__cxa_pure_virtual),
+    ADD_EXPORTED_SYMBOL(atexitOverride, "atexit"),
+    ADD_EXPORTED_SYMBOL(deleteOverride, "_ZdlPv"),
+    ADD_EXPORTED_C_SYMBOL(dlsym),
+    ADD_EXPORTED_C_SYMBOL(memcmp),
+    ADD_EXPORTED_C_SYMBOL(memcpy),
+    ADD_EXPORTED_C_SYMBOL(memmove),
+    ADD_EXPORTED_C_SYMBOL(memset),
+    ADD_EXPORTED_C_SYMBOL(strcmp),
+    ADD_EXPORTED_C_SYMBOL(strlen),
+    ADD_EXPORTED_C_SYMBOL(strncmp),
+    ADD_EXPORTED_C_SYMBOL(tolower),
+    /* ash symbols */
+    ADD_EXPORTED_C_SYMBOL(ashLoadCalibrationParams),
+    ADD_EXPORTED_C_SYMBOL(ashSaveCalibrationParams),
+    ADD_EXPORTED_C_SYMBOL(ashSetCalibration),
+    /* CHRE symbols */
+    ADD_EXPORTED_C_SYMBOL(chreAbort),
+    ADD_EXPORTED_C_SYMBOL(chreAudioConfigureSource),
+    ADD_EXPORTED_C_SYMBOL(chreAudioGetSource),
+    ADD_EXPORTED_C_SYMBOL(chreConfigureDebugDumpEvent),
+    ADD_EXPORTED_C_SYMBOL(chreConfigureHostSleepStateEvents),
+    ADD_EXPORTED_C_SYMBOL(chreConfigureNanoappInfoEvents),
+    ADD_EXPORTED_C_SYMBOL(chreGetApiVersion),
+    ADD_EXPORTED_C_SYMBOL(chreGetAppId),
+    ADD_EXPORTED_C_SYMBOL(chreGetInstanceId),
+    ADD_EXPORTED_C_SYMBOL(chreGetEstimatedHostTimeOffset),
+    ADD_EXPORTED_C_SYMBOL(chreGetNanoappInfoByAppId),
+    ADD_EXPORTED_C_SYMBOL(chreGetPlatformId),
+    ADD_EXPORTED_C_SYMBOL(chreGetSensorInfo),
+    ADD_EXPORTED_C_SYMBOL(chreGetSensorSamplingStatus),
+    ADD_EXPORTED_C_SYMBOL(chreGetTime),
+    ADD_EXPORTED_C_SYMBOL(chreGnssGetCapabilities),
+    ADD_EXPORTED_C_SYMBOL(chreGnssLocationSessionStartAsync),
+    ADD_EXPORTED_C_SYMBOL(chreGnssLocationSessionStopAsync),
+    ADD_EXPORTED_C_SYMBOL(chreGnssMeasurementSessionStartAsync),
+    ADD_EXPORTED_C_SYMBOL(chreGnssMeasurementSessionStopAsync),
+    ADD_EXPORTED_C_SYMBOL(chreHeapAlloc),
+    ADD_EXPORTED_C_SYMBOL(chreHeapFree),
+    ADD_EXPORTED_C_SYMBOL(chreIsHostAwake),
+    ADD_EXPORTED_C_SYMBOL(chreLog),
+    ADD_EXPORTED_C_SYMBOL(chreSendEvent),
+    ADD_EXPORTED_C_SYMBOL(chreSendMessageToHostEndpoint),
+    ADD_EXPORTED_C_SYMBOL(chreSensorConfigure),
+    ADD_EXPORTED_C_SYMBOL(chreSensorFindDefault),
+    ADD_EXPORTED_C_SYMBOL(chreTimerCancel),
+    ADD_EXPORTED_C_SYMBOL(chreTimerSet),
+    ADD_EXPORTED_C_SYMBOL(chreWifiConfigureScanMonitorAsync),
+    ADD_EXPORTED_C_SYMBOL(chreWifiGetCapabilities),
+    ADD_EXPORTED_C_SYMBOL(chreWifiRequestScanAsync),
+    ADD_EXPORTED_C_SYMBOL(chreWwanGetCapabilities),
+    ADD_EXPORTED_C_SYMBOL(chreWwanGetCellInfoAsync),
+    ADD_EXPORTED_C_SYMBOL(platform_chreDebugDumpVaLog),
+};
+
+}  // namespace
+
+void *NanoappLoader::create(void *elfInput, bool mapIntoTcm) {
+  void *instance = nullptr;
+  NanoappLoader *loader = memoryAllocDram<NanoappLoader>(elfInput, mapIntoTcm);
+  if (loader != nullptr) {
+    if (loader->open()) {
+      instance = loader;
+    } else {
+      memoryFreeDram(loader);
+    }
+  } else {
+    LOG_OOM();
+  }
+
+  return instance;
+}
+
+void NanoappLoader::destroy(NanoappLoader *loader) {
+  loader->close();
+  // TODO(b/151847750): Modify utilities to support free'ing from regions other
+  // than SRAM.
+  loader->~NanoappLoader();
+  memoryFreeDram(loader);
+}
+
+void *NanoappLoader::findExportedSymbol(const char *name) {
+  for (size_t i = 0; i < ARRAY_SIZE(gExportedData); i++) {
+    if (strncmp(name, gExportedData[i].dataName,
+                strlen(gExportedData[i].dataName)) == 0) {
+      return gExportedData[i].data;
+    }
+  }
+
+  LOGE("Unable to find %s", name);
+  return nullptr;
+}
+
+bool NanoappLoader::open() {
+  bool success = false;
+  if (mBinary.dataPtr != nullptr) {
+    if (!copyAndVerifyHeaders()) {
+      LOGE("Failed to verify headers");
+    } else if (!createMappings()) {
+      LOGE("Failed to create mappings");
+    } else if (!fixRelocations()) {
+      LOGE("Failed to fix relocations");
+    } else if (!resolveGot()) {
+      LOGE("Failed to resolve GOT");
+    } else {
+      // Wipe caches before calling init array to ensure initializers are not in
+      // the data cache.
+      wipeSystemCaches();
+      if (!callInitArray()) {
+        LOGE("Failed to perform static init");
+      } else {
+        success = true;
+      }
+    }
+  }
+
+  if (!success) {
+    freeAllocatedData();
+  }
+
+  return success;
+}
+
+void NanoappLoader::close() {
+  callAtexitFunctions();
+  callTerminatorArray();
+  freeAllocatedData();
+}
+
+void *NanoappLoader::findSymbolByName(const char *name) {
+  void *symbol = nullptr;
+  uint8_t *index = mSymbolTablePtr;
+  while (index < (mSymbolTablePtr + mSymbolTableSize)) {
+    ElfSym *currSym = reinterpret_cast<ElfSym *>(index);
+    const char *symbolName = &mStringTablePtr[currSym->st_name];
+
+    if (strncmp(symbolName, name, strlen(name)) == 0) {
+      symbol = reinterpret_cast<void *>(mMapping.data + currSym->st_value);
+      break;
+    }
+
+    index += sizeof(ElfSym);
+  }
+  return symbol;
+}
+
+void NanoappLoader::registerAtexitFunction(void (*function)(void)) {
+  if (!mAtexitFunctions.push_back(function)) {
+    LOG_OOM();
+    gStaticInitFailure = true;
+  }
+}
+
+void NanoappLoader::mapBss(const ProgramHeader *hdr) {
+  // if the memory size of this segment exceeds the file size zero fill the
+  // difference.
+  LOGV("Program Hdr mem sz: %zu file size: %zu", hdr->p_memsz, hdr->p_filesz);
+  if (hdr->p_memsz > hdr->p_filesz) {
+    ElfAddr endOfFile = hdr->p_vaddr + hdr->p_filesz + mLoadBias;
+    ElfAddr endOfMem = hdr->p_vaddr + hdr->p_memsz + mLoadBias;
+    if (endOfMem > endOfFile) {
+      auto deltaMem = endOfMem - endOfFile;
+      LOGV("Zeroing out %zu from page %p", deltaMem, endOfFile);
+      memset(reinterpret_cast<void *>(endOfFile), 0, deltaMem);
+    }
+  }
+}
+
+bool NanoappLoader::callInitArray() {
+  bool success = true;
+  // Sets global variable used by atexit in case it's invoked as part of
+  // initializing static data.
+  gCurrentlyLoadingNanoapp = this;
+
+  // TODO(b/151847750): ELF can have other sections like .init, .preinit, .fini
+  // etc. Be sure to look for those if they end up being something that should
+  // be supported for nanoapps.
+  for (size_t i = 0; i < mNumSectionHeaders; ++i) {
+    const char *name = getSectionHeaderName(mSectionHeadersPtr[i].sh_name);
+    if (strncmp(name, kInitArrayName, strlen(kInitArrayName)) == 0) {
+      LOGV("Invoking init function");
+      uintptr_t initArray = reinterpret_cast<uintptr_t>(
+          mLoadBias + mSectionHeadersPtr[i].sh_addr);
+      uintptr_t offset = 0;
+      while (offset < mSectionHeadersPtr[i].sh_size) {
+        ElfAddr *funcPtr = reinterpret_cast<ElfAddr *>(initArray + offset);
+        uintptr_t initFunction = reinterpret_cast<uintptr_t>(*funcPtr);
+        ((void (*)())initFunction)();
+        offset += sizeof(initFunction);
+        if (gStaticInitFailure) {
+          success = false;
+          break;
+        }
+      }
+      break;
+    }
+  }
+
+  //! Reset global state so it doesn't leak into the next load.
+  gCurrentlyLoadingNanoapp = nullptr;
+  gStaticInitFailure = false;
+  return success;
+}
+
+uintptr_t NanoappLoader::roundDownToAlign(uintptr_t virtualAddr) {
+  return virtualAddr & -kBinaryAlignment;
+}
+
+void NanoappLoader::freeAllocatedData() {
+  if (mIsTcmBinary) {
+    memoryFree(mMapping.dataPtr);
+  } else {
+    memoryFreeDram(mMapping.dataPtr);
+  }
+  memoryFreeDram(mSectionHeadersPtr);
+  memoryFreeDram(mSectionNamesPtr);
+  memoryFreeDram(mSymbolTablePtr);
+  memoryFreeDram(mStringTablePtr);
+}
+
+bool NanoappLoader::verifyElfHeader() {
+  bool success = false;
+  ElfHeader *elfHeader = getElfHeader();
+  if (elfHeader != nullptr && (elfHeader->e_ident[EI_MAG0] == ELFMAG0) &&
+      (elfHeader->e_ident[EI_MAG1] == ELFMAG1) &&
+      (elfHeader->e_ident[EI_MAG2] == ELFMAG2) &&
+      (elfHeader->e_ident[EI_MAG3] == ELFMAG3) &&
+      (elfHeader->e_ehsize == sizeof(ElfHeader)) &&
+      (elfHeader->e_phentsize == sizeof(ProgramHeader)) &&
+      (elfHeader->e_shentsize == sizeof(SectionHeader)) &&
+      (elfHeader->e_shstrndx < elfHeader->e_shnum) &&
+      (elfHeader->e_version == EV_CURRENT) &&
+      (elfHeader->e_machine == CHRE_LOADER_ARCH) &&
+      (elfHeader->e_type == ET_DYN)) {
+    success = true;
+  }
+  return success;
+}
+
+bool NanoappLoader::verifyProgramHeaders() {
+  // This is a minimal check for now -
+  // there should be at least one load segment.
+  bool success = false;
+  for (size_t i = 0; i < getProgramHeaderArraySize(); ++i) {
+    if (getProgramHeaderArray()[i].p_type == PT_LOAD) {
+      success = true;
+      break;
+    }
+  }
+  return success;
+}
+
+const char *NanoappLoader::getSectionHeaderName(size_t headerOffset) {
+  if (headerOffset == 0) {
+    return "";
+  }
+
+  return &mSectionNamesPtr[headerOffset];
+}
+
+NanoappLoader::SectionHeader *NanoappLoader::getSectionHeader(
+    const char *headerName) {
+  SectionHeader *rv = nullptr;
+  for (size_t i = 0; i < mNumSectionHeaders; ++i) {
+    const char *name = getSectionHeaderName(mSectionHeadersPtr[i].sh_name);
+    if (strncmp(name, headerName, strlen(headerName)) == 0) {
+      rv = &mSectionHeadersPtr[i];
+      break;
+    }
+  }
+  return rv;
+}
+
+ElfHeader *NanoappLoader::getElfHeader() {
+  return reinterpret_cast<ElfHeader *>(mBinary.data);
+}
+
+ProgramHeader *NanoappLoader::getProgramHeaderArray() {
+  ElfHeader *elfHeader = getElfHeader();
+  ProgramHeader *programHeader = nullptr;
+  if (elfHeader != nullptr) {
+    programHeader =
+        reinterpret_cast<ProgramHeader *>(mBinary.data + elfHeader->e_phoff);
+  }
+
+  return programHeader;
+}
+
+size_t NanoappLoader::getProgramHeaderArraySize() {
+  ElfHeader *elfHeader = getElfHeader();
+  size_t arraySize = 0;
+  if (elfHeader != nullptr) {
+    arraySize = elfHeader->e_phnum;
+  }
+
+  return arraySize;
+}
+
+char *NanoappLoader::getDynamicStringTable() {
+  char *table = nullptr;
+
+  SectionHeader *dynamicStringTablePtr = getSectionHeader(".dynstr");
+  CHRE_ASSERT(dynamicStringTablePtr != nullptr);
+  if (dynamicStringTablePtr != nullptr && mBinary.dataPtr != nullptr) {
+    table = reinterpret_cast<char *>(mBinary.data +
+                                     dynamicStringTablePtr->sh_offset);
+  }
+
+  return table;
+}
+
+uint8_t *NanoappLoader::getDynamicSymbolTable() {
+  uint8_t *table = nullptr;
+
+  SectionHeader *dynamicSymbolTablePtr = getSectionHeader(".dynsym");
+  CHRE_ASSERT(dynamicSymbolTablePtr != nullptr);
+  if (dynamicSymbolTablePtr != nullptr && mBinary.dataPtr != nullptr) {
+    table = reinterpret_cast<uint8_t *>(mBinary.data +
+                                        dynamicSymbolTablePtr->sh_offset);
+  }
+
+  return table;
+}
+
+size_t NanoappLoader::getDynamicSymbolTableSize() {
+  size_t tableSize = 0;
+
+  SectionHeader *dynamicSymbolTablePtr = getSectionHeader(".dynsym");
+  CHRE_ASSERT(dynamicSymbolTablePtr != nullptr);
+  if (dynamicSymbolTablePtr != nullptr) {
+    tableSize = dynamicSymbolTablePtr->sh_size;
+  }
+
+  return tableSize;
+}
+
+bool NanoappLoader::verifySectionHeaders() {
+  bool foundSymbolTableHeader = false;
+  bool foundStringTableHeader = false;
+
+  for (size_t i = 0; i < mNumSectionHeaders; ++i) {
+    const char *name = getSectionHeaderName(mSectionHeadersPtr[i].sh_name);
+
+    if (strncmp(name, kSymTableName, strlen(kSymTableName)) == 0) {
+      foundSymbolTableHeader = true;
+    } else if (strncmp(name, kStrTableName, strlen(kStrTableName)) == 0) {
+      foundStringTableHeader = true;
+    }
+  }
+
+  return foundSymbolTableHeader && foundStringTableHeader;
+}
+
+bool NanoappLoader::copyAndVerifyHeaders() {
+  size_t offset = 0;
+  bool success = false;
+  uint8_t *pDataBytes = static_cast<uint8_t *>(mBinary.dataPtr);
+
+  // Verify the ELF Header
+  ElfHeader *elfHeader = getElfHeader();
+  success = verifyElfHeader();
+
+  LOGV("Verified ELF header %d", success);
+
+  // Verify Program Headers
+  if (success) {
+    success = verifyProgramHeaders();
+  }
+
+  LOGV("Verified Program headers %d", success);
+
+  // Load Section Headers
+  if (success) {
+    offset = elfHeader->e_shoff;
+    size_t sectionHeaderSizeBytes = sizeof(SectionHeader) * elfHeader->e_shnum;
+    mSectionHeadersPtr =
+        static_cast<SectionHeader *>(memoryAllocDram(sectionHeaderSizeBytes));
+    if (mSectionHeadersPtr == nullptr) {
+      success = false;
+      LOG_OOM();
+    } else {
+      memcpy(mSectionHeadersPtr, (pDataBytes + offset), sectionHeaderSizeBytes);
+      mNumSectionHeaders = elfHeader->e_shnum;
+    }
+  }
+
+  LOGV("Loaded section headers %d", success);
+
+  // Load section header names
+  if (success) {
+    SectionHeader &stringSection = mSectionHeadersPtr[elfHeader->e_shstrndx];
+    size_t sectionSize = stringSection.sh_size;
+    mSectionNamesPtr = static_cast<char *>(memoryAllocDram(sectionSize));
+    if (mSectionNamesPtr == nullptr) {
+      LOG_OOM();
+      success = false;
+    } else {
+      memcpy(mSectionNamesPtr,
+             reinterpret_cast<void *>(mBinary.data + stringSection.sh_offset),
+             sectionSize);
+    }
+  }
+
+  LOGV("Loaded section header names %d", success);
+
+  success = verifySectionHeaders();
+  LOGV("Verified Section headers %d", success);
+
+  // Load symbol table
+  if (success) {
+    SectionHeader *symbolTableHeader = getSectionHeader(kSymTableName);
+    mSymbolTableSize = symbolTableHeader->sh_size;
+    if (mSymbolTableSize == 0) {
+      LOGE("No symbols to resolve");
+      success = false;
+    } else {
+      mSymbolTablePtr =
+          static_cast<uint8_t *>(memoryAllocDram(mSymbolTableSize));
+      if (mSymbolTablePtr == nullptr) {
+        LOG_OOM();
+        success = false;
+      } else {
+        memcpy(mSymbolTablePtr,
+               reinterpret_cast<void *>(mBinary.data +
+                                        symbolTableHeader->sh_offset),
+               mSymbolTableSize);
+      }
+    }
+  }
+
+  LOGV("Loaded symbol table %d", success);
+
+  // Load string table
+  if (success) {
+    SectionHeader *stringTableHeader = getSectionHeader(kStrTableName);
+    size_t stringTableSize = stringTableHeader->sh_size;
+    if (mSymbolTableSize == 0) {
+      LOGE("No string table corresponding to symbols");
+      success = false;
+    } else {
+      mStringTablePtr = static_cast<char *>(memoryAllocDram(stringTableSize));
+      if (mStringTablePtr == nullptr) {
+        LOG_OOM();
+        success = false;
+      } else {
+        memcpy(mStringTablePtr,
+               reinterpret_cast<void *>(mBinary.data +
+                                        stringTableHeader->sh_offset),
+               stringTableSize);
+      }
+    }
+  }
+
+  LOGV("Loaded string table %d", success);
+
+  return success;
+}
+
+bool NanoappLoader::createMappings() {
+  // ELF needs pt_load segments to be in contiguous ascending order of
+  // virtual addresses. So the first and last segs can be used to
+  // calculate the entire address span of the image.
+  ElfHeader *elfHeader = getElfHeader();
+  ProgramHeader *programHeaderArray = getProgramHeaderArray();
+  size_t numProgramHeaders = getProgramHeaderArraySize();
+  const ProgramHeader *first = &programHeaderArray[0];
+  const ProgramHeader *last = &programHeaderArray[numProgramHeaders - 1];
+
+  // Find first load segment
+  while (first->p_type != PT_LOAD && first <= last) {
+    ++first;
+  }
+
+  bool success = false;
+  if (first->p_type != PT_LOAD) {
+    LOGE("Unable to find any load segments in the binary");
+  } else {
+    // Verify that the first load segment has a program header
+    // first byte of a valid load segment can't be greater than the
+    // program header offset
+    bool valid =
+        (first->p_offset < elfHeader->e_phoff) &&
+        (first->p_filesz >
+         (elfHeader->e_phoff + (numProgramHeaders * sizeof(ProgramHeader))));
+    if (!valid) {
+      LOGE("Load segment program header validation failed");
+    } else {
+      // Get the last load segment
+      while (last > first && last->p_type != PT_LOAD) --last;
+
+      size_t memorySpan = last->p_vaddr + last->p_memsz - first->p_vaddr;
+      LOGV("Nanoapp image Memory Span: %u", memorySpan);
+
+      if (mIsTcmBinary) {
+        mMapping.dataPtr = memoryAllocAligned(kBinaryAlignment, memorySpan);
+      } else {
+        mMapping.dataPtr = memoryAllocDramAligned(kBinaryAlignment, memorySpan);
+      }
+
+      if (mMapping.dataPtr == nullptr) {
+        LOG_OOM();
+      } else {
+        LOGV("Starting location of mappings %p", mMapping.dataPtr);
+
+        // Calculate the load bias using the first load segment.
+        uintptr_t adjustedFirstLoadSegAddr = roundDownToAlign(first->p_vaddr);
+        mLoadBias = mMapping.data - adjustedFirstLoadSegAddr;
+        LOGV("Load bias is %" PRIu32, mLoadBias);
+
+        success = true;
+      }
+    }
+  }
+
+  if (success) {
+    // Map the remaining segments
+    for (const ProgramHeader *ph = first; ph <= last; ++ph) {
+      if (ph->p_type == PT_LOAD) {
+        ElfAddr segStart = ph->p_vaddr + mLoadBias;
+        void *startPage = reinterpret_cast<void *>(roundDownToAlign(segStart));
+        ElfAddr phOffsetPage = roundDownToAlign(ph->p_offset);
+        void *binaryStartPage =
+            reinterpret_cast<void *>(mBinary.data + phOffsetPage);
+        size_t segmentLen = ph->p_filesz;
+
+        LOGV("Mapping start page %p from %p with length %zu", startPage,
+             binaryStartPage, segmentLen);
+        memcpy(startPage, binaryStartPage, segmentLen);
+        mapBss(ph);
+      } else {
+        LOGE("Non-load segment found between load segments");
+        success = false;
+        break;
+      }
+    }
+  }
+
+  return success;
+}
+
+const char *NanoappLoader::getDataName(size_t posInSymbolTable) {
+  size_t sectionSize = getDynamicSymbolTableSize();
+  uint8_t *dynamicSymbolTable = getDynamicSymbolTable();
+  size_t numElements = sectionSize / sizeof(ElfSym);
+  CHRE_ASSERT(posInSymbolTable < numElements);
+  char *dataName = nullptr;
+  if (posInSymbolTable < numElements) {
+    ElfSym *sym = reinterpret_cast<ElfSym *>(
+        &dynamicSymbolTable[posInSymbolTable * sizeof(ElfSym)]);
+    dataName = &getDynamicStringTable()[sym->st_name];
+  }
+  return dataName;
+}
+
+void *NanoappLoader::resolveData(size_t posInSymbolTable) {
+  const char *dataName = getDataName(posInSymbolTable);
+
+  if (dataName != nullptr) {
+    LOGV("Resolving %s", dataName);
+    return findExportedSymbol(dataName);
+  }
+
+  return nullptr;
+}
+
+NanoappLoader::DynamicHeader *NanoappLoader::getDynamicHeader() {
+  DynamicHeader *dyn = nullptr;
+  ProgramHeader *programHeaders = getProgramHeaderArray();
+  for (size_t i = 0; i < getProgramHeaderArraySize(); ++i) {
+    if (programHeaders[i].p_type == PT_DYNAMIC) {
+      dyn = reinterpret_cast<DynamicHeader *>(programHeaders[i].p_vaddr +
+                                              mLoadBias);
+      break;
+    }
+  }
+  return dyn;
+}
+
+NanoappLoader::ProgramHeader *NanoappLoader::getFirstRoSegHeader() {
+  // return the first read only segment found
+  ProgramHeader *ro = nullptr;
+  ProgramHeader *programHeaders = getProgramHeaderArray();
+  for (size_t i = 0; i < getProgramHeaderArraySize(); ++i) {
+    if (!(programHeaders[i].p_flags & PF_W)) {
+      ro = &programHeaders[i];
+      break;
+    }
+  }
+  return ro;
+}
+
+NanoappLoader::ElfWord NanoappLoader::getDynEntry(DynamicHeader *dyn,
+                                                  int field) {
+  ElfWord rv = 0;
+
+  while (dyn->d_tag != DT_NULL) {
+    if (dyn->d_tag == field) {
+      rv = dyn->d_un.d_val;
+      break;
+    }
+    ++dyn;
+  }
+
+  return rv;
+}
+
+bool NanoappLoader::fixRelocations() {
+  ElfAddr *addr;
+  DynamicHeader *dyn = getDynamicHeader();
+  ProgramHeader *roSeg = getFirstRoSegHeader();
+
+  bool success = false;
+  if ((dyn == nullptr) || (roSeg == nullptr)) {
+    LOGE("Mandatory headers missing from shared object, aborting load");
+  } else if (getDynEntry(dyn, DT_RELA) != 0) {
+    LOGE("Elf binaries with a DT_RELA dynamic entry are unsupported");
+  } else {
+    ElfRel *reloc =
+        reinterpret_cast<ElfRel *>(getDynEntry(dyn, DT_REL) + mBinary.data);
+    size_t relocSize = getDynEntry(dyn, DT_RELSZ);
+    size_t nRelocs = relocSize / sizeof(ElfRel);
+    LOGV("Relocation %zu entries in DT_REL table", nRelocs);
+
+    size_t i;
+    for (i = 0; i < nRelocs; ++i) {
+      ElfRel *curr = &reloc[i];
+      int relocType = ELFW_R_TYPE(curr->r_info);
+      switch (relocType) {
+        case R_ARM_RELATIVE:
+          LOGV("Resolving ARM_RELATIVE at offset %" PRIx32, curr->r_offset);
+          addr = reinterpret_cast<ElfAddr *>(curr->r_offset + mMapping.data);
+          // TODO: When we move to DRAM allocations, we need to check if the
+          // above address is in a Read-Only section of memory, and give it
+          // temporary write permission if that is the case.
+          *addr += mMapping.data;
+          break;
+
+        case R_ARM_ABS32:
+        case R_ARM_GLOB_DAT: {
+          LOGV("Resolving type %d at offset %" PRIx32, relocType,
+               curr->r_offset);
+          addr = reinterpret_cast<ElfAddr *>(curr->r_offset + mMapping.data);
+          size_t posInSymbolTable = ELFW_R_SYM(curr->r_info);
+          void *resolved = resolveData(posInSymbolTable);
+          if (resolved == nullptr) {
+            LOGV("Failed to resolve global symbol(%d) at offset 0x%x", i,
+                 curr->r_offset);
+            return false;
+          }
+          // TODO: When we move to DRAM allocations, we need to check if the
+          // above address is in a Read-Only section of memory, and give it
+          // temporary write permission if that is the case.
+          *addr = reinterpret_cast<ElfAddr>(resolved);
+          break;
+        }
+
+        case R_ARM_COPY:
+          LOGE("R_ARM_COPY is an invalid relocation for shared libraries");
+          break;
+        default:
+          LOGE("Invalid relocation type %u", relocType);
+          break;
+      }
+    }
+
+    if (i != nRelocs) {
+      LOGE("Unable to resolve all symbols in the binary");
+    } else {
+      success = true;
+    }
+  }
+
+  return success;
+}
+
+bool NanoappLoader::resolveGot() {
+  ElfAddr *addr;
+  ElfRel *reloc = reinterpret_cast<ElfRel *>(
+      getDynEntry(getDynamicHeader(), DT_JMPREL) + mMapping.data);
+  size_t relocSize = getDynEntry(getDynamicHeader(), DT_PLTRELSZ);
+  size_t nRelocs = relocSize / sizeof(ElfRel);
+  LOGV("Resolving GOT with %zu relocations", nRelocs);
+
+  for (size_t i = 0; i < nRelocs; ++i) {
+    ElfRel *curr = &reloc[i];
+    int relocType = ELFW_R_TYPE(curr->r_info);
+
+    switch (relocType) {
+      case R_ARM_JUMP_SLOT: {
+        LOGV("Resolving ARM_JUMP_SLOT at offset %" PRIx32, curr->r_offset);
+        addr = reinterpret_cast<ElfAddr *>(curr->r_offset + mMapping.data);
+        size_t posInSymbolTable = ELFW_R_SYM(curr->r_info);
+        void *resolved = resolveData(posInSymbolTable);
+        if (resolved == nullptr) {
+          LOGV("Failed to resolve symbol(%d) at offset 0x%x", i,
+               curr->r_offset);
+          return false;
+        }
+        *addr = reinterpret_cast<ElfAddr>(resolved);
+        break;
+      }
+
+      default:
+        LOGE("Unsupported relocation type: %u for symbol %s", relocType,
+             getDataName(ELFW_R_SYM(curr->r_info)));
+        return false;
+    }
+  }
+  return true;
+}
+
+void NanoappLoader::callAtexitFunctions() {
+  while (!mAtexitFunctions.empty()) {
+    LOGV("Calling atexit at %p", mAtexitFunctions.back());
+    mAtexitFunctions.back()();
+    mAtexitFunctions.pop_back();
+  }
+}
+
+void NanoappLoader::callTerminatorArray() {
+  for (size_t i = 0; i < mNumSectionHeaders; ++i) {
+    const char *name = getSectionHeaderName(mSectionHeadersPtr[i].sh_name);
+    if (strncmp(name, kFiniArrayName, strlen(kFiniArrayName)) == 0) {
+      uintptr_t finiArray = reinterpret_cast<uintptr_t>(
+          mLoadBias + mSectionHeadersPtr[i].sh_addr);
+      uintptr_t offset = 0;
+      while (offset < mSectionHeadersPtr[i].sh_size) {
+        ElfAddr *funcPtr = reinterpret_cast<ElfAddr *>(finiArray + offset);
+        uintptr_t finiFunction = reinterpret_cast<uintptr_t>(*funcPtr);
+        ((void (*)())finiFunction)();
+        offset += sizeof(finiFunction);
+      }
+      break;
+    }
+  }
+}
+
+}  // namespace chre
diff --git a/platform/slpi/host_link.cc b/platform/slpi/host_link.cc
index 990f02d..ed2f6b8 100644
--- a/platform/slpi/host_link.cc
+++ b/platform/slpi/host_link.cc
@@ -514,6 +514,7 @@
  * @param transactionId the ID of the transaction
  * @param appId the ID of the app to load
  * @param appVersion the version of the app to load
+ * @param appFlags The flags provided by the app being loaded
  * @param targetApiVersion the API version this nanoapp is targeted for
  * @param buffer the nanoapp binary data. May be only part of the nanoapp's
  *     binary if it's being sent over multiple fragments
@@ -524,20 +525,23 @@
  * @return A valid pointer to a nanoapp that can be loaded into the system. A
  *     nullptr if the preparation process fails.
  */
-UniquePtr<Nanoapp> handleLoadNanoappData(
-    uint16_t hostClientId, uint32_t transactionId, uint64_t appId,
-    uint32_t appVersion, uint32_t targetApiVersion, const void *buffer,
-    size_t bufferLen, uint32_t fragmentId, size_t appBinaryLen) {
+UniquePtr<Nanoapp> handleLoadNanoappData(uint16_t hostClientId,
+                                         uint32_t transactionId, uint64_t appId,
+                                         uint32_t appVersion, uint32_t appFlags,
+                                         uint32_t targetApiVersion,
+                                         const void *buffer, size_t bufferLen,
+                                         uint32_t fragmentId,
+                                         size_t appBinaryLen) {
   static NanoappLoadManager sLoadManager;
 
   bool success = true;
   if (fragmentId == 0 || fragmentId == 1) {  // first fragment
     size_t totalAppBinaryLen = (fragmentId == 0) ? bufferLen : appBinaryLen;
     LOGD("Load nanoapp request for app ID 0x%016" PRIx64 " ver 0x%" PRIx32
-         " target API 0x%08" PRIx32 " size %zu (txnId %" PRIu32
-         " client %" PRIu16 ")",
-         appId, appVersion, targetApiVersion, totalAppBinaryLen, transactionId,
-         hostClientId);
+         " flags 0x%" PRIx32 " target API 0x%08" PRIx32
+         " size %zu (txnId %" PRIu32 " client %" PRIu16 ")",
+         appId, appVersion, appFlags, targetApiVersion, totalAppBinaryLen,
+         transactionId, hostClientId);
 
     if (sLoadManager.hasPendingLoadTransaction()) {
       FragmentedLoadInfo info = sLoadManager.getTransactionInfo();
@@ -546,8 +550,9 @@
       sLoadManager.markFailure();
     }
 
-    success = sLoadManager.prepareForLoad(hostClientId, transactionId, appId,
-                                          appVersion, totalAppBinaryLen);
+    success =
+        sLoadManager.prepareForLoad(hostClientId, transactionId, appId,
+                                    appVersion, appFlags, totalAppBinaryLen);
   }
   success &= sLoadManager.copyNanoappFragment(
       hostClientId, transactionId, (fragmentId == 0) ? 1 : fragmentId, buffer,
@@ -844,18 +849,18 @@
 
 void HostMessageHandlers::handleLoadNanoappRequest(
     uint16_t hostClientId, uint32_t transactionId, uint64_t appId,
-    uint32_t appVersion, uint32_t targetApiVersion, const void *buffer,
-    size_t bufferLen, const char *appFileName, uint32_t fragmentId,
-    size_t appBinaryLen) {
+    uint32_t appVersion, uint32_t appFlags, uint32_t targetApiVersion,
+    const void *buffer, size_t bufferLen, const char *appFileName,
+    uint32_t fragmentId, size_t appBinaryLen) {
   UniquePtr<Nanoapp> pendingNanoapp;
   if (appFileName != nullptr) {
     pendingNanoapp =
         handleLoadNanoappFile(hostClientId, transactionId, appId, appVersion,
                               targetApiVersion, appFileName);
   } else {
-    pendingNanoapp = handleLoadNanoappData(hostClientId, transactionId, appId,
-                                           appVersion, targetApiVersion, buffer,
-                                           bufferLen, fragmentId, appBinaryLen);
+    pendingNanoapp = handleLoadNanoappData(
+        hostClientId, transactionId, appId, appVersion, appFlags,
+        targetApiVersion, buffer, bufferLen, fragmentId, appBinaryLen);
   }
 
   if (!pendingNanoapp.isNull()) {
diff --git a/platform/slpi/include/chre/target_platform/platform_nanoapp_base.h b/platform/slpi/include/chre/target_platform/platform_nanoapp_base.h
index 72aeb61..0dab266 100644
--- a/platform/slpi/include/chre/target_platform/platform_nanoapp_base.h
+++ b/platform/slpi/include/chre/target_platform/platform_nanoapp_base.h
@@ -48,11 +48,13 @@
    *
    * @param appId The unique app identifier associated with this binary
    * @param appVersion An application-defined version number
+   * @param appFlags The flags provided by the app being loaded
    * @param appBinaryLen Size of appBinary, in bytes
    *
    * @return true if the allocation was successful, false otherwise
    */
-  bool reserveBuffer(uint64_t appId, uint32_t appVersion, size_t appBinarylen);
+  bool reserveBuffer(uint64_t appId, uint32_t appVersion, uint32_t appFlags,
+                     size_t appBinarylen);
 
   /**
    * Copies the (possibly fragmented) application binary data into the allocated
diff --git a/platform/slpi/platform_nanoapp.cc b/platform/slpi/platform_nanoapp.cc
index 10d68f1..3ac54df 100644
--- a/platform/slpi/platform_nanoapp.cc
+++ b/platform/slpi/platform_nanoapp.cc
@@ -207,6 +207,7 @@
 }
 
 bool PlatformNanoappBase::reserveBuffer(uint64_t appId, uint32_t appVersion,
+                                        uint32_t /* appFlags */,
                                         size_t appBinaryLen) {
   CHRE_ASSERT(!isLoaded());
   bool success = false;
diff --git a/platform/usf/include/chre/platform/usf/usf_helper.h b/platform/usf/include/chre/platform/usf/usf_helper.h
new file mode 100644
index 0000000..98fa6a2
--- /dev/null
+++ b/platform/usf/include/chre/platform/usf/usf_helper.h
@@ -0,0 +1,368 @@
+/*
+ * Copyright (C) 2020 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 CHRE_PLATFORM_USF_USF_HELPER_H_
+#define CHRE_PLATFORM_USF_USF_HELPER_H_
+
+#include "chre/core/sensor_type.h"
+#include "chre/platform/system_time.h"
+#include "chre/util/dynamic_vector.h"
+#include "chre/util/unique_ptr.h"
+
+/*
+ * Include USF FlatBuffers services header before including other USF
+ * FlatBuffers headers.
+ */
+#include "usf/usf_flatbuffers.h"
+
+#include "usf/error.h"
+#include "usf/fbs/usf_msg_sample_root_generated.h"
+#include "usf/reffed_ptr.h"
+#include "usf/usf_power.h"
+#include "usf/usf_sensor.h"
+#include "usf/usf_sensor_report.h"
+#include "usf/usf_sensor_req.h"
+#include "usf/usf_transport_client.h"
+
+namespace chre {
+
+struct SensorInfo {
+  //! Used to fill in the chreSensorDataHeader field. If the user of the helper
+  //! doesn't care about this field, then it can be set to 0.
+  uint32_t sensorHandle;
+
+  //! Sampling ID corresponding to the active subscription to this sensor, if
+  //! one exists. 0 otherwise.
+  uint32_t samplingId;
+};
+
+//! A callback interface for receiving UsfHelper data events and to allow the
+//! UsfHelper to ask for information.
+class UsfHelperCallbackInterface {
+ public:
+  //! Invoked from the USF worker thread to provide sensor data events. The
+  //! event data format is one of the chreSensorXXXData defined in the CHRE API,
+  //! implicitly specified by sensorType.
+  virtual void onSensorDataEvent(uint8_t sensorType,
+                                 UniquePtr<uint8_t> &&eventData) = 0;
+
+  /**
+   * Invoked by the USF Helper to obtain sensor info for a given sensor type.
+   *
+   * @param sensorType The sensor type for which info should be populated
+   * @param sensorInfo Non-null pointer that will be populated with sensor info
+   *     if this method is successful
+   * @return true if all sensor info was obtained.
+   */
+  virtual bool getSensorInfo(uint8_t sensorType, SensorInfo *sensorInfo) = 0;
+
+  //! Invoked from the USF worker thread to provide a sensor sampling status
+  //! update. All fields are guaranteed to be valid.
+  virtual void onSamplingStatusUpdate(
+      uint8_t sensorType, const usf::UsfSensorSamplingEvent *update) = 0;
+
+  //! Invoked from the USF worker thread to indicate that flushing the given
+  //! sensor has completed.
+  virtual void onFlushComplete(usf::UsfErr err, uint32_t requestId,
+                               void *cookie) = 0;
+
+  //! Invoked from the USF worker thread to provide a bias update.
+  virtual void onBiasUpdate(
+      uint8_t sensorType,
+      UniquePtr<struct chreSensorThreeAxisData> &&eventData) = 0;
+
+  //! Invoked from the USF worker thread to provide an host wake suspend update.
+  virtual void onHostWakeSuspendEvent(bool awake) = 0;
+};
+
+//! Class that allows clients of UsfHelper that only want to listen for sensor
+//! events to implement only functions that relate to those events.
+class SensorEventCallbackInterface : public UsfHelperCallbackInterface {
+ public:
+  void onSamplingStatusUpdate(
+      uint8_t /*sensorType*/,
+      const usf::UsfSensorSamplingEvent * /*update*/) override {}
+
+  void onFlushComplete(usf::UsfErr /*err*/, uint32_t /*requestId*/,
+                       void * /*cookie*/) override {}
+
+  void onBiasUpdate(
+      uint8_t /*sensorType*/,
+      UniquePtr<struct chreSensorThreeAxisData> && /*eventData*/) override {}
+
+  void onHostWakeSuspendEvent(bool /*awake*/) override {}
+};
+
+//! Default timeout to wait for the USF transport client to return a response
+//! to a request
+constexpr Nanoseconds kDefaultUsfWaitTimeout = Seconds(5);
+
+/**
+ * Helper class used to abstract away most details of communicating with USF.
+ */
+class UsfHelper {
+ public:
+  ~UsfHelper();
+
+  //! Maps USF error codes to CHRE error codes.
+  static uint8_t usfErrorToChreError(usf::UsfErr err);
+
+  /**
+   * Initializes connection to USF.
+   *
+   * @param callback USF helper callback.
+   * @param worker USF worker pointer. If null, a new worker is created.
+   */
+  void init(UsfHelperCallbackInterface *callback,
+            usf::UsfWorker *worker = nullptr);
+
+  /**
+   * Retrieves the list of sensors available from USF.
+   *
+   * @param sensorList Non-null pointer to a UsfVector that will be populated
+   *     with all the available sensors from USF.
+   * @return true if the list of sensors was retrieved successfully.
+   */
+  bool getSensorList(
+      usf::UsfVector<refcount::reffed_ptr<usf::UsfSensor>> *sensorList);
+
+  /**
+   * Starts sampling sensor data from the given sensor.
+   *
+   * @param request The various parameters needed to issue the request to the
+   *     sensor.
+   * @param samplingId Non-null pointer that will be filled in with a valid
+   *     sampling ID if the request is issued successfully.
+   * @return true if the sensor request was issued successfully.
+   */
+  bool startSampling(usf::UsfStartSamplingReq *request, uint32_t *samplingId);
+
+  /**
+   * Reconfigures an existing sensor request.
+   *
+   * @param request The various parameters needed to issue the request to the
+   *     sensor.
+   * @return true if the sensor request was reconfigured successfully.
+   */
+  bool reconfigureSampling(usf::UsfReconfigSamplingReq *request);
+
+  /**
+   * Stops an active sensor request.
+   *
+   * @param request The various parameters needed to issue the request to the
+   *     sensor.
+   * @return true if the sensor request was stopped successfully.
+   */
+  bool stopSampling(usf::UsfStopSamplingReq *request);
+
+  /**
+   * Registers the helper for sensor sampling status updates from USF for the
+   * given sensor.
+   *
+   * @param sensor The sensor to listen to sampling status updates from.
+   * @return true if registration was successful.
+   */
+  bool registerForStatusUpdates(refcount::reffed_ptr<usf::UsfSensor> &sensor);
+
+  /**
+   * Registers the helper for bias updates from USF for the given sensor.
+   * This method is not idempotent meaning a call to unregisterForBiasUpdates
+   * must be made for the same sensor prior to another call to this method.
+   *
+   * @param sensor A non-null reffed_ptr to the sensor to listen for bias
+   *     updates from.
+   * @return true if registration was successful.
+   */
+  bool registerForBiasUpdates(
+      const refcount::reffed_ptr<usf::UsfSensor> &sensor);
+
+  /**
+   * Registers the helper for AP power state update from USF.
+   *
+   * @return true if successful.
+   */
+  bool registerForApPowerStateUpdates();
+
+  /**
+   * Unregisters the helper from bias updates from USF for the given sensor.
+   * If not registered, this method is a no-op.
+   *
+   * @param sensor A non-null reffed_ptr to the sensor to stop listening to bias
+   *     updates from.
+   */
+  void unregisterForBiasUpdates(
+      const refcount::reffed_ptr<usf::UsfSensor> &sensor);
+
+  /**
+   * Obtains the latest three axis bias received from the given sensor, if
+   * available.
+   *
+   * @param sensor The USF sensor to obtain the latest bias values from.
+   * @param bias A non-null pointer that will be filled with the latest bias
+   *     values if available.
+   * @return true if a bias value was available for the given sensor.
+   */
+  bool getThreeAxisBias(const refcount::reffed_ptr<usf::UsfSensor> &sensor,
+                        struct chreSensorThreeAxisData *bias) const;
+
+  /**
+   * Flushes sensor data from the provided sensor asynchronously. Once all data
+   * has been flushed, {@link #onFlushCompleteEvent} will be invoked on the
+   * callback the helper was initialized with.
+   *
+   * @param usfSensorHandle The USF handle corresponding to the sensor to flush
+   * @param cookie A cookie that will be delivered when the flush has completed
+   * @param requestId The ID associated with this flush request, if it was
+   *     made successfully
+   * @return True if the flush request was successfully sent.
+   */
+  bool flushAsync(const usf::UsfServerHandle usfSensorHandle, void *cookie,
+                  uint32_t *requestId);
+
+  /**
+   * Used to process sensor reports delivered through a listener registered with
+   * USF.
+   *
+   * @param event Pointer containing a valid sensor report
+   */
+  void processSensorReport(const usf::UsfMsgEvent *event);
+
+  /**
+   * Used to process a sensor sampling status update delivered through a
+   * listener registered with USF.
+   *
+   * @param update Update containing the latest state from USF.
+   */
+  void processStatusUpdate(const usf::UsfSensorSamplingEvent *update);
+
+  /**
+   * Process the bias update delivered through a listener registered with USF.
+   *
+   * @param update Update containing latest bias information for a sensor.
+   */
+  void processBiasUpdate(const usf::UsfSensorTransformConfigEvent *update);
+
+  /**
+   * Process the AP power state update delivered through a  listener registered
+   * with USF.
+   *
+   * @param update Update containing the current AP power state.
+   */
+  void processApPowerStateUpdate(const usf::UsfApPowerStateEvent *update);
+
+ private:
+  /**
+   * Sends a synchronous request to USF with any returned data stored in the
+   * given callback.
+   *
+   * @param req Request that should be issued to USF
+   * @param callback Helper callback function used to perform the synchronous
+   *     request and contains any response message after the method returns
+   * @return true if no errors were encountered issuing the request
+   */
+  bool sendUsfReqSync(usf::UsfReq *req, usf::UsfReqSyncCallback *callback);
+
+  /**
+   * Retrieves the response message from the synchronous callback. The memory
+   * associated with the message is owned by the callback itself and will be
+   * freed upon callback going out of scope.
+   *
+   * @param callback Synchronous callback previously used in a successful
+   *     sendUsfReqSync invocation
+   * @param respMsg Variable containing the response message if this method
+   *     succeeds
+   * @return true if the response message was decoded and verified successfully
+   *     and respMsg is valid.
+   */
+  template <class T>
+  bool getRespMsg(usf::UsfReqSyncCallback &callback, const T **respMsg);
+
+  /**
+   * Creates a CHRE sensor event from a USF sensor event
+   *
+   * @param sampleReport Valid decoded USF sensor sample report
+   * @param sensorType CHRE sensor type corresponding to the event
+   * @param sensorSample Upon success, populated with a valid CHRE sensor event
+   * @return true if the USF sensor event corresponds to a valid, active
+   *     sampling request and event creation was successful
+   */
+  bool createSensorEvent(const usf::UsfSensorSampleReport *sampleReport,
+                         uint8_t sensorType, UniquePtr<uint8_t> &sensorSample);
+
+  /**
+   * Converts a USF bias update event into the chreSensorThreeAxisData format.
+   *
+   * @param update USF bias update that needs to be converted.
+   * @return If successful, contains a populated UniquePtr. Otherwise, the
+   *     UniquePtr will be set to nullptr.
+   */
+  UniquePtr<struct chreSensorThreeAxisData> convertUsfBiasUpdateToData(
+      const usf::UsfSensorTransformConfigEvent *update);
+
+  /**
+   * Get the index into the calibrated sensor data array for the given sensor
+   * type.
+   *
+   * @param usfSensorType The USF sensor type to obtain the index for.
+   * @return The index into the data array for the given sensor type. If the
+   *     sensor type isn't present in the array, kNumUsfCalSensors is returned.
+   */
+  static size_t getCalArrayIndex(const usf::UsfSensorType usfSensorType);
+
+  //! A struct to store a sensor's calibration data.
+  struct UsfCalData {
+    uint64_t timestamp;
+    float bias[3];
+    bool hasBias;
+    uint8_t accuracy;
+  };
+
+  //! The list of calibrated USF sensors supported.
+  enum class UsfCalSensor : size_t {
+    AccelCal,
+    GyroCal,
+    MagCal,
+    NumCalSensors,
+  };
+
+  static constexpr size_t kNumUsfCalSensors =
+      static_cast<size_t>(UsfCalSensor::NumCalSensors);
+
+  //! Cal data of all the cal sensors.
+  UsfCalData mCalData[kNumUsfCalSensors] = {};
+
+  //! Client used to send messages to USF
+  refcount::reffed_ptr<usf::UsfTransportClient> mTransportClient;
+
+  //! UsfWorker used to dispatch messages to CHRE on its own thread
+  refcount::reffed_ptr<usf::UsfWorker> mWorker;
+
+  //! All registered USF event listeners.
+  DynamicVector<usf::UsfEventListener *> mUsfEventListeners;
+
+  //! Handle to the USF sensor manager
+  usf::UsfServerHandle mSensorMgrHandle;
+
+  //! Currently registered callback
+  UsfHelperCallbackInterface *mCallback = nullptr;
+
+  //! The error encountered during initialization, if any.
+  usf::UsfErr mInitError = usf::UsfErr::kErrNone;
+};
+
+}  // namespace chre
+
+#endif  // CHRE_PLATFORM_USF_USF_HELPER_H_
diff --git a/platform/usf/include/chre/target_platform/platform_sensor_base.h b/platform/usf/include/chre/target_platform/platform_sensor_base.h
new file mode 100644
index 0000000..a209d36
--- /dev/null
+++ b/platform/usf/include/chre/target_platform/platform_sensor_base.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2020 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 CHRE_PLATFORM_USF_PLATFORM_SENSOR_BASE_H_
+#define CHRE_PLATFORM_USF_PLATFORM_SENSOR_BASE_H_
+
+#include "chre/util/optional.h"
+
+#include "usf/usf.h"
+#include "usf/usf_sensor.h"
+
+namespace chre {
+
+//! The max length of sensorName
+constexpr size_t kSensorNameMaxLen = 64;
+
+class PlatformSensorBase {
+ public:
+  /**
+   * Initializes various members of PlatformSensorBase.
+   */
+  void initBase(refcount::reffed_ptr<usf::UsfSensor> &usfSensor,
+                uint8_t sensorType);
+
+  usf::UsfServerHandle getServerHandle() const {
+    return mUsfSensor->GetHandle();
+  }
+
+  bool isSamplingIdValid() const {
+    return mSamplingId.has_value();
+  }
+
+  void setSamplingIdInvalid() {
+    mSamplingId.reset();
+  }
+
+  void setSamplingId(uint32_t samplingId) {
+    mSamplingId = samplingId;
+  }
+
+  uint32_t getSamplingId() const {
+    return mSamplingId.value();
+  }
+
+  const refcount::reffed_ptr<usf::UsfSensor> &getUsfSensor() const {
+    return mUsfSensor;
+  }
+
+ protected:
+  //! The USF sensor instance for this sensor.
+  refcount::reffed_ptr<usf::UsfSensor> mUsfSensor;
+
+  //! The sampling ID of the active request with this sensor.
+  Optional<uint32_t> mSamplingId;
+
+  //! Sensor type of this sensor. Needed because USF shares the same sensor
+  //! type for two different CHRE sensors (ACCELEROMETER_TEMPERATURE and
+  //! GYROSCOPE_TEMPERATURE).
+  uint8_t mSensorType;
+};
+
+}  // namespace chre
+
+#endif  // CHRE_PLATFORM_USF_PLATFORM_SENSOR_BASE_H_
diff --git a/platform/usf/include/chre/target_platform/platform_sensor_manager_base.h b/platform/usf/include/chre/target_platform/platform_sensor_manager_base.h
new file mode 100644
index 0000000..24b882a
--- /dev/null
+++ b/platform/usf/include/chre/target_platform/platform_sensor_manager_base.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2020 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 CHRE_PLATFORM_USF_PLATFORM_SENSOR_MANAGER_BASE_H_
+#define CHRE_PLATFORM_USF_PLATFORM_SENSOR_MANAGER_BASE_H_
+
+#include "chre/platform/usf/usf_helper.h"
+#include "chre/util/unique_ptr.h"
+
+namespace chre {
+
+class PlatformSensorManagerBase : public UsfHelperCallbackInterface {
+ public:
+  void onSensorDataEvent(uint8_t sensorType,
+                         UniquePtr<uint8_t> &&eventData) override;
+
+  void onSamplingStatusUpdate(
+      uint8_t sensorType, const usf::UsfSensorSamplingEvent *update) override;
+
+  bool getSensorInfo(uint8_t sensorType, SensorInfo *sensorInfo) override;
+
+  void onFlushComplete(usf::UsfErr err, uint32_t requestId,
+                       void *cookie) override;
+
+  void onBiasUpdate(
+      uint8_t sensorType,
+      UniquePtr<struct chreSensorThreeAxisData> &&eventData) override;
+
+  void onHostWakeSuspendEvent(bool awake) override;
+
+  /**
+   * Converts the given UsfSensor into one or more CHRE sensors and adds them
+   * to the given dynamic vector.
+   *
+   * @param usfSensor UsfSensor to be converted to one or more CHRE sensors
+   * @param chreSensors Dynamic vector that converted sensors must be added to
+   */
+  void addSensorsWithInfo(refcount::reffed_ptr<usf::UsfSensor> &usfSensor,
+                          DynamicVector<Sensor> *chreSensors);
+
+ protected:
+  //! Helper used to communicate with USF.
+  UsfHelper mHelper;
+};
+
+}  // namespace chre
+
+#endif  // CHRE_PLATFORM_USF_PLATFORM_SENSOR_MANAGER_BASE_H_
diff --git a/platform/usf/include/chre/target_platform/platform_sensor_type_helpers_base.h b/platform/usf/include/chre/target_platform/platform_sensor_type_helpers_base.h
new file mode 100644
index 0000000..f762706
--- /dev/null
+++ b/platform/usf/include/chre/target_platform/platform_sensor_type_helpers_base.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2020 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 CHRE_TARGET_PLATFORM_SENSOR_TYPE_HELPERS_BASE_H_
+#define CHRE_TARGET_PLATFORM_SENSOR_TYPE_HELPERS_BASE_H_
+
+#include "usf/usf_sensor_defs.h"
+
+namespace chre {
+
+/**
+ * This SensorSampleType is designed to help classify sensor's data type in
+ * event handling.
+ */
+enum class SensorSampleType {
+  Byte,
+  Float,
+  Uint64,
+  Occurrence,
+  ThreeAxis,
+  Unknown,
+};
+
+/**
+ * Can be used to expose static methods to the PlatformSensorTypeHelpers class
+ * for use in working with vendor sensor types. Currently, this is stubbed out
+ * in the AOC implementation.
+ */
+class PlatformSensorTypeHelpersBase {
+ public:
+  /**
+   * Maps a sensorType to its SensorSampleType.
+   *
+   * @param sensorType The type of the sensor to obtain its SensorSampleType
+   *     for.
+   * @return The SensorSampleType of the sensorType.
+   */
+  static SensorSampleType getSensorSampleTypeFromSensorType(uint8_t sensorType);
+
+  /**
+   * @return Whether the given sensor type reports bias events.
+   */
+  static bool reportsBias(uint8_t sensorType);
+
+  //! Helper functions used to convert between USF and CHRE types
+  static usf::UsfSensorReportingMode getUsfReportingMode(ReportingMode mode);
+  static bool convertUsfToChreSensorType(usf::UsfSensorType usfSensorType,
+                                         uint8_t *chreSensorType);
+  static uint8_t convertUsfToChreSampleAccuracy(
+      usf::UsfSampleAccuracy usfSampleAccuracy);
+};
+
+}  // namespace chre
+
+#endif  // CHRE_TARGET_PLATFORM_SENSOR_TYPE_HELPERS_BASE_H_
diff --git a/platform/usf/memory.cc b/platform/usf/memory.cc
new file mode 100644
index 0000000..13ad629
--- /dev/null
+++ b/platform/usf/memory.cc
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2020 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 "usf/usf_mem.h"
+
+namespace chre {
+
+void *memoryAlloc(size_t size) {
+  return usf::UsfAlloc(usf::kUsfHeapLowPower, size);
+}
+
+void memoryFree(void *pointer) {
+  usf::UsfFree(pointer);
+}
+
+}  // namespace chre
diff --git a/platform/usf/platform_sensor.cc b/platform/usf/platform_sensor.cc
new file mode 100644
index 0000000..e4a310c
--- /dev/null
+++ b/platform/usf/platform_sensor.cc
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2020 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 "chre/platform/platform_sensor.h"
+
+#include "chre/core/sensor_type_helpers.h"
+
+namespace chre {
+
+void PlatformSensorBase::initBase(
+    refcount::reffed_ptr<usf::UsfSensor> &usfSensor, uint8_t sensorType) {
+  mUsfSensor = usfSensor;
+  mSensorType = sensorType;
+}
+
+uint8_t PlatformSensor::getSensorType() const {
+  return mSensorType;
+}
+
+uint64_t PlatformSensor::getMinInterval() const {
+  return SensorTypeHelpers::isOneShot(mSensorType)
+             ? CHRE_SENSOR_INTERVAL_DEFAULT
+             : mUsfSensor->GetMinDelayNs();
+  ;
+}
+
+bool PlatformSensor::reportsBiasEvents() const {
+  return PlatformSensorTypeHelpersBase::reportsBias(getSensorType());
+}
+
+bool PlatformSensor::supportsPassiveMode() const {
+  return true;
+}
+
+const char *PlatformSensor::getSensorName() const {
+  return SensorTypeHelpers::getSensorTypeName(getSensorType());
+}
+
+PlatformSensor::PlatformSensor(PlatformSensor &&other) {
+  *this = std::move(other);
+}
+
+PlatformSensor &PlatformSensor::operator=(PlatformSensor &&other) {
+  // Note: if this implementation is ever changed to depend on "this" containing
+  // initialized values, the move constructor implementation must be updated.
+  mUsfSensor = other.mUsfSensor;
+  mSensorType = other.mSensorType;
+
+  return *this;
+}
+
+}  // namespace chre
diff --git a/platform/usf/platform_sensor_manager.cc b/platform/usf/platform_sensor_manager.cc
new file mode 100644
index 0000000..1b35b39
--- /dev/null
+++ b/platform/usf/platform_sensor_manager.cc
@@ -0,0 +1,323 @@
+/*
+ * Copyright (C) 2020 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 "chre/platform/platform_sensor_manager.h"
+
+#include "chre/core/event_loop_manager.h"
+#include "chre/util/nested_data_ptr.h"
+
+namespace chre {
+namespace {
+
+void addSensor(refcount::reffed_ptr<usf::UsfSensor> &usfSensor,
+               uint8_t sensorType, DynamicVector<Sensor> *chreSensors) {
+  if (!chreSensors->emplace_back()) {
+    FATAL_ERROR("Failed to allocate new sensor");
+  }
+
+  // The sensor base class must be initialized before the main Sensor init()
+  // can be invoked as init() is allowed to invoke base class methods.
+  chreSensors->back().initBase(usfSensor, sensorType);
+  chreSensors->back().init();
+
+  const Sensor &newSensor = chreSensors->back();
+  LOGI("%s, type %" PRIu8 ", minInterval %" PRIu64 " handle %" PRId32,
+       newSensor.getSensorName(), newSensor.getSensorType(),
+       newSensor.getMinInterval(), newSensor.getServerHandle());
+}
+
+ReportingMode getReportingMode(Sensor &sensor) {
+  if (sensor.isOneShot()) {
+    return ReportingMode::OneShot;
+  } else if (sensor.isOnChange()) {
+    return ReportingMode::OnChange;
+  } else {
+    return ReportingMode::Continuous;
+  }
+}
+
+void handleMissingSensor() {
+  // Try rebooting if a sensor is missing, which might help recover from a
+  // transient failure/race condition at startup. But to avoid endless crashes,
+  // only do this within 45 seconds of boot time - we rely on knowledge that
+  // getMonotonicTime() only resets when the device reboots and not from an SSR.
+#ifndef CHRE_LOG_ONLY_NO_SENSOR
+  if (SystemTime::getMonotonicTime() < (kDefaultUsfWaitTimeout + Seconds(15))) {
+    FATAL_ERROR("Missing required sensor(s)");
+  } else
+#endif
+  {
+    LOGE("Missing required sensor(s)");
+  }
+}
+
+}  // namespace
+
+PlatformSensorManager::~PlatformSensorManager() {}
+
+void PlatformSensorManager::init() {
+  mHelper.init(this);
+  if (!mHelper.registerForApPowerStateUpdates()) {
+    LOGE("Failed to register for AP power state updates.");
+  }
+}
+
+DynamicVector<Sensor> PlatformSensorManager::getSensors() {
+  DynamicVector<Sensor> sensors;
+
+  usf::UsfVector<refcount::reffed_ptr<usf::UsfSensor>> sensorList;
+  if (mHelper.getSensorList(&sensorList)) {
+    for (size_t i = 0; i < sensorList.size(); i++) {
+      refcount::reffed_ptr<usf::UsfSensor> &sensor = sensorList[i];
+      addSensorsWithInfo(sensor, &sensors);
+    }
+  }
+
+  // TODO: Determine how many sensors are expected to be available through USF.
+  if (sensors.empty()) {
+    handleMissingSensor();
+  }
+
+  return sensors;
+}
+
+bool PlatformSensorManager::configureSensor(Sensor &sensor,
+                                            const SensorRequest &request) {
+  bool success = false;
+
+  if (request.getMode() == SensorMode::Off && !sensor.isSamplingIdValid()) {
+    // If no existing sampling ID exists and the sensor should be turned off,
+    // return true. This can happen when the core platform tries to remove a
+    // request for a one-shot sensor after it fires which automatically happens
+    // inside USF.
+    success = true;
+  } else if (request.getMode() == SensorMode::Off) {
+    usf::UsfStopSamplingReq usfReq;
+    usfReq.SetReqType(usf::UsfMsgReqType_STOP_SAMPLING);
+    usfReq.SetServerHandle(sensor.getServerHandle());
+    usfReq.SetSamplingId(sensor.getSamplingId());
+
+    if (mHelper.stopSampling(&usfReq)) {
+      sensor.setSamplingIdInvalid();
+      success = true;
+    }
+  } else if (sensor.isSamplingIdValid()) {
+    // TODO: See if a reconfigure request can be the same as reissuing a start
+    // request.
+    usf::UsfReconfigSamplingReq usfReq;
+    usfReq.SetReqType(usf::UsfMsgReqType_RECONFIG_SAMPLING);
+    usfReq.SetServerHandle(sensor.getServerHandle());
+    usfReq.SetSamplingId(sensor.getSamplingId());
+    usfReq.SetPeriodNs(request.getInterval().toRawNanoseconds());
+    usfReq.SetMaxLatencyNs(
+        sensor.isContinuous() ? request.getLatency().toRawNanoseconds() : 0);
+
+    success = mHelper.reconfigureSampling(&usfReq);
+  } else {
+    usf::UsfStartSamplingReq usfReq;
+    usfReq.SetReqType(usf::UsfMsgReqType_START_SAMPLING);
+    usfReq.SetServerHandle(sensor.getServerHandle());
+    // TODO(147438885): Make setting the reporting mode of a sensor optional
+    usfReq.SetReportingMode(PlatformSensorTypeHelpersBase::getUsfReportingMode(
+        getReportingMode(sensor)));
+    usfReq.SetPeriodNs(request.getInterval().toRawNanoseconds());
+    usfReq.SetMaxLatencyNs(
+        sensor.isContinuous() ? request.getLatency().toRawNanoseconds() : 0);
+    usfReq.SetPassive(sensorModeIsPassive(request.getMode()));
+    usf::UsfSensorTransformLevel transformLevel =
+        sensor.isCalibrated() ? usf::kUsfSensorTransformLevelAll
+                              : usf::kUsfSensorTransformEnvCompensation;
+    usfReq.SetTransformLevel(transformLevel);
+
+    uint32_t samplingId;
+    if (mHelper.startSampling(&usfReq, &samplingId)) {
+      sensor.setSamplingId(samplingId);
+      success = true;
+    }
+  }
+
+  return success;
+}
+
+bool PlatformSensorManager::configureBiasEvents(const Sensor &sensor,
+                                                bool enable,
+                                                uint64_t /* latencyNs */) {
+  bool success = true;
+  if (enable) {
+    success = mHelper.registerForBiasUpdates(sensor.getUsfSensor());
+  } else {
+    // Unconditional success for unregistering
+    mHelper.unregisterForBiasUpdates(sensor.getUsfSensor());
+  }
+
+  return success;
+}
+
+bool PlatformSensorManager::getThreeAxisBias(
+    const Sensor &sensor, struct chreSensorThreeAxisData *bias) const {
+  bool success = sensor.reportsBiasEvents();
+  if (success) {
+    if (!mHelper.getThreeAxisBias(sensor.getUsfSensor(), bias)) {
+      // Set to zero bias + unknown accuracy per CHRE API requirements.
+      memset(bias, 0, sizeof(chreSensorThreeAxisData));
+      bias->header.readingCount = 1;
+      bias->header.accuracy = CHRE_SENSOR_ACCURACY_UNKNOWN;
+    }
+
+    // Overwrite sensorHandle to match the request type.
+    getSensorRequestManager().getSensorHandle(sensor.getSensorType(),
+                                              &bias->header.sensorHandle);
+  }
+  return success;
+}
+
+bool PlatformSensorManager::flush(const Sensor &sensor,
+                                  uint32_t *flushRequestId) {
+  NestedDataPtr<uint32_t> cookie(sensor.getSensorType());
+  return mHelper.flushAsync(sensor.getServerHandle(), cookie.dataPtr,
+                            flushRequestId);
+}
+
+void PlatformSensorManager::releaseSamplingStatusUpdate(
+    struct chreSensorSamplingStatus *status) {
+  memoryFree(status);
+}
+
+void PlatformSensorManager::releaseSensorDataEvent(void *data) {
+  memoryFree(data);
+}
+
+void PlatformSensorManager::releaseBiasEvent(void *biasData) {
+  memoryFree(biasData);
+}
+
+void PlatformSensorManagerBase::onSamplingStatusUpdate(
+    uint8_t sensorType, const usf::UsfSensorSamplingEvent *update) {
+  uint32_t sensorHandle;
+  if (getSensorRequestManager().getSensorHandle(sensorType, &sensorHandle)) {
+    // Memory will be freed via releaseSamplingStatusUpdate once core framework
+    // performs its updates.
+    auto statusUpdate = MakeUniqueZeroFill<struct chreSensorSamplingStatus>();
+    if (statusUpdate.isNull()) {
+      LOG_OOM();
+    } else {
+      statusUpdate->interval = update->GetPeriodNs();
+      statusUpdate->latency = update->GetMaxLatencyNs();
+      statusUpdate->enabled = update->GetActive();
+      getSensorRequestManager().handleSamplingStatusUpdate(
+          sensorHandle, statusUpdate.release());
+    }
+  }
+}
+
+bool PlatformSensorManagerBase::getSensorInfo(uint8_t sensorType,
+                                              SensorInfo *sensorInfo) {
+  bool success = false;
+
+  if (getSensorRequestManager().getSensorHandle(sensorType,
+                                                &sensorInfo->sensorHandle)) {
+    Sensor *sensor =
+        getSensorRequestManager().getSensor(sensorInfo->sensorHandle);
+    if (sensor != nullptr && sensor->isSamplingIdValid()) {
+      success = true;
+      sensorInfo->samplingId = sensor->getSamplingId();
+    } else {
+      sensorInfo->sensorHandle = 0;
+    }
+  }
+  return success;
+}
+
+void PlatformSensorManagerBase::onSensorDataEvent(
+    uint8_t sensorType, UniquePtr<uint8_t> &&eventData) {
+  uint32_t sensorHandle;
+  if (getSensorRequestManager().getSensorHandle(sensorType, &sensorHandle)) {
+    Sensor *sensor = getSensorRequestManager().getSensor(sensorHandle);
+    // Invalidate the sampling ID for one shot sensors after they've fired since
+    // USF automatically removes the sampling ID from its list.
+    if (sensor->isOneShot()) {
+      sensor->setSamplingIdInvalid();
+    }
+
+    getSensorRequestManager().handleSensorDataEvent(sensorHandle,
+                                                    eventData.release());
+  }
+}
+
+void PlatformSensorManagerBase::onFlushComplete(usf::UsfErr err,
+                                                uint32_t requestId,
+                                                void *cookie) {
+  NestedDataPtr<uint32_t> nestedSensorType;
+  nestedSensorType.dataPtr = cookie;
+
+  uint32_t sensorHandle;
+  if (getSensorRequestManager().getSensorHandle(nestedSensorType.data,
+                                                &sensorHandle)) {
+    getSensorRequestManager().handleFlushCompleteEvent(
+        sensorHandle, requestId, UsfHelper::usfErrorToChreError(err));
+  }
+}
+
+void PlatformSensorManagerBase::onBiasUpdate(
+    uint8_t sensorType, UniquePtr<struct chreSensorThreeAxisData> &&eventData) {
+  uint32_t sensorHandle;
+  if (getSensorRequestManager().getSensorHandle(sensorType, &sensorHandle)) {
+    eventData->header.sensorHandle = sensorHandle;
+    getSensorRequestManager().handleBiasEvent(sensorHandle,
+                                              eventData.release());
+  }
+}
+
+void PlatformSensorManagerBase::onHostWakeSuspendEvent(bool awake) {
+  EventLoopManagerSingleton::get()
+      ->getEventLoop()
+      .getPowerControlManager()
+      .onHostWakeSuspendEvent(awake);
+}
+
+void PlatformSensorManagerBase::addSensorsWithInfo(
+    refcount::reffed_ptr<usf::UsfSensor> &usfSensor,
+    DynamicVector<Sensor> *chreSensors) {
+  uint8_t sensorType;
+  if (PlatformSensorTypeHelpersBase::convertUsfToChreSensorType(
+          usfSensor->GetType(), &sensorType)) {
+    // Only register for a USF sensor once. If it maps to multiple sensors,
+    // code down the line will handle sending multiple updates.
+    if (!mHelper.registerForStatusUpdates(usfSensor)) {
+      LOGE("Failed to register for status updates for %s",
+           usfSensor->GetName());
+    }
+
+    addSensor(usfSensor, sensorType, chreSensors);
+
+    // USF shares the same sensor type for calibrated and uncalibrated sensors
+    // so populate a calibrated / uncalibrated sensor for known calibrated
+    // sensor types
+    uint8_t uncalibratedType =
+        SensorTypeHelpers::toUncalibratedSensorType(sensorType);
+    if (uncalibratedType != sensorType) {
+      addSensor(usfSensor, uncalibratedType, chreSensors);
+    }
+
+    // USF shares a sensor type for both gyro and accel temp.
+    if (sensorType == CHRE_SENSOR_TYPE_GYROSCOPE_TEMPERATURE) {
+      addSensor(usfSensor, CHRE_SENSOR_TYPE_ACCELEROMETER_TEMPERATURE,
+                chreSensors);
+    }
+  }
+}
+
+}  // namespace chre
diff --git a/platform/usf/platform_sensor_type_helpers.cc b/platform/usf/platform_sensor_type_helpers.cc
new file mode 100644
index 0000000..aa68c2c
--- /dev/null
+++ b/platform/usf/platform_sensor_type_helpers.cc
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2020 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 "chre/platform/platform_sensor_type_helpers.h"
+#include "chre/target_platform/assert.h"
+#include "chre/util/macros.h"
+
+#ifdef CHREX_SENSOR_SUPPORT
+#include "chre/extensions/platform/vendor_sensor_types.h"
+#endif  // CHREX_SENSOR_SUPPORT
+
+namespace chre {
+
+ReportingMode PlatformSensorTypeHelpers::getVendorSensorReportingMode(
+    uint8_t /* sensorType */) {
+  // TODO: Stubbed out, implement this
+  return ReportingMode::Continuous;
+}
+
+bool PlatformSensorTypeHelpers::getVendorSensorIsCalibrated(
+    uint8_t /* sensorType */) {
+  // TODO: Stubbed out, implement this
+  return false;
+}
+
+bool PlatformSensorTypeHelpers::getVendorSensorBiasEventType(
+    uint8_t /* sensorType */, uint16_t * /* eventType */) {
+  // TODO: Stubbed out, implement this
+  return false;
+}
+
+const char *PlatformSensorTypeHelpers::getVendorSensorTypeName(
+    uint8_t sensorType) {
+#ifdef CHREX_SENSOR_SUPPORT
+  return extension::vendorSensorTypeName(sensorType);
+#else
+  UNUSED_VAR(sensorType);
+  return "";
+#endif
+}
+
+size_t PlatformSensorTypeHelpers::getVendorSensorLastEventSize(
+    uint8_t /* sensorType */) {
+  // TODO: Stubbed out, implement this
+  return 0;
+}
+
+void PlatformSensorTypeHelpers::getVendorLastSample(
+    uint8_t /* sensorType */, const ChreSensorData * /* event */,
+    ChreSensorData * /* lastEvent */) {
+  // TODO: Stubbed out, implement this
+}
+
+bool PlatformSensorTypeHelpersBase::reportsBias(uint8_t sensorType) {
+  switch (sensorType) {
+    case CHRE_SENSOR_TYPE_ACCELEROMETER:
+    case CHRE_SENSOR_TYPE_GYROSCOPE:
+    case CHRE_SENSOR_TYPE_GEOMAGNETIC_FIELD:
+    case CHRE_SENSOR_TYPE_UNCALIBRATED_ACCELEROMETER:
+    case CHRE_SENSOR_TYPE_UNCALIBRATED_GYROSCOPE:
+    case CHRE_SENSOR_TYPE_UNCALIBRATED_GEOMAGNETIC_FIELD:
+      return true;
+    default:
+      return false;
+  }
+}
+
+SensorSampleType
+PlatformSensorTypeHelpersBase::getSensorSampleTypeFromSensorType(
+    uint8_t sensorType) {
+  switch (sensorType) {
+    case CHRE_SENSOR_TYPE_ACCELEROMETER:
+    case CHRE_SENSOR_TYPE_GYROSCOPE:
+    case CHRE_SENSOR_TYPE_GEOMAGNETIC_FIELD:
+    case CHRE_SENSOR_TYPE_UNCALIBRATED_ACCELEROMETER:
+    case CHRE_SENSOR_TYPE_UNCALIBRATED_GYROSCOPE:
+    case CHRE_SENSOR_TYPE_UNCALIBRATED_GEOMAGNETIC_FIELD:
+      return SensorSampleType::ThreeAxis;
+    case CHRE_SENSOR_TYPE_PRESSURE:
+    case CHRE_SENSOR_TYPE_LIGHT:
+    case CHRE_SENSOR_TYPE_ACCELEROMETER_TEMPERATURE:
+    case CHRE_SENSOR_TYPE_GYROSCOPE_TEMPERATURE:
+    case CHRE_SENSOR_TYPE_GEOMAGNETIC_FIELD_TEMPERATURE:
+      return SensorSampleType::Float;
+    case CHRE_SENSOR_TYPE_INSTANT_MOTION_DETECT:
+    case CHRE_SENSOR_TYPE_STATIONARY_DETECT:
+    case CHRE_SENSOR_TYPE_STEP_DETECT:
+      return SensorSampleType::Occurrence;
+    case CHRE_SENSOR_TYPE_PROXIMITY:
+      return SensorSampleType::Byte;
+    case CHRE_SENSOR_TYPE_STEP_COUNTER:
+      return SensorSampleType::Uint64;
+    default:
+#ifdef CHREX_SENSOR_SUPPORT
+      return extension::vendorSensorSampleTypeFromSensorType(sensorType);
+#else
+      // Update implementation to prevent undefined from being used.
+      CHRE_ASSERT(false);
+      return SensorSampleType::Unknown;
+#endif
+  }
+}
+
+usf::UsfSensorReportingMode PlatformSensorTypeHelpersBase::getUsfReportingMode(
+    ReportingMode mode) {
+  if (mode == ReportingMode::OnChange) {
+    return usf::kUsfSensorReportOnChange;
+  } else if (mode == ReportingMode::OneShot) {
+    return usf::kUsfSensorReportOneShot;
+  } else {
+    return usf::kUsfSensorReportContinuous;
+  }
+}
+
+bool PlatformSensorTypeHelpersBase::convertUsfToChreSensorType(
+    usf::UsfSensorType usfSensorType, uint8_t *chreSensorType) {
+  bool success = true;
+  switch (usfSensorType) {
+    case usf::UsfSensorType::kUsfSensorAccelerometer:
+      *chreSensorType = CHRE_SENSOR_TYPE_ACCELEROMETER;
+      break;
+    case usf::UsfSensorType::kUsfSensorGyroscope:
+      *chreSensorType = CHRE_SENSOR_TYPE_GYROSCOPE;
+      break;
+    case usf::UsfSensorType::kUsfSensorProx:
+      *chreSensorType = CHRE_SENSOR_TYPE_PROXIMITY;
+      break;
+    case usf::UsfSensorType::kUsfSensorBaro:
+      *chreSensorType = CHRE_SENSOR_TYPE_PRESSURE;
+      break;
+    case usf::UsfSensorType::kUsfSensorMag:
+      *chreSensorType = CHRE_SENSOR_TYPE_GEOMAGNETIC_FIELD;
+      break;
+    case usf::UsfSensorType::kUsfSensorMagTemp:
+      *chreSensorType = CHRE_SENSOR_TYPE_GEOMAGNETIC_FIELD_TEMPERATURE;
+      break;
+    case usf::UsfSensorType::kUsfSensorImuTemp:
+      *chreSensorType = CHRE_SENSOR_TYPE_GYROSCOPE_TEMPERATURE;
+      break;
+    case usf::UsfSensorType::kUsfSensorAmbientLight:
+      *chreSensorType = CHRE_SENSOR_TYPE_LIGHT;
+      break;
+    case usf::UsfSensorType::kUsfSensorMotionDetect:
+      *chreSensorType = CHRE_SENSOR_TYPE_INSTANT_MOTION_DETECT;
+      break;
+    case usf::UsfSensorType::kUsfSensorStationaryDetect:
+      *chreSensorType = CHRE_SENSOR_TYPE_STATIONARY_DETECT;
+      break;
+    case usf::UsfSensorType::kUsfSensorStepDetector:
+      *chreSensorType = CHRE_SENSOR_TYPE_STEP_DETECT;
+      break;
+    case usf::UsfSensorType::kUsfSensorStepCounter:
+      *chreSensorType = CHRE_SENSOR_TYPE_STEP_COUNTER;
+      break;
+    default:
+#ifdef CHREX_SENSOR_SUPPORT
+      success = extension::vendorConvertUsfToChreSensorType(usfSensorType,
+                                                            chreSensorType);
+#else
+      // Don't print anything as USF exposes sensor types CHRE doesn't care
+      // about (e.g. Camera Vsync, and color)
+      success = false;
+#endif
+      break;
+  }
+  return success;
+}
+
+uint8_t PlatformSensorTypeHelpersBase::convertUsfToChreSampleAccuracy(
+    usf::UsfSampleAccuracy usfSampleAccuracy) {
+  switch (usfSampleAccuracy) {
+    case usf::kUsfSampleAccuracyUnreliable:
+      return CHRE_SENSOR_ACCURACY_UNRELIABLE;
+    case usf::kUsfSampleAccuracyLow:
+      return CHRE_SENSOR_ACCURACY_LOW;
+    case usf::kUsfSampleAccuracyMedium:
+      return CHRE_SENSOR_ACCURACY_MEDIUM;
+    case usf::kUsfSampleAccuracyHigh:
+      return CHRE_SENSOR_ACCURACY_HIGH;
+    case usf::kUsfSampleAccuracyUnknown:
+    default:
+      return CHRE_SENSOR_ACCURACY_UNKNOWN;
+  }
+}
+
+}  // namespace chre
diff --git a/platform/usf/usf_helper.cc b/platform/usf/usf_helper.cc
new file mode 100644
index 0000000..f3336d4
--- /dev/null
+++ b/platform/usf/usf_helper.cc
@@ -0,0 +1,706 @@
+/*
+ * Copyright (C) 2020 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 "chre/platform/usf/usf_helper.h"
+
+#include <cinttypes>
+
+#include "chre/core/sensor_type_helpers.h"
+#include "chre/platform/fatal_error.h"
+#include "chre/util/macros.h"
+
+#include "usf/fbs/usf_msg_event_root_generated.h"
+#include "usf/fbs/usf_msg_sample_batch_root_generated.h"
+#include "usf/fbs/usf_msg_sensor_info_resp_root_generated.h"
+#include "usf/fbs/usf_msg_sensor_list_resp_root_generated.h"
+#include "usf/fbs/usf_msg_start_sampling_resp_root_generated.h"
+#include "usf/usf.h"
+#include "usf/usf_device.h"
+#include "usf/usf_flatbuffers.h"
+#include "usf/usf_sensor_defs.h"
+#include "usf/usf_sensor_mgr.h"
+#include "usf/usf_time.h"
+#include "usf/usf_work.h"
+
+using usf::UsfErr;
+using usf::UsfErr::kErrNone;
+
+#define LOG_USF_ERR(x) LOGE("USF failure %" PRIu8 ": line %d", x, __LINE__)
+
+namespace chre {
+namespace {
+
+//! Callback data delivered to async callback from USF. This struct must be
+//! freed using memoryFree.
+struct AsyncCallbackData {
+  UsfHelperCallbackInterface *callback;
+  void *cookie;
+};
+
+void eventHandler(void *context, usf::UsfEvent *event) {
+  const usf::UsfMsgEvent *msgEvent =
+      static_cast<usf::UsfTransportMsgEvent *>(event)->GetMsgEvent();
+
+  if (msgEvent != nullptr) {
+    auto *mgr = static_cast<UsfHelper *>(context);
+
+    switch (msgEvent->event_type()) {
+      case usf::UsfMsgEventType_kSensorReport:
+        mgr->processSensorReport(msgEvent);
+        break;
+      default:
+        LOGD("Received unknown event type %" PRIu8, msgEvent->event_type());
+        break;
+    }
+  }
+}
+
+void statusUpdateHandler(void *context, usf::UsfEvent *event) {
+  const usf::UsfSensorSamplingEvent *updateEvent =
+      static_cast<usf::UsfSensorSamplingEvent *>(event);
+
+  if (context != nullptr && updateEvent != nullptr) {
+    auto *mgr = static_cast<UsfHelper *>(context);
+    mgr->processStatusUpdate(updateEvent);
+  }
+}
+
+void biasUpdateHandler(void *context, usf::UsfEvent *event) {
+  const auto *updateEvent =
+      static_cast<usf::UsfSensorTransformConfigEvent *>(event);
+
+  if (context != nullptr && updateEvent != nullptr) {
+    auto *mgr = static_cast<UsfHelper *>(context);
+    mgr->processBiasUpdate(updateEvent);
+  }
+}
+
+void apPowerStateUpdateHandler(void *context, usf::UsfEvent *event) {
+  const auto *updateEvent = static_cast<usf::UsfApPowerStateEvent *>(event);
+
+  if (context != nullptr && updateEvent != nullptr) {
+    auto *mgr = static_cast<UsfHelper *>(context);
+    mgr->processApPowerStateUpdate(updateEvent);
+  }
+}
+
+void asyncCallback(usf::UsfReq *req, const usf::UsfResp *resp, void *data) {
+  auto callbackData = static_cast<AsyncCallbackData *>(data);
+  callbackData->callback->onFlushComplete(resp->GetErr(), req->GetReqId(),
+                                          callbackData->cookie);
+
+  memoryFree(data);
+  memoryFree(req);
+}
+
+void *allocateEvent(uint8_t sensorType, size_t numSamples) {
+  SensorSampleType sampleType =
+      PlatformSensorTypeHelpers::getSensorSampleTypeFromSensorType(sensorType);
+  size_t sampleSize = 0;
+  switch (sampleType) {
+    case SensorSampleType::ThreeAxis:
+      sampleSize =
+          sizeof(chreSensorThreeAxisData::chreSensorThreeAxisSampleData);
+      break;
+    case SensorSampleType::Float:
+      sampleSize = sizeof(chreSensorFloatData::chreSensorFloatSampleData);
+      break;
+    case SensorSampleType::Byte:
+      sampleSize = sizeof(chreSensorByteData::chreSensorByteSampleData);
+      break;
+    case SensorSampleType::Occurrence:
+      sampleSize =
+          sizeof(chreSensorOccurrenceData::chreSensorOccurrenceSampleData);
+      break;
+    default:
+      LOGE("Unhandled SensorSampleType for SensorType %" PRIu8,
+           static_cast<uint8_t>(sensorType));
+      sampleType = SensorSampleType::Unknown;
+  }
+
+  size_t memorySize =
+      (sampleType == SensorSampleType::Unknown)
+          ? 0
+          : (sizeof(chreSensorDataHeader) + numSamples * sampleSize);
+  void *event = (memorySize == 0) ? nullptr : memoryAlloc(memorySize);
+
+  if (event == nullptr && memorySize != 0) {
+    LOG_OOM();
+  }
+  return event;
+}
+
+/**
+ * Allocates the memory and sets up the chreSensorDataHeader used to deliver
+ * the sensor samples to nanoapps.
+ *
+ * @param numSamples The number of samples there should be space for in the
+ *     sensor event
+ * @param sensorHandle The handle to the sensor that produced the events
+ * @param sensor The CHRE sensor instance for the sensor that produced the
+ *     events
+ * @param sensorSamples A non-null UniquePtr used to hold the sensor samples
+ * @return If successful, sensorSamples will point to valid memory that
+ *     contains a valid chreSensorDataHeader and enough space to store all
+ *     sensor samples
+ */
+bool prepareSensorEvent(size_t numSamples, uint32_t sensorHandle,
+                        uint8_t sensorType, UniquePtr<uint8_t> &sensorSamples) {
+  bool success = false;
+
+  UniquePtr<uint8_t> buf(
+      static_cast<uint8_t *>(allocateEvent(sensorType, numSamples)));
+  sensorSamples = std::move(buf);
+
+  if (!sensorSamples.isNull()) {
+    success = true;
+
+    auto *header =
+        reinterpret_cast<chreSensorDataHeader *>(sensorSamples.get());
+    header->sensorHandle = sensorHandle;
+    header->readingCount = numSamples;
+    header->reserved = 0;
+  }
+
+  return success;
+}
+
+/**
+ * Populates CHRE sensor sample structure using USF sensor samples.
+ *
+ * @param sampleReport The USF report that contains sensor samples
+ * @param sensor The CHRE sensor instance for the sensor that produced the
+ *     events
+ * @param sensorSample Reference to a UniquePtr instance that points to non-null
+ *     memory that will be populated with sensor samples
+ */
+void populateSensorEvent(const usf::UsfSensorSampleReport *sampleReport,
+                         uint8_t sensorType, UniquePtr<uint8_t> &sensorSample) {
+  uint64_t prevSampleTimeNs = 0;
+
+  for (int i = 0; i < sampleReport->GetSampleCount(); i++) {
+    usf::UsfSampleEvent sampleEvent;
+    UsfErr err = sampleReport->GetSample(i, &sampleEvent);
+    if (err != kErrNone) {
+      LOG_USF_ERR(err);
+      continue;
+    }
+
+    SensorSampleType sampleType =
+        PlatformSensorTypeHelpers::getSensorSampleTypeFromSensorType(
+            sensorType);
+
+    uint32_t *timestampDelta = nullptr;
+    switch (sampleType) {
+      case SensorSampleType::ThreeAxis: {
+        auto *event =
+            reinterpret_cast<chreSensorThreeAxisData *>(sensorSample.get());
+
+        // TODO(b/143139477): Apply calibration to data from these sensors
+        for (size_t valIndex = 0; valIndex < 3; valIndex++) {
+          event->readings[i].values[valIndex] = sampleEvent.data[valIndex];
+        }
+        timestampDelta = &event->readings[i].timestampDelta;
+        break;
+      }
+      case SensorSampleType::Float: {
+        auto *event =
+            reinterpret_cast<chreSensorFloatData *>(sensorSample.get());
+        event->readings[i].value = sampleEvent.data[0];
+        timestampDelta = &event->readings[i].timestampDelta;
+        break;
+      }
+      case SensorSampleType::Byte: {
+        auto *event =
+            reinterpret_cast<chreSensorByteData *>(sensorSample.get());
+        event->readings[i].value = 0;
+        event->readings[i].isNear = (sampleEvent.data[0] > 0.5f);
+        timestampDelta = &event->readings[i].timestampDelta;
+        break;
+      }
+      case SensorSampleType::Occurrence: {
+        // Occurrence samples don't store any readings
+        auto *event =
+            reinterpret_cast<chreSensorOccurrenceData *>(sensorSample.get());
+        timestampDelta = &event->readings[0].timestampDelta;
+        break;
+      }
+      default:
+        LOGE("Invalid sample type %" PRIu8, static_cast<uint8_t>(sampleType));
+    }
+
+    // First sample determines the base timestamp and accuracy for all other
+    // sensor samples
+    if (i == 0) {
+      auto *header =
+          reinterpret_cast<chreSensorDataHeader *>(sensorSample.get());
+      header->baseTimestamp = sampleEvent.timestamp_ns;
+      if (timestampDelta != nullptr) {
+        *timestampDelta = 0;
+      }
+      auto usfAccuracy = sampleEvent.accuracy;
+      header->accuracy =
+          PlatformSensorTypeHelpersBase::convertUsfToChreSampleAccuracy(
+              usfAccuracy);
+
+    } else {
+      uint64_t delta = sampleEvent.timestamp_ns - prevSampleTimeNs;
+      if (delta > UINT32_MAX) {
+        LOGE("Sensor %" PRIu8 " timestampDelta overflow: prev %" PRIu64
+             " curr %" PRIu64,
+             sensorType, prevSampleTimeNs, sampleEvent.timestamp_ns);
+        delta = UINT32_MAX;
+      }
+      *timestampDelta = static_cast<uint32_t>(delta);
+    }
+
+    prevSampleTimeNs = sampleEvent.timestamp_ns;
+  }
+}
+
+}  // namespace
+
+UsfHelper::~UsfHelper() {
+  for (usf::UsfEventListener *listener : mUsfEventListeners) {
+    listener->event_type->RemoveListener(listener);
+  }
+
+  if (mWorker.get() != nullptr) {
+    mWorker->Stop();
+    mWorker.reset();
+  }
+
+  if (mTransportClient.get() != nullptr) {
+    mTransportClient->Disconnect();
+    mTransportClient.reset();
+  }
+}
+
+uint8_t UsfHelper::usfErrorToChreError(UsfErr err) {
+  switch (err) {
+    case UsfErr::kErrNone:
+      return CHRE_ERROR_NONE;
+    case UsfErr::kErrNotSupported:
+      return CHRE_ERROR_NOT_SUPPORTED;
+    case UsfErr::kErrMemAlloc:
+      return CHRE_ERROR_NO_MEMORY;
+    case UsfErr::kErrNotAvailable:
+      return CHRE_ERROR_BUSY;
+    case UsfErr::kErrTimedOut:
+      return CHRE_ERROR_TIMEOUT;
+    default:
+      return CHRE_ERROR;
+  }
+}
+
+void UsfHelper::init(UsfHelperCallbackInterface *callback,
+                     usf::UsfWorker *worker) {
+  CHRE_ASSERT(callback != nullptr);
+
+  usf::UsfDeviceMgr::GetDeviceProbeCompletePrecondition()->Wait();
+
+  UsfErr err = UsfErr::kErrAllocation;
+  if (worker != nullptr) {
+    mWorker.reset(worker, refcount::RefTakingMode::kCreate);
+    err = kErrNone;
+  } else {
+    err = usf::UsfWorkMgr::CreateWorker(&mWorker);
+  }
+
+  if (callback == nullptr) {
+    err = UsfErr::kErrBadValue;
+  } else if (!mUsfEventListeners.emplace_back()) {
+    LOG_OOM();
+  } else if ((err != kErrNone) ||
+             ((err = usf::UsfTransportClient::Create(
+                   &mTransportClient, usf::kUsfHeapLowPower)) != kErrNone) ||
+             ((err = mTransportClient->Connect()) != kErrNone) ||
+             ((err = mTransportClient->GetMsgEventType()->AddListener(
+                   mWorker.get(), eventHandler, this,
+                   &mUsfEventListeners.back())) != kErrNone) ||
+             ((err = usf::UsfClientGetServer(mTransportClient.get(),
+                                             &usf::kUsfSensorMgrServerUuid,
+                                             &mSensorMgrHandle)) != kErrNone)) {
+    LOG_USF_ERR(err);
+    // TODO(b/143139477): Debate removing the error capture if it proves to be
+    // unneeded in ramdump analysis.
+    mInitError = err;
+  } else {
+    mCallback = callback;
+  }
+
+  if (err != kErrNone) {
+    FATAL_ERROR("Failed to initialize UsfHelper: %" PRIu8, err);
+  }
+}
+
+bool UsfHelper::getSensorList(
+    usf::UsfVector<refcount::reffed_ptr<usf::UsfSensor>> *sensorList) {
+  UsfErr err = usf::UsfSensorMgr::GetSensorList(sensorList);
+  if (err != kErrNone) {
+    LOG_USF_ERR(err);
+  }
+  return err == kErrNone;
+}
+
+bool UsfHelper::startSampling(usf::UsfStartSamplingReq *request,
+                              uint32_t *samplingId) {
+  bool success = false;
+
+  usf::UsfReqSyncCallback callback;
+  const usf::UsfMsgStartSamplingResp *resp;
+  if (sendUsfReqSync(request, &callback) && getRespMsg(callback, &resp)) {
+    *samplingId = resp->sampling_id();
+    success = true;
+  }
+
+  return success;
+}
+
+bool UsfHelper::reconfigureSampling(usf::UsfReconfigSamplingReq *request) {
+  usf::UsfReqSyncCallback callback;
+  return sendUsfReqSync(request, &callback);
+}
+
+bool UsfHelper::stopSampling(usf::UsfStopSamplingReq *request) {
+  usf::UsfReqSyncCallback callback;
+  return sendUsfReqSync(request, &callback);
+}
+
+bool UsfHelper::registerForStatusUpdates(
+    refcount::reffed_ptr<usf::UsfSensor> &sensor) {
+  bool success = false;
+  if (!mUsfEventListeners.emplace_back()) {
+    LOG_OOM();
+  } else {
+    UsfErr err = sensor->GetSamplingEventType()->AddListener(
+        mWorker.get(), statusUpdateHandler, this, &mUsfEventListeners.back());
+    success = (err == kErrNone);
+    if (!success) {
+      LOG_USF_ERR(err);
+    }
+  }
+
+  return success;
+}
+
+bool UsfHelper::registerForBiasUpdates(
+    const refcount::reffed_ptr<usf::UsfSensor> &sensor) {
+  // TODO(b/147595659): Obtain the latest bias values when registering for bias
+  // updates to ensure chreSensorGetThreeAxisBias doesn't return stale values.
+  bool success = false;
+  if (!mUsfEventListeners.emplace_back()) {
+    LOG_OOM();
+  } else {
+    UsfErr err = sensor->GetTransformConfigEventType()->AddListener(
+        mWorker.get(), biasUpdateHandler, this, &mUsfEventListeners.back());
+    success = (err == kErrNone);
+    if (!success) {
+      LOG_USF_ERR(err);
+    }
+  }
+
+  return success;
+}
+
+bool UsfHelper::registerForApPowerStateUpdates() {
+  bool success = false;
+  if (!mUsfEventListeners.emplace_back()) {
+    LOG_OOM();
+  } else {
+    UsfErr err = usf::UsfPowerMgr::GetApPowerEventType()->AddListener(
+        mWorker.get(), apPowerStateUpdateHandler, this,
+        &mUsfEventListeners.back());
+    success = (err == kErrNone);
+    if (!success) {
+      LOG_USF_ERR(err);
+    }
+  }
+  return success;
+}
+
+void UsfHelper::unregisterForBiasUpdates(
+    const refcount::reffed_ptr<usf::UsfSensor> &sensor) {
+  for (size_t i = 0; i < mUsfEventListeners.size(); i++) {
+    usf::UsfEventListener *listener = mUsfEventListeners[i];
+    if (listener->event_type.get() == sensor->GetTransformConfigEventType()) {
+      listener->event_type->RemoveListener(listener);
+      mUsfEventListeners.erase(i);
+      break;
+    }
+  }
+}
+
+bool UsfHelper::getThreeAxisBias(
+    const refcount::reffed_ptr<usf::UsfSensor> &sensor,
+    struct chreSensorThreeAxisData *bias) const {
+  bool success = false;
+  size_t index = getCalArrayIndex(sensor->GetType());
+  if (bias != nullptr && index < kNumUsfCalSensors) {
+    const UsfCalData &data = mCalData[index];
+    if (data.hasBias) {
+      bias->header.baseTimestamp = data.timestamp;
+      bias->header.readingCount = 1;
+      bias->header.accuracy = data.accuracy;
+      bias->header.reserved = 0;
+      for (size_t i = 0; i < ARRAY_SIZE(data.bias); i++) {
+        bias->readings[0].bias[i] = data.bias[i];
+      }
+      bias->readings[0].timestampDelta = 0;
+      success = true;
+    }
+  }
+
+  return success;
+}
+
+bool UsfHelper::flushAsync(const usf::UsfServerHandle usfSensorHandle,
+                           void *cookie, uint32_t *requestId) {
+  bool success = false;
+  auto req = MakeUnique<usf::UsfReq>();
+  auto callbackData = MakeUnique<AsyncCallbackData>();
+  if (req.isNull() || callbackData.isNull()) {
+    LOG_OOM();
+  } else {
+    req->SetReqType(usf::UsfMsgReqType_FLUSH_SAMPLES);
+    req->SetServerHandle(usfSensorHandle);
+    callbackData->callback = mCallback;
+    callbackData->cookie = cookie;
+
+    UsfErr err = mTransportClient->SendRequest(req.get(), asyncCallback,
+                                               callbackData.get());
+    success = (err == kErrNone);
+    if (!success) {
+      LOG_USF_ERR(err);
+    } else {
+      *requestId = req->GetReqId();
+      req.release();
+      callbackData.release();
+    }
+  }
+
+  return success;
+}
+
+void UsfHelper::processSensorReport(const usf::UsfMsgEvent *event) {
+  usf::UsfSensorReport *sensorReport = nullptr;
+  UniquePtr<uint8_t> sensorSample;
+  UsfErr err = usf::UsfSensorReport::Get(event, &sensorReport);
+
+  if (err != kErrNone) {
+    LOG_USF_ERR(err);
+  } else if (sensorReport->GetType() == usf::UsfSensorReport::kTypeSample) {
+    auto *sampleReport =
+        static_cast<usf::UsfSensorSampleReport *>(sensorReport);
+    if (sampleReport->GetSampleCount() == 0) {
+      LOGE("Received empty sample batch");
+    } else {
+      auto usfType = sampleReport->GetSensorType();
+      uint8_t sensorType;
+      if (PlatformSensorTypeHelpersBase::convertUsfToChreSensorType(
+              usfType, &sensorType) &&
+          !createSensorEvent(sampleReport, sensorType, sensorSample)) {
+        // USF shares the same sensor type between calibrated and uncalibrated
+        // sensors. Try the uncalibrated type to see if the sampling ID matches.
+        uint8_t uncalType =
+            SensorTypeHelpers::toUncalibratedSensorType(sensorType);
+        if (uncalType != sensorType) {
+          sensorType = uncalType;
+          createSensorEvent(sampleReport, uncalType, sensorSample);
+          // USF shares a sensor type for both gyro and accel temp.
+        } else if (sensorType == CHRE_SENSOR_TYPE_GYROSCOPE_TEMPERATURE) {
+          createSensorEvent(sampleReport,
+                            CHRE_SENSOR_TYPE_ACCELEROMETER_TEMPERATURE,
+                            sensorSample);
+        }
+      }
+      if (!sensorSample.isNull() && mCallback != nullptr) {
+        mCallback->onSensorDataEvent(sensorType, std::move(sensorSample));
+      }
+    }
+  }
+  delete sensorReport;
+}
+
+void UsfHelper::processStatusUpdate(const usf::UsfSensorSamplingEvent *update) {
+  // TODO(stange): See if the latest status can be shared among sensor types
+  // with the same underlying physical sensor to avoid allocating multiple
+  // updates.
+  uint8_t sensorType;
+  if (PlatformSensorTypeHelpersBase::convertUsfToChreSensorType(
+          update->GetSensor()->GetType(), &sensorType)) {
+    mCallback->onSamplingStatusUpdate(sensorType, update);
+
+    // USF shares the same sensor type for calibrated and uncalibrated sensors
+    // so populate a calibrated / uncalibrated sensor for known calibrated
+    // sensor types
+    uint8_t uncalibratedType =
+        SensorTypeHelpers::toUncalibratedSensorType(sensorType);
+    if (uncalibratedType != sensorType) {
+      mCallback->onSamplingStatusUpdate(uncalibratedType, update);
+    }
+
+    // USF shares a sensor type for both gyro and accel temp.
+    if (sensorType == CHRE_SENSOR_TYPE_GYROSCOPE_TEMPERATURE) {
+      mCallback->onSamplingStatusUpdate(
+          CHRE_SENSOR_TYPE_ACCELEROMETER_TEMPERATURE, update);
+    }
+  }
+}
+
+void UsfHelper::processBiasUpdate(
+    const usf::UsfSensorTransformConfigEvent *update) {
+  uint8_t sensorType;
+  if (PlatformSensorTypeHelpersBase::convertUsfToChreSensorType(
+          update->GetSensor()->GetType(), &sensorType)) {
+    UniquePtr<struct chreSensorThreeAxisData> biasData =
+        convertUsfBiasUpdateToData(update);
+    if (!biasData.isNull()) {
+      // USF shares the same sensor type for calibrated and uncalibrated sensors
+      // so populate a calibrated / uncalibrated sensor for known calibrated
+      // sensor types
+      uint8_t uncalibratedType =
+          SensorTypeHelpers::toUncalibratedSensorType(sensorType);
+      if (uncalibratedType != sensorType) {
+        auto uncalBiasData =
+            MakeUniqueZeroFill<struct chreSensorThreeAxisData>();
+        if (uncalBiasData.isNull()) {
+          LOG_OOM();
+        } else {
+          *uncalBiasData = *biasData;
+          mCallback->onBiasUpdate(uncalibratedType, std::move(uncalBiasData));
+        }
+      }
+      mCallback->onBiasUpdate(sensorType, std::move(biasData));
+    }
+  }
+}
+
+void UsfHelper::processApPowerStateUpdate(
+    const usf::UsfApPowerStateEvent *update) {
+  usf::UsfApPowerState state = update->GetState();
+  if (state == usf::kUsfApPowerStateInvalid) {
+    LOGE("Invalid AP power state received.");
+  } else {
+    mCallback->onHostWakeSuspendEvent(state == usf::kUsfApPowerStateOn);
+  }
+}
+
+bool UsfHelper::createSensorEvent(
+    const usf::UsfSensorSampleReport *sampleReport, uint8_t sensorType,
+    UniquePtr<uint8_t> &sensorSample) {
+  bool success = false;
+
+  SensorInfo info;
+  if (mCallback != nullptr && mCallback->getSensorInfo(sensorType, &info) &&
+      (info.samplingId == sampleReport->GetSamplingId())) {
+    if (prepareSensorEvent(sampleReport->GetSampleCount(), info.sensorHandle,
+                           sensorType, sensorSample)) {
+      populateSensorEvent(sampleReport, sensorType, sensorSample);
+      success = true;
+    }
+  }
+
+  return success;
+}
+
+bool UsfHelper::sendUsfReqSync(usf::UsfReq *req,
+                               usf::UsfReqSyncCallback *callback) {
+  UsfErr err = mTransportClient->SendRequest(
+      req, usf::UsfReqSyncCallback::Callback, callback);
+  if (err != kErrNone) {
+    LOG_USF_ERR(err);
+  } else {
+    // TODO: Provide a different timeout for operations we expect to take longer
+    // e.g. initial sensor discovery.
+    callback->Wait(kDefaultUsfWaitTimeout.toRawNanoseconds());
+    if ((err = callback->GetResp()->GetErr()) != kErrNone) {
+      LOG_USF_ERR(err);
+    }
+  }
+
+  return err == kErrNone;
+}
+
+template <class T>
+bool UsfHelper::getRespMsg(usf::UsfReqSyncCallback &callback,
+                           const T **respMsg) {
+  UsfErr err = usf::UsfRespGetMsg(callback.GetResp(), respMsg);
+  if (err != kErrNone) {
+    LOG_USF_ERR(err);
+  }
+
+  return err == kErrNone;
+}
+
+UniquePtr<struct chreSensorThreeAxisData> UsfHelper::convertUsfBiasUpdateToData(
+    const usf::UsfSensorTransformConfigEvent *update) {
+  UniquePtr<struct chreSensorThreeAxisData> biasData;
+  size_t index = getCalArrayIndex(update->GetSensor()->GetType());
+  if (index < kNumUsfCalSensors &&
+      update->GetLevel() == usf::kUsfSensorTransformRunTimeCalibration) {
+    UsfCalData &data = mCalData[index];
+    UsfErr err = usf::UsfTimeMgr::GetAndroidTimeNs(&data.timestamp);
+    if (err != kErrNone) {
+      LOG_USF_ERR(err);
+    }
+    data.hasBias = (update->GetOffset() != nullptr);
+    for (size_t i = 0; i < ARRAY_SIZE(data.bias); i++) {
+      data.bias[i] = data.hasBias ? update->GetOffset()[i] : 0;
+    }
+    data.accuracy =
+        PlatformSensorTypeHelpersBase::convertUsfToChreSampleAccuracy(
+            update->GetAccuracy());
+
+    biasData = MakeUniqueZeroFill<struct chreSensorThreeAxisData>();
+    if (biasData.isNull()) {
+      LOG_OOM();
+    } else {
+      biasData->header.baseTimestamp = data.timestamp;
+      biasData->header.readingCount = 1;
+      biasData->header.accuracy = data.accuracy;
+      biasData->header.reserved = 0;
+      for (size_t i = 0; i < ARRAY_SIZE(data.bias); i++) {
+        biasData->readings[0].bias[i] = data.bias[i];
+      }
+      biasData->readings[0].timestampDelta = 0;
+    }
+  }
+
+  return biasData;
+}
+
+size_t UsfHelper::getCalArrayIndex(const usf::UsfSensorType usfSensorType) {
+  UsfCalSensor index;
+  switch (usfSensorType) {
+    case usf::UsfSensorType::kUsfSensorAccelerometer:
+      index = UsfCalSensor::AccelCal;
+      break;
+    case usf::UsfSensorType::kUsfSensorGyroscope:
+      index = UsfCalSensor::GyroCal;
+      break;
+    case usf::UsfSensorType::kUsfSensorMag:
+      index = UsfCalSensor::MagCal;
+      break;
+    default:
+      index = UsfCalSensor::NumCalSensors;
+      break;
+  }
+
+  return static_cast<size_t>(index);
+}
+
+}  // namespace chre
diff --git a/util/include/chre/util/macros.h b/util/include/chre/util/macros.h
index 92160b8..2fe7d7e 100644
--- a/util/include/chre/util/macros.h
+++ b/util/include/chre/util/macros.h
@@ -24,14 +24,26 @@
 /**
  * Obtains the number of elements in a C-style array.
  */
+#ifndef ARRAY_SIZE
 #define ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0]))
+#endif
+
+#ifndef ARRAY_END
 #define ARRAY_END(array) (array + ARRAY_SIZE(array))
+#endif
+
+/** Determines if the provided bit is set in the provided value. */
+#ifndef IS_BIT_SET
+#define IS_BIT_SET(value, bit) (((value) & (bit)) == (bit))
+#endif
 
 /**
  * Performs macro expansion then converts the value into a string literal
  */
+#ifndef STRINGIFY
 #define STRINGIFY(x) STRINGIFY2(x)
 #define STRINGIFY2(x) #x
+#endif
 
 // Compiler-specific functionality
 #if defined(__clang__) || defined(__GNUC__)
diff --git a/util/include/chre/util/system/napp_header_utils.h b/util/include/chre/util/system/napp_header_utils.h
new file mode 100644
index 0000000..e9ab416
--- /dev/null
+++ b/util/include/chre/util/system/napp_header_utils.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2020 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 CHRE_UTIL_SYSTEM_NAPP_HEADER_UTILS_H_
+#define CHRE_UTIL_SYSTEM_NAPP_HEADER_UTILS_H_
+
+//! Defines various constants used in the nanoapp headers defined inside
+//! build/build_template.mk.
+#define CHRE_NAPP_HEADER_SIGNED 0x00000001
+#define CHRE_NAPP_HEADER_ENCRYPTED 0x00000002
+#define CHRE_NAPP_HEADER_TCM_CAPABLE 0x00000004
+
+#endif  // CHRE_UTIL_SYSTEM_NAPP_HEADER_UTILS_H_