Merge "Bumping the HAL client version."
diff --git a/debuggerd/Android.bp b/debuggerd/Android.bp
index edcea44..a541f6e 100644
--- a/debuggerd/Android.bp
+++ b/debuggerd/Android.bp
@@ -391,6 +391,9 @@
     apex_available: [
         "com.android.runtime",
     ],
+
+    // Required for tests.
+    required: ["crash_dump.policy"],
 }
 
 cc_binary {
diff --git a/debuggerd/debuggerd_test.cpp b/debuggerd/debuggerd_test.cpp
index 0737612..85adbea 100644
--- a/debuggerd/debuggerd_test.cpp
+++ b/debuggerd/debuggerd_test.cpp
@@ -1409,6 +1409,16 @@
   return true;
 }
 
+extern "C" void foo() {
+  LOG(INFO) << "foo";
+  std::this_thread::sleep_for(1s);
+}
+
+extern "C" void bar() {
+  LOG(INFO) << "bar";
+  std::this_thread::sleep_for(1s);
+}
+
 TEST_F(CrasherTest, seccomp_tombstone) {
   int intercept_result;
   unique_fd output_fd;
@@ -1416,6 +1426,11 @@
   static const auto dump_type = kDebuggerdTombstone;
   StartProcess(
       []() {
+        std::thread a(foo);
+        std::thread b(bar);
+
+        std::this_thread::sleep_for(100ms);
+
         raise_debugger_signal(dump_type);
         _exit(0);
       },
@@ -1430,16 +1445,8 @@
   std::string result;
   ConsumeFd(std::move(output_fd), &result);
   ASSERT_BACKTRACE_FRAME(result, "raise_debugger_signal");
-}
-
-extern "C" void foo() {
-  LOG(INFO) << "foo";
-  std::this_thread::sleep_for(1s);
-}
-
-extern "C" void bar() {
-  LOG(INFO) << "bar";
-  std::this_thread::sleep_for(1s);
+  ASSERT_BACKTRACE_FRAME(result, "foo");
+  ASSERT_BACKTRACE_FRAME(result, "bar");
 }
 
 TEST_F(CrasherTest, seccomp_backtrace) {
diff --git a/debuggerd/handler/debuggerd_fallback.cpp b/debuggerd/handler/debuggerd_fallback.cpp
index baf994f..4c1f9eb 100644
--- a/debuggerd/handler/debuggerd_fallback.cpp
+++ b/debuggerd/handler/debuggerd_fallback.cpp
@@ -98,32 +98,6 @@
   __linker_disable_fallback_allocator();
 }
 
-static void iterate_siblings(bool (*callback)(pid_t, int), int output_fd) {
-  pid_t current_tid = gettid();
-  char buf[BUFSIZ];
-  snprintf(buf, sizeof(buf), "/proc/%d/task", current_tid);
-  DIR* dir = opendir(buf);
-
-  if (!dir) {
-    async_safe_format_log(ANDROID_LOG_ERROR, "libc", "failed to open %s: %s", buf, strerror(errno));
-    return;
-  }
-
-  struct dirent* ent;
-  while ((ent = readdir(dir))) {
-    char* end;
-    long tid = strtol(ent->d_name, &end, 10);
-    if (end == ent->d_name || *end != '\0') {
-      continue;
-    }
-
-    if (tid != current_tid) {
-      callback(tid, output_fd);
-    }
-  }
-  closedir(dir);
-}
-
 static bool forward_output(int src_fd, int dst_fd, pid_t expected_tid) {
   // Make sure the thread actually got the signal.
   struct pollfd pfd = {
@@ -216,21 +190,21 @@
   }
 
   // Only allow one thread to perform a trace at a time.
-  static pthread_mutex_t trace_mutex = PTHREAD_MUTEX_INITIALIZER;
-  int ret = pthread_mutex_trylock(&trace_mutex);
-  if (ret != 0) {
-    async_safe_format_log(ANDROID_LOG_INFO, "libc", "pthread_mutex_try_lock failed: %s",
-                          strerror(ret));
+  static std::mutex trace_mutex;
+  if (!trace_mutex.try_lock()) {
+    async_safe_format_log(ANDROID_LOG_INFO, "libc", "trace lock failed");
     return;
   }
 
+  std::lock_guard<std::mutex> scoped_lock(trace_mutex, std::adopt_lock);
+
   // Fetch output fd from tombstoned.
   unique_fd tombstone_socket, output_fd;
   if (!tombstoned_connect(getpid(), &tombstone_socket, &output_fd, nullptr,
                           kDebuggerdNativeBacktrace)) {
     async_safe_format_log(ANDROID_LOG_ERROR, "libc",
                           "missing crash_dump_fallback() in selinux policy?");
-    goto exit;
+    return;
   }
 
   dump_backtrace_header(output_fd.get());
@@ -239,15 +213,15 @@
   debuggerd_fallback_trace(output_fd.get(), ucontext);
 
   // Send a signal to all of our siblings, asking them to dump their stack.
-  iterate_siblings(
-      [](pid_t tid, int output_fd) {
+  pid_t current_tid = gettid();
+  if (!iterate_tids(current_tid, [&output_fd](pid_t tid) {
         // Use a pipe, to be able to detect situations where the thread gracefully exits before
         // receiving our signal.
         unique_fd pipe_read, pipe_write;
         if (!Pipe(&pipe_read, &pipe_write)) {
           async_safe_format_log(ANDROID_LOG_ERROR, "libc", "failed to create pipe: %s",
                                 strerror(errno));
-          return false;
+          return;
         }
 
         uint64_t expected = pack_thread_fd(-1, -1);
@@ -257,7 +231,7 @@
           async_safe_format_log(ANDROID_LOG_ERROR, "libc",
                                 "thread %d is already outputting to fd %d?", tid, fd);
           close(sent_fd);
-          return false;
+          return;
         }
 
         siginfo_t siginfo = {};
@@ -269,10 +243,10 @@
         if (syscall(__NR_rt_tgsigqueueinfo, getpid(), tid, BIONIC_SIGNAL_DEBUGGER, &siginfo) != 0) {
           async_safe_format_log(ANDROID_LOG_ERROR, "libc", "failed to send trace signal to %d: %s",
                                 tid, strerror(errno));
-          return false;
+          return;
         }
 
-        bool success = forward_output(pipe_read.get(), output_fd, tid);
+        bool success = forward_output(pipe_read.get(), output_fd.get(), tid);
         if (!success) {
           async_safe_format_log(ANDROID_LOG_ERROR, "libc",
                                 "timeout expired while waiting for thread %d to dump", tid);
@@ -288,15 +262,14 @@
           }
         }
 
-        return true;
-      },
-      output_fd.get());
+        return;
+      })) {
+    async_safe_format_log(ANDROID_LOG_ERROR, "libc", "failed to open /proc/%d/task: %s",
+                          current_tid, strerror(errno));
+  }
 
   dump_backtrace_footer(output_fd.get());
   tombstoned_notify_completion(tombstone_socket.get());
-
-exit:
-  pthread_mutex_unlock(&trace_mutex);
 }
 
 static void crash_handler(siginfo_t* info, ucontext_t* ucontext, void* abort_message) {
diff --git a/debuggerd/libdebuggerd/tombstone.cpp b/debuggerd/libdebuggerd/tombstone.cpp
index f21a203..14caaf6 100644
--- a/debuggerd/libdebuggerd/tombstone.cpp
+++ b/debuggerd/libdebuggerd/tombstone.cpp
@@ -23,6 +23,7 @@
 #include <stddef.h>
 #include <stdio.h>
 #include <stdlib.h>
+#include <sys/prctl.h>
 #include <sys/types.h>
 #include <unistd.h>
 
@@ -73,22 +74,40 @@
 
   std::map<pid_t, ThreadInfo> threads;
   threads[tid] = ThreadInfo{
-      .registers = std::move(regs),
-      .uid = uid,
-      .tid = tid,
-      .thread_name = std::move(thread_name),
-      .pid = pid,
-      .command_line = std::move(command_line),
-      .selinux_label = std::move(selinux_label),
-      .siginfo = siginfo,
+    .registers = std::move(regs), .uid = uid, .tid = tid, .thread_name = std::move(thread_name),
+    .pid = pid, .command_line = std::move(command_line), .selinux_label = std::move(selinux_label),
+    .siginfo = siginfo,
+#if defined(__aarch64__)
+    // Only supported on aarch64 for now.
+        .tagged_addr_ctrl = prctl(PR_GET_TAGGED_ADDR_CTRL, 0, 0, 0, 0),
+    .pac_enabled_keys = prctl(PR_PAC_GET_ENABLED_KEYS, 0, 0, 0, 0),
+#endif
   };
+  if (pid == tid) {
+    const ThreadInfo& thread = threads[pid];
+    if (!iterate_tids(pid, [&threads, &thread](pid_t tid) {
+          threads[tid] = ThreadInfo{
+              .uid = thread.uid,
+              .tid = tid,
+              .pid = thread.pid,
+              .command_line = thread.command_line,
+              .thread_name = get_thread_name(tid),
+              .tagged_addr_ctrl = thread.tagged_addr_ctrl,
+              .pac_enabled_keys = thread.pac_enabled_keys,
+          };
+        })) {
+      async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "failed to open /proc/%d/task: %s", pid,
+                            strerror(errno));
+    }
+  }
 
   unwindstack::UnwinderFromPid unwinder(kMaxFrames, pid, unwindstack::Regs::CurrentArch());
   auto process_memory =
       unwindstack::Memory::CreateProcessMemoryCached(getpid());
   unwinder.SetProcessMemory(process_memory);
   if (!unwinder.Init()) {
-    async_safe_fatal("failed to init unwinder object");
+    async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "failed to init unwinder object");
+    return;
   }
 
   ProcessInfo process_info;
diff --git a/debuggerd/libdebuggerd/tombstone_proto.cpp b/debuggerd/libdebuggerd/tombstone_proto.cpp
index b7d5bc4..3e31bb7 100644
--- a/debuggerd/libdebuggerd/tombstone_proto.cpp
+++ b/debuggerd/libdebuggerd/tombstone_proto.cpp
@@ -48,6 +48,7 @@
 
 #include <android/log.h>
 #include <bionic/macros.h>
+#include <bionic/reserved_signals.h>
 #include <log/log.h>
 #include <log/log_read.h>
 #include <log/logprint.h>
@@ -346,6 +347,93 @@
   f->set_build_id(frame.map_info->GetPrintableBuildID());
 }
 
+static void dump_registers(unwindstack::Unwinder* unwinder,
+                           const std::unique_ptr<unwindstack::Regs>& regs, Thread& thread,
+                           bool memory_dump) {
+  if (regs == nullptr) {
+    return;
+  }
+
+  unwindstack::Maps* maps = unwinder->GetMaps();
+  unwindstack::Memory* memory = unwinder->GetProcessMemory().get();
+
+  regs->IterateRegisters([&thread, memory_dump, maps, memory](const char* name, uint64_t value) {
+    Register r;
+    r.set_name(name);
+    r.set_u64(value);
+    *thread.add_registers() = r;
+
+    if (memory_dump) {
+      MemoryDump dump;
+
+      dump.set_register_name(name);
+      std::shared_ptr<unwindstack::MapInfo> map_info = maps->Find(untag_address(value));
+      if (map_info) {
+        dump.set_mapping_name(map_info->name());
+      }
+
+      constexpr size_t kNumBytesAroundRegister = 256;
+      constexpr size_t kNumTagsAroundRegister = kNumBytesAroundRegister / kTagGranuleSize;
+      char buf[kNumBytesAroundRegister];
+      uint8_t tags[kNumTagsAroundRegister];
+      ssize_t bytes = dump_memory(buf, sizeof(buf), tags, sizeof(tags), &value, memory);
+      if (bytes == -1) {
+        return;
+      }
+      dump.set_begin_address(value);
+      dump.set_memory(buf, bytes);
+
+      bool has_tags = false;
+#if defined(__aarch64__)
+      for (size_t i = 0; i < kNumTagsAroundRegister; ++i) {
+        if (tags[i] != 0) {
+          has_tags = true;
+        }
+      }
+#endif  // defined(__aarch64__)
+
+      if (has_tags) {
+        dump.mutable_arm_mte_metadata()->set_memory_tags(tags, kNumTagsAroundRegister);
+      }
+
+      *thread.add_memory_dump() = std::move(dump);
+    }
+  });
+}
+
+static void log_unwinder_error(unwindstack::Unwinder* unwinder) {
+  if (unwinder->LastErrorCode() == unwindstack::ERROR_NONE) {
+    return;
+  }
+
+  async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "  error code: %s",
+                        unwinder->LastErrorCodeString());
+  async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "  error address: 0x%" PRIx64,
+                        unwinder->LastErrorAddress());
+}
+
+static void dump_thread_backtrace(unwindstack::Unwinder* unwinder, Thread& thread) {
+  if (unwinder->NumFrames() == 0) {
+    async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "failed to unwind");
+    log_unwinder_error(unwinder);
+    return;
+  }
+
+  if (unwinder->elf_from_memory_not_file()) {
+    auto backtrace_note = thread.mutable_backtrace_note();
+    *backtrace_note->Add() =
+        "Function names and BuildId information is missing for some frames due";
+    *backtrace_note->Add() = "to unreadable libraries. For unwinds of apps, only shared libraries";
+    *backtrace_note->Add() = "found under the lib/ directory are readable.";
+    *backtrace_note->Add() = "On this device, run setenforce 0 to make the libraries readable.";
+  }
+  unwinder->SetDisplayBuildID(true);
+  for (const auto& frame : unwinder->frames()) {
+    BacktraceFrame* f = thread.add_current_backtrace();
+    fill_in_backtrace_frame(f, frame);
+  }
+}
+
 static void dump_thread(Tombstone* tombstone, unwindstack::Unwinder* unwinder,
                         const ThreadInfo& thread_info, bool memory_dump = false) {
   Thread thread;
@@ -355,97 +443,32 @@
   thread.set_tagged_addr_ctrl(thread_info.tagged_addr_ctrl);
   thread.set_pac_enabled_keys(thread_info.pac_enabled_keys);
 
-  unwindstack::Maps* maps = unwinder->GetMaps();
-  unwindstack::Memory* memory = unwinder->GetProcessMemory().get();
-
-  thread_info.registers->IterateRegisters(
-      [&thread, memory_dump, maps, memory](const char* name, uint64_t value) {
-        Register r;
-        r.set_name(name);
-        r.set_u64(value);
-        *thread.add_registers() = r;
-
-        if (memory_dump) {
-          MemoryDump dump;
-
-          dump.set_register_name(name);
-          std::shared_ptr<unwindstack::MapInfo> map_info = maps->Find(untag_address(value));
-          if (map_info) {
-            dump.set_mapping_name(map_info->name());
-          }
-
-          constexpr size_t kNumBytesAroundRegister = 256;
-          constexpr size_t kNumTagsAroundRegister = kNumBytesAroundRegister / kTagGranuleSize;
-          char buf[kNumBytesAroundRegister];
-          uint8_t tags[kNumTagsAroundRegister];
-          size_t start_offset = 0;
-          ssize_t bytes = dump_memory(buf, sizeof(buf), tags, sizeof(tags), &value, memory);
-          if (bytes == -1) {
-            return;
-          }
-          dump.set_begin_address(value);
-
-          if (start_offset + bytes > sizeof(buf)) {
-            async_safe_fatal("dump_memory overflowed? start offset = %zu, bytes read = %zd",
-                             start_offset, bytes);
-          }
-
-          dump.set_memory(buf, bytes);
-
-          bool has_tags = false;
-#if defined(__aarch64__)
-          for (size_t i = 0; i < kNumTagsAroundRegister; ++i) {
-            if (tags[i] != 0) {
-              has_tags = true;
-            }
-          }
-#endif  // defined(__aarch64__)
-
-          if (has_tags) {
-            dump.mutable_arm_mte_metadata()->set_memory_tags(tags, kNumTagsAroundRegister);
-          }
-
-          *thread.add_memory_dump() = std::move(dump);
-        }
-      });
-
-  std::unique_ptr<unwindstack::Regs> regs_copy(thread_info.registers->Clone());
-  unwinder->SetRegs(regs_copy.get());
-  unwinder->Unwind();
-  if (unwinder->NumFrames() == 0) {
-    async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "failed to unwind");
-    if (unwinder->LastErrorCode() != unwindstack::ERROR_NONE) {
-      async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "  error code: %s",
-                            unwinder->LastErrorCodeString());
-      async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "  error address: 0x%" PRIx64,
-                            unwinder->LastErrorAddress());
+  if (thread_info.pid == getpid() && thread_info.pid != thread_info.tid) {
+    // Fallback path for non-main thread, doing unwind from running process.
+    unwindstack::ThreadUnwinder thread_unwinder(kMaxFrames, unwinder->GetMaps());
+    if (!thread_unwinder.Init()) {
+      async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG,
+                            "Unable to initialize ThreadUnwinder object.");
+      log_unwinder_error(&thread_unwinder);
+      return;
     }
+
+    std::unique_ptr<unwindstack::Regs> initial_regs;
+    thread_unwinder.UnwindWithSignal(BIONIC_SIGNAL_BACKTRACE, thread_info.tid, &initial_regs);
+    dump_registers(&thread_unwinder, initial_regs, thread, memory_dump);
+    dump_thread_backtrace(&thread_unwinder, thread);
   } else {
-    if (unwinder->elf_from_memory_not_file()) {
-      auto backtrace_note = thread.mutable_backtrace_note();
-      *backtrace_note->Add() =
-          "Function names and BuildId information is missing for some frames due";
-      *backtrace_note->Add() =
-          "to unreadable libraries. For unwinds of apps, only shared libraries";
-      *backtrace_note->Add() = "found under the lib/ directory are readable.";
-      *backtrace_note->Add() = "On this device, run setenforce 0 to make the libraries readable.";
-    }
-    unwinder->SetDisplayBuildID(true);
-    for (const auto& frame : unwinder->frames()) {
-      BacktraceFrame* f = thread.add_current_backtrace();
-      fill_in_backtrace_frame(f, frame);
-    }
+    dump_registers(unwinder, thread_info.registers, thread, memory_dump);
+    std::unique_ptr<unwindstack::Regs> regs_copy(thread_info.registers->Clone());
+    unwinder->SetRegs(regs_copy.get());
+    unwinder->Unwind();
+    dump_thread_backtrace(unwinder, thread);
   }
 
   auto& threads = *tombstone->mutable_threads();
   threads[thread_info.tid] = thread;
 }
 
-static void dump_main_thread(Tombstone* tombstone, unwindstack::Unwinder* unwinder,
-                             const ThreadInfo& thread_info) {
-  dump_thread(tombstone, unwinder, thread_info, true);
-}
-
 static void dump_mappings(Tombstone* tombstone, unwindstack::Unwinder* unwinder) {
   unwindstack::Maps* maps = unwinder->GetMaps();
   std::shared_ptr<unwindstack::Memory> process_memory = unwinder->GetProcessMemory();
@@ -663,7 +686,8 @@
 
   dump_abort_message(&result, unwinder, process_info);
 
-  dump_main_thread(&result, unwinder, main_thread);
+  // Dump the main thread, but save the memory around the registers.
+  dump_thread(&result, unwinder, main_thread, /* memory_dump */ true);
 
   for (const auto& [tid, thread_info] : threads) {
     if (tid != target_thread) {
diff --git a/debuggerd/seccomp_policy/crash_dump.arm64.policy b/debuggerd/seccomp_policy/crash_dump.arm64.policy
index 858a338..4e8fdf9 100644
--- a/debuggerd/seccomp_policy/crash_dump.arm64.policy
+++ b/debuggerd/seccomp_policy/crash_dump.arm64.policy
@@ -25,7 +25,7 @@
 rt_sigprocmask: 1
 rt_sigaction: 1
 rt_tgsigqueueinfo: 1
-prctl: arg0 == PR_GET_NO_NEW_PRIVS || arg0 == 0x53564d41 || arg0 == PR_PAC_RESET_KEYS
+prctl: arg0 == PR_GET_NO_NEW_PRIVS || arg0 == 0x53564d41 || arg0 == PR_PAC_RESET_KEYS || arg0 == PR_GET_TAGGED_ADDR_CTRL || arg0 == PR_PAC_GET_ENABLED_KEYS
 madvise: 1
 mprotect: arg2 in 0x1|0x2
 munmap: 1
diff --git a/debuggerd/seccomp_policy/crash_dump.policy.def b/debuggerd/seccomp_policy/crash_dump.policy.def
index 152697c..4eb996e 100644
--- a/debuggerd/seccomp_policy/crash_dump.policy.def
+++ b/debuggerd/seccomp_policy/crash_dump.policy.def
@@ -37,7 +37,7 @@
 #define PR_SET_VMA 0x53564d41
 #if defined(__aarch64__)
 // PR_PAC_RESET_KEYS happens on aarch64 in pthread_create path.
-prctl: arg0 == PR_GET_NO_NEW_PRIVS || arg0 == PR_SET_VMA || arg0 == PR_PAC_RESET_KEYS
+prctl: arg0 == PR_GET_NO_NEW_PRIVS || arg0 == PR_SET_VMA || arg0 == PR_PAC_RESET_KEYS || arg0 == PR_GET_TAGGED_ADDR_CTRL || arg0 == PR_PAC_GET_ENABLED_KEYS
 #else
 prctl: arg0 == PR_GET_NO_NEW_PRIVS || arg0 == PR_SET_VMA
 #endif
diff --git a/debuggerd/util.cpp b/debuggerd/util.cpp
index ce0fd30..5c6abc9 100644
--- a/debuggerd/util.cpp
+++ b/debuggerd/util.cpp
@@ -18,6 +18,7 @@
 
 #include <time.h>
 
+#include <functional>
 #include <string>
 #include <utility>
 
@@ -74,3 +75,24 @@
   n = strftime(s, sz, "%z", &tm), s += n, sz -= n;
   return buf;
 }
+
+bool iterate_tids(pid_t pid, std::function<void(pid_t)> callback) {
+  char buf[BUFSIZ];
+  snprintf(buf, sizeof(buf), "/proc/%d/task", pid);
+  std::unique_ptr<DIR, int (*)(DIR*)> dir(opendir(buf), closedir);
+  if (dir == nullptr) {
+    return false;
+  }
+
+  struct dirent* entry;
+  while ((entry = readdir(dir.get())) != nullptr) {
+    pid_t tid = atoi(entry->d_name);
+    if (tid == 0) {
+      continue;
+    }
+    if (pid != tid) {
+      callback(tid);
+    }
+  }
+  return true;
+}
diff --git a/debuggerd/util.h b/debuggerd/util.h
index ec2862a..4375870 100644
--- a/debuggerd/util.h
+++ b/debuggerd/util.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include <functional>
 #include <string>
 #include <vector>
 
@@ -27,3 +28,4 @@
 std::string get_thread_name(pid_t tid);
 
 std::string get_timestamp();
+bool iterate_tids(pid_t, std::function<void(pid_t)>);
diff --git a/fastboot/device/flashing.cpp b/fastboot/device/flashing.cpp
index 44dc81f..22f8363 100644
--- a/fastboot/device/flashing.cpp
+++ b/fastboot/device/flashing.cpp
@@ -186,6 +186,11 @@
     return result;
 }
 
+static void RemoveScratchPartition() {
+    AutoMountMetadata mount_metadata;
+    android::fs_mgr::TeardownAllOverlayForMountPoint();
+}
+
 bool UpdateSuper(FastbootDevice* device, const std::string& super_name, bool wipe) {
     std::vector<char> data = std::move(device->download_data());
     if (data.empty()) {
@@ -218,7 +223,7 @@
         if (!FlashPartitionTable(super_name, *new_metadata.get())) {
             return device->WriteFail("Unable to flash new partition table");
         }
-        android::fs_mgr::TeardownAllOverlayForMountPoint();
+        RemoveScratchPartition();
         sync();
         return device->WriteOkay("Successfully flashed partition table");
     }
@@ -262,7 +267,7 @@
     if (!UpdateAllPartitionMetadata(device, super_name, *new_metadata.get())) {
         return device->WriteFail("Unable to write new partition table");
     }
-    android::fs_mgr::TeardownAllOverlayForMountPoint();
+    RemoveScratchPartition();
     sync();
     return device->WriteOkay("Successfully updated partition table");
 }
diff --git a/fastboot/fastboot.cpp b/fastboot/fastboot.cpp
index fde1dab..39d86f9 100644
--- a/fastboot/fastboot.cpp
+++ b/fastboot/fastboot.cpp
@@ -1084,7 +1084,11 @@
 
     // Rewrite vbmeta if that's what we're flashing and modification has been requested.
     if (g_disable_verity || g_disable_verification) {
-        if (partition == "vbmeta" || partition == "vbmeta_a" || partition == "vbmeta_b") {
+        // The vbmeta partition might have additional prefix if running in virtual machine
+        // e.g., guest_vbmeta_a.
+        if (android::base::EndsWith(partition, "vbmeta") ||
+            android::base::EndsWith(partition, "vbmeta_a") ||
+            android::base::EndsWith(partition, "vbmeta_b")) {
             rewrite_vbmeta_buffer(buf, false /* vbmeta_in_boot */);
         } else if (!has_vbmeta_partition() &&
                    (partition == "boot" || partition == "boot_a" || partition == "boot_b")) {
diff --git a/fastboot/fuzzer/Android.bp b/fastboot/fuzzer/Android.bp
index fcd3bd6..1b59e4a 100644
--- a/fastboot/fuzzer/Android.bp
+++ b/fastboot/fuzzer/Android.bp
@@ -15,6 +15,11 @@
  *
  */
 
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_fuzz {
     name: "fastboot_fuzzer",
     host_supported: true,
diff --git a/fs_mgr/fs_mgr.cpp b/fs_mgr/fs_mgr.cpp
index 8ce961b..960173a 100644
--- a/fs_mgr/fs_mgr.cpp
+++ b/fs_mgr/fs_mgr.cpp
@@ -2256,7 +2256,8 @@
 #if ALLOW_ADBD_DISABLE_VERITY == 0
     // Allowlist the mount point if user build.
     static const std::vector<const std::string> kAllowedPaths = {
-            "/odm", "/odm_dlkm", "/oem", "/product", "/system_ext", "/vendor", "/vendor_dlkm",
+            "/odm",         "/odm_dlkm",   "/oem",    "/product",
+            "/system_dlkm", "/system_ext", "/vendor", "/vendor_dlkm",
     };
     static const std::vector<const std::string> kAllowedPrefixes = {
             "/mnt/product/",
diff --git a/fs_mgr/fs_mgr_fstab.cpp b/fs_mgr/fs_mgr_fstab.cpp
index 0ca1946..143e980 100644
--- a/fs_mgr/fs_mgr_fstab.cpp
+++ b/fs_mgr/fs_mgr_fstab.cpp
@@ -657,7 +657,12 @@
             if (partition_ext4 == fstab->end()) {
                 auto new_entry = *GetEntryForMountPoint(fstab, mount_point);
                 new_entry.fs_type = "ext4";
-                fstab->emplace_back(new_entry);
+                auto it = std::find_if(fstab->rbegin(), fstab->rend(),
+                                       [&mount_point](const auto& entry) {
+                                           return entry.mount_point == mount_point;
+                                       });
+                auto end_of_mount_point_group = fstab->begin() + std::distance(it, fstab->rend());
+                fstab->insert(end_of_mount_point_group, new_entry);
             }
         }
     }
diff --git a/fs_mgr/fs_mgr_overlayfs.cpp b/fs_mgr/fs_mgr_overlayfs.cpp
index 2da5b0f..996fa5e 100644
--- a/fs_mgr/fs_mgr_overlayfs.cpp
+++ b/fs_mgr/fs_mgr_overlayfs.cpp
@@ -33,6 +33,7 @@
 
 #include <algorithm>
 #include <memory>
+#include <optional>
 #include <string>
 #include <vector>
 
@@ -1121,6 +1122,23 @@
     return true;
 }
 
+static inline uint64_t GetIdealDataScratchSize() {
+    BlockDeviceInfo super_info;
+    PartitionOpener opener;
+    if (!opener.GetInfo(fs_mgr_get_super_partition_name(), &super_info)) {
+        LERROR << "could not get block device info for super";
+        return 0;
+    }
+
+    struct statvfs s;
+    if (statvfs("/data", &s) < 0) {
+        PERROR << "could not statfs /data";
+        return 0;
+    }
+
+    return std::min(super_info.size, (uint64_t(s.f_frsize) * s.f_bfree) / 2);
+}
+
 static bool CreateScratchOnData(std::string* scratch_device, bool* partition_exists, bool* change) {
     *partition_exists = false;
     if (change) *change = false;
@@ -1136,13 +1154,6 @@
         return true;
     }
 
-    BlockDeviceInfo info;
-    PartitionOpener opener;
-    if (!opener.GetInfo(fs_mgr_get_super_partition_name(), &info)) {
-        LERROR << "could not get block device info for super";
-        return false;
-    }
-
     if (change) *change = true;
 
     // Note: calling RemoveDisabledImages here ensures that we do not race with
@@ -1152,10 +1163,11 @@
         return false;
     }
     if (!images->BackingImageExists(partition_name)) {
-        static constexpr uint64_t kMinimumSize = 64_MiB;
-        static constexpr uint64_t kMaximumSize = 2_GiB;
+        uint64_t size = GetIdealDataScratchSize();
+        if (!size) {
+            size = 2_GiB;
+        }
 
-        uint64_t size = std::clamp(info.size / 2, kMinimumSize, kMaximumSize);
         auto flags = IImageManager::CREATE_IMAGE_DEFAULT;
 
         if (!images->CreateBackingImage(partition_name, size, flags)) {
@@ -1396,18 +1408,35 @@
     return ret;
 }
 
+struct MapInfo {
+    // If set, partition is owned by ImageManager.
+    std::unique_ptr<IImageManager> images;
+    // If set, and images is null, this is a DAP partition.
+    std::string name;
+    // If set, and images and name are empty, this is a non-dynamic partition.
+    std::string device;
+
+    MapInfo() = default;
+    MapInfo(MapInfo&&) = default;
+    ~MapInfo() {
+        if (images) {
+            images->UnmapImageDevice(name);
+        } else if (!name.empty()) {
+            DestroyLogicalPartition(name);
+        }
+    }
+};
+
 // Note: This function never returns the DSU scratch device in recovery or fastbootd,
 // because the DSU scratch is created in the first-stage-mount, which is not run in recovery.
-static bool EnsureScratchMapped(std::string* device, bool* mapped) {
-    *mapped = false;
-    *device = GetBootScratchDevice();
-    if (!device->empty()) {
-        return true;
+static std::optional<MapInfo> EnsureScratchMapped() {
+    MapInfo info;
+    info.device = GetBootScratchDevice();
+    if (!info.device.empty()) {
+        return {std::move(info)};
     }
-
     if (!fs_mgr_in_recovery()) {
-        errno = EINVAL;
-        return false;
+        return {};
     }
 
     auto partition_name = android::base::Basename(kScratchMountPoint);
@@ -1417,11 +1446,15 @@
     // would otherwise always be mapped.
     auto images = IImageManager::Open("remount", 10s);
     if (images && images->BackingImageExists(partition_name)) {
-        if (!images->MapImageDevice(partition_name, 10s, device)) {
-            return false;
+        if (images->IsImageDisabled(partition_name)) {
+            return {};
         }
-        *mapped = true;
-        return true;
+        if (!images->MapImageDevice(partition_name, 10s, &info.device)) {
+            return {};
+        }
+        info.name = partition_name;
+        info.images = std::move(images);
+        return {std::move(info)};
     }
 
     // Avoid uart spam by first checking for a scratch partition.
@@ -1429,12 +1462,12 @@
     auto super_device = fs_mgr_overlayfs_super_device(metadata_slot);
     auto metadata = ReadCurrentMetadata(super_device);
     if (!metadata) {
-        return false;
+        return {};
     }
 
     auto partition = FindPartition(*metadata.get(), partition_name);
     if (!partition) {
-        return false;
+        return {};
     }
 
     CreateLogicalPartitionParams params = {
@@ -1444,11 +1477,11 @@
             .force_writable = true,
             .timeout_ms = 10s,
     };
-    if (!CreateLogicalPartition(params, device)) {
-        return false;
+    if (!CreateLogicalPartition(params, &info.device)) {
+        return {};
     }
-    *mapped = true;
-    return true;
+    info.name = partition_name;
+    return {std::move(info)};
 }
 
 // This should only be reachable in recovery, where DSU scratch is not
@@ -1602,26 +1635,35 @@
         fs_mgr_overlayfs_teardown_one(overlay_mount_point, teardown_dir, ignore_change);
     }
 
-    // Map scratch device, mount kScratchMountPoint and teardown kScratchMountPoint.
-    bool mapped = false;
-    std::string scratch_device;
-    if (EnsureScratchMapped(&scratch_device, &mapped)) {
+    if (mount_point.empty()) {
+        // Throw away the entire partition.
+        auto partition_name = android::base::Basename(kScratchMountPoint);
+        auto images = IImageManager::Open("remount", 10s);
+        if (images && images->BackingImageExists(partition_name)) {
+            if (images->DisableImage(partition_name)) {
+                LOG(INFO) << "Disabled scratch partition for: " << kScratchMountPoint;
+            } else {
+                LOG(ERROR) << "Unable to disable scratch partition for " << kScratchMountPoint;
+            }
+        }
+    }
+
+    if (auto info = EnsureScratchMapped(); info.has_value()) {
+        // Map scratch device, mount kScratchMountPoint and teardown kScratchMountPoint.
         fs_mgr_overlayfs_umount_scratch();
-        if (fs_mgr_overlayfs_mount_scratch(scratch_device, fs_mgr_overlayfs_scratch_mount_type())) {
+        if (fs_mgr_overlayfs_mount_scratch(info->device, fs_mgr_overlayfs_scratch_mount_type())) {
             bool should_destroy_scratch = false;
             fs_mgr_overlayfs_teardown_one(kScratchMountPoint, teardown_dir, ignore_change,
                                           &should_destroy_scratch);
+            fs_mgr_overlayfs_umount_scratch();
             if (should_destroy_scratch) {
                 fs_mgr_overlayfs_teardown_scratch(kScratchMountPoint, nullptr);
             }
-            fs_mgr_overlayfs_umount_scratch();
-        }
-        if (mapped) {
-            DestroyLogicalPartition(android::base::Basename(kScratchMountPoint));
         }
     }
 
     // Teardown DSU overlay if present.
+    std::string scratch_device;
     if (MapDsuScratchDevice(&scratch_device)) {
         fs_mgr_overlayfs_umount_scratch();
         if (fs_mgr_overlayfs_mount_scratch(scratch_device, fs_mgr_overlayfs_scratch_mount_type())) {
diff --git a/fs_mgr/fuzz/Android.bp b/fs_mgr/fuzz/Android.bp
index f0afd28..b2b9be8 100644
--- a/fs_mgr/fuzz/Android.bp
+++ b/fs_mgr/fuzz/Android.bp
@@ -14,6 +14,11 @@
 // limitations under the License.
 //
 
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_fuzz {
   name: "libfstab_fuzzer",
   srcs: [
diff --git a/fs_mgr/libfiemap/binder.cpp b/fs_mgr/libfiemap/binder.cpp
index 31a57a8..003e6ed 100644
--- a/fs_mgr/libfiemap/binder.cpp
+++ b/fs_mgr/libfiemap/binder.cpp
@@ -66,6 +66,7 @@
     bool RemoveDisabledImages() override;
     bool GetMappedImageDevice(const std::string& name, std::string* device) override;
     bool MapAllImages(const std::function<bool(std::set<std::string>)>& init) override;
+    bool IsImageDisabled(const std::string& name) override;
 
     std::vector<std::string> GetAllBackingImages() override;
 
@@ -219,6 +220,17 @@
     return !device->empty();
 }
 
+bool ImageManagerBinder::IsImageDisabled(const std::string& name) {
+    bool retval;
+    auto status = manager_->isImageDisabled(name, &retval);
+    if (!status.isOk()) {
+        LOG(ERROR) << __PRETTY_FUNCTION__
+                   << " binder returned: " << status.exceptionMessage().string();
+        return false;
+    }
+    return retval;
+}
+
 bool ImageManagerBinder::MapAllImages(const std::function<bool(std::set<std::string>)>&) {
     LOG(ERROR) << __PRETTY_FUNCTION__ << " not available over binder";
     return false;
diff --git a/fs_mgr/libfiemap/image_manager.cpp b/fs_mgr/libfiemap/image_manager.cpp
index e3f5716..c416f4d 100644
--- a/fs_mgr/libfiemap/image_manager.cpp
+++ b/fs_mgr/libfiemap/image_manager.cpp
@@ -79,7 +79,7 @@
     partition_opener_ = std::make_unique<android::fs_mgr::PartitionOpener>();
 
     // Allow overriding whether ImageManager thinks it's in recovery, for testing.
-#ifdef __ANDROID_RECOVERY__
+#ifdef __ANDROID_RAMDISK__
     device_info_.is_recovery = {true};
 #else
     if (!device_info_.is_recovery.has_value()) {
@@ -523,7 +523,7 @@
 
     auto image_header = GetImageHeaderPath(name);
 
-#if !defined __ANDROID_RECOVERY__
+#ifndef __ANDROID_RAMDISK__
     // If there is a device-mapper node wrapping the block device, then we're
     // able to create another node around it; the dm layer does not carry the
     // exclusion lock down the stack when a mount occurs.
@@ -854,6 +854,24 @@
     return true;
 }
 
+bool ImageManager::IsImageDisabled(const std::string& name) {
+    if (!MetadataExists(metadata_dir_)) {
+        return true;
+    }
+
+    auto metadata = OpenMetadata(metadata_dir_);
+    if (!metadata) {
+        return false;
+    }
+
+    auto partition = FindPartition(*metadata.get(), name);
+    if (!partition) {
+        return false;
+    }
+
+    return !!(partition->attributes & LP_PARTITION_ATTR_DISABLED);
+}
+
 std::unique_ptr<MappedDevice> MappedDevice::Open(IImageManager* manager,
                                                  const std::chrono::milliseconds& timeout_ms,
                                                  const std::string& name) {
diff --git a/fs_mgr/libfiemap/image_test.cpp b/fs_mgr/libfiemap/image_test.cpp
index 6d09751..7472949 100644
--- a/fs_mgr/libfiemap/image_test.cpp
+++ b/fs_mgr/libfiemap/image_test.cpp
@@ -119,6 +119,7 @@
     ASSERT_TRUE(manager_->CreateBackingImage(base_name_, kTestImageSize, false, nullptr));
     ASSERT_TRUE(manager_->BackingImageExists(base_name_));
     ASSERT_TRUE(manager_->DisableImage(base_name_));
+    ASSERT_TRUE(manager_->IsImageDisabled(base_name_));
     ASSERT_TRUE(manager_->RemoveDisabledImages());
     ASSERT_TRUE(!manager_->BackingImageExists(base_name_));
 }
diff --git a/fs_mgr/libfiemap/include/libfiemap/image_manager.h b/fs_mgr/libfiemap/include/libfiemap/image_manager.h
index b23a7f7..00dd661 100644
--- a/fs_mgr/libfiemap/include/libfiemap/image_manager.h
+++ b/fs_mgr/libfiemap/include/libfiemap/image_manager.h
@@ -131,6 +131,9 @@
     virtual bool RemoveAllImages() = 0;
 
     virtual bool UnmapImageIfExists(const std::string& name);
+
+    // Returns whether DisableImage() was called.
+    virtual bool IsImageDisabled(const std::string& name) = 0;
 };
 
 class ImageManager final : public IImageManager {
@@ -162,6 +165,7 @@
     bool RemoveDisabledImages() override;
     bool GetMappedImageDevice(const std::string& name, std::string* device) override;
     bool MapAllImages(const std::function<bool(std::set<std::string>)>& init) override;
+    bool IsImageDisabled(const std::string& name) override;
 
     std::vector<std::string> GetAllBackingImages();
 
diff --git a/fs_mgr/libsnapshot/Android.bp b/fs_mgr/libsnapshot/Android.bp
index 8b269cd..6db8f13 100644
--- a/fs_mgr/libsnapshot/Android.bp
+++ b/fs_mgr/libsnapshot/Android.bp
@@ -275,6 +275,24 @@
     ],
 }
 
+cc_test {
+    name: "vts_ota_config_test",
+    srcs: [
+        "vts_ota_config_test.cpp",
+    ],
+    shared_libs: [
+        "libbase",
+    ],
+    test_suites: [
+        "vts",
+    ],
+    test_options: {
+        min_shipping_api_level: 33,
+    },
+    auto_gen_config: true,
+    require_root: true,
+}
+
 cc_binary {
     name: "snapshotctl",
     srcs: [
diff --git a/fs_mgr/libsnapshot/android/snapshot/snapshot.proto b/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
index 532f66d..5daa84d 100644
--- a/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
+++ b/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
@@ -197,6 +197,9 @@
 
     // user-space snapshots
     bool userspace_snapshots = 9;
+
+    // io_uring support
+    bool io_uring_enabled = 10;
 }
 
 // Next: 10
diff --git a/fs_mgr/libsnapshot/cow_reader.cpp b/fs_mgr/libsnapshot/cow_reader.cpp
index 9b5fd2a..746feeb 100644
--- a/fs_mgr/libsnapshot/cow_reader.cpp
+++ b/fs_mgr/libsnapshot/cow_reader.cpp
@@ -550,6 +550,9 @@
     const CowOperation& Get() override;
     void Next() override;
 
+    void Prev() override;
+    bool RDone() override;
+
   private:
     std::shared_ptr<std::vector<CowOperation>> ops_;
     std::vector<CowOperation>::iterator op_iter_;
@@ -560,6 +563,15 @@
     op_iter_ = ops_->begin();
 }
 
+bool CowOpIter::RDone() {
+    return op_iter_ == ops_->begin();
+}
+
+void CowOpIter::Prev() {
+    CHECK(!RDone());
+    op_iter_--;
+}
+
 bool CowOpIter::Done() {
     return op_iter_ == ops_->end();
 }
@@ -585,6 +597,9 @@
     const CowOperation& Get() override;
     void Next() override;
 
+    void Prev() override;
+    bool RDone() override;
+
   private:
     std::shared_ptr<std::vector<CowOperation>> ops_;
     std::shared_ptr<std::vector<uint32_t>> merge_op_blocks_;
@@ -603,6 +618,9 @@
     const CowOperation& Get() override;
     void Next() override;
 
+    void Prev() override;
+    bool RDone() override;
+
   private:
     std::shared_ptr<std::vector<CowOperation>> ops_;
     std::shared_ptr<std::vector<uint32_t>> merge_op_blocks_;
@@ -623,6 +641,15 @@
     block_iter_ = merge_op_blocks->begin() + start;
 }
 
+bool CowMergeOpIter::RDone() {
+    return block_iter_ == merge_op_blocks_->begin();
+}
+
+void CowMergeOpIter::Prev() {
+    CHECK(!RDone());
+    block_iter_--;
+}
+
 bool CowMergeOpIter::Done() {
     return block_iter_ == merge_op_blocks_->end();
 }
@@ -649,6 +676,15 @@
     block_riter_ = merge_op_blocks->rbegin();
 }
 
+bool CowRevMergeOpIter::RDone() {
+    return block_riter_ == merge_op_blocks_->rbegin();
+}
+
+void CowRevMergeOpIter::Prev() {
+    CHECK(!RDone());
+    block_riter_--;
+}
+
 bool CowRevMergeOpIter::Done() {
     return block_riter_ == merge_op_blocks_->rend() - start_;
 }
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
index d5b4335..8e6bbd9 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
@@ -92,7 +92,7 @@
   public:
     virtual ~ICowOpIter() {}
 
-    // True if there are more items to read, false otherwise.
+    // True if there are no more items to read forward, false otherwise.
     virtual bool Done() = 0;
 
     // Read the current operation.
@@ -100,6 +100,12 @@
 
     // Advance to the next item.
     virtual void Next() = 0;
+
+    // Advance to the previous item.
+    virtual void Prev() = 0;
+
+    // True if there are no more items to read backwards, false otherwise
+    virtual bool RDone() = 0;
 };
 
 class CowReader final : public ICowReader {
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
index 120f95b..38b47d5 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
@@ -809,6 +809,9 @@
     // userspace snapshots.
     bool UpdateUsesUserSnapshots(LockedFile* lock);
 
+    // Check if io_uring API's need to be used
+    bool UpdateUsesIouring(LockedFile* lock);
+
     // Wrapper around libdm, with diagnostics.
     bool DeleteDeviceIfExists(const std::string& name,
                               const std::chrono::milliseconds& timeout_ms = {});
diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp
index f3de2b4..797d627 100644
--- a/fs_mgr/libsnapshot/snapshot.cpp
+++ b/fs_mgr/libsnapshot/snapshot.cpp
@@ -1685,6 +1685,9 @@
 
     if (UpdateUsesUserSnapshots(lock.get()) && transition == InitTransition::SELINUX_DETACH) {
         snapuserd_argv->emplace_back("-user_snapshot");
+        if (UpdateUsesIouring(lock.get())) {
+            snapuserd_argv->emplace_back("-io_uring");
+        }
     }
 
     size_t num_cows = 0;
@@ -2062,6 +2065,11 @@
     return update_status.compression_enabled();
 }
 
+bool SnapshotManager::UpdateUsesIouring(LockedFile* lock) {
+    SnapshotUpdateStatus update_status = ReadSnapshotUpdateStatus(lock);
+    return update_status.io_uring_enabled();
+}
+
 bool SnapshotManager::UpdateUsesUserSnapshots() {
     // This and the following function is constantly
     // invoked during snapshot merge. We want to avoid
@@ -2877,6 +2885,7 @@
         status.set_source_build_fingerprint(old_status.source_build_fingerprint());
         status.set_merge_phase(old_status.merge_phase());
         status.set_userspace_snapshots(old_status.userspace_snapshots());
+        status.set_io_uring_enabled(old_status.io_uring_enabled());
     }
     return WriteSnapshotUpdateStatus(lock, status);
 }
@@ -3200,6 +3209,7 @@
             status.set_userspace_snapshots(IsUserspaceSnapshotsEnabled());
             if (IsUserspaceSnapshotsEnabled()) {
                 is_snapshot_userspace_ = true;
+                status.set_io_uring_enabled(IsIouringEnabled());
                 LOG(INFO) << "User-space snapshots enabled";
             } else {
                 is_snapshot_userspace_ = false;
diff --git a/fs_mgr/libsnapshot/snapshot_fuzz_utils.h b/fs_mgr/libsnapshot/snapshot_fuzz_utils.h
index c1a5af7..a648384 100644
--- a/fs_mgr/libsnapshot/snapshot_fuzz_utils.h
+++ b/fs_mgr/libsnapshot/snapshot_fuzz_utils.h
@@ -199,6 +199,7 @@
     bool UnmapImageIfExists(const std::string& name) override {
         return impl_->UnmapImageIfExists(name);
     }
+    bool IsImageDisabled(const std::string& name) override { return impl_->IsImageDisabled(name); }
 
   private:
     std::unique_ptr<android::fiemap::IImageManager> impl_;
diff --git a/fs_mgr/libsnapshot/snapshot_test.cpp b/fs_mgr/libsnapshot/snapshot_test.cpp
index d76558b..04d228d 100644
--- a/fs_mgr/libsnapshot/snapshot_test.cpp
+++ b/fs_mgr/libsnapshot/snapshot_test.cpp
@@ -46,8 +46,6 @@
 #include "partition_cow_creator.h"
 #include "utility.h"
 
-#include <android-base/properties.h>
-
 // Mock classes are not used. Header included to ensure mocked class definition aligns with the
 // class itself.
 #include <libsnapshot/mock_device_info.h>
diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp b/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp
index a082742..0b88567 100644
--- a/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp
@@ -28,6 +28,7 @@
 DEFINE_bool(socket_handoff, false,
             "If true, perform a socket hand-off with an existing snapuserd instance, then exit.");
 DEFINE_bool(user_snapshot, false, "If true, user-space snapshots are used");
+DEFINE_bool(io_uring, false, "If true, io_uring feature is enabled");
 
 namespace android {
 namespace snapshot {
@@ -51,7 +52,12 @@
     // is applied will check for the property. This is ok as the system
     // properties are valid at this point. We can't do this during first
     // stage init and hence use the command line flags to get the information.
-    if (!IsDmSnapshotTestingEnabled() && (FLAGS_user_snapshot || IsUserspaceSnapshotsEnabled())) {
+    bool user_snapshots = FLAGS_user_snapshot;
+    if (!user_snapshots) {
+        user_snapshots = (!IsDmSnapshotTestingEnabled() && IsUserspaceSnapshotsEnabled());
+    }
+
+    if (user_snapshots) {
         LOG(INFO) << "Starting daemon for user-space snapshots.....";
         return StartServerForUserspaceSnapshots(arg_start, argc, argv);
     } else {
@@ -75,6 +81,11 @@
 
     MaskAllSignalsExceptIntAndTerm();
 
+    user_server_.SetServerRunning();
+    if (FLAGS_io_uring) {
+        user_server_.SetIouringEnabled();
+    }
+
     if (FLAGS_socket_handoff) {
         return user_server_.RunForSocketHandoff();
     }
@@ -165,7 +176,10 @@
 }
 
 void Daemon::Interrupt() {
-    if (IsUserspaceSnapshotsEnabled()) {
+    // TODO: We cannot access system property during first stage init.
+    // Until we remove the dm-snapshot code, we will have this check
+    // and verify it through a temp variable.
+    if (user_server_.IsServerRunning()) {
         user_server_.Interrupt();
     } else {
         server_.Interrupt();
@@ -173,7 +187,7 @@
 }
 
 void Daemon::ReceivedSocketSignal() {
-    if (IsUserspaceSnapshotsEnabled()) {
+    if (user_server_.IsServerRunning()) {
         user_server_.ReceivedSocketSignal();
     } else {
         server_.ReceivedSocketSignal();
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp
index 5109d82..692cb74 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp
@@ -39,7 +39,21 @@
 }
 
 bool SnapshotHandler::InitializeWorkers() {
-    for (int i = 0; i < kNumWorkerThreads; i++) {
+    int num_worker_threads = kNumWorkerThreads;
+
+    // We will need multiple worker threads only during
+    // device boot after OTA. For all other purposes,
+    // one thread is sufficient. We don't want to consume
+    // unnecessary memory especially during OTA install phase
+    // when daemon will be up during entire post install phase.
+    //
+    // During boot up, we need multiple threads primarily for
+    // update-verification.
+    if (is_socket_present_) {
+        num_worker_threads = 1;
+    }
+
+    for (int i = 0; i < num_worker_threads; i++) {
         std::unique_ptr<Worker> wt =
                 std::make_unique<Worker>(cow_device_, backing_store_device_, control_device_,
                                          misc_name_, base_path_merge_, GetSharedPtr());
@@ -478,22 +492,17 @@
         return;
     }
 
-    if (IsIouringSupported()) {
-        std::async(std::launch::async, &SnapshotHandler::ReadBlocksAsync, this, dm_block_device,
-                   partition_name, dev_sz);
-    } else {
-        int num_threads = 2;
-        size_t num_blocks = dev_sz >> BLOCK_SHIFT;
-        size_t num_blocks_per_thread = num_blocks / num_threads;
-        size_t read_sz_per_thread = num_blocks_per_thread << BLOCK_SHIFT;
-        off_t offset = 0;
+    int num_threads = 2;
+    size_t num_blocks = dev_sz >> BLOCK_SHIFT;
+    size_t num_blocks_per_thread = num_blocks / num_threads;
+    size_t read_sz_per_thread = num_blocks_per_thread << BLOCK_SHIFT;
+    off_t offset = 0;
 
-        for (int i = 0; i < num_threads; i++) {
-            std::async(std::launch::async, &SnapshotHandler::ReadBlocksToCache, this,
-                       dm_block_device, partition_name, offset, read_sz_per_thread);
+    for (int i = 0; i < num_threads; i++) {
+        std::async(std::launch::async, &SnapshotHandler::ReadBlocksToCache, this, dm_block_device,
+                   partition_name, offset, read_sz_per_thread);
 
-            offset += read_sz_per_thread;
-        }
+        offset += read_sz_per_thread;
     }
 }
 
@@ -677,6 +686,14 @@
         return false;
     }
 
+    // During selinux init transition, libsnapshot will propagate the
+    // status of io_uring enablement. As properties are not initialized,
+    // we cannot query system property.
+    if (is_io_uring_enabled_) {
+        return true;
+    }
+
+    // Finally check the system property
     return android::base::GetBoolProperty("ro.virtual_ab.io_uring.enabled", false);
 }
 
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.h b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.h
index b0f2d65..83d40f6 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.h
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.h
@@ -99,6 +99,7 @@
     void InitializeRAIter();
     bool RAIterDone();
     void RAIterNext();
+    void RAResetIter(uint64_t num_blocks);
     const CowOperation* GetRAOpIter();
 
     void InitializeBuffer();
@@ -151,12 +152,16 @@
     std::unique_ptr<uint8_t[]> ra_temp_meta_buffer_;
     BufferSink bufsink_;
 
+    uint64_t total_ra_blocks_completed_ = 0;
     bool read_ahead_async_ = false;
-    // Queue depth of 32 seems optimal. We don't want
+    // Queue depth of 8 seems optimal. We don't want
     // to have a huge depth as it may put more memory pressure
     // on the kernel worker threads given that we use
-    // IOSQE_ASYNC flag.
-    int queue_depth_ = 32;
+    // IOSQE_ASYNC flag - ASYNC flags can potentially
+    // result in EINTR; Since we don't restart
+    // syscalls and fallback to synchronous I/O, we
+    // don't want huge queue depth
+    int queue_depth_ = 8;
     std::unique_ptr<struct io_uring> ring_;
 };
 
@@ -210,11 +215,12 @@
 
     // Merge related ops
     bool Merge();
-    bool MergeOrderedOps(const std::unique_ptr<ICowOpIter>& cowop_iter);
-    bool MergeOrderedOpsAsync(const std::unique_ptr<ICowOpIter>& cowop_iter);
-    bool MergeReplaceZeroOps(const std::unique_ptr<ICowOpIter>& cowop_iter);
+    bool AsyncMerge();
+    bool SyncMerge();
+    bool MergeOrderedOps();
+    bool MergeOrderedOpsAsync();
+    bool MergeReplaceZeroOps();
     int PrepareMerge(uint64_t* source_offset, int* pending_ops,
-                     const std::unique_ptr<ICowOpIter>& cowop_iter,
                      std::vector<const CowOperation*>* replace_zero_vec = nullptr);
 
     sector_t ChunkToSector(chunk_t chunk) { return chunk << CHUNK_SHIFT; }
@@ -238,12 +244,18 @@
     unique_fd base_path_merge_fd_;
     unique_fd ctrl_fd_;
 
+    std::unique_ptr<ICowOpIter> cowop_iter_;
+    size_t ra_block_index_ = 0;
+    uint64_t blocks_merged_in_group_ = 0;
     bool merge_async_ = false;
-    // Queue depth of 32 seems optimal. We don't want
+    // Queue depth of 8 seems optimal. We don't want
     // to have a huge depth as it may put more memory pressure
     // on the kernel worker threads given that we use
-    // IOSQE_ASYNC flag.
-    int queue_depth_ = 32;
+    // IOSQE_ASYNC flag - ASYNC flags can potentially
+    // result in EINTR; Since we don't restart
+    // syscalls and fallback to synchronous I/O, we
+    // don't want huge queue depth
+    int queue_depth_ = 8;
     std::unique_ptr<struct io_uring> ring_;
 
     std::shared_ptr<SnapshotHandler> snapuserd_;
@@ -319,6 +331,7 @@
     void SetMergedBlockCountForNextCommit(int x) { total_ra_blocks_merged_ = x; }
     int GetTotalBlocksToMerge() { return total_ra_blocks_merged_; }
     void SetSocketPresent(bool socket) { is_socket_present_ = socket; }
+    void SetIouringEnabled(bool io_uring_enabled) { is_io_uring_enabled_ = io_uring_enabled; }
     bool MergeInitiated() { return merge_initiated_; }
     double GetMergePercentage() { return merge_completion_percentage_; }
 
@@ -396,6 +409,7 @@
     bool merge_initiated_ = false;
     bool attached_ = false;
     bool is_socket_present_;
+    bool is_io_uring_enabled_ = false;
     bool scratch_space_ = false;
 
     std::unique_ptr<struct io_uring> ring_;
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_merge.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_merge.cpp
index d4d4efe..0cb41d3 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_merge.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_merge.cpp
@@ -24,15 +24,14 @@
 using android::base::unique_fd;
 
 int Worker::PrepareMerge(uint64_t* source_offset, int* pending_ops,
-                         const std::unique_ptr<ICowOpIter>& cowop_iter,
                          std::vector<const CowOperation*>* replace_zero_vec) {
     int num_ops = *pending_ops;
     int nr_consecutive = 0;
     bool checkOrderedOp = (replace_zero_vec == nullptr);
 
     do {
-        if (!cowop_iter->Done() && num_ops) {
-            const CowOperation* cow_op = &cowop_iter->Get();
+        if (!cowop_iter_->Done() && num_ops) {
+            const CowOperation* cow_op = &cowop_iter_->Get();
             if (checkOrderedOp && !IsOrderedOp(*cow_op)) {
                 break;
             }
@@ -42,12 +41,12 @@
                 replace_zero_vec->push_back(cow_op);
             }
 
-            cowop_iter->Next();
+            cowop_iter_->Next();
             num_ops -= 1;
             nr_consecutive = 1;
 
-            while (!cowop_iter->Done() && num_ops) {
-                const CowOperation* op = &cowop_iter->Get();
+            while (!cowop_iter_->Done() && num_ops) {
+                const CowOperation* op = &cowop_iter_->Get();
                 if (checkOrderedOp && !IsOrderedOp(*op)) {
                     break;
                 }
@@ -63,7 +62,7 @@
 
                 nr_consecutive += 1;
                 num_ops -= 1;
-                cowop_iter->Next();
+                cowop_iter_->Next();
             }
         }
     } while (0);
@@ -71,7 +70,7 @@
     return nr_consecutive;
 }
 
-bool Worker::MergeReplaceZeroOps(const std::unique_ptr<ICowOpIter>& cowop_iter) {
+bool Worker::MergeReplaceZeroOps() {
     // Flush every 8192 ops. Since all ops are independent and there is no
     // dependency between COW ops, we will flush the data and the number
     // of ops merged in COW file for every 8192 ops. If there is a crash,
@@ -84,15 +83,17 @@
     int total_ops_merged_per_commit = (PAYLOAD_BUFFER_SZ / BLOCK_SZ) * 32;
     int num_ops_merged = 0;
 
-    while (!cowop_iter->Done()) {
+    SNAP_LOG(INFO) << "MergeReplaceZeroOps started....";
+
+    while (!cowop_iter_->Done()) {
         int num_ops = PAYLOAD_BUFFER_SZ / BLOCK_SZ;
         std::vector<const CowOperation*> replace_zero_vec;
         uint64_t source_offset;
 
-        int linear_blocks = PrepareMerge(&source_offset, &num_ops, cowop_iter, &replace_zero_vec);
+        int linear_blocks = PrepareMerge(&source_offset, &num_ops, &replace_zero_vec);
         if (linear_blocks == 0) {
             // Merge complete
-            CHECK(cowop_iter->Done());
+            CHECK(cowop_iter_->Done());
             break;
         }
 
@@ -117,8 +118,8 @@
         size_t io_size = linear_blocks * BLOCK_SZ;
 
         // Merge - Write the contents back to base device
-        int ret = pwrite(base_path_merge_fd_.get(), bufsink_.GetPayloadBufPtr(), io_size,
-                         source_offset);
+        int ret = TEMP_FAILURE_RETRY(pwrite(base_path_merge_fd_.get(), bufsink_.GetPayloadBufPtr(),
+                                            io_size, source_offset));
         if (ret < 0 || ret != io_size) {
             SNAP_LOG(ERROR)
                     << "Merge: ReplaceZeroOps: Failed to write to backing device while merging "
@@ -172,16 +173,15 @@
     return true;
 }
 
-bool Worker::MergeOrderedOpsAsync(const std::unique_ptr<ICowOpIter>& cowop_iter) {
+bool Worker::MergeOrderedOpsAsync() {
     void* mapped_addr = snapuserd_->GetMappedAddr();
     void* read_ahead_buffer =
             static_cast<void*>((char*)mapped_addr + snapuserd_->GetBufferDataOffset());
-    size_t block_index = 0;
 
     SNAP_LOG(INFO) << "MergeOrderedOpsAsync started....";
 
-    while (!cowop_iter->Done()) {
-        const CowOperation* cow_op = &cowop_iter->Get();
+    while (!cowop_iter_->Done()) {
+        const CowOperation* cow_op = &cowop_iter_->Get();
         if (!IsOrderedOp(*cow_op)) {
             break;
         }
@@ -190,11 +190,10 @@
         // Wait for RA thread to notify that the merge window
         // is ready for merging.
         if (!snapuserd_->WaitForMergeBegin()) {
-            snapuserd_->SetMergeFailed(block_index);
             return false;
         }
 
-        snapuserd_->SetMergeInProgress(block_index);
+        snapuserd_->SetMergeInProgress(ra_block_index_);
 
         loff_t offset = 0;
         int num_ops = snapuserd_->GetTotalBlocksToMerge();
@@ -202,12 +201,13 @@
         int pending_sqe = queue_depth_;
         int pending_ios_to_submit = 0;
         bool flush_required = false;
+        blocks_merged_in_group_ = 0;
 
         SNAP_LOG(DEBUG) << "Merging copy-ops of size: " << num_ops;
         while (num_ops) {
             uint64_t source_offset;
 
-            int linear_blocks = PrepareMerge(&source_offset, &num_ops, cowop_iter);
+            int linear_blocks = PrepareMerge(&source_offset, &num_ops);
 
             if (linear_blocks != 0) {
                 size_t io_size = (linear_blocks * BLOCK_SZ);
@@ -216,7 +216,6 @@
                 struct io_uring_sqe* sqe = io_uring_get_sqe(ring_.get());
                 if (!sqe) {
                     SNAP_PLOG(ERROR) << "io_uring_get_sqe failed during merge-ordered ops";
-                    snapuserd_->SetMergeFailed(block_index);
                     return false;
                 }
 
@@ -225,10 +224,18 @@
 
                 offset += io_size;
                 num_ops -= linear_blocks;
+                blocks_merged_in_group_ += linear_blocks;
 
                 pending_sqe -= 1;
                 pending_ios_to_submit += 1;
-                sqe->flags |= IOSQE_ASYNC;
+                // These flags are important - We need to make sure that the
+                // blocks are linked and are written in the same order as
+                // populated. This is because of overlapping block writes.
+                //
+                // If there are no dependency, we can optimize this further by
+                // allowing parallel writes; but for now, just link all the SQ
+                // entries.
+                sqe->flags |= (IOSQE_IO_LINK | IOSQE_ASYNC);
             }
 
             // Ring is full or no more COW ops to be merged in this batch
@@ -256,7 +263,7 @@
                             pending_sqe -= 1;
                             flush_required = false;
                             pending_ios_to_submit += 1;
-                            sqe->flags |= IOSQE_ASYNC;
+                            sqe->flags |= (IOSQE_IO_LINK | IOSQE_ASYNC);
                         }
                     } else {
                         flush_required = true;
@@ -269,35 +276,45 @@
                     SNAP_PLOG(ERROR)
                             << "io_uring_submit failed for read-ahead: "
                             << " io submit: " << ret << " expected: " << pending_ios_to_submit;
-                    snapuserd_->SetMergeFailed(block_index);
                     return false;
                 }
 
                 int pending_ios_to_complete = pending_ios_to_submit;
                 pending_ios_to_submit = 0;
 
+                bool status = true;
+
                 // Reap I/O completions
                 while (pending_ios_to_complete) {
                     struct io_uring_cqe* cqe;
 
+                    // We need to make sure to reap all the I/O's submitted
+                    // even if there are any errors observed.
+                    //
+                    // io_uring_wait_cqe can potentially return -EAGAIN or -EINTR;
+                    // these error codes are not truly I/O errors; we can retry them
+                    // by re-populating the SQE entries and submitting the I/O
+                    // request back. However, we don't do that now; instead we
+                    // will fallback to synchronous I/O.
                     ret = io_uring_wait_cqe(ring_.get(), &cqe);
                     if (ret) {
-                        SNAP_LOG(ERROR) << "Read-ahead - io_uring_wait_cqe failed: " << ret;
-                        snapuserd_->SetMergeFailed(block_index);
-                        return false;
+                        SNAP_LOG(ERROR) << "Merge: io_uring_wait_cqe failed: " << ret;
+                        status = false;
                     }
 
                     if (cqe->res < 0) {
-                        SNAP_LOG(ERROR)
-                                << "Read-ahead - io_uring_Wait_cqe failed with res: " << cqe->res;
-                        snapuserd_->SetMergeFailed(block_index);
-                        return false;
+                        SNAP_LOG(ERROR) << "Merge: io_uring_wait_cqe failed with res: " << cqe->res;
+                        status = false;
                     }
 
                     io_uring_cqe_seen(ring_.get(), cqe);
                     pending_ios_to_complete -= 1;
                 }
 
+                if (!status) {
+                    return false;
+                }
+
                 pending_sqe = queue_depth_;
             }
 
@@ -312,7 +329,6 @@
         // Flush the data
         if (flush_required && (fsync(base_path_merge_fd_.get()) < 0)) {
             SNAP_LOG(ERROR) << " Failed to fsync merged data";
-            snapuserd_->SetMergeFailed(block_index);
             return false;
         }
 
@@ -320,35 +336,34 @@
         // the merge completion
         if (!snapuserd_->CommitMerge(snapuserd_->GetTotalBlocksToMerge())) {
             SNAP_LOG(ERROR) << " Failed to commit the merged block in the header";
-            snapuserd_->SetMergeFailed(block_index);
             return false;
         }
 
         SNAP_LOG(DEBUG) << "Block commit of size: " << snapuserd_->GetTotalBlocksToMerge();
+
         // Mark the block as merge complete
-        snapuserd_->SetMergeCompleted(block_index);
+        snapuserd_->SetMergeCompleted(ra_block_index_);
 
         // Notify RA thread that the merge thread is ready to merge the next
         // window
         snapuserd_->NotifyRAForMergeReady();
 
         // Get the next block
-        block_index += 1;
+        ra_block_index_ += 1;
     }
 
     return true;
 }
 
-bool Worker::MergeOrderedOps(const std::unique_ptr<ICowOpIter>& cowop_iter) {
+bool Worker::MergeOrderedOps() {
     void* mapped_addr = snapuserd_->GetMappedAddr();
     void* read_ahead_buffer =
             static_cast<void*>((char*)mapped_addr + snapuserd_->GetBufferDataOffset());
-    size_t block_index = 0;
 
     SNAP_LOG(INFO) << "MergeOrderedOps started....";
 
-    while (!cowop_iter->Done()) {
-        const CowOperation* cow_op = &cowop_iter->Get();
+    while (!cowop_iter_->Done()) {
+        const CowOperation* cow_op = &cowop_iter_->Get();
         if (!IsOrderedOp(*cow_op)) {
             break;
         }
@@ -357,11 +372,11 @@
         // Wait for RA thread to notify that the merge window
         // is ready for merging.
         if (!snapuserd_->WaitForMergeBegin()) {
-            snapuserd_->SetMergeFailed(block_index);
+            snapuserd_->SetMergeFailed(ra_block_index_);
             return false;
         }
 
-        snapuserd_->SetMergeInProgress(block_index);
+        snapuserd_->SetMergeInProgress(ra_block_index_);
 
         loff_t offset = 0;
         int num_ops = snapuserd_->GetTotalBlocksToMerge();
@@ -369,7 +384,7 @@
         while (num_ops) {
             uint64_t source_offset;
 
-            int linear_blocks = PrepareMerge(&source_offset, &num_ops, cowop_iter);
+            int linear_blocks = PrepareMerge(&source_offset, &num_ops);
             if (linear_blocks == 0) {
                 break;
             }
@@ -378,12 +393,13 @@
             // Write to the base device. Data is already in the RA buffer. Note
             // that XOR ops is already handled by the RA thread. We just write
             // the contents out.
-            int ret = pwrite(base_path_merge_fd_.get(), (char*)read_ahead_buffer + offset, io_size,
-                             source_offset);
+            int ret = TEMP_FAILURE_RETRY(pwrite(base_path_merge_fd_.get(),
+                                                (char*)read_ahead_buffer + offset, io_size,
+                                                source_offset));
             if (ret < 0 || ret != io_size) {
                 SNAP_LOG(ERROR) << "Failed to write to backing device while merging "
                                 << " at offset: " << source_offset << " io_size: " << io_size;
-                snapuserd_->SetMergeFailed(block_index);
+                snapuserd_->SetMergeFailed(ra_block_index_);
                 return false;
             }
 
@@ -397,7 +413,7 @@
         // Flush the data
         if (fsync(base_path_merge_fd_.get()) < 0) {
             SNAP_LOG(ERROR) << " Failed to fsync merged data";
-            snapuserd_->SetMergeFailed(block_index);
+            snapuserd_->SetMergeFailed(ra_block_index_);
             return false;
         }
 
@@ -405,47 +421,87 @@
         // the merge completion
         if (!snapuserd_->CommitMerge(snapuserd_->GetTotalBlocksToMerge())) {
             SNAP_LOG(ERROR) << " Failed to commit the merged block in the header";
-            snapuserd_->SetMergeFailed(block_index);
+            snapuserd_->SetMergeFailed(ra_block_index_);
             return false;
         }
 
         SNAP_LOG(DEBUG) << "Block commit of size: " << snapuserd_->GetTotalBlocksToMerge();
         // Mark the block as merge complete
-        snapuserd_->SetMergeCompleted(block_index);
+        snapuserd_->SetMergeCompleted(ra_block_index_);
 
         // Notify RA thread that the merge thread is ready to merge the next
         // window
         snapuserd_->NotifyRAForMergeReady();
 
         // Get the next block
-        block_index += 1;
+        ra_block_index_ += 1;
     }
 
     return true;
 }
 
-bool Worker::Merge() {
-    std::unique_ptr<ICowOpIter> cowop_iter = reader_->GetMergeOpIter();
+bool Worker::AsyncMerge() {
+    if (!MergeOrderedOpsAsync()) {
+        SNAP_LOG(ERROR) << "MergeOrderedOpsAsync failed - Falling back to synchronous I/O";
+        // Reset the iter so that we retry the merge
+        while (blocks_merged_in_group_ && !cowop_iter_->RDone()) {
+            cowop_iter_->Prev();
+            blocks_merged_in_group_ -= 1;
+        }
 
+        return false;
+    }
+
+    SNAP_LOG(INFO) << "MergeOrderedOpsAsync completed";
+    return true;
+}
+
+bool Worker::SyncMerge() {
+    if (!MergeOrderedOps()) {
+        SNAP_LOG(ERROR) << "Merge failed for ordered ops";
+        return false;
+    }
+
+    SNAP_LOG(INFO) << "MergeOrderedOps completed";
+    return true;
+}
+
+bool Worker::Merge() {
+    cowop_iter_ = reader_->GetMergeOpIter();
+
+    bool retry = false;
+    bool ordered_ops_merge_status;
+
+    // Start Async Merge
     if (merge_async_) {
-        if (!MergeOrderedOpsAsync(cowop_iter)) {
+        ordered_ops_merge_status = AsyncMerge();
+        if (!ordered_ops_merge_status) {
+            FinalizeIouring();
+            retry = true;
+            merge_async_ = false;
+        }
+    }
+
+    // Check if we need to fallback and retry the merge
+    //
+    // If the device doesn't support async merge, we
+    // will directly enter here (aka devices with 4.x kernels)
+    const bool sync_merge_required = (retry || !merge_async_);
+
+    if (sync_merge_required) {
+        ordered_ops_merge_status = SyncMerge();
+        if (!ordered_ops_merge_status) {
+            // Merge failed. Device will continue to be mounted
+            // off snapshots; merge will be retried during
+            // next reboot
             SNAP_LOG(ERROR) << "Merge failed for ordered ops";
             snapuserd_->MergeFailed();
             return false;
         }
-        SNAP_LOG(INFO) << "MergeOrderedOpsAsync completed.....";
-    } else {
-        // Start with Copy and Xor ops
-        if (!MergeOrderedOps(cowop_iter)) {
-            SNAP_LOG(ERROR) << "Merge failed for ordered ops";
-            snapuserd_->MergeFailed();
-            return false;
-        }
-        SNAP_LOG(INFO) << "MergeOrderedOps completed.....";
     }
 
     // Replace and Zero ops
-    if (!MergeReplaceZeroOps(cowop_iter)) {
+    if (!MergeReplaceZeroOps()) {
         SNAP_LOG(ERROR) << "Merge failed for replace/zero ops";
         snapuserd_->MergeFailed();
         return false;
@@ -506,7 +562,7 @@
     CloseFds();
     reader_->CloseCowFd();
 
-    SNAP_LOG(INFO) << "Merge finish";
+    SNAP_LOG(INFO) << "Snapshot-Merge completed";
 
     return true;
 }
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp
index 26c5f19..7d9d392 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp
@@ -279,7 +279,6 @@
             sqe = io_uring_get_sqe(ring_.get());
             if (!sqe) {
                 SNAP_PLOG(ERROR) << "io_uring_get_sqe failed during read-ahead";
-                snapuserd_->ReadAheadIOFailed();
                 return false;
             }
 
@@ -309,7 +308,6 @@
             if (ret != pending_ios_to_submit) {
                 SNAP_PLOG(ERROR) << "io_uring_submit failed for read-ahead: "
                                  << " io submit: " << ret << " expected: " << pending_ios_to_submit;
-                snapuserd_->ReadAheadIOFailed();
                 return false;
             }
 
@@ -321,14 +319,12 @@
             // Read XOR data from COW file in parallel when I/O's are in-flight
             if (xor_processing_required && !ReadXorData(block_index, xor_op_index, xor_op_vec)) {
                 SNAP_LOG(ERROR) << "ReadXorData failed";
-                snapuserd_->ReadAheadIOFailed();
                 return false;
             }
 
             // Fetch I/O completions
             if (!ReapIoCompletions(pending_ios_to_complete)) {
                 SNAP_LOG(ERROR) << "ReapIoCompletions failed";
-                snapuserd_->ReadAheadIOFailed();
                 return false;
             }
 
@@ -393,26 +389,36 @@
 }
 
 bool ReadAhead::ReapIoCompletions(int pending_ios_to_complete) {
+    bool status = true;
+
     // Reap I/O completions
     while (pending_ios_to_complete) {
         struct io_uring_cqe* cqe;
 
+        // We need to make sure to reap all the I/O's submitted
+        // even if there are any errors observed.
+        //
+        // io_uring_wait_cqe can potentially return -EAGAIN or -EINTR;
+        // these error codes are not truly I/O errors; we can retry them
+        // by re-populating the SQE entries and submitting the I/O
+        // request back. However, we don't do that now; instead we
+        // will fallback to synchronous I/O.
         int ret = io_uring_wait_cqe(ring_.get(), &cqe);
         if (ret) {
             SNAP_LOG(ERROR) << "Read-ahead - io_uring_wait_cqe failed: " << ret;
-            return false;
+            status = false;
         }
 
         if (cqe->res < 0) {
             SNAP_LOG(ERROR) << "Read-ahead - io_uring_Wait_cqe failed with res: " << cqe->res;
-            return false;
+            status = false;
         }
 
         io_uring_cqe_seen(ring_.get(), cqe);
         pending_ios_to_complete -= 1;
     }
 
-    return true;
+    return status;
 }
 
 void ReadAhead::ProcessXorData(size_t& block_xor_index, size_t& xor_index,
@@ -610,18 +616,38 @@
         return ReconstructDataFromCow();
     }
 
+    bool retry = false;
+    bool ra_status;
+
+    // Start Async read-ahead
     if (read_ahead_async_) {
-        if (!ReadAheadAsyncIO()) {
-            SNAP_LOG(ERROR) << "ReadAheadAsyncIO failed - io_uring processing failure.";
-            return false;
+        ra_status = ReadAheadAsyncIO();
+        if (!ra_status) {
+            SNAP_LOG(ERROR) << "ReadAheadAsyncIO failed - Falling back synchronous I/O";
+            FinalizeIouring();
+            RAResetIter(total_blocks_merged_);
+            retry = true;
+            read_ahead_async_ = false;
         }
-    } else {
-        if (!ReadAheadSyncIO()) {
+    }
+
+    // Check if we need to fallback and retry the merge
+    //
+    // If the device doesn't support async operations, we
+    // will directly enter here (aka devices with 4.x kernels)
+
+    const bool ra_sync_required = (retry || !read_ahead_async_);
+
+    if (ra_sync_required) {
+        ra_status = ReadAheadSyncIO();
+        if (!ra_status) {
             SNAP_LOG(ERROR) << "ReadAheadSyncIO failed";
             return false;
         }
     }
 
+    SNAP_LOG(DEBUG) << "Read-ahead: total_ra_blocks_merged: " << total_ra_blocks_completed_;
+
     // Wait for the merge to finish for the previous RA window. We shouldn't
     // be touching the scratch space until merge is complete of previous RA
     // window. If there is a crash during this time frame, merge should resume
@@ -646,6 +672,7 @@
         offset += BLOCK_SZ;
     }
 
+    total_ra_blocks_completed_ += total_blocks_merged_;
     snapuserd_->SetMergedBlockCountForNextCommit(total_blocks_merged_);
 
     // Flush the data only if we have a overlapping blocks in the region
@@ -763,6 +790,13 @@
     cowop_iter_->Next();
 }
 
+void ReadAhead::RAResetIter(uint64_t num_blocks) {
+    while (num_blocks && !cowop_iter_->RDone()) {
+        cowop_iter_->Prev();
+        num_blocks -= 1;
+    }
+}
+
 const CowOperation* ReadAhead::GetRAOpIter() {
     const CowOperation* cow_op = &cowop_iter_->Get();
     return cow_op;
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.cpp
index eb64704..82b2b25 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.cpp
@@ -293,7 +293,6 @@
 void UserSnapshotServer::RunThread(std::shared_ptr<UserSnapshotDmUserHandler> handler) {
     LOG(INFO) << "Entering thread for handler: " << handler->misc_name();
 
-    handler->snapuserd()->SetSocketPresent(is_socket_present_);
     if (!handler->snapuserd()->Start()) {
         LOG(ERROR) << " Failed to launch all worker threads";
     }
@@ -471,6 +470,9 @@
         return nullptr;
     }
 
+    snapuserd->SetSocketPresent(is_socket_present_);
+    snapuserd->SetIouringEnabled(io_uring_enabled_);
+
     if (!snapuserd->InitializeWorkers()) {
         LOG(ERROR) << "Failed to initialize workers";
         return nullptr;
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.h b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.h
index c645456..34e7941 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.h
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.h
@@ -84,6 +84,8 @@
     std::vector<struct pollfd> watched_fds_;
     bool is_socket_present_ = false;
     int num_partitions_merge_complete_ = 0;
+    bool is_server_running_ = false;
+    bool io_uring_enabled_ = false;
 
     std::mutex lock_;
 
@@ -136,6 +138,10 @@
 
     void SetTerminating() { terminating_ = true; }
     void ReceivedSocketSignal() { received_socket_signal_ = true; }
+    void SetServerRunning() { is_server_running_ = true; }
+    bool IsServerRunning() { return is_server_running_; }
+    void SetIouringEnabled() { io_uring_enabled_ = true; }
+    bool IsIouringEnabled() { return io_uring_enabled_; }
 };
 
 }  // namespace snapshot
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_transitions.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_transitions.cpp
index 6dec1e2..d4e1d7c 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_transitions.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_transitions.cpp
@@ -531,6 +531,13 @@
     {
         std::unique_lock<std::mutex> lock(blk_state->m_lock);
 
+        // We may have fallback from Async-merge to synchronous merging
+        // on the existing block. There is no need to reset as the
+        // merge is already in progress.
+        if (blk_state->merge_state_ == MERGE_GROUP_STATE::GROUP_MERGE_IN_PROGRESS) {
+            return;
+        }
+
         CHECK(blk_state->merge_state_ == MERGE_GROUP_STATE::GROUP_MERGE_PENDING);
 
         // First set the state to RA_READY so that in-flight I/O will drain
diff --git a/fs_mgr/libsnapshot/utility.cpp b/fs_mgr/libsnapshot/utility.cpp
index 89d6145..f01bec9 100644
--- a/fs_mgr/libsnapshot/utility.cpp
+++ b/fs_mgr/libsnapshot/utility.cpp
@@ -192,6 +192,10 @@
     return android::base::GetBoolProperty("ro.virtual_ab.userspace.snapshots.enabled", false);
 }
 
+bool IsIouringEnabled() {
+    return android::base::GetBoolProperty("ro.virtual_ab.io_uring.enabled", false);
+}
+
 std::string GetOtherPartitionName(const std::string& name) {
     auto suffix = android::fs_mgr::GetPartitionSlotSuffix(name);
     CHECK(suffix == "_a" || suffix == "_b");
diff --git a/fs_mgr/libsnapshot/utility.h b/fs_mgr/libsnapshot/utility.h
index a032b68..0ef3234 100644
--- a/fs_mgr/libsnapshot/utility.h
+++ b/fs_mgr/libsnapshot/utility.h
@@ -135,6 +135,8 @@
 
 bool IsDmSnapshotTestingEnabled();
 
+bool IsIouringEnabled();
+
 // Swap the suffix of a partition name.
 std::string GetOtherPartitionName(const std::string& name);
 }  // namespace snapshot
diff --git a/fs_mgr/libsnapshot/vts_ota_config_test.cpp b/fs_mgr/libsnapshot/vts_ota_config_test.cpp
new file mode 100644
index 0000000..afc2d81
--- /dev/null
+++ b/fs_mgr/libsnapshot/vts_ota_config_test.cpp
@@ -0,0 +1,23 @@
+//
+// Copyright (C) 2022 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 <android-base/properties.h>
+#include <gtest/gtest.h>
+
+TEST(VAB, Enabled) {
+    ASSERT_TRUE(android::base::GetBoolProperty("ro.virtual_ab.enabled", false));
+    ASSERT_TRUE(android::base::GetBoolProperty("ro.virtual_ab.userspace.snapshots.enabled", false));
+}
diff --git a/fs_mgr/tests/Android.bp b/fs_mgr/tests/Android.bp
index 335da9e..7e3924a 100644
--- a/fs_mgr/tests/Android.bp
+++ b/fs_mgr/tests/Android.bp
@@ -88,3 +88,32 @@
 
     test_suites: ["general-tests"],
 }
+
+cc_test {
+    name: "vts_fs_test",
+    test_suites: [
+        "vts",
+        "device-tests",
+    ],
+    test_options: {
+        min_shipping_api_level: 31,
+    },
+    require_root: true,
+    auto_gen_config: true,
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+    srcs: [
+        "vts_fs_test.cpp",
+    ],
+    shared_libs: [
+        "libbase",
+        "libfs_mgr",
+    ],
+    static_libs: [
+        "libfstab",
+        "libgmock",
+        "libgtest",
+    ],
+}
diff --git a/fs_mgr/tests/fs_mgr_test.cpp b/fs_mgr/tests/fs_mgr_test.cpp
index eccb902..1dbee75 100644
--- a/fs_mgr/tests/fs_mgr_test.cpp
+++ b/fs_mgr/tests/fs_mgr_test.cpp
@@ -1104,3 +1104,76 @@
     EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
     EXPECT_EQ(0, entry->readahead_size_kb);
 }
+
+TEST(fs_mgr, TransformFstabForDsu) {
+    TemporaryFile tf;
+    ASSERT_TRUE(tf.fd != -1);
+    std::string fstab_contents = R"fs(
+system /system      erofs   ro  wait,logical,first_stage_mount
+system /system      ext4    ro  wait,logical,first_stage_mount
+vendor /vendor      ext4    ro  wait,logical,first_stage_mount
+data   /data        f2fs    noatime     wait
+)fs";
+
+    ASSERT_TRUE(android::base::WriteStringToFile(fstab_contents, tf.path));
+
+    Fstab fstab;
+    EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
+    TransformFstabForDsu(&fstab, "dsu", {"system_gsi", "userdata_gsi"});
+    ASSERT_EQ(4U, fstab.size());
+
+    auto entry = fstab.begin();
+
+    EXPECT_EQ("/system", entry->mount_point);
+    EXPECT_EQ("system_gsi", entry->blk_device);
+    entry++;
+
+    EXPECT_EQ("/system", entry->mount_point);
+    EXPECT_EQ("system_gsi", entry->blk_device);
+    entry++;
+
+    EXPECT_EQ("/vendor", entry->mount_point);
+    EXPECT_EQ("vendor", entry->blk_device);
+    entry++;
+
+    EXPECT_EQ("/data", entry->mount_point);
+    EXPECT_EQ("userdata_gsi", entry->blk_device);
+    entry++;
+}
+
+TEST(fs_mgr, TransformFstabForDsu_synthesisExt4Entry) {
+    TemporaryFile tf;
+    ASSERT_TRUE(tf.fd != -1);
+    std::string fstab_contents = R"fs(
+system /system      erofs   ro  wait,logical,first_stage_mount
+vendor /vendor      ext4    ro  wait,logical,first_stage_mount
+data   /data        f2fs    noatime     wait
+)fs";
+
+    ASSERT_TRUE(android::base::WriteStringToFile(fstab_contents, tf.path));
+
+    Fstab fstab;
+    EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
+    TransformFstabForDsu(&fstab, "dsu", {"system_gsi", "userdata_gsi"});
+    ASSERT_EQ(4U, fstab.size());
+
+    auto entry = fstab.begin();
+
+    EXPECT_EQ("/system", entry->mount_point);
+    EXPECT_EQ("system_gsi", entry->blk_device);
+    EXPECT_EQ("erofs", entry->fs_type);
+    entry++;
+
+    EXPECT_EQ("/system", entry->mount_point);
+    EXPECT_EQ("system_gsi", entry->blk_device);
+    EXPECT_EQ("ext4", entry->fs_type);
+    entry++;
+
+    EXPECT_EQ("/vendor", entry->mount_point);
+    EXPECT_EQ("vendor", entry->blk_device);
+    entry++;
+
+    EXPECT_EQ("/data", entry->mount_point);
+    EXPECT_EQ("userdata_gsi", entry->blk_device);
+    entry++;
+}
diff --git a/fs_mgr/tests/vts_fs_test.cpp b/fs_mgr/tests/vts_fs_test.cpp
new file mode 100644
index 0000000..415e67e
--- /dev/null
+++ b/fs_mgr/tests/vts_fs_test.cpp
@@ -0,0 +1,111 @@
+// 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 <sys/mount.h>
+#include <sys/utsname.h>
+
+#include <android-base/file.h>
+#include <android-base/properties.h>
+#include <android-base/strings.h>
+#include <fstab/fstab.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <libdm/dm.h>
+
+static int GetVsrLevel() {
+    return android::base::GetIntProperty("ro.vendor.api_level", -1);
+}
+
+TEST(fs, ErofsSupported) {
+    // S and higher for this test.
+    if (GetVsrLevel() < __ANDROID_API_S__) {
+        GTEST_SKIP();
+    }
+
+    struct utsname uts;
+    ASSERT_EQ(uname(&uts), 0);
+
+    unsigned int major, minor;
+    ASSERT_EQ(sscanf(uts.release, "%u.%u", &major, &minor), 2);
+
+    // EROFS support only required in 5.10+
+    if (major < 5 || (major == 5 && minor < 10)) {
+        GTEST_SKIP();
+    }
+
+    std::string fs;
+    ASSERT_TRUE(android::base::ReadFileToString("/proc/filesystems", &fs));
+    EXPECT_THAT(fs, ::testing::HasSubstr("\terofs\n"));
+}
+
+TEST(fs, PartitionTypes) {
+    android::fs_mgr::Fstab fstab;
+    ASSERT_TRUE(android::fs_mgr::ReadFstabFromFile("/proc/mounts", &fstab));
+
+    auto& dm = android::dm::DeviceMapper::Instance();
+
+    std::string super_bdev, userdata_bdev;
+    ASSERT_TRUE(android::base::Readlink("/dev/block/by-name/super", &super_bdev));
+    ASSERT_TRUE(android::base::Readlink("/dev/block/by-name/userdata", &userdata_bdev));
+
+    int vsr_level = GetVsrLevel();
+
+    for (const auto& entry : fstab) {
+        std::string parent_bdev = entry.blk_device;
+        while (true) {
+            auto basename = android::base::Basename(parent_bdev);
+            if (!android::base::StartsWith(basename, "dm-")) {
+                break;
+            }
+
+            auto parent = dm.GetParentBlockDeviceByPath(parent_bdev);
+            if (!parent || *parent == parent_bdev) {
+                break;
+            }
+            parent_bdev = *parent;
+        }
+
+        if (parent_bdev == userdata_bdev ||
+            android::base::StartsWith(parent_bdev, "/dev/block/loop")) {
+            if (entry.flags & MS_RDONLY) {
+                // APEXes should not be F2FS.
+                EXPECT_NE(entry.fs_type, "f2fs");
+            }
+            continue;
+        }
+
+        if (vsr_level < __ANDROID_API_T__) {
+            continue;
+        }
+        if (vsr_level == __ANDROID_API_T__ && parent_bdev != super_bdev) {
+            // Only check for dynamic partitions at this VSR level.
+            continue;
+        }
+
+        if (entry.flags & MS_RDONLY) {
+            EXPECT_EQ(entry.fs_type, "erofs") << entry.mount_point;
+        } else {
+            EXPECT_NE(entry.fs_type, "ext4") << entry.mount_point;
+        }
+    }
+}
+
+TEST(fs, NoDtFstab) {
+    if (GetVsrLevel() <= __ANDROID_API_S__) {
+        GTEST_SKIP();
+    }
+
+    android::fs_mgr::Fstab fstab;
+    EXPECT_FALSE(android::fs_mgr::ReadFstabFromDt(&fstab, false));
+}
diff --git a/init/Android.bp b/init/Android.bp
index 66427dc..c39d163 100644
--- a/init/Android.bp
+++ b/init/Android.bp
@@ -162,12 +162,15 @@
         "libavb",
         "libc++fs",
         "libcgrouprc_format",
+        "libfsverity_init",
         "liblmkd_utils",
+        "libmini_keyctl_static",
         "libmodprobe",
         "libprocinfo",
         "libprotobuf-cpp-lite",
         "libpropertyinfoserializer",
         "libpropertyinfoparser",
+        "libsigningutils",
         "libsnapshot_cow",
         "libsnapshot_init",
         "libxml2",
@@ -178,6 +181,7 @@
         "libbacktrace",
         "libbase",
         "libbootloader_message",
+        "libcrypto",
         "libcutils",
         "libdl",
         "libext4_utils",
@@ -192,6 +196,7 @@
         "libprocessgroup_setup",
         "libselinux",
         "libutils",
+        "libziparchive",
     ],
     bootstrap: true,
     visibility: [":__subpackages__"],
diff --git a/init/first_stage_init.cpp b/init/first_stage_init.cpp
index c7b7b0c..3cd0252 100644
--- a/init/first_stage_init.cpp
+++ b/init/first_stage_init.cpp
@@ -254,6 +254,9 @@
     // stage init
     CHECKCALL(mount("tmpfs", kSecondStageRes, "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
                     "mode=0755,uid=0,gid=0"))
+
+    // First stage init stores Mainline sepolicy here.
+    CHECKCALL(mkdir("/dev/selinux", 0744));
 #undef CHECKCALL
 
     SetStdioToDevNull(argv);
diff --git a/init/property_service.cpp b/init/property_service.cpp
index f2b2cda..9f7c215 100644
--- a/init/property_service.cpp
+++ b/init/property_service.cpp
@@ -1035,8 +1035,8 @@
     constexpr auto VENDOR_API_LEVEL_PROP = "ro.vendor.api_level";
 
     // Api level properties of the board. The order of the properties must be kept.
-    std::vector<std::string> BOARD_API_LEVEL_PROPS = {
-            "ro.board.api_level", "ro.board.first_api_level", "ro.vendor.build.version.sdk"};
+    std::vector<std::string> BOARD_API_LEVEL_PROPS = {"ro.board.api_level",
+                                                      "ro.board.first_api_level"};
     // Api level properties of the device. The order of the properties must be kept.
     std::vector<std::string> DEVICE_API_LEVEL_PROPS = {"ro.product.first_api_level",
                                                        "ro.build.version.sdk"};
@@ -1172,6 +1172,9 @@
         // Don't check for failure here, since we don't always have all of these partitions.
         // E.g. In case of recovery, the vendor partition will not have mounted and we
         // still need the system / platform properties to function.
+        if (access("/dev/selinux/apex_property_contexts", R_OK) != -1) {
+            LoadPropertyInfoFromFile("/dev/selinux/apex_property_contexts", &property_infos);
+        }
         if (access("/system_ext/etc/selinux/system_ext_property_contexts", R_OK) != -1) {
             LoadPropertyInfoFromFile("/system_ext/etc/selinux/system_ext_property_contexts",
                                      &property_infos);
@@ -1195,6 +1198,7 @@
         LoadPropertyInfoFromFile("/vendor_property_contexts", &property_infos);
         LoadPropertyInfoFromFile("/product_property_contexts", &property_infos);
         LoadPropertyInfoFromFile("/odm_property_contexts", &property_infos);
+        LoadPropertyInfoFromFile("/dev/selinux/apex_property_contexts", &property_infos);
     }
 
     auto serialized_contexts = std::string();
diff --git a/init/selinux.cpp b/init/selinux.cpp
index 28cd012..c89c5ab 100644
--- a/init/selinux.cpp
+++ b/init/selinux.cpp
@@ -26,26 +26,29 @@
 // The monolithic policy variant is for legacy non-treble devices that contain a single SEPolicy
 // file located at /sepolicy and is directly loaded into the kernel SELinux subsystem.
 
-// The split policy is for supporting treble devices.  It splits the SEPolicy across files on
-// /system/etc/selinux (the 'plat' portion of the policy) and /vendor/etc/selinux (the 'vendor'
-// portion of the policy).  This is necessary to allow the system image to be updated independently
-// of the vendor image, while maintaining contributions from both partitions in the SEPolicy.  This
-// is especially important for VTS testing, where the SEPolicy on the Google System Image may not be
-// identical to the system image shipped on a vendor's device.
+// The split policy is for supporting treble devices and updateable apexes.  It splits the SEPolicy
+// across files on /system/etc/selinux (the 'plat' portion of the policy), /vendor/etc/selinux
+// (the 'vendor' portion of the policy), /system_ext/etc/selinux, /product/etc/selinux,
+// /odm/etc/selinux, and /dev/selinux (the apex portion of policy).  This is necessary to allow
+// images to be updated independently of the vendor image, while maintaining contributions from
+// multiple partitions in the SEPolicy.  This is especially important for VTS testing, where the
+// SEPolicy on the Google System Image may not be identical to the system image shipped on a
+// vendor's device.
 
 // The split SEPolicy is loaded as described below:
 // 1) There is a precompiled SEPolicy located at either /vendor/etc/selinux/precompiled_sepolicy or
 //    /odm/etc/selinux/precompiled_sepolicy if odm parition is present.  Stored along with this file
-//    are the sha256 hashes of the parts of the SEPolicy on /system, /system_ext and /product that
-//    were used to compile this precompiled policy.  The system partition contains a similar sha256
-//    of the parts of the SEPolicy that it currently contains.  Symmetrically, system_ext and
-//    product paritition contain sha256 hashes of their SEPolicy.  The init loads this
+//    are the sha256 hashes of the parts of the SEPolicy on /system, /system_ext, /product, and apex
+//    that were used to compile this precompiled policy.  The system partition contains a similar
+//    sha256 of the parts of the SEPolicy that it currently contains. Symmetrically, system_ext,
+//    product, and apex contain sha256 hashes of their SEPolicy. Init loads this
 //    precompiled_sepolicy directly if and only if the hashes along with the precompiled SEPolicy on
-//    /vendor or /odm match the hashes for system, system_ext and product SEPolicy, respectively.
-// 2) If these hashes do not match, then either /system or /system_ext or /product (or some of them)
-//    have been updated out of sync with /vendor (or /odm if it is present) and the init needs to
-//    compile the SEPolicy.  /system contains the SEPolicy compiler, secilc, and it is used by the
-//    OpenSplitPolicy() function below to compile the SEPolicy to a temp directory and load it.
+//    /vendor or /odm match the hashes for system, system_ext, product, and apex SEPolicy,
+//    respectively.
+// 2) If these hashes do not match, then either /system or /system_ext /product, or apex (or some of
+//    them) have been updated out of sync with /vendor (or /odm if it is present) and the init needs
+//    to compile the SEPolicy.  /system contains the SEPolicy compiler, secilc, and it is used by
+//    the OpenSplitPolicy() function below to compile the SEPolicy to a temp directory and load it.
 //    That function contains even more documentation with the specific implementation details of how
 //    the SEPolicy is compiled if needed.
 
@@ -58,19 +61,25 @@
 #include <stdlib.h>
 #include <sys/wait.h>
 #include <unistd.h>
+#include <fstream>
 
+#include <CertUtils.h>
 #include <android-base/chrono_utils.h>
 #include <android-base/file.h>
 #include <android-base/logging.h>
 #include <android-base/parseint.h>
 #include <android-base/result.h>
+#include <android-base/scopeguard.h>
 #include <android-base/strings.h>
 #include <android-base/unique_fd.h>
 #include <fs_avb/fs_avb.h>
 #include <fs_mgr.h>
+#include <fsverity_init.h>
 #include <libgsi/libgsi.h>
 #include <libsnapshot/snapshot.h>
+#include <mini_keyctl_utils.h>
 #include <selinux/android.h>
+#include <ziparchive/zip_archive.h>
 
 #include "block_dev_initializer.h"
 #include "debug_ramdisk.h"
@@ -247,6 +256,7 @@
              precompiled_sepolicy + ".system_ext_sepolicy_and_mapping.sha256"},
             {"/product/etc/selinux/product_sepolicy_and_mapping.sha256",
              precompiled_sepolicy + ".product_sepolicy_and_mapping.sha256"},
+            {"/dev/selinux/apex_sepolicy.sha256", precompiled_sepolicy + ".apex_sepolicy.sha256"},
     };
 
     for (const auto& [actual_id_path, precompiled_id_path] : sepolicy_hashes) {
@@ -325,7 +335,7 @@
     // * vendor -- policy needed due to logic contained in the vendor image,
     // * mapping -- mapping policy which helps preserve forward-compatibility of non-platform policy
     //   with newer versions of platform policy.
-    // * (optional) policy needed due to logic on product, system_ext, or odm images.
+    // * (optional) policy needed due to logic on product, system_ext, odm, or apex.
     // secilc is invoked to compile the above three policy files into a single monolithic policy
     // file. This file is then loaded into the kernel.
 
@@ -421,6 +431,12 @@
     if (access(odm_policy_cil_file.c_str(), F_OK) == -1) {
         odm_policy_cil_file.clear();
     }
+
+    // apex_sepolicy.cil is default but optional.
+    std::string apex_policy_cil_file("/dev/selinux/apex_sepolicy.cil");
+    if (access(apex_policy_cil_file.c_str(), F_OK) == -1) {
+        apex_policy_cil_file.clear();
+    }
     const std::string version_as_string = std::to_string(SEPOLICY_VERSION);
 
     // clang-format off
@@ -463,6 +479,9 @@
     if (!odm_policy_cil_file.empty()) {
         compile_args.push_back(odm_policy_cil_file.c_str());
     }
+    if (!apex_policy_cil_file.empty()) {
+        compile_args.push_back(apex_policy_cil_file.c_str());
+    }
     compile_args.push_back(nullptr);
 
     if (!ForkExecveAndWaitForCompletion(compile_args[0], (char**)compile_args.data())) {
@@ -489,6 +508,197 @@
     return true;
 }
 
+constexpr const char* kSigningCertRelease =
+        "/system/etc/selinux/com.android.sepolicy.cert-release.der";
+constexpr const char* kFsVerityProcPath = "/proc/sys/fs/verity";
+const std::string kSepolicyApexMetadataDir = "/metadata/sepolicy/";
+const std::string kSepolicyApexSystemDir = "/system/etc/selinux/apex/";
+const std::string kSepolicyZip = "SEPolicy.zip";
+const std::string kSepolicySignature = "SEPolicy.zip.sig";
+
+const std::string kTmpfsDir = "/dev/selinux/";
+
+// Files that are deleted after policy is compiled/loaded.
+const std::vector<std::string> kApexSepolicyTmp{"apex_sepolicy.cil", "apex_sepolicy.sha256"};
+// Files that need to persist because they are used by userspace processes.
+const std::vector<std::string> kApexSepolicy{"apex_file_contexts", "apex_property_contexts",
+                                             "apex_service_contexts", "apex_seapp_contexts",
+                                             "apex_test"};
+
+Result<void> PutFileInTmpfs(ZipArchiveHandle archive, const std::string& fileName) {
+    ZipEntry entry;
+    std::string dstPath = kTmpfsDir + fileName;
+
+    int ret = FindEntry(archive, fileName, &entry);
+    if (ret != 0) {
+        // All files are optional. If a file doesn't exist, return without error.
+        return {};
+    }
+
+    unique_fd fd(TEMP_FAILURE_RETRY(
+            open(dstPath.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, S_IRUSR | S_IWUSR)));
+    if (fd == -1) {
+        return Error() << "Failed to open " << dstPath;
+    }
+
+    ret = ExtractEntryToFile(archive, &entry, fd);
+    if (ret != 0) {
+        return Error() << "Failed to extract entry \"" << fileName << "\" ("
+                       << entry.uncompressed_length << " bytes) to \"" << dstPath
+                       << "\": " << ErrorCodeString(ret);
+    }
+
+    return {};
+}
+
+Result<void> GetPolicyFromApex(const std::string& dir) {
+    LOG(INFO) << "Loading APEX Sepolicy from " << dir + kSepolicyZip;
+    unique_fd fd(open((dir + kSepolicyZip).c_str(), O_RDONLY | O_BINARY | O_CLOEXEC));
+    if (fd < 0) {
+        return ErrnoError() << "Failed to open package " << dir + kSepolicyZip;
+    }
+
+    ZipArchiveHandle handle;
+    int ret = OpenArchiveFd(fd.get(), (dir + kSepolicyZip).c_str(), &handle,
+                            /*assume_ownership=*/false);
+    if (ret < 0) {
+        return Error() << "Failed to open package " << dir + kSepolicyZip << ": "
+                       << ErrorCodeString(ret);
+    }
+
+    auto handle_guard = android::base::make_scope_guard([&handle] { CloseArchive(handle); });
+
+    for (const auto& file : kApexSepolicy) {
+        auto extract = PutFileInTmpfs(handle, file);
+        if (!extract.ok()) {
+            return extract.error();
+        }
+    }
+    for (const auto& file : kApexSepolicyTmp) {
+        auto extract = PutFileInTmpfs(handle, file);
+        if (!extract.ok()) {
+            return extract.error();
+        }
+    }
+    return {};
+}
+
+Result<void> LoadSepolicyApexCerts() {
+    key_serial_t keyring_id = android::GetKeyringId(".fs-verity");
+    if (keyring_id < 0) {
+        return Error() << "Failed to find .fs-verity keyring id";
+    }
+
+    // TODO(b/199914227) the release key should always exist. Once it's checked in, start
+    // throwing an error here if it doesn't exist.
+    if (access(kSigningCertRelease, F_OK) == 0) {
+        LoadKeyFromFile(keyring_id, "fsv_sepolicy_apex_release", kSigningCertRelease);
+    }
+    return {};
+}
+
+Result<void> SepolicyFsVerityCheck() {
+    return Error() << "TODO implementent support for fsverity SEPolicy.";
+}
+
+Result<void> SepolicyCheckSignature(const std::string& dir) {
+    std::string signature;
+    if (!android::base::ReadFileToString(dir + kSepolicySignature, &signature)) {
+        return ErrnoError() << "Failed to read " << kSepolicySignature;
+    }
+
+    std::fstream sepolicyZip(dir + kSepolicyZip, std::ios::in | std::ios::binary);
+    if (!sepolicyZip) {
+        return Error() << "Failed to open " << kSepolicyZip;
+    }
+    sepolicyZip.seekg(0);
+    std::string sepolicyStr((std::istreambuf_iterator<char>(sepolicyZip)),
+                            std::istreambuf_iterator<char>());
+
+    auto releaseKey = extractPublicKeyFromX509(kSigningCertRelease);
+    if (!releaseKey.ok()) {
+        return releaseKey.error();
+    }
+
+    return verifySignature(sepolicyStr, signature, *releaseKey);
+}
+
+Result<void> SepolicyVerify(const std::string& dir, bool supportsFsVerity) {
+    if (supportsFsVerity) {
+        auto fsVerityCheck = SepolicyFsVerityCheck();
+        if (fsVerityCheck.ok()) {
+            return fsVerityCheck;
+        }
+        // TODO(b/199914227) If the device supports fsverity, but we fail here, we should fail to
+        // boot and not carry on. For now, fallback to a signature checkuntil the fsverity
+        // logic is implemented.
+        LOG(INFO) << "Falling back to standard signature check. " << fsVerityCheck.error();
+    }
+
+    auto sepolicySignature = SepolicyCheckSignature(dir);
+    if (!sepolicySignature.ok()) {
+        return Error() << "Apex SEPolicy failed signature check";
+    }
+    return {};
+}
+
+void CleanupApexSepolicy() {
+    for (const auto& file : kApexSepolicyTmp) {
+        std::string path = kTmpfsDir + file;
+        unlink(path.c_str());
+    }
+}
+
+// Updatable sepolicy is shipped within an zip within an APEX. Because
+// it needs to be available before Apexes are mounted, apexd copies
+// the zip from the APEX and stores it in /metadata/sepolicy. If there is
+// no updatable sepolicy in /metadata/sepolicy, then the updatable policy is
+// loaded from /system/etc/selinux/apex. Init performs the following
+// steps on boot:
+//
+// 1. Validates the zip by checking its signature against a public key that is
+// stored in /system/etc/selinux.
+// 2. Extracts files from zip and stores them in /dev/selinux.
+// 3. Checks if the apex_sepolicy.sha256 matches the sha256 of precompiled_sepolicy.
+// if so, the precompiled sepolicy is used. Otherwise, an on-device compile of the policy
+// is used. This is the same flow as on-device compilation of policy for Treble.
+// 4. Cleans up files in /dev/selinux which are no longer needed.
+// 5. Restorecons the remaining files in /dev/selinux.
+// 6. Sets selinux into enforcing mode and continues normal booting.
+//
+void PrepareApexSepolicy() {
+    bool supportsFsVerity = access(kFsVerityProcPath, F_OK) == 0;
+    if (supportsFsVerity) {
+        auto loadSepolicyApexCerts = LoadSepolicyApexCerts();
+        if (!loadSepolicyApexCerts.ok()) {
+            // TODO(b/199914227) If the device supports fsverity, but we fail here, we should fail
+            // to boot and not carry on. For now, fallback to a signature checkuntil the fsverity
+            // logic is implemented.
+            LOG(INFO) << loadSepolicyApexCerts.error();
+        }
+    }
+    // If apex sepolicy zip exists in /metadata/sepolicy, use that, otherwise use version on
+    // /system.
+    auto dir = (access((kSepolicyApexMetadataDir + kSepolicyZip).c_str(), F_OK) == 0)
+                       ? kSepolicyApexMetadataDir
+                       : kSepolicyApexSystemDir;
+
+    auto sepolicyVerify = SepolicyVerify(dir, supportsFsVerity);
+    if (!sepolicyVerify.ok()) {
+        LOG(INFO) << "Error: " << sepolicyVerify.error();
+        // If signature verification fails, fall back to version on /system.
+        // This file doesn't need to be verified because it lives on the system partition which
+        // is signed and protected by verified boot.
+        dir = kSepolicyApexSystemDir;
+    }
+
+    auto apex = GetPolicyFromApex(dir);
+    if (!apex.ok()) {
+        // TODO(b/199914227) Make failure fatal. For now continue booting with non-apex sepolicy.
+        LOG(ERROR) << apex.error();
+    }
+}
+
 void ReadPolicy(std::string* policy) {
     PolicyFile policy_file;
 
@@ -740,9 +950,12 @@
 
     LOG(INFO) << "Opening SELinux policy";
 
+    PrepareApexSepolicy();
+
     // Read the policy before potentially killing snapuserd.
     std::string policy;
     ReadPolicy(&policy);
+    CleanupApexSepolicy();
 
     auto snapuserd_helper = SnapuserdSelinuxHelper::CreateIfNeeded();
     if (snapuserd_helper) {
@@ -760,6 +973,13 @@
         snapuserd_helper = nullptr;
     }
 
+    // This restorecon is intentionally done before SelinuxSetEnforcement because the permissions
+    // needed to transition files from tmpfs to *_contexts_file context should not be granted to
+    // any process after selinux is set into enforcing mode.
+    if (selinux_android_restorecon("/dev/selinux/", SELINUX_ANDROID_RESTORECON_RECURSE) == -1) {
+        PLOG(FATAL) << "restorecon failed of /dev/selinux failed";
+    }
+
     SelinuxSetEnforcement();
 
     // We're in the kernel domain and want to transition to the init domain.  File systems that
diff --git a/init/service.cpp b/init/service.cpp
index f7318cb..f6dd9b9 100644
--- a/init/service.cpp
+++ b/init/service.cpp
@@ -397,6 +397,14 @@
     return {};
 }
 
+static void ClosePipe(const std::array<int, 2>* pipe) {
+    for (const auto fd : *pipe) {
+        if (fd >= 0) {
+            close(fd);
+        }
+    }
+}
+
 Result<void> Service::Start() {
     auto reboot_on_failure = make_scope_guard([this] {
         if (on_failure_reboot_target_) {
@@ -428,6 +436,12 @@
         return {};
     }
 
+    std::unique_ptr<std::array<int, 2>, decltype(&ClosePipe)> pipefd(new std::array<int, 2>{-1, -1},
+                                                                     ClosePipe);
+    if (pipe(pipefd->data()) < 0) {
+        return ErrnoError() << "pipe()";
+    }
+
     bool needs_console = (flags_ & SVC_CONSOLE);
     if (needs_console) {
         if (proc_attr_.console.empty()) {
@@ -532,6 +546,13 @@
             LOG(ERROR) << "failed to write pid to files: " << result.error();
         }
 
+        // Wait until the cgroups have been created and until the cgroup controllers have been
+        // activated.
+        if (std::byte byte; read((*pipefd)[0], &byte, 1) < 0) {
+            PLOG(ERROR) << "failed to read from notification channel";
+        }
+        pipefd.reset();
+
         if (task_profiles_.size() > 0 && !SetTaskProfiles(getpid(), task_profiles_)) {
             LOG(ERROR) << "failed to set task profiles";
         }
@@ -618,6 +639,10 @@
         LmkdRegister(name_, proc_attr_.uid, pid_, oom_score_adjust_);
     }
 
+    if (write((*pipefd)[1], "", 1) < 0) {
+        return ErrnoError() << "sending notification failed";
+    }
+
     NotifyStateChange("running");
     reboot_on_failure.Disable();
     return {};
diff --git a/libcutils/include/cutils/qtaguid.h b/libcutils/include/cutils/qtaguid.h
index 3f5e41f..a5ffb03 100644
--- a/libcutils/include/cutils/qtaguid.h
+++ b/libcutils/include/cutils/qtaguid.h
@@ -34,24 +34,6 @@
 extern int qtaguid_untagSocket(int sockfd);
 
 /*
- * For the given uid, switch counter sets.
- * The kernel only keeps a limited number of sets.
- * 2 for now.
- */
-extern int qtaguid_setCounterSet(int counterSetNum, uid_t uid);
-
-/*
- * Delete all tag info that relates to the given tag an uid.
- * If the tag is 0, then ALL info about the uid is freed.
- * The delete data also affects active tagged sockets, which are
- * then untagged.
- * The calling process can only operate on its own tags.
- * Unless it is part of the happy AID_NET_BW_ACCT group.
- * In which case it can clobber everything.
- */
-extern int qtaguid_deleteTagData(int tag, uid_t uid);
-
-/*
  * Enable/disable qtaguid functionnality at a lower level.
  * When pacified, the kernel will accept commands but do nothing.
  */
diff --git a/libcutils/include/cutils/trace.h b/libcutils/include/cutils/trace.h
index 97e93f8..17a0070 100644
--- a/libcutils/include/cutils/trace.h
+++ b/libcutils/include/cutils/trace.h
@@ -75,7 +75,8 @@
 #define ATRACE_TAG_AIDL             (1<<24)
 #define ATRACE_TAG_NNAPI            (1<<25)
 #define ATRACE_TAG_RRO              (1<<26)
-#define ATRACE_TAG_LAST             ATRACE_TAG_RRO
+#define ATRACE_TAG_THERMAL          (1 << 27)
+#define ATRACE_TAG_LAST             ATRACE_TAG_THERMAL
 
 // Reserved for initialization.
 #define ATRACE_TAG_NOT_READY        (1ULL<<63)
diff --git a/libcutils/include/private/android_filesystem_config.h b/libcutils/include/private/android_filesystem_config.h
index 23d1415..8e6b81c 100644
--- a/libcutils/include/private/android_filesystem_config.h
+++ b/libcutils/include/private/android_filesystem_config.h
@@ -132,6 +132,7 @@
 #define AID_UWB 1083              /* UWB subsystem */
 #define AID_THREAD_NETWORK 1084   /* Thread Network subsystem */
 #define AID_DICED 1085            /* Android's DICE daemon */
+#define AID_DMESGD 1086           /* dmesg parsing daemon for kernel report collection */
 /* Changes to this file must be made in AOSP, *not* in internal branches. */
 
 #define AID_SHELL 2000 /* adb and debug shell user */
diff --git a/libcutils/qtaguid.cpp b/libcutils/qtaguid.cpp
index 2fe877c..a987b85 100644
--- a/libcutils/qtaguid.cpp
+++ b/libcutils/qtaguid.cpp
@@ -34,8 +34,6 @@
   public:
     int (*netdTagSocket)(int, uint32_t, uid_t);
     int (*netdUntagSocket)(int);
-    int (*netdSetCounterSet)(uint32_t, uid_t);
-    int (*netdDeleteTagData)(uint32_t, uid_t);
 };
 
 int stubTagSocket(int, uint32_t, uid_t) {
@@ -46,16 +44,8 @@
     return -EREMOTEIO;
 }
 
-int stubSetCounterSet(uint32_t, uid_t) {
-    return -EREMOTEIO;
-}
-
-int stubDeleteTagData(uint32_t, uid_t) {
-    return -EREMOTEIO;
-}
-
 netdHandler initHandler(void) {
-    netdHandler handler = {stubTagSocket, stubUntagSocket, stubSetCounterSet, stubDeleteTagData};
+    netdHandler handler = {stubTagSocket, stubUntagSocket};
 
     void* netdClientHandle = dlopen("libnetd_client.so", RTLD_NOW);
     if (!netdClientHandle) {
@@ -73,15 +63,6 @@
         ALOGE("load netdUntagSocket handler failed: %s", dlerror());
     }
 
-    handler.netdSetCounterSet = (int (*)(uint32_t, uid_t))dlsym(netdClientHandle, "setCounterSet");
-    if (!handler.netdSetCounterSet) {
-        ALOGE("load netdSetCounterSet handler failed: %s", dlerror());
-    }
-
-    handler.netdDeleteTagData = (int (*)(uint32_t, uid_t))dlsym(netdClientHandle, "deleteTagData");
-    if (!handler.netdDeleteTagData) {
-        ALOGE("load netdDeleteTagData handler failed: %s", dlerror());
-    }
     return handler;
 }
 
@@ -114,13 +95,3 @@
     ALOGV("Untagging socket %d", sockfd);
     return getHandler().netdUntagSocket(sockfd);
 }
-
-int qtaguid_setCounterSet(int counterSetNum, uid_t uid) {
-    ALOGV("Setting counters to set %d for uid %d", counterSetNum, uid);
-    return getHandler().netdSetCounterSet(counterSetNum, uid);
-}
-
-int qtaguid_deleteTagData(int tag, uid_t uid) {
-    ALOGV("Deleting tag data with tag %u for uid %d", tag, uid);
-    return getHandler().netdDeleteTagData(tag, uid);
-}
diff --git a/libgrallocusage/Android.bp b/libgrallocusage/Android.bp
index f31b5f1..16103d2 100644
--- a/libgrallocusage/Android.bp
+++ b/libgrallocusage/Android.bp
@@ -44,4 +44,9 @@
     shared_libs: ["android.hardware.graphics.allocator@2.0"],
     header_libs: ["libhardware_headers"],
     min_sdk_version: "29",
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.media.swcodec",
+        "test_com.android.media.swcodec",
+    ],
 }
diff --git a/libprocessgroup/cgroup_map.cpp b/libprocessgroup/cgroup_map.cpp
index 352847a..8c00326 100644
--- a/libprocessgroup/cgroup_map.cpp
+++ b/libprocessgroup/cgroup_map.cpp
@@ -231,7 +231,8 @@
             const ACgroupController* controller = ACgroupFile_getController(i);
             if (ACgroupController_getFlags(controller) &
                 CGROUPRC_CONTROLLER_FLAG_NEEDS_ACTIVATION) {
-                std::string str = std::string("+") + ACgroupController_getName(controller);
+                std::string str("+");
+                str.append(ACgroupController_getName(controller));
                 if (!WriteStringToFile(str, path + "/cgroup.subtree_control")) {
                     return -errno;
                 }
diff --git a/libprocessgroup/processgroup.cpp b/libprocessgroup/processgroup.cpp
index cb2fe0a..76d5e13 100644
--- a/libprocessgroup/processgroup.cpp
+++ b/libprocessgroup/processgroup.cpp
@@ -85,7 +85,7 @@
 
 bool CgroupGetAttributePath(const std::string& attr_name, std::string* path) {
     const TaskProfiles& tp = TaskProfiles::GetInstance();
-    const ProfileAttribute* attr = tp.GetAttribute(attr_name);
+    const IProfileAttribute* attr = tp.GetAttribute(attr_name);
 
     if (attr == nullptr) {
         return false;
@@ -100,7 +100,7 @@
 
 bool CgroupGetAttributePathForTask(const std::string& attr_name, int tid, std::string* path) {
     const TaskProfiles& tp = TaskProfiles::GetInstance();
-    const ProfileAttribute* attr = tp.GetAttribute(attr_name);
+    const IProfileAttribute* attr = tp.GetAttribute(attr_name);
 
     if (attr == nullptr) {
         return false;
@@ -211,7 +211,7 @@
     for (std::string cgroup_root_path : cgroups) {
         std::unique_ptr<DIR, decltype(&closedir)> root(opendir(cgroup_root_path.c_str()), closedir);
         if (root == NULL) {
-            PLOG(ERROR) << "Failed to open " << cgroup_root_path;
+            PLOG(ERROR) << __func__ << " failed to open " << cgroup_root_path;
         } else {
             dirent* dir;
             while ((dir = readdir(root.get())) != nullptr) {
@@ -297,7 +297,8 @@
             // This happens when process is already dead
             return 0;
         }
-        PLOG(WARNING) << "Failed to open process cgroup uid " << uid << " pid " << initialPid;
+        PLOG(WARNING) << __func__ << " failed to open process cgroup uid " << uid << " pid "
+                      << initialPid;
         return -1;
     }
 
diff --git a/libprocessgroup/task_profiles.cpp b/libprocessgroup/task_profiles.cpp
index 74ba7f6..73aa2af 100644
--- a/libprocessgroup/task_profiles.cpp
+++ b/libprocessgroup/task_profiles.cpp
@@ -112,6 +112,8 @@
     return path.find("<uid>", 0) != std::string::npos || path.find("<pid>", 0) != std::string::npos;
 }
 
+IProfileAttribute::~IProfileAttribute() = default;
+
 void ProfileAttribute::Reset(const CgroupController& controller, const std::string& file_name) {
     controller_ = controller;
     file_name_ = file_name;
@@ -183,6 +185,12 @@
     return true;
 }
 
+#else
+
+bool SetTimerSlackAction::ExecuteForTask(int) const {
+    return true;
+};
+
 #endif
 
 bool SetAttributeAction::ExecuteForProcess(uid_t, pid_t pid) const {
@@ -469,6 +477,7 @@
 bool TaskProfile::ExecuteForProcess(uid_t uid, pid_t pid) const {
     for (const auto& element : elements_) {
         if (!element->ExecuteForProcess(uid, pid)) {
+            LOG(VERBOSE) << "Applying profile action " << element->Name() << " failed";
             return false;
         }
     }
@@ -481,6 +490,7 @@
     }
     for (const auto& element : elements_) {
         if (!element->ExecuteForTask(tid)) {
+            LOG(VERBOSE) << "Applying profile action " << element->Name() << " failed";
             return false;
         }
     }
@@ -592,7 +602,7 @@
 
         std::string profile_name = profile_val["Name"].asString();
         const Json::Value& actions = profile_val["Actions"];
-        auto profile = std::make_shared<TaskProfile>();
+        auto profile = std::make_shared<TaskProfile>(profile_name);
 
         for (Json::Value::ArrayIndex act_idx = 0; act_idx < actions.size(); ++act_idx) {
             const Json::Value& action_val = actions[act_idx];
@@ -702,7 +712,7 @@
             }
         }
         if (ret) {
-            auto profile = std::make_shared<TaskProfile>();
+            auto profile = std::make_shared<TaskProfile>(aggregateprofile_name);
             profile->Add(std::make_unique<ApplyProfileAction>(profiles));
             profiles_[aggregateprofile_name] = profile;
         }
@@ -720,7 +730,7 @@
     return nullptr;
 }
 
-const ProfileAttribute* TaskProfiles::GetAttribute(const std::string& name) const {
+const IProfileAttribute* TaskProfiles::GetAttribute(const std::string& name) const {
     auto iter = attributes_.find(name);
 
     if (iter != attributes_.end()) {
diff --git a/libprocessgroup/task_profiles.h b/libprocessgroup/task_profiles.h
index 1aaa196..e6e65fb 100644
--- a/libprocessgroup/task_profiles.h
+++ b/libprocessgroup/task_profiles.h
@@ -26,16 +26,26 @@
 #include <android-base/unique_fd.h>
 #include <cgroup_map.h>
 
-class ProfileAttribute {
+class IProfileAttribute {
+  public:
+    virtual ~IProfileAttribute() = 0;
+    virtual void Reset(const CgroupController& controller, const std::string& file_name) = 0;
+    virtual const CgroupController* controller() const = 0;
+    virtual const std::string& file_name() const = 0;
+    virtual bool GetPathForTask(int tid, std::string* path) const = 0;
+};
+
+class ProfileAttribute : public IProfileAttribute {
   public:
     ProfileAttribute(const CgroupController& controller, const std::string& file_name)
         : controller_(controller), file_name_(file_name) {}
+    ~ProfileAttribute() = default;
 
-    const CgroupController* controller() const { return &controller_; }
-    const std::string& file_name() const { return file_name_; }
-    void Reset(const CgroupController& controller, const std::string& file_name);
+    const CgroupController* controller() const override { return &controller_; }
+    const std::string& file_name() const override { return file_name_; }
+    void Reset(const CgroupController& controller, const std::string& file_name) override;
 
-    bool GetPathForTask(int tid, std::string* path) const;
+    bool GetPathForTask(int tid, std::string* path) const override;
 
   private:
     CgroupController controller_;
@@ -49,6 +59,8 @@
 
     virtual ~ProfileAction() {}
 
+    virtual const char* Name() const = 0;
+
     // Default implementations will fail
     virtual bool ExecuteForProcess(uid_t, pid_t) const { return false; };
     virtual bool ExecuteForTask(int) const { return false; };
@@ -65,22 +77,21 @@
   public:
     SetClampsAction(int boost, int clamp) noexcept : boost_(boost), clamp_(clamp) {}
 
-    virtual bool ExecuteForProcess(uid_t uid, pid_t pid) const;
-    virtual bool ExecuteForTask(int tid) const;
+    const char* Name() const override { return "SetClamps"; }
+    bool ExecuteForProcess(uid_t uid, pid_t pid) const override;
+    bool ExecuteForTask(int tid) const override;
 
   protected:
     int boost_;
     int clamp_;
 };
 
-// To avoid issues in sdk_mac build
-#if defined(__ANDROID__)
-
 class SetTimerSlackAction : public ProfileAction {
   public:
     SetTimerSlackAction(unsigned long slack) noexcept : slack_(slack) {}
 
-    virtual bool ExecuteForTask(int tid) const;
+    const char* Name() const override { return "SetTimerSlack"; }
+    bool ExecuteForTask(int tid) const override;
 
   private:
     unsigned long slack_;
@@ -88,28 +99,18 @@
     static bool IsTimerSlackSupported(int tid);
 };
 
-#else
-
-class SetTimerSlackAction : public ProfileAction {
-  public:
-    SetTimerSlackAction(unsigned long) noexcept {}
-
-    virtual bool ExecuteForTask(int) const { return true; }
-};
-
-#endif
-
 // Set attribute profile element
 class SetAttributeAction : public ProfileAction {
   public:
-    SetAttributeAction(const ProfileAttribute* attribute, const std::string& value)
+    SetAttributeAction(const IProfileAttribute* attribute, const std::string& value)
         : attribute_(attribute), value_(value) {}
 
-    virtual bool ExecuteForProcess(uid_t uid, pid_t pid) const;
-    virtual bool ExecuteForTask(int tid) const;
+    const char* Name() const override { return "SetAttribute"; }
+    bool ExecuteForProcess(uid_t uid, pid_t pid) const override;
+    bool ExecuteForTask(int tid) const override;
 
   private:
-    const ProfileAttribute* attribute_;
+    const IProfileAttribute* attribute_;
     std::string value_;
 };
 
@@ -118,10 +119,11 @@
   public:
     SetCgroupAction(const CgroupController& c, const std::string& p);
 
-    virtual bool ExecuteForProcess(uid_t uid, pid_t pid) const;
-    virtual bool ExecuteForTask(int tid) const;
-    virtual void EnableResourceCaching(ResourceCacheType cache_type);
-    virtual void DropResourceCaching(ResourceCacheType cache_type);
+    const char* Name() const override { return "SetCgroup"; }
+    bool ExecuteForProcess(uid_t uid, pid_t pid) const override;
+    bool ExecuteForTask(int tid) const override;
+    void EnableResourceCaching(ResourceCacheType cache_type) override;
+    void DropResourceCaching(ResourceCacheType cache_type) override;
 
     const CgroupController* controller() const { return &controller_; }
 
@@ -140,10 +142,11 @@
   public:
     WriteFileAction(const std::string& path, const std::string& value, bool logfailures);
 
-    virtual bool ExecuteForProcess(uid_t uid, pid_t pid) const;
-    virtual bool ExecuteForTask(int tid) const;
-    virtual void EnableResourceCaching(ResourceCacheType cache_type);
-    virtual void DropResourceCaching(ResourceCacheType cache_type);
+    const char* Name() const override { return "WriteFile"; }
+    bool ExecuteForProcess(uid_t uid, pid_t pid) const override;
+    bool ExecuteForTask(int tid) const override;
+    void EnableResourceCaching(ResourceCacheType cache_type) override;
+    void DropResourceCaching(ResourceCacheType cache_type) override;
 
   private:
     std::string path_, value_;
@@ -158,8 +161,9 @@
 
 class TaskProfile {
   public:
-    TaskProfile() : res_cached_(false) {}
+    TaskProfile(const std::string& name) : name_(name), res_cached_(false) {}
 
+    const std::string& Name() const { return name_; }
     void Add(std::unique_ptr<ProfileAction> e) { elements_.push_back(std::move(e)); }
     void MoveTo(TaskProfile* profile);
 
@@ -169,6 +173,7 @@
     void DropResourceCaching(ProfileAction::ResourceCacheType cache_type);
 
   private:
+    const std::string name_;
     bool res_cached_;
     std::vector<std::unique_ptr<ProfileAction>> elements_;
 };
@@ -179,10 +184,11 @@
     ApplyProfileAction(const std::vector<std::shared_ptr<TaskProfile>>& profiles)
         : profiles_(profiles) {}
 
-    virtual bool ExecuteForProcess(uid_t uid, pid_t pid) const;
-    virtual bool ExecuteForTask(int tid) const;
-    virtual void EnableResourceCaching(ProfileAction::ResourceCacheType cache_type);
-    virtual void DropResourceCaching(ProfileAction::ResourceCacheType cache_type);
+    const char* Name() const override { return "ApplyProfileAction"; }
+    bool ExecuteForProcess(uid_t uid, pid_t pid) const override;
+    bool ExecuteForTask(int tid) const override;
+    void EnableResourceCaching(ProfileAction::ResourceCacheType cache_type) override;
+    void DropResourceCaching(ProfileAction::ResourceCacheType cache_type) override;
 
   private:
     std::vector<std::shared_ptr<TaskProfile>> profiles_;
@@ -194,7 +200,7 @@
     static TaskProfiles& GetInstance();
 
     TaskProfile* GetProfile(const std::string& name) const;
-    const ProfileAttribute* GetAttribute(const std::string& name) const;
+    const IProfileAttribute* GetAttribute(const std::string& name) const;
     void DropResourceCaching(ProfileAction::ResourceCacheType cache_type) const;
     bool SetProcessProfiles(uid_t uid, pid_t pid, const std::vector<std::string>& profiles,
                             bool use_fd_cache);
@@ -202,7 +208,7 @@
 
   private:
     std::map<std::string, std::shared_ptr<TaskProfile>> profiles_;
-    std::map<std::string, std::unique_ptr<ProfileAttribute>> attributes_;
+    std::map<std::string, std::unique_ptr<IProfileAttribute>> attributes_;
 
     TaskProfiles();
 
diff --git a/libutils/Android.bp b/libutils/Android.bp
index 234638b..53f6065 100644
--- a/libutils/Android.bp
+++ b/libutils/Android.bp
@@ -100,7 +100,6 @@
             cflags: ["-fvisibility=protected"],
 
             shared_libs: [
-                "libprocessgroup",
                 "libvndksupport",
             ],
 
diff --git a/libutils/Threads.cpp b/libutils/Threads.cpp
index 3bf5779..4dacdc6 100644
--- a/libutils/Threads.cpp
+++ b/libutils/Threads.cpp
@@ -84,14 +84,6 @@
         delete t;
         setpriority(PRIO_PROCESS, 0, prio);
 
-        // A new thread will be in its parent's sched group by default,
-        // so we just need to handle the background case.
-        // currently set to system_background group which is different
-        // from background group for app.
-        if (prio >= ANDROID_PRIORITY_BACKGROUND) {
-            SetTaskProfiles(0, {"SCHED_SP_SYSTEM"}, true);
-        }
-
         if (name) {
             androidSetThreadName(name);
             free(name);
@@ -307,27 +299,16 @@
 int androidSetThreadPriority(pid_t tid, int pri)
 {
     int rc = 0;
-    int lasterr = 0;
     int curr_pri = getpriority(PRIO_PROCESS, tid);
 
     if (curr_pri == pri) {
         return rc;
     }
 
-    if (pri >= ANDROID_PRIORITY_BACKGROUND) {
-        rc = SetTaskProfiles(tid, {"SCHED_SP_SYSTEM"}, true) ? 0 : -1;
-    } else if (curr_pri >= ANDROID_PRIORITY_BACKGROUND) {
-        rc = SetTaskProfiles(tid, {"SCHED_SP_FOREGROUND"}, true) ? 0 : -1;
-    }
-
-    if (rc) {
-        lasterr = errno;
-    }
-
     if (setpriority(PRIO_PROCESS, tid, pri) < 0) {
         rc = INVALID_OPERATION;
     } else {
-        errno = lasterr;
+        errno = 0;
     }
 
     return rc;
diff --git a/rootdir/Android.mk b/rootdir/Android.mk
index d592366..11f414f 100644
--- a/rootdir/Android.mk
+++ b/rootdir/Android.mk
@@ -1,5 +1,7 @@
 LOCAL_PATH:= $(call my-dir)
 
+$(eval $(call declare-1p-copy-files,system/core/rootdir,))
+
 #######################################
 # init-debug.rc
 include $(CLEAR_VARS)
@@ -197,15 +199,9 @@
 LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)
 LOCAL_MODULE_STEM := $(LOCAL_MODULE)
 include $(BUILD_SYSTEM)/base_rules.mk
-$(LOCAL_BUILT_MODULE): PRIVATE_SANITIZER_RUNTIME_LIBRARIES := $(addsuffix .so,\
-  $(ADDRESS_SANITIZER_RUNTIME_LIBRARY) \
-  $(HWADDRESS_SANITIZER_RUNTIME_LIBRARY) \
-  $(UBSAN_RUNTIME_LIBRARY) \
-  $(TSAN_RUNTIME_LIBRARY) \
-  $(2ND_ADDRESS_SANITIZER_RUNTIME_LIBRARY) \
-  $(2ND_HWADDRESS_SANITIZER_RUNTIME_LIBRARY) \
-  $(2ND_UBSAN_RUNTIME_LIBRARY) \
-  $(2ND_TSAN_RUNTIME_LIBRARY))
+$(LOCAL_BUILT_MODULE): PRIVATE_SANITIZER_RUNTIME_LIBRARIES := \
+    $(SANITIZER_STEMS) \
+    $(2ND_SANITIZER_STEMS)
 $(LOCAL_BUILT_MODULE):
 	@echo "Generate: $@"
 	@mkdir -p $(dir $@)
diff --git a/rootdir/init.no_zygote.rc b/rootdir/init.no_zygote.rc
new file mode 100644
index 0000000..8e0afcb
--- /dev/null
+++ b/rootdir/init.no_zygote.rc
@@ -0,0 +1,2 @@
+# This is an empty file that does not specify any zygote services.
+# It exists to support targets that do not include a zygote.
diff --git a/trusty/keymaster/include/trusty_keymaster/TrustyKeyMintDevice.h b/trusty/keymaster/include/trusty_keymaster/TrustyKeyMintDevice.h
index 5fd628f..c8d8932 100644
--- a/trusty/keymaster/include/trusty_keymaster/TrustyKeyMintDevice.h
+++ b/trusty/keymaster/include/trusty_keymaster/TrustyKeyMintDevice.h
@@ -27,6 +27,7 @@
 using ::keymaster::TrustyKeymaster;
 using ::ndk::ScopedAStatus;
 using secureclock::TimeStampToken;
+using ::std::array;
 using ::std::optional;
 using ::std::shared_ptr;
 using ::std::vector;
@@ -77,8 +78,13 @@
                                const optional<TimeStampToken>& timestampToken) override;
     ScopedAStatus earlyBootEnded() override;
 
-    ScopedAStatus convertStorageKeyToEphemeral(const std::vector<uint8_t>& storageKeyBlob,
-                                               std::vector<uint8_t>* ephemeralKeyBlob) override;
+    ScopedAStatus convertStorageKeyToEphemeral(const vector<uint8_t>& storageKeyBlob,
+                                               vector<uint8_t>* ephemeralKeyBlob) override;
+
+    ScopedAStatus getRootOfTrustChallenge(array<uint8_t, 16>* challenge) override;
+    ScopedAStatus getRootOfTrust(const array<uint8_t, 16>& challenge,
+                                 vector<uint8_t>* rootOfTrust) override;
+    ScopedAStatus sendRootOfTrust(const vector<uint8_t>& rootOfTrust) override;
 
   protected:
     std::shared_ptr<TrustyKeymaster> impl_;
diff --git a/trusty/keymaster/keymint/TrustyKeyMintDevice.cpp b/trusty/keymaster/keymint/TrustyKeyMintDevice.cpp
index 68a7912..44780e8 100644
--- a/trusty/keymaster/keymint/TrustyKeyMintDevice.cpp
+++ b/trusty/keymaster/keymint/TrustyKeyMintDevice.cpp
@@ -306,7 +306,7 @@
 }
 
 ScopedAStatus TrustyKeyMintDevice::convertStorageKeyToEphemeral(
-        const std::vector<uint8_t>& storageKeyBlob, std::vector<uint8_t>* ephemeralKeyBlob) {
+        const vector<uint8_t>& storageKeyBlob, vector<uint8_t>* ephemeralKeyBlob) {
     keymaster::ExportKeyRequest request(impl_->message_version());
     request.SetKeyMaterial(storageKeyBlob.data(), storageKeyBlob.size());
     request.key_format = KM_KEY_FORMAT_RAW;
@@ -321,4 +321,17 @@
     return ScopedAStatus::ok();
 }
 
+ScopedAStatus TrustyKeyMintDevice::getRootOfTrustChallenge(array<uint8_t, 16>* /* challenge */) {
+    return kmError2ScopedAStatus(KM_ERROR_UNIMPLEMENTED);
+}
+
+ScopedAStatus TrustyKeyMintDevice::getRootOfTrust(const array<uint8_t, 16>& /* challenge */,
+                                                  vector<uint8_t>* /* rootOfTrust */) {
+    return kmError2ScopedAStatus(KM_ERROR_UNIMPLEMENTED);
+}
+
+ScopedAStatus TrustyKeyMintDevice::sendRootOfTrust(const vector<uint8_t>& /* rootOfTrust */) {
+    return kmError2ScopedAStatus(KM_ERROR_UNIMPLEMENTED);
+}
+
 }  // namespace aidl::android::hardware::security::keymint::trusty
diff --git a/trusty/test/driver/Android.bp b/trusty/test/driver/Android.bp
new file mode 100644
index 0000000..b813a04
--- /dev/null
+++ b/trusty/test/driver/Android.bp
@@ -0,0 +1,32 @@
+// Copyright (C) 2022 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.
+
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+python_test {
+    name: "trusty_driver_test",
+    srcs: [
+        "**/*.py",
+    ],
+    test_suites: ["general-tests"],
+    version: {
+        py3: {
+            embedded_launcher: true,
+            enabled: true,
+        },
+    },
+}
diff --git a/trusty/test/driver/trusty_driver_test.py b/trusty/test/driver/trusty_driver_test.py
new file mode 100644
index 0000000..608fd47
--- /dev/null
+++ b/trusty/test/driver/trusty_driver_test.py
@@ -0,0 +1,81 @@
+#!/usr/bin/python
+#
+# Copyright 2022 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.
+
+"""Test cases for Trusty Linux Driver."""
+
+import os
+import unittest
+
+def ReadFile(file_path):
+    with open(file_path, 'r') as f:
+        # Strip all trailing spaces, newline and null characters.
+        return f.read().rstrip(' \n\x00')
+
+def WriteFile(file_path, s):
+    with open(file_path, 'w') as f:
+        f.write(s)
+
+def IsTrustySupported():
+    return os.path.exists("/dev/trusty-ipc-dev0")
+
+@unittest.skipIf(not IsTrustySupported(), "Device does not support Trusty")
+class TrustyDriverTest(unittest.TestCase):
+    def testIrqDriverBinding(self):
+        WriteFile("/sys/bus/platform/drivers/trusty-irq/unbind", "trusty:irq")
+        WriteFile("/sys/bus/platform/drivers/trusty-irq/bind", "trusty:irq")
+
+    def testLogDriverBinding(self):
+        WriteFile("/sys/bus/platform/drivers/trusty-log/unbind", "trusty:log")
+        WriteFile("/sys/bus/platform/drivers/trusty-log/bind", "trusty:log")
+
+    @unittest.skip("TODO(b/142275662): virtio remove currently hangs")
+    def testVirtioDriverBinding(self):
+        WriteFile("/sys/bus/platform/drivers/trusty-virtio/unbind",
+                  "trusty:virtio")
+        WriteFile("/sys/bus/platform/drivers/trusty-virtio/bind",
+                  "trusty:virtio")
+
+    @unittest.skip("TODO(b/142275662): virtio remove currently hangs")
+    def testTrustyDriverBinding(self):
+        WriteFile("/sys/bus/platform/drivers/trusty/unbind", "trusty")
+        WriteFile("/sys/bus/platform/drivers/trusty/bind", "trusty")
+
+    def testTrustyDriverVersion(self):
+        ver = ReadFile("/sys/bus/platform/devices/trusty/trusty_version")
+        self.assertTrue(ver.startswith("Project:"))
+
+    def testUntaintedLinux(self):
+        tainted = ReadFile("/proc/sys/kernel/tainted")
+        self.assertEqual(tainted, "0")
+
+    # stdcall test with shared memory buffers.
+    # Each test run takes up to 4 arguments:
+    # <obj_size>,<obj_count=1>,<repeat_share=1>,<repeat_access=3>
+    #
+    # Test single 4K shared memory object.
+    # Test 10 8MB objects, shared twice, each accessed twice. (8MB non-
+    # contiguous object is large enough to need several 4KB messages to
+    # describe)
+    # Test sharing 2 8MB objects 100 times without accessing it.
+    # Test 10 4K shared memory objects, shared 10 times, each accessed
+    # 10 times.
+    def testStdCall(self):
+        test = "/sys/devices/platform/trusty/trusty:test/trusty_test_run"
+        args = "0x1000 0x800000,10,2,2 0x800000,2,100,0 0x1000,10,10,10"
+        WriteFile(test, args)
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/trusty/trusty-test.mk b/trusty/trusty-test.mk
index 74106ec..3a43774 100644
--- a/trusty/trusty-test.mk
+++ b/trusty/trusty-test.mk
@@ -13,7 +13,7 @@
 # limitations under the License.
 
 PRODUCT_PACKAGES += \
-	spiproxyd \
-	trusty_keymaster_set_attestation_key \
 	keymaster_soft_attestation_keys.xml \
-
+	spiproxyd \
+	trusty_driver_test \
+	trusty_keymaster_set_attestation_key \