Merge "libfiemap: Disable loop mapping code." into main
diff --git a/debuggerd/Android.bp b/debuggerd/Android.bp
index 31e284d..7d20995 100644
--- a/debuggerd/Android.bp
+++ b/debuggerd/Android.bp
@@ -32,9 +32,10 @@
     recovery_available: true,
     vendor_ramdisk_available: true,
     apex_available: [
+        "com.android.runtime",
         "com.android.virt",
         "//apex_available:platform",
-   ],
+    ],
 }
 
 cc_library_shared {
@@ -85,6 +86,7 @@
 
     export_header_lib_headers: ["libdebuggerd_common_headers"],
     export_include_dirs: ["tombstoned/include"],
+    apex_available: ["com.android.runtime"],
 }
 
 // Core implementation, linked into libdebuggerd_handler and the dynamic linker.
@@ -110,6 +112,9 @@
 
     export_header_lib_headers: ["libdebuggerd_common_headers"],
     export_include_dirs: ["include"],
+    apex_available: [
+        "com.android.runtime",
+    ],
 }
 
 // Implementation with a no-op fallback.
@@ -311,6 +316,9 @@
             header_libs: ["scudo_headers"],
         },
     },
+    apex_available: [
+        "com.android.runtime",
+    ],
 }
 
 cc_binary {
diff --git a/debuggerd/debuggerd.cpp b/debuggerd/debuggerd.cpp
index 26726cf..0d4b91f 100644
--- a/debuggerd/debuggerd.cpp
+++ b/debuggerd/debuggerd.cpp
@@ -91,6 +91,15 @@
     }
   }
 
+  // unfreeze if pid is frozen.
+  const std::string freeze_file = android::base::StringPrintf(
+      "/sys/fs/cgroup/uid_%d/pid_%d/cgroup.freeze", proc_info.uid, proc_info.pid);
+  if (std::string freeze_status;
+      android::base::ReadFileToString(freeze_file, &freeze_status) && freeze_status[0] == '1') {
+    android::base::WriteStringToFile("0", freeze_file);
+    // we don't restore the frozen state as this is considered a benign change.
+  }
+
   unique_fd output_fd(fcntl(STDOUT_FILENO, F_DUPFD_CLOEXEC, 0));
   if (output_fd.get() == -1) {
     err(1, "failed to fcntl dup stdout");
diff --git a/debuggerd/debuggerd_test.cpp b/debuggerd/debuggerd_test.cpp
index a6d8226..c0522aa 100644
--- a/debuggerd/debuggerd_test.cpp
+++ b/debuggerd/debuggerd_test.cpp
@@ -2221,28 +2221,10 @@
   ASSERT_MATCH(result, match_str);
 }
 
-TEST(tombstoned, proto) {
-  const pid_t self = getpid();
-  unique_fd tombstoned_socket, text_fd, proto_fd;
-  ASSERT_TRUE(
-      tombstoned_connect(self, &tombstoned_socket, &text_fd, &proto_fd, kDebuggerdTombstoneProto));
-
-  tombstoned_notify_completion(tombstoned_socket.get());
-
-  ASSERT_NE(-1, text_fd.get());
-  ASSERT_NE(-1, proto_fd.get());
-
-  struct stat text_st;
-  ASSERT_EQ(0, fstat(text_fd.get(), &text_st));
-
-  // Give tombstoned some time to link the files into place.
-  std::this_thread::sleep_for(100ms);
-
-  // Find the tombstone.
-  std::optional<std::string> tombstone_file;
+void CheckForTombstone(const struct stat& text_st, std::optional<std::string>& tombstone_file) {
+  static std::regex tombstone_re("tombstone_\\d+");
   std::unique_ptr<DIR, decltype(&closedir)> dir_h(opendir("/data/tombstones"), closedir);
   ASSERT_TRUE(dir_h != nullptr);
-  std::regex tombstone_re("tombstone_\\d+");
   dirent* entry;
   while ((entry = readdir(dir_h.get())) != nullptr) {
     if (!std::regex_match(entry->d_name, tombstone_re)) {
@@ -2260,8 +2242,38 @@
       break;
     }
   }
+}
 
-  ASSERT_TRUE(tombstone_file);
+TEST(tombstoned, proto) {
+  const pid_t self = getpid();
+  unique_fd tombstoned_socket, text_fd, proto_fd;
+  ASSERT_TRUE(
+      tombstoned_connect(self, &tombstoned_socket, &text_fd, &proto_fd, kDebuggerdTombstoneProto));
+
+  tombstoned_notify_completion(tombstoned_socket.get());
+
+  ASSERT_NE(-1, text_fd.get());
+  ASSERT_NE(-1, proto_fd.get());
+
+  struct stat text_st;
+  ASSERT_EQ(0, fstat(text_fd.get(), &text_st));
+
+  std::optional<std::string> tombstone_file;
+  // Allow up to 5 seconds for the tombstone to be written to the system.
+  const auto max_wait_time = std::chrono::seconds(5) * android::base::HwTimeoutMultiplier();
+  const auto start = std::chrono::high_resolution_clock::now();
+  while (true) {
+    std::this_thread::sleep_for(100ms);
+    CheckForTombstone(text_st, tombstone_file);
+    if (tombstone_file) {
+      break;
+    }
+    if (std::chrono::high_resolution_clock::now() - start > max_wait_time) {
+      break;
+    }
+  }
+
+  ASSERT_TRUE(tombstone_file) << "Timed out trying to find tombstone file.";
   std::string proto_path = tombstone_file.value() + ".pb";
 
   struct stat proto_fd_st;
diff --git a/fastboot/fastboot.cpp b/fastboot/fastboot.cpp
index 235d723..12a1ddc 100644
--- a/fastboot/fastboot.cpp
+++ b/fastboot/fastboot.cpp
@@ -571,7 +571,8 @@
             "                            Format a flash partition.\n"
             " set_active SLOT            Set the active slot.\n"
             " oem [COMMAND...]           Execute OEM-specific command.\n"
-            " gsi wipe|disable           Wipe or disable a GSI installation (fastbootd only).\n"
+            " gsi wipe|disable|status    Wipe, disable or show status of a GSI installation\n"
+            "                            (fastbootd only).\n"
             " wipe-super [SUPER_EMPTY]   Wipe the super partition. This will reset it to\n"
             "                            contain an empty set of default dynamic partitions.\n"
             " create-logical-partition NAME SIZE\n"
diff --git a/fs_mgr/TEST_MAPPING b/fs_mgr/TEST_MAPPING
index edecd7c..1989a5c 100644
--- a/fs_mgr/TEST_MAPPING
+++ b/fs_mgr/TEST_MAPPING
@@ -42,9 +42,6 @@
       "name": "liblp_test"
     },
     {
-      "name": "vts_libsnapshot_test"
-    },
-    {
       "name": "vab_legacy_tests"
     },
     // TODO(b/279009697):
diff --git a/fs_mgr/fs_mgr.cpp b/fs_mgr/fs_mgr.cpp
index af2b35a..8c0c1ef 100644
--- a/fs_mgr/fs_mgr.cpp
+++ b/fs_mgr/fs_mgr.cpp
@@ -16,7 +16,6 @@
 
 #include "fs_mgr.h"
 
-#include <ctype.h>
 #include <dirent.h>
 #include <errno.h>
 #include <fcntl.h>
@@ -819,9 +818,13 @@
 // __mount(): wrapper around the mount() system call which also
 // sets the underlying block device to read-only if the mount is read-only.
 // See "man 2 mount" for return values.
-static int __mount(const std::string& source, const std::string& target, const FstabEntry& entry) {
+static int __mount(const std::string& source, const std::string& target, const FstabEntry& entry,
+                   bool read_only = false) {
     errno = 0;
     unsigned long mountflags = entry.flags;
+    if (read_only) {
+        mountflags |= MS_RDONLY;
+    }
     int ret = 0;
     int save_errno = 0;
     int gc_allowance = 0;
@@ -915,6 +918,10 @@
     return true;
 }
 
+static bool should_use_metadata_encryption(const FstabEntry& entry) {
+    return !entry.metadata_key_dir.empty() && entry.fs_mgr_flags.file_encryption;
+}
+
 // Tries to mount any of the consecutive fstab entries that match
 // the mountpoint of the one given by fstab[start_idx].
 //
@@ -922,8 +929,7 @@
 // attempted_idx: On return, will indicate which fstab entry
 //     succeeded. In case of failure, it will be the start_idx.
 // Sets errno to match the 1st mount failure on failure.
-static bool mount_with_alternatives(Fstab& fstab, int start_idx, int* end_idx,
-                                    int* attempted_idx) {
+static bool mount_with_alternatives(Fstab& fstab, int start_idx, int* end_idx, int* attempted_idx) {
     unsigned long i;
     int mount_errno = 0;
     bool mounted = false;
@@ -961,8 +967,15 @@
         }
 
         int retry_count = 2;
+        const auto read_only = should_use_metadata_encryption(fstab[i]);
+        if (read_only) {
+            LOG(INFO) << "Mount point " << fstab[i].blk_device << " @ " << fstab[i].mount_point
+                      << " uses metadata encryption, which means we need to unmount it later and "
+                         "call encryptFstab/encrypt_inplace. To avoid file operations before "
+                         "encryption, we will mount it as read-only first";
+        }
         while (retry_count-- > 0) {
-            if (!__mount(fstab[i].blk_device, fstab[i].mount_point, fstab[i])) {
+            if (!__mount(fstab[i].blk_device, fstab[i].mount_point, fstab[i], read_only)) {
                 *attempted_idx = i;
                 mounted = true;
                 if (i != start_idx) {
@@ -1054,10 +1067,6 @@
     return false;
 }
 
-static bool should_use_metadata_encryption(const FstabEntry& entry) {
-    return !entry.metadata_key_dir.empty() && entry.fs_mgr_flags.file_encryption;
-}
-
 // Check to see if a mountable volume has encryption requirements
 static int handle_encryptable(const FstabEntry& entry) {
     if (should_use_metadata_encryption(entry)) {
@@ -1541,6 +1550,7 @@
                 }
                 encryptable = status;
                 if (status == FS_MGR_MNTALL_DEV_NEEDS_METADATA_ENCRYPTION) {
+                    fs_mgr_set_blk_ro(attempted_entry.blk_device, false);
                     if (!call_vdc({"cryptfs", "encryptFstab", attempted_entry.blk_device,
                                    attempted_entry.mount_point, wiped ? "true" : "false",
                                    attempted_entry.fs_type, attempted_entry.zoned_device},
@@ -1978,6 +1988,8 @@
                 if (retry_count <= 0) break;  // run check_fs only once
                 if (!first_mount_errno) first_mount_errno = errno;
                 mount_errors++;
+                PERROR << "Cannot mount filesystem on " << n_blk_device << " at " << mount_point
+                       << " with fstype " << fstab_entry.fs_type;
                 fs_stat |= FS_STAT_FULL_MOUNT_FAILED;
                 // try again after fsck
                 check_fs(n_blk_device, fstab_entry.fs_type, mount_point, &fs_stat);
diff --git a/fs_mgr/fs_mgr_overlayfs_mount.cpp b/fs_mgr/fs_mgr_overlayfs_mount.cpp
index e168436..a1ec63b 100644
--- a/fs_mgr/fs_mgr_overlayfs_mount.cpp
+++ b/fs_mgr/fs_mgr_overlayfs_mount.cpp
@@ -98,7 +98,12 @@
     if (mount_point.empty() || !android::base::Realpath(mount_point, &normalized_path)) {
         return "";
     }
-    return android::base::StringReplace(normalized_path, "/", "@", true);
+    std::string_view sv(normalized_path);
+    if (sv != "/") {
+        android::base::ConsumePrefix(&sv, "/");
+        android::base::ConsumeSuffix(&sv, "/");
+    }
+    return android::base::StringReplace(sv, "/", "@", true);
 }
 
 static bool fs_mgr_is_dir(const std::string& path) {
diff --git a/fs_mgr/fs_mgr_remount.cpp b/fs_mgr/fs_mgr_remount.cpp
index 7ba4d2b..733ba2f 100644
--- a/fs_mgr/fs_mgr_remount.cpp
+++ b/fs_mgr/fs_mgr_remount.cpp
@@ -380,8 +380,8 @@
 
     // Now remount!
     for (const auto& mnt_point : {mount_point, entry.mount_point}) {
-        if (::mount(blk_device.c_str(), mnt_point.c_str(), entry.fs_type.c_str(), MS_REMOUNT,
-                    nullptr) == 0) {
+        if (::mount(blk_device.c_str(), mnt_point.c_str(), entry.fs_type.c_str(),
+                    MS_REMOUNT | MS_NOATIME, nullptr) == 0) {
             LOG(INFO) << "Remounted " << mnt_point << " as RW";
             return true;
         }
diff --git a/fs_mgr/libdm/dm_test.cpp b/fs_mgr/libdm/dm_test.cpp
index c522eaf..a0129c2 100644
--- a/fs_mgr/libdm/dm_test.cpp
+++ b/fs_mgr/libdm/dm_test.cpp
@@ -580,9 +580,18 @@
     ASSERT_TRUE(dm.GetDmDevicePathByName("libdm-test-dm-linear", &path));
     ASSERT_EQ(0, access(path.c_str(), F_OK));
 
+    std::string unique_path;
+    ASSERT_TRUE(dm.GetDeviceUniquePath("libdm-test-dm-linear", &unique_path));
+    ASSERT_EQ(0, access(unique_path.c_str(), F_OK));
+
     ASSERT_TRUE(dm.DeleteDevice("libdm-test-dm-linear", 5s));
     ASSERT_EQ(DmDeviceState::INVALID, dm.GetState("libdm-test-dm-linear"));
-    ASSERT_NE(0, access(path.c_str(), F_OK));
+    // Check that unique path of this device has been deleteted.
+    // Previously this test case used to check that dev node (i.e. /dev/block/dm-XX) has been
+    // deleted. However, this introduces a race condition, ueventd will remove the unique symlink
+    // (i.e. /dev/block/mapper/by-uuid/...) **before** removing the device node, while DeleteDevice
+    // API synchronizes on the unique symlink being deleted.
+    ASSERT_NE(0, access(unique_path.c_str(), F_OK));
     ASSERT_EQ(ENOENT, errno);
 }
 
diff --git a/fs_mgr/libsnapshot/android/snapshot/snapshot.proto b/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
index fa04c43..7e97dc0 100644
--- a/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
+++ b/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
@@ -114,6 +114,9 @@
 
     // Enable batching for COW writes
     bool batched_writes = 14;
+
+    // Size of v3 operation buffer. Needs to be determined during writer initialization
+    uint64 estimated_ops_buffer_size = 15;
 }
 
 // Next: 8
@@ -250,4 +253,7 @@
 
     // Whether this update attempt used io_uring.
     bool iouring_used = 13;
+
+    // Size of v3 operation buffer. Needs to be determined during writer initialization
+    uint64 estimated_op_count_max = 14;
 }
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h
index 5b1e56c..7df976d 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h
@@ -33,7 +33,10 @@
 
 namespace android {
 namespace snapshot {
-
+struct CowSizeInfo {
+    uint64_t cow_size;
+    uint64_t op_count_max;
+};
 struct CowOptions {
     uint32_t block_size = 4096;
     std::string compression;
@@ -56,7 +59,7 @@
     bool batch_write = false;
 
     // Size of the cow operation buffer; used in v3 only.
-    uint32_t op_count_max = 0;
+    uint64_t op_count_max = 0;
 };
 
 // Interface for writing to a snapuserd COW. All operations are ordered; merges
@@ -92,8 +95,9 @@
     // to ensure that the correct headers and footers are written.
     virtual bool Finalize() = 0;
 
-    // Return number of bytes the cow image occupies on disk.
-    virtual uint64_t GetCowSize() = 0;
+    // Return number of bytes the cow image occupies on disk + the size of sequence && ops buffer
+    // The latter two fields are used in v3 cow format and left as 0 for v2 cow format
+    virtual CowSizeInfo GetCowSizeInfo() const = 0;
 
     virtual uint32_t GetBlockSize() const = 0;
     virtual std::optional<uint32_t> GetMaxBlocks() const = 0;
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/mock_cow_writer.h b/fs_mgr/libsnapshot/include/libsnapshot/mock_cow_writer.h
index c58c654..8491fb0 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/mock_cow_writer.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/mock_cow_writer.h
@@ -24,8 +24,7 @@
     using FileDescriptor = chromeos_update_engine::FileDescriptor;
 
     MOCK_METHOD(bool, Finalize, (), (override));
-
-    MOCK_METHOD(uint64_t, GetCowSize, (), (override));
+    MOCK_METHOD(CowSizeInfo, GetCowSizeInfo, (), (const, override));
 
     MOCK_METHOD(bool, AddCopy, (uint64_t, uint64_t, uint64_t), (override));
     MOCK_METHOD(bool, AddRawBlocks, (uint64_t, const void*, size_t), (override));
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/test_v2.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/test_v2.cpp
index 49d86d8..1d1d24c 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/test_v2.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/test_v2.cpp
@@ -601,14 +601,14 @@
     ASSERT_TRUE(writer.AddCopy(10, 20));
     ASSERT_TRUE(writer.AddRawBlocks(50, data.data(), data.size()));
     ASSERT_TRUE(writer.AddZeroBlocks(51, 2));
-    auto size_before = writer.GetCowSize();
+    auto size_before = writer.GetCowSizeInfo().cow_size;
     ASSERT_TRUE(writer.Finalize());
-    auto size_after = writer.GetCowSize();
+    auto size_after = writer.GetCowSizeInfo().cow_size;
     ASSERT_EQ(size_before, size_after);
     struct stat buf;
 
     ASSERT_GE(fstat(cow_->fd, &buf), 0) << strerror(errno);
-    ASSERT_EQ(buf.st_size, writer.GetCowSize());
+    ASSERT_EQ(buf.st_size, writer.GetCowSizeInfo().cow_size);
 }
 
 TEST_F(CowTest, AppendLabelSmall) {
@@ -637,7 +637,7 @@
 
     struct stat buf;
     ASSERT_EQ(fstat(cow_->fd, &buf), 0);
-    ASSERT_EQ(buf.st_size, writer->GetCowSize());
+    ASSERT_EQ(buf.st_size, writer->GetCowSizeInfo().cow_size);
 
     // Read back both operations, and label.
     CowReader reader;
@@ -690,7 +690,7 @@
     ASSERT_TRUE(writer->AddRawBlocks(50, data.data(), data.size()));
     ASSERT_TRUE(writer->AddLabel(1));
     // Drop the tail end of the last op header, corrupting it.
-    ftruncate(cow_->fd, writer->GetCowSize() - sizeof(CowFooter) - 3);
+    ftruncate(cow_->fd, writer->GetCowSizeInfo().cow_size - sizeof(CowFooter) - 3);
 
     ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
 
@@ -705,7 +705,7 @@
 
     struct stat buf;
     ASSERT_EQ(fstat(cow_->fd, &buf), 0);
-    ASSERT_EQ(buf.st_size, writer->GetCowSize());
+    ASSERT_EQ(buf.st_size, writer->GetCowSizeInfo().cow_size);
 
     // Read back both operations.
     CowReader reader;
@@ -763,7 +763,7 @@
 
     struct stat buf;
     ASSERT_EQ(fstat(cow_->fd, &buf), 0);
-    ASSERT_EQ(buf.st_size, writer->GetCowSize());
+    ASSERT_EQ(buf.st_size, writer->GetCowSizeInfo().cow_size);
 
     // Read back all valid operations
     CowReader reader;
@@ -812,7 +812,7 @@
 
     struct stat buf;
     ASSERT_EQ(fstat(cow_->fd, &buf), 0);
-    ASSERT_EQ(buf.st_size, writer->GetCowSize());
+    ASSERT_EQ(buf.st_size, writer->GetCowSizeInfo().cow_size);
 
     // Read back all ops
     CowReader reader;
@@ -989,7 +989,7 @@
 
     struct stat buf;
     ASSERT_EQ(fstat(cow_->fd, &buf), 0);
-    ASSERT_EQ(buf.st_size, writer->GetCowSize());
+    ASSERT_EQ(buf.st_size, writer->GetCowSizeInfo().cow_size);
 
     // Read back both operations, plus cluster op at end
     CowReader reader;
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/test_v3.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/test_v3.cpp
index 3383a58..8cf46f4 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/test_v3.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/test_v3.cpp
@@ -658,14 +658,14 @@
     options.compression = "none";
     auto estimator = android::snapshot::CreateCowEstimator(3, options);
     ASSERT_TRUE(estimator->AddZeroBlocks(0, 1024 * 1024));
-    const auto cow_size = estimator->GetCowSize();
+    const auto cow_size = estimator->GetCowSizeInfo().cow_size;
     options.op_count_max = 1024 * 1024;
     options.max_blocks = 1024 * 1024;
     CowWriterV3 writer(options, GetCowFd());
     ASSERT_TRUE(writer.Initialize());
     ASSERT_TRUE(writer.AddZeroBlocks(0, 1024 * 1024));
 
-    ASSERT_LE(writer.GetCowSize(), cow_size);
+    ASSERT_LE(writer.GetCowSizeInfo().cow_size, cow_size);
 }
 
 TEST_F(CowTestV3, CopyOpMany) {
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v2.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v2.cpp
index f9a4e47..75cd111 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v2.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v2.cpp
@@ -576,12 +576,14 @@
     return Sync();
 }
 
-uint64_t CowWriterV2::GetCowSize() {
+CowSizeInfo CowWriterV2::GetCowSizeInfo() const {
+    CowSizeInfo info;
     if (current_data_size_ > 0) {
-        return next_data_pos_ + sizeof(footer_);
+        info.cow_size = next_data_pos_ + sizeof(footer_);
     } else {
-        return next_op_pos_ + sizeof(footer_);
+        info.cow_size = next_op_pos_ + sizeof(footer_);
     }
+    return info;
 }
 
 bool CowWriterV2::GetDataPos(uint64_t* pos) {
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v2.h b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v2.h
index 50e635f..05de2ad 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v2.h
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v2.h
@@ -27,7 +27,7 @@
 
     bool Initialize(std::optional<uint64_t> label = {}) override;
     bool Finalize() override;
-    uint64_t GetCowSize() override;
+    CowSizeInfo GetCowSizeInfo() const override;
 
   protected:
     virtual bool EmitCopy(uint64_t new_block, uint64_t old_block, uint64_t num_blocks = 1) override;
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.cpp
index d99e6e6..be6b6da 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.cpp
@@ -34,6 +34,7 @@
 #include <zlib.h>
 
 #include <fcntl.h>
+#include <libsnapshot/cow_compress.h>
 #include <libsnapshot_cow/parser_v3.h>
 #include <linux/fs.h>
 #include <sys/ioctl.h>
@@ -55,11 +56,35 @@
 
 using android::base::unique_fd;
 
+// Divide |x| by |y| and round up to the nearest integer.
+constexpr uint64_t DivRoundUp(uint64_t x, uint64_t y) {
+    return (x + y - 1) / y;
+}
+
 CowWriterV3::CowWriterV3(const CowOptions& options, unique_fd&& fd)
     : CowWriterBase(options, std::move(fd)), batch_size_(std::max<size_t>(options.cluster_ops, 1)) {
     SetupHeaders();
 }
 
+void CowWriterV3::InitWorkers() {
+    if (num_compress_threads_ <= 1) {
+        LOG_INFO << "Not creating new threads for compression.";
+        return;
+    }
+    compress_threads_.reserve(num_compress_threads_);
+    compress_threads_.clear();
+    threads_.reserve(num_compress_threads_);
+    threads_.clear();
+    for (size_t i = 0; i < num_compress_threads_; i++) {
+        std::unique_ptr<ICompressor> compressor =
+                ICompressor::Create(compression_, header_.block_size);
+        auto&& wt = compress_threads_.emplace_back(
+                std::make_unique<CompressWorker>(std::move(compressor), header_.block_size));
+        threads_.emplace_back(std::thread([wt = wt.get()]() { wt->RunThread(); }));
+    }
+    LOG(INFO) << num_compress_threads_ << " thread used for compression";
+}
+
 void CowWriterV3::SetupHeaders() {
     header_ = {};
     header_.prefix.magic = kCowMagicNumber;
@@ -135,10 +160,24 @@
     } else {
         LOG(INFO) << "Batch writes: disabled";
     }
+    if (android::base::GetBoolProperty("ro.virtual_ab.compression.threads", false) &&
+        options_.num_compress_threads) {
+        num_compress_threads_ = options_.num_compress_threads;
+    }
+    InitWorkers();
     return true;
 }
 
-CowWriterV3::~CowWriterV3() {}
+CowWriterV3::~CowWriterV3() {
+    for (const auto& t : compress_threads_) {
+        t->Finalize();
+    }
+    for (auto& t : threads_) {
+        if (t.joinable()) {
+            t.join();
+        }
+    }
+}
 
 bool CowWriterV3::Initialize(std::optional<uint64_t> label) {
     if (!InitFd() || !ParseOptions()) {
@@ -289,19 +328,24 @@
                    << " but compressor is uninitialized.";
         return false;
     }
+    const auto bytes = reinterpret_cast<const uint8_t*>(data);
     const size_t num_blocks = (size / header_.block_size);
 
     for (size_t i = 0; i < num_blocks;) {
         const auto blocks_to_write =
                 std::min<size_t>(batch_size_ - cached_data_.size(), num_blocks - i);
         size_t compressed_bytes = 0;
+        auto&& blocks = CompressBlocks(blocks_to_write, bytes + header_.block_size * i);
+        if (blocks.size() != blocks_to_write) {
+            LOG(ERROR) << "Failed to compress blocks " << new_block_start + i << ", "
+                       << blocks_to_write << ", actual number of blocks received from compressor "
+                       << blocks.size();
+            return false;
+        }
         for (size_t j = 0; j < blocks_to_write; j++) {
-            const uint8_t* const iter =
-                    reinterpret_cast<const uint8_t*>(data) + (header_.block_size * (i + j));
-
             CowOperation& op = cached_ops_.emplace_back();
             auto& vec = data_vec_.emplace_back();
-            auto& compressed_data = cached_data_.emplace_back();
+            auto& compressed_data = cached_data_.emplace_back(std::move(blocks[j]));
             op.new_block = new_block_start + i + j;
 
             op.set_type(type);
@@ -310,20 +354,6 @@
             } else {
                 op.set_source(next_data_pos_ + compressed_bytes);
             }
-            if (compression_.algorithm == kCowCompressNone) {
-                compressed_data.resize(header_.block_size);
-            } else {
-                compressed_data = compressor_->Compress(iter, header_.block_size);
-                if (compressed_data.empty()) {
-                    LOG(ERROR) << "Compression failed during EmitBlocks(" << new_block_start << ", "
-                               << num_blocks << ");";
-                    return false;
-                }
-            }
-            if (compressed_data.size() >= header_.block_size) {
-                compressed_data.resize(header_.block_size);
-                std::memcpy(compressed_data.data(), iter, header_.block_size);
-            }
             vec = {.iov_base = compressed_data.data(), .iov_len = compressed_data.size()};
             op.data_length = vec.iov_len;
             compressed_bytes += op.data_length;
@@ -392,7 +422,6 @@
 }
 
 bool CowWriterV3::EmitSequenceData(size_t num_ops, const uint32_t* data) {
-    // TODO: size sequence buffer based on options
     if (header_.op_count > 0 || !cached_ops_.empty()) {
         LOG(ERROR) << "There's " << header_.op_count << " operations written to disk and "
                    << cached_ops_.size()
@@ -400,8 +429,14 @@
                       "operation writes.";
         return false;
     }
+
     header_.sequence_data_count = num_ops;
+
+    // Ensure next_data_pos_ is updated as previously initialized + the newly added sequence buffer.
+    CHECK_EQ(next_data_pos_ + header_.sequence_data_count * sizeof(uint32_t),
+             GetDataOffset(header_));
     next_data_pos_ = GetDataOffset(header_);
+
     if (!android::base::WriteFullyAtOffset(fd_, data, sizeof(data[0]) * num_ops,
                                            GetSequenceOffset(header_))) {
         PLOG(ERROR) << "writing sequence buffer failed";
@@ -438,6 +473,57 @@
     return true;
 }
 
+std::vector<std::basic_string<uint8_t>> CowWriterV3::CompressBlocks(const size_t num_blocks,
+                                                                    const void* data) {
+    const size_t num_threads = (num_blocks == 1) ? 1 : num_compress_threads_;
+    const size_t blocks_per_thread = DivRoundUp(num_blocks, num_threads);
+    std::vector<std::basic_string<uint8_t>> compressed_buf;
+    compressed_buf.clear();
+    const uint8_t* const iter = reinterpret_cast<const uint8_t*>(data);
+    if (compression_.algorithm == kCowCompressNone) {
+        for (size_t i = 0; i < num_blocks; i++) {
+            auto& buf = compressed_buf.emplace_back();
+            buf.resize(header_.block_size);
+            std::memcpy(buf.data(), iter + i * header_.block_size, header_.block_size);
+        }
+        return compressed_buf;
+    }
+    if (num_threads <= 1) {
+        if (!CompressWorker::CompressBlocks(compressor_.get(), header_.block_size, data, num_blocks,
+                                            &compressed_buf)) {
+            return {};
+        }
+    } else {
+        // Submit the blocks per thread. The retrieval of
+        // compressed buffers has to be done in the same order.
+        // We should not poll for completed buffers in a different order as the
+        // buffers are tightly coupled with block ordering.
+        for (size_t i = 0; i < num_threads; i++) {
+            CompressWorker* worker = compress_threads_[i].get();
+            const auto blocks_in_batch =
+                    std::min(num_blocks - i * blocks_per_thread, blocks_per_thread);
+            worker->EnqueueCompressBlocks(iter + i * blocks_per_thread * header_.block_size,
+                                          blocks_in_batch);
+        }
+
+        for (size_t i = 0; i < num_threads; i++) {
+            CompressWorker* worker = compress_threads_[i].get();
+            if (!worker->GetCompressedBuffers(&compressed_buf)) {
+                return {};
+            }
+        }
+    }
+    for (size_t i = 0; i < num_blocks; i++) {
+        auto& block = compressed_buf[i];
+        if (block.size() >= header_.block_size) {
+            block.resize(header_.block_size);
+            std::memcpy(block.data(), iter + header_.block_size * i, header_.block_size);
+        }
+    }
+
+    return compressed_buf;
+}
+
 bool CowWriterV3::WriteOperation(std::basic_string_view<CowOperationV3> ops,
                                  std::basic_string_view<struct iovec> data) {
     const auto total_data_size =
@@ -491,8 +577,11 @@
     return Sync();
 }
 
-uint64_t CowWriterV3::GetCowSize() {
-    return next_data_pos_;
+CowSizeInfo CowWriterV3::GetCowSizeInfo() const {
+    CowSizeInfo info;
+    info.cow_size = next_data_pos_;
+    info.op_count_max = header_.op_count_max;
+    return info;
 }
 
 }  // namespace snapshot
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.h b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.h
index 3a7b877..b19af60 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.h
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.h
@@ -16,6 +16,7 @@
 
 #include <android-base/logging.h>
 #include <string_view>
+#include <thread>
 #include <vector>
 
 #include "writer_base.h"
@@ -30,7 +31,7 @@
 
     bool Initialize(std::optional<uint64_t> label = {}) override;
     bool Finalize() override;
-    uint64_t GetCowSize() override;
+    CowSizeInfo GetCowSizeInfo() const override;
 
   protected:
     virtual bool EmitCopy(uint64_t new_block, uint64_t old_block, uint64_t num_blocks = 1) override;
@@ -51,12 +52,14 @@
                         std::basic_string_view<struct iovec> data);
     bool EmitBlocks(uint64_t new_block_start, const void* data, size_t size, uint64_t old_block,
                     uint16_t offset, CowOperationType type);
-    bool CompressBlocks(size_t num_blocks, const void* data);
     bool CheckOpCount(size_t op_count);
 
   private:
+    std::vector<std::basic_string<uint8_t>> CompressBlocks(const size_t num_blocks,
+                                                           const void* data);
     bool ReadBackVerification();
     bool FlushCacheOps();
+    void InitWorkers();
     CowHeaderV3 header_{};
     CowCompression compression_;
     // in the case that we are using one thread for compression, we can store and re-use the same
@@ -75,6 +78,8 @@
     std::vector<CowOperationV3> cached_ops_;
     std::vector<std::basic_string<uint8_t>> cached_data_;
     std::vector<struct iovec> data_vec_;
+
+    std::vector<std::thread> threads_;
 };
 
 }  // namespace snapshot
diff --git a/fs_mgr/libsnapshot/partition_cow_creator.cpp b/fs_mgr/libsnapshot/partition_cow_creator.cpp
index 5bc7e65..c0d2073 100644
--- a/fs_mgr/libsnapshot/partition_cow_creator.cpp
+++ b/fs_mgr/libsnapshot/partition_cow_creator.cpp
@@ -217,6 +217,7 @@
 
     if (update && update->has_estimate_cow_size()) {
         ret.snapshot_status.set_estimated_cow_size(update->estimate_cow_size());
+        ret.snapshot_status.set_estimated_ops_buffer_size(update->estimate_op_count_max());
     }
 
     if (ret.snapshot_status.snapshot_size() == 0) {
diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp
index e33bdff..9eb41b2 100644
--- a/fs_mgr/libsnapshot/snapshot.cpp
+++ b/fs_mgr/libsnapshot/snapshot.cpp
@@ -3551,6 +3551,9 @@
                 options.scratch_space = false;
             }
             options.compression = it->second.compression_algorithm();
+            if (cow_version >= 3) {
+                options.op_count_max = it->second.estimated_ops_buffer_size();
+            }
 
             auto writer = CreateCowWriter(cow_version, options, std::move(fd));
             if (!writer->Finalize()) {
@@ -3662,9 +3665,7 @@
     cow_options.max_blocks = {status.device_size() / cow_options.block_size};
     cow_options.batch_write = status.batched_writes();
     cow_options.num_compress_threads = status.enable_threading() ? 2 : 1;
-    // TODO(b/313962438) Improve op_count estimate. For now, use number of
-    // blocks as an upper bound.
-    cow_options.op_count_max = status.device_size() / cow_options.block_size;
+    cow_options.op_count_max = status.estimated_ops_buffer_size();
     // Disable scratch space for vts tests
     if (device()->IsTestDevice()) {
         cow_options.scratch_space = false;
diff --git a/fs_mgr/libsnapshot/snapuserd/Android.bp b/fs_mgr/libsnapshot/snapuserd/Android.bp
index 6b8e084..bd296a3 100644
--- a/fs_mgr/libsnapshot/snapuserd/Android.bp
+++ b/fs_mgr/libsnapshot/snapuserd/Android.bp
@@ -86,10 +86,15 @@
         "libext4_utils",
         "libsnapshot_cow",
         "liburing",
+        "libprocessgroup",
+        "libjsoncpp",
+        "libcgrouprc",
+        "libcgrouprc_format",
     ],
     include_dirs: ["bionic/libc/kernel"],
     export_include_dirs: ["include"],
     header_libs: [
+        "libcutils_headers",
         "libstorage_literals_headers",
     ],
     ramdisk_available: true,
@@ -126,6 +131,10 @@
         "liblog",
         "libsnapshot_cow",
         "libsnapuserd",
+        "libprocessgroup",
+        "libjsoncpp",
+        "libcgrouprc",
+        "libcgrouprc_format",
         "libsnapuserd_client",
         "libz",
         "liblz4",
@@ -135,6 +144,7 @@
     ],
 
     header_libs: [
+        "libcutils_headers",
         "libstorage_literals_headers",
     ],
 
@@ -251,6 +261,10 @@
         "libgtest",
         "libsnapshot_cow",
         "libsnapuserd",
+        "libprocessgroup",
+        "libjsoncpp",
+        "libcgrouprc",
+        "libcgrouprc_format",
         "liburing",
         "libz",
     ],
@@ -261,6 +275,7 @@
     header_libs: [
         "libstorage_literals_headers",
         "libfiemap_headers",
+        "libcutils_headers",
     ],
     test_options: {
         min_shipping_api_level: 30,
@@ -320,6 +335,10 @@
         "libgflags",
         "libsnapshot_cow",
         "libsnapuserd",
+        "libprocessgroup",
+        "libjsoncpp",
+        "libcgrouprc",
+        "libcgrouprc_format",
         "liburing",
         "libz",
     ],
@@ -330,5 +349,6 @@
     header_libs: [
         "libstorage_literals_headers",
         "libfiemap_headers",
+        "libcutils_headers",
     ],
 }
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/merge_worker.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/merge_worker.cpp
index bcf9aab..1e7d0c0 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/merge_worker.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/merge_worker.cpp
@@ -80,16 +80,16 @@
 }
 
 bool MergeWorker::MergeReplaceZeroOps() {
-    // Flush after merging 2MB. Since all ops are independent and there is no
+    // Flush after merging 1MB. 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 block device. If there is a crash, we will
     // end up replaying some of the COW ops which were already merged. That is
     // ok.
     //
-    // Although increasing this greater than 2MB may help in improving merge
+    // Although increasing this greater than 1MB may help in improving merge
     // times; however, on devices with low memory, this can be problematic
     // when there are multiple merge threads in parallel.
-    int total_ops_merged_per_commit = (PAYLOAD_BUFFER_SZ / BLOCK_SZ) * 2;
+    int total_ops_merged_per_commit = (PAYLOAD_BUFFER_SZ / BLOCK_SZ);
     int num_ops_merged = 0;
 
     SNAP_LOG(INFO) << "MergeReplaceZeroOps started....";
@@ -561,6 +561,10 @@
         SNAP_PLOG(ERROR) << "Failed to set thread priority";
     }
 
+    if (!SetProfiles({"CPUSET_SP_BACKGROUND"})) {
+        SNAP_PLOG(ERROR) << "Failed to assign task profile to Mergeworker thread";
+    }
+
     SNAP_LOG(INFO) << "Merge starting..";
 
     bufsink_.Initialize(PAYLOAD_BUFFER_SZ);
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 c08c1b1..2baf20d 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp
@@ -782,6 +782,10 @@
         SNAP_PLOG(ERROR) << "Failed to set thread priority";
     }
 
+    if (!SetProfiles({"CPUSET_SP_BACKGROUND"})) {
+        SNAP_PLOG(ERROR) << "Failed to assign task profile to readahead thread";
+    }
+
     SNAP_LOG(INFO) << "ReadAhead processing.";
     while (!RAIterDone()) {
         if (!ReadAheadIOStart()) {
diff --git a/fs_mgr/libsnapshot/snapuserd/utility.cpp b/fs_mgr/libsnapshot/snapuserd/utility.cpp
index fcdb69d..684ca3d 100644
--- a/fs_mgr/libsnapshot/snapuserd/utility.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/utility.cpp
@@ -19,6 +19,9 @@
 #include <unistd.h>
 
 #include <android-base/file.h>
+#include <processgroup/processgroup.h>
+
+#include <private/android_filesystem_config.h>
 
 namespace android {
 namespace snapshot {
@@ -33,6 +36,17 @@
 #endif
 }
 
+bool SetProfiles([[maybe_unused]] std::initializer_list<std::string_view> profiles) {
+#ifdef __ANDROID__
+    if (setgid(AID_SYSTEM)) {
+        return false;
+    }
+    return SetTaskProfiles(gettid(), profiles);
+#else
+    return true;
+#endif
+}
+
 bool KernelSupportsIoUring() {
     struct utsname uts {};
     unsigned int major, minor;
diff --git a/fs_mgr/libsnapshot/snapuserd/utility.h b/fs_mgr/libsnapshot/snapuserd/utility.h
index 255aee1..c3c3cba 100644
--- a/fs_mgr/libsnapshot/snapuserd/utility.h
+++ b/fs_mgr/libsnapshot/snapuserd/utility.h
@@ -14,10 +14,14 @@
 
 #pragma once
 
+#include <initializer_list>
+#include <string_view>
+
 namespace android {
 namespace snapshot {
 
 bool SetThreadPriority(int priority);
+bool SetProfiles(std::initializer_list<std::string_view> profiles);
 bool KernelSupportsIoUring();
 
 }  // namespace snapshot
diff --git a/fs_mgr/tests/adb-remount-test.sh b/fs_mgr/tests/adb-remount-test.sh
index c87e564..7ac7a16 100755
--- a/fs_mgr/tests/adb-remount-test.sh
+++ b/fs_mgr/tests/adb-remount-test.sh
@@ -1233,6 +1233,12 @@
 adb_sh grep -q " /vendor [^ ]* rw," /proc/mounts </dev/null &&
   die "/vendor is not RO"
 
+data_device=$(adb_sh awk '$2 == "/data" { print $1; exit }' /proc/mounts)
+RO=$(adb_sh grep " ro," /proc/mounts </dev/null |
+    grep -v "^${data_device}" |
+    skip_administrative_mounts |
+    awk '{ print $1 }')
+
 T=$(adb_date)
 adb remount >&2 ||
   die -t "${T}" "adb remount"
@@ -1241,6 +1247,12 @@
 adb_sh grep -q " /vendor [^ ]* rw," /proc/mounts </dev/null ||
   die -t "${T}" "/vendor is not RW"
 
+# Only find mounts that are remounted RO -> RW
+RW=$(adb_sh grep " rw," /proc/mounts </dev/null |
+    grep -v "^${data_device}" |
+    skip_administrative_mounts |
+    grep -E "^($(join_with '|' ${RO})) ")
+
 scratch_on_super=false
 if ${overlayfs_needed}; then
   is_overlayfs_mounted /system ||
@@ -1287,27 +1299,19 @@
     fi
   done
 
-  data_device=$(adb_sh awk '$2 == "/data" { print $1; exit }' /proc/mounts)
   # KISS (we do not support sub-mounts for system partitions currently)
   adb_sh grep "^overlay " /proc/mounts </dev/null |
     grep -vE "^overlay.* /(apex|system|vendor)/[^ ]" |
     grep " overlay ro," &&
     die "expected overlay to be RW after remount"
-  adb_sh grep -v noatime /proc/mounts </dev/null |
-    grep -v "^${data_device}" |
-    skip_administrative_mounts |
-    grep -v ' ro,' &&
-    die "mounts are not noatime"
 
-  D=$(adb_sh grep " rw," /proc/mounts </dev/null |
-      grep -v "^${data_device}" |
-      skip_administrative_mounts |
+  D=$(echo "${RW}" |
       awk '{ print $1 }' |
       sed 's|/dev/root|/|' |
       sort -u)
   if [ -n "${D}" ]; then
     adb_sh df -k ${D} </dev/null |
-      sed -e 's/^Filesystem      /Filesystem (rw) /'
+      sed -e 's/^Filesystem     /Filesystem (rw)/'
   fi >&2
   for d in ${D}; do
     if adb_sh tune2fs -l "${d}" </dev/null 2>&1 | grep -q "Filesystem features:.*shared_blocks" ||
@@ -1319,6 +1323,10 @@
   is_overlayfs_mounted && die -t "${T}" "unexpected overlay takeover"
 fi
 
+echo -n "${RW}" |
+  grep -v noatime &&
+  die "mounts (rw) are not noatime"
+
 LOG OK "adb remount RW"
 
 ################################################################################
diff --git a/fs_mgr/tests/vts_fs_test.cpp b/fs_mgr/tests/vts_fs_test.cpp
index 8f15220..9503072 100644
--- a/fs_mgr/tests/vts_fs_test.cpp
+++ b/fs_mgr/tests/vts_fs_test.cpp
@@ -99,12 +99,7 @@
     ASSERT_TRUE(android::base::Readlink("/dev/block/by-name/super", &super_bdev));
     ASSERT_TRUE(android::base::Readlink("/dev/block/by-name/userdata", &userdata_bdev));
 
-    std::vector<std::string> must_be_f2fs = {"/data"};
-    if (vsr_level >= __ANDROID_API_U__ &&
-        !DeviceSupportsFeature("android.hardware.type.automotive")) {
-        must_be_f2fs.emplace_back("/metadata");
-    }
-
+    std::vector<std::string> data_fs = {"/data", "/metadata"};
     for (const auto& entry : fstab) {
         std::string parent_bdev = entry.blk_device;
         while (true) {
@@ -138,11 +133,10 @@
             std::vector<std::string> allowed = {"erofs", "ext4", "f2fs"};
             EXPECT_NE(std::find(allowed.begin(), allowed.end(), entry.fs_type), allowed.end())
                     << entry.mount_point;
-        } else {
-            if (std::find(must_be_f2fs.begin(), must_be_f2fs.end(), entry.mount_point) !=
-                must_be_f2fs.end()) {
-                EXPECT_EQ(entry.fs_type, "f2fs") << entry.mount_point;
-            }
+        } else if (std::find(data_fs.begin(), data_fs.end(), entry.mount_point) != data_fs.end()) {
+            std::vector<std::string> allowed = {"ext4", "f2fs"};
+            EXPECT_NE(std::find(allowed.begin(), allowed.end(), entry.fs_type), allowed.end())
+                    << entry.mount_point << ", " << entry.fs_type;
         }
     }
 }
diff --git a/healthd/Android.bp b/healthd/Android.bp
index 427ac48..e158e07 100644
--- a/healthd/Android.bp
+++ b/healthd/Android.bp
@@ -100,44 +100,6 @@
     ],
 }
 
-cc_defaults {
-    name: "android.hardware.health@2.0-service_defaults",
-
-    cflags: [
-        "-Wall",
-        "-Werror",
-    ],
-
-    static_libs: [
-        "android.hardware.health@2.0-impl",
-        "android.hardware.health@1.0-convert",
-        "libhealthservice",
-        "libhealthstoragedefault",
-        "libbatterymonitor",
-    ],
-
-    shared_libs: [
-        "libbase",
-        "libcutils",
-        "libhidlbase",
-        "liblog",
-        "libutils",
-        "android.hardware.health@2.0",
-    ],
-}
-
-cc_binary {
-    name: "android.hardware.health@2.0-service",
-    defaults: ["android.hardware.health@2.0-service_defaults"],
-
-    vendor: true,
-    relative_install_path: "hw",
-    init_rc: ["android.hardware.health@2.0-service.rc"],
-    srcs: [
-        "HealthServiceDefault.cpp",
-    ],
-}
-
 cc_library_static {
     name: "libhealthd_charger_nops",
     recovery_available: true,
diff --git a/healthd/HealthServiceDefault.cpp b/healthd/HealthServiceDefault.cpp
deleted file mode 100644
index 89ecc2f..0000000
--- a/healthd/HealthServiceDefault.cpp
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2017 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 <health2/service.h>
-#include <healthd/healthd.h>
-
-void healthd_board_init(struct healthd_config*) {
-    // Implementation-defined init logic goes here.
-    // 1. config->periodic_chores_interval_* variables
-    // 2. config->battery*Path variables
-    // 3. config->energyCounter. In this implementation, energyCounter is not defined.
-
-    // use defaults
-}
-
-int healthd_board_battery_update(struct android::BatteryProperties*) {
-    // Implementation-defined update logic goes here. An implementation
-    // can make modifications to prop before broadcasting it to all callbacks.
-
-    // return 0 to log periodic polled battery status to kernel log
-    return 0;
-}
-
-int main() {
-    return health_service_main();
-}
diff --git a/healthd/android.hardware.health@2.0-service.rc b/healthd/android.hardware.health@2.0-service.rc
deleted file mode 100644
index 762771e..0000000
--- a/healthd/android.hardware.health@2.0-service.rc
+++ /dev/null
@@ -1,6 +0,0 @@
-service health-hal-2-0 /vendor/bin/hw/android.hardware.health@2.0-service
-    class hal
-    user system
-    group system
-    capabilities WAKE_ALARM BLOCK_SUSPEND
-    file /dev/kmsg w
diff --git a/init/Android.bp b/init/Android.bp
index e5512e6..a7278d6 100644
--- a/init/Android.bp
+++ b/init/Android.bp
@@ -188,7 +188,6 @@
         "libfs_mgr",
         "libgsi",
         "libhidl-gen-utils",
-        "libkeyutils",
         "liblog",
         "liblogwrap",
         "liblp",
@@ -197,6 +196,7 @@
         "libselinux",
         "libunwindstack",
         "libutils",
+        "libvendorsupport",
     ],
     header_libs: ["bionic_libc_platform_headers"],
     bootstrap: true,
diff --git a/init/builtins.cpp b/init/builtins.cpp
index a95a4a3..606ea8c 100644
--- a/init/builtins.cpp
+++ b/init/builtins.cpp
@@ -592,9 +592,6 @@
     } else if (code == FS_MGR_MNTALL_DEV_FILE_ENCRYPTED ||
                code == FS_MGR_MNTALL_DEV_IS_METADATA_ENCRYPTED ||
                code == FS_MGR_MNTALL_DEV_NEEDS_METADATA_ENCRYPTION) {
-        if (!FscryptInstallKeyring()) {
-            return Error() << "FscryptInstallKeyring() failed";
-        }
         SetProperty("ro.crypto.state", "encrypted");
 
         // Although encrypted, vold has already set the device up, so we do not need to
diff --git a/init/fscrypt_init_extensions.cpp b/init/fscrypt_init_extensions.cpp
index fbd8189..6a561e5 100644
--- a/init/fscrypt_init_extensions.cpp
+++ b/init/fscrypt_init_extensions.cpp
@@ -34,28 +34,12 @@
 #include <cutils/properties.h>
 #include <cutils/sockets.h>
 #include <fscrypt/fscrypt.h>
-#include <keyutils.h>
 #include <logwrap/logwrap.h>
 
 #define TAG "fscrypt"
 
 using namespace android::fscrypt;
 
-bool FscryptInstallKeyring() {
-    if (keyctl_search(KEY_SPEC_SESSION_KEYRING, "keyring", "fscrypt", 0) != -1) {
-        LOG(INFO) << "Keyring is already created";
-        return true;
-    }
-    key_serial_t device_keyring = add_key("keyring", "fscrypt", 0, 0, KEY_SPEC_SESSION_KEYRING);
-
-    if (device_keyring == -1) {
-        PLOG(ERROR) << "Failed to create keyring";
-        return false;
-    }
-    LOG(INFO) << "Keyring created with id " << device_keyring << " in process " << getpid();
-    return true;
-}
-
 // TODO(b/139378601): use a single central implementation of this.
 static void delete_dir_contents(const std::string& dir) {
     char* const paths[2] = {const_cast<char*>(dir.c_str()), nullptr};
diff --git a/init/fscrypt_init_extensions.h b/init/fscrypt_init_extensions.h
index d357bb2..5e0269a 100644
--- a/init/fscrypt_init_extensions.h
+++ b/init/fscrypt_init_extensions.h
@@ -25,6 +25,5 @@
     kDeleteIfNecessary,
 };
 
-bool FscryptInstallKeyring();
 bool FscryptSetDirectoryPolicy(const std::string& ref_basename, FscryptAction action,
                                const std::string& dir);
diff --git a/init/fuzzer/Android.bp b/init/fuzzer/Android.bp
index 856ca8c..9916246 100644
--- a/init/fuzzer/Android.bp
+++ b/init/fuzzer/Android.bp
@@ -32,7 +32,6 @@
         "libbase",
         "libfs_mgr",
         "libhidl-gen-utils",
-        "libkeyutils",
         "liblog",
         "libprocessgroup",
         "libselinux",
diff --git a/init/init.cpp b/init/init.cpp
index aeccd66..19e909f 100644
--- a/init/init.cpp
+++ b/init/init.cpp
@@ -54,7 +54,6 @@
 #include <android-base/thread_annotations.h>
 #include <fs_avb/fs_avb.h>
 #include <fs_mgr_vendor_overlay.h>
-#include <keyutils.h>
 #include <libavb/libavb.h>
 #include <libgsi/libgsi.h>
 #include <libsnapshot/snapshot.h>
@@ -971,11 +970,6 @@
                    << " to /proc/1/oom_score_adj: " << result.error();
     }
 
-    // Set up a session keyring that all processes will have access to. It
-    // will hold things like FBE encryption keys. No process should override
-    // its session keyring.
-    keyctl_get_keyring_ID(KEY_SPEC_SESSION_KEYRING, 1);
-
     // Indicate that booting is in progress to background fw loaders, etc.
     close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));
 
diff --git a/init/property_service.cpp b/init/property_service.cpp
index e2cff95..30ad800 100644
--- a/init/property_service.cpp
+++ b/init/property_service.cpp
@@ -63,6 +63,7 @@
 #include <selinux/android.h>
 #include <selinux/label.h>
 #include <selinux/selinux.h>
+#include <vendorsupport/api_level.h>
 
 #include "debug_ramdisk.h"
 #include "epoll.h"
@@ -113,7 +114,6 @@
 constexpr auto LEGACY_ID_PROP = "ro.build.legacy.id";
 constexpr auto VBMETA_DIGEST_PROP = "ro.boot.vbmeta.digest";
 constexpr auto DIGEST_SIZE_USED = 8;
-constexpr auto MAX_VENDOR_API_LEVEL = 1000000;
 
 static bool persistent_properties_loaded = false;
 
@@ -1085,25 +1085,13 @@
     }
 }
 
-static int vendor_api_level_of(int sdk_api_level) {
-    if (sdk_api_level < __ANDROID_API_V__) {
-        return sdk_api_level;
-    }
-    // In Android V, vendor API level started with version 202404.
-    // The calculation assumes that the SDK api level bumps once a year.
-    if (sdk_api_level < __ANDROID_API_FUTURE__) {
-        return 202404 + ((sdk_api_level - __ANDROID_API_V__) * 100);
-    }
-    return MAX_VENDOR_API_LEVEL;
-}
-
 static void property_initialize_ro_vendor_api_level() {
     // ro.vendor.api_level shows the api_level that the vendor images (vendor, odm, ...) are
     // required to support.
     constexpr auto VENDOR_API_LEVEL_PROP = "ro.vendor.api_level";
 
-    auto vendor_api_level = GetIntProperty("ro.board.first_api_level", MAX_VENDOR_API_LEVEL);
-    if (vendor_api_level != MAX_VENDOR_API_LEVEL) {
+    auto vendor_api_level = GetIntProperty("ro.board.first_api_level", __ANDROID_VENDOR_API_MAX__);
+    if (vendor_api_level != __ANDROID_VENDOR_API_MAX__) {
         // Update the vendor_api_level with "ro.board.api_level" only if both "ro.board.api_level"
         // and "ro.board.first_api_level" are defined.
         vendor_api_level = GetIntProperty("ro.board.api_level", vendor_api_level);
@@ -1118,6 +1106,12 @@
 
     vendor_api_level = std::min(vendor_api_level_of(product_first_api_level), vendor_api_level);
 
+    if (vendor_api_level < 0) {
+        LOG(ERROR) << "Unexpected vendor api level for " << VENDOR_API_LEVEL_PROP << ". Check "
+                   << "ro.product.first_api_level and ro.build.version.sdk.";
+        vendor_api_level = __ANDROID_VENDOR_API_MAX__;
+    }
+
     std::string error;
     auto res = PropertySetNoSocket(VENDOR_API_LEVEL_PROP, std::to_string(vendor_api_level), &error);
     if (res != PROP_SUCCESS) {
diff --git a/libcutils/include/private/android_filesystem_config.h b/libcutils/include/private/android_filesystem_config.h
index 1e035bb..8c6e548 100644
--- a/libcutils/include/private/android_filesystem_config.h
+++ b/libcutils/include/private/android_filesystem_config.h
@@ -141,6 +141,7 @@
 #define AID_SDK_SANDBOX 1090      /* SDK sandbox virtual UID */
 #define AID_SECURITY_LOG_WRITER 1091 /* write to security log */
 #define AID_PRNG_SEEDER 1092         /* PRNG seeder daemon */
+#define AID_UPROBESTATS 1093         /* uid for uprobestats */
 /* 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/libutils/Android.bp b/libutils/Android.bp
index 1c622ff..ad5b752 100644
--- a/libutils/Android.bp
+++ b/libutils/Android.bp
@@ -191,6 +191,7 @@
     target: {
         product: {
             header_abi_checker: {
+                enabled: true,
                 // AFDO affects weak symbols.
                 diff_flags: ["-allow-adding-removing-weak-symbols"],
                 ref_dump_dirs: ["abi-dumps"],
@@ -198,6 +199,7 @@
         },
         vendor: {
             header_abi_checker: {
+                enabled: true,
                 // AFDO affects weak symbols.
                 diff_flags: ["-allow-adding-removing-weak-symbols"],
                 ref_dump_dirs: ["abi-dumps"],
diff --git a/libvendorsupport/Android.bp b/libvendorsupport/Android.bp
new file mode 100644
index 0000000..b4457b1
--- /dev/null
+++ b/libvendorsupport/Android.bp
@@ -0,0 +1,36 @@
+// Copyright (C) 2024 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_library {
+    name: "libvendorsupport",
+    native_bridge_supported: true,
+    recovery_available: true,
+    llndk: {
+        symbol_file: "libvendorsupport.map.txt",
+    },
+    srcs: ["version_props.c"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+    local_include_dirs: ["include/vendorsupport"],
+    export_include_dirs: ["include"],
+    shared_libs: [
+        "liblog",
+    ],
+}
diff --git a/libvendorsupport/OWNERS b/libvendorsupport/OWNERS
new file mode 100644
index 0000000..2ab18eb
--- /dev/null
+++ b/libvendorsupport/OWNERS
@@ -0,0 +1,2 @@
+jiyong@google.com
+justinyun@google.com
diff --git a/libvendorsupport/TEST_MAPPING b/libvendorsupport/TEST_MAPPING
new file mode 100644
index 0000000..5bd09ba
--- /dev/null
+++ b/libvendorsupport/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "postsubmit": [
+    {
+      "name": "libvendorsupport-tests"
+    }
+  ]
+}
diff --git a/libvendorsupport/include/vendorsupport/api_level.h b/libvendorsupport/include/vendorsupport/api_level.h
new file mode 100644
index 0000000..ba1a6b8
--- /dev/null
+++ b/libvendorsupport/include/vendorsupport/api_level.h
@@ -0,0 +1,51 @@
+// Copyright (C) 2024 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.
+
+#pragma once
+
+#include <android/api-level.h>
+
+#define __ANDROID_VENDOR_API_MAX__ 1000000
+#define __INVALID_API_LEVEL -1
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief Find corresponding vendor API level from an SDK API version.
+ *
+ * @details
+ * SDK API versions and vendor API levels are not compatible and not
+ * convertible. However, this function can be used to compare the two versions
+ * to know which one is newer than the other.
+ *
+ * @param sdk_api_level The SDK version int. This must be less than 10000.
+ * @return The corresponding vendor API level of the SDK version. -1 if the SDK
+ * version is invalid or 10000.
+ */
+int vendor_api_level_of(int sdk_api_level);
+
+/**
+ * @brief Find corresponding SDK API version from a vendor API level.
+ *
+ * @param vendor_api_level The vendor API level int.
+ * @return The corresponding SDK API version of the vendor API level. -1 if the
+ * vendor API level is invalid.
+ */
+int sdk_api_level_of(int vendor_api_level);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/libvendorsupport/libvendorsupport.map.txt b/libvendorsupport/libvendorsupport.map.txt
new file mode 100644
index 0000000..9a23b94
--- /dev/null
+++ b/libvendorsupport/libvendorsupport.map.txt
@@ -0,0 +1,7 @@
+LIBVENDORSUPPORT {
+  global:
+    vendor_api_level_of; # llndk systemapi
+    sdk_api_level_of; # llndk systemapi
+  local:
+    *;
+};
diff --git a/libvendorsupport/tests/Android.bp b/libvendorsupport/tests/Android.bp
new file mode 100644
index 0000000..42e3371
--- /dev/null
+++ b/libvendorsupport/tests/Android.bp
@@ -0,0 +1,33 @@
+// Copyright (C) 2024 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_test {
+    name: "libvendorsupport-tests",
+    srcs: [
+        "version_props_test.cpp",
+    ],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+    shared_libs: [
+        "libvendorsupport",
+    ],
+    test_suites: ["general-tests"],
+}
+
diff --git a/libvendorsupport/tests/version_props_test.cpp b/libvendorsupport/tests/version_props_test.cpp
new file mode 100644
index 0000000..538a2e2
--- /dev/null
+++ b/libvendorsupport/tests/version_props_test.cpp
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 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 <gtest/gtest.h>
+
+#include <vendorsupport/api_level.h>
+
+using namespace std;
+
+namespace {
+
+TEST(vendorsupport, get_corresponding_vendor_api_level) {
+    ASSERT_EQ(__ANDROID_API_U__, vendor_api_level_of(__ANDROID_API_U__));
+    ASSERT_EQ(202404, vendor_api_level_of(__ANDROID_API_V__));
+    ASSERT_EQ(__INVALID_API_LEVEL, vendor_api_level_of(__ANDROID_API_FUTURE__));
+}
+
+TEST(vendorsupport, get_corresponding_sdk_api_level) {
+    ASSERT_EQ(__ANDROID_API_U__, sdk_api_level_of(__ANDROID_API_U__));
+    ASSERT_EQ(__ANDROID_API_V__, sdk_api_level_of(202404));
+    ASSERT_EQ(__INVALID_API_LEVEL, sdk_api_level_of(__ANDROID_VENDOR_API_MAX__));
+    ASSERT_EQ(__INVALID_API_LEVEL, sdk_api_level_of(35));
+}
+
+}  // namespace
\ No newline at end of file
diff --git a/libvendorsupport/version_props.c b/libvendorsupport/version_props.c
new file mode 100644
index 0000000..4d0e45e
--- /dev/null
+++ b/libvendorsupport/version_props.c
@@ -0,0 +1,41 @@
+// Copyright (C) 2024 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 "api_level.h"
+
+#include <log/log.h>
+
+int vendor_api_level_of(int sdk_api_level) {
+    if (sdk_api_level < __ANDROID_API_V__) {
+        return sdk_api_level;
+    }
+    // In Android V, vendor API level started with version 202404.
+    // The calculation assumes that the SDK api level bumps once a year.
+    if (sdk_api_level < __ANDROID_API_FUTURE__) {
+        return 202404 + ((sdk_api_level - __ANDROID_API_V__) * 100);
+    }
+    ALOGE("The SDK version must be less than 10000: %d", sdk_api_level);
+    return __INVALID_API_LEVEL;
+}
+
+int sdk_api_level_of(int vendor_api_level) {
+    if (vendor_api_level < __ANDROID_API_V__) {
+        return vendor_api_level;
+    }
+    if (vendor_api_level >= 202404 && vendor_api_level < __ANDROID_VENDOR_API_MAX__) {
+        return (vendor_api_level - 202404) / 100 + __ANDROID_API_V__;
+    }
+    ALOGE("Unexpected vendor api level: %d", vendor_api_level);
+    return __INVALID_API_LEVEL;
+}
diff --git a/property_service/libpropertyinfoparser/Android.bp b/property_service/libpropertyinfoparser/Android.bp
index 87646f9..b4a16d3 100644
--- a/property_service/libpropertyinfoparser/Android.bp
+++ b/property_service/libpropertyinfoparser/Android.bp
@@ -25,4 +25,8 @@
         },
     },
     export_include_dirs: ["include"],
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.runtime",
+    ],
 }
diff --git a/rootdir/init.rc b/rootdir/init.rc
index 12c46eb..6b783fa 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -782,7 +782,6 @@
     mkdir /data/misc/vpn 0770 system vpn
     mkdir /data/misc/shared_relro 0771 shared_relro shared_relro
     mkdir /data/misc/systemkeys 0700 system system
-    mkdir /data/misc/threadnetwork 0770 thread_network thread_network
     mkdir /data/misc/wifi 0770 wifi wifi
     mkdir /data/misc/wifi/sockets 0770 wifi wifi
     mkdir /data/misc/wifi/wpa_supplicant 0770 wifi wifi
@@ -814,6 +813,7 @@
     mkdir /data/misc/apexdata 0711 root root
     mkdir /data/misc/apexrollback 0700 root root
     mkdir /data/misc/appcompat/ 0700 system system
+    mkdir /data/misc/uprobestats-configs/ 0777 uprobestats uprobestats
     mkdir /data/misc/snapshotctl_log 0755 root root
     # create location to store pre-reboot information
     mkdir /data/misc/prereboot 0700 system system
diff --git a/trusty/libtrusty/tipc-test/tipc_test.c b/trusty/libtrusty/tipc-test/tipc_test.c
index 0f50787..dabe118 100644
--- a/trusty/libtrusty/tipc-test/tipc_test.c
+++ b/trusty/libtrusty/tipc-test/tipc_test.c
@@ -22,8 +22,10 @@
 #include <unistd.h>
 #include <getopt.h>
 #define __USE_GNU
+#include <inttypes.h>
 #include <sys/mman.h>
 #include <sys/uio.h>
+#include <time.h>
 
 #include <BufferAllocator/BufferAllocatorWrapper.h>
 
@@ -31,8 +33,27 @@
 
 #define TIPC_DEFAULT_DEVNAME "/dev/trusty-ipc-dev0"
 
-static const char *dev_name = NULL;
-static const char *test_name = NULL;
+/* clang-format off */
+#define BENCH_RESULT_TPL                                    \
+"{"                                                         \
+"    \"schema_version\": 3,"                                \
+"    \"suite_name\": \"crypto\","                           \
+"    \"bench_name\": \"%s\","                               \
+"    \"results\": ["                                        \
+"        {"                                                 \
+"            \"metric_name\": \"time_micro_sec\","          \
+"            \"min\": \"%" PRId64 "\","                     \
+"            \"max\": \"%" PRId64 "\","                     \
+"            \"avg\": \"%" PRId64 "\","                     \
+"            \"cold\": \"%" PRId64 "\","                    \
+"            \"raw_min\": %" PRId64 ","                     \
+"            \"raw_max\": %" PRId64 ","                     \
+"            \"raw_avg\": %" PRId64 ","                     \
+"            \"raw_cold\": %" PRId64 ""                     \
+"        },"                                                \
+"    ]"                                                     \
+"}"
+/* clang-format on */
 
 static const char *uuid_name = "com.android.ipc-unittest.srv.uuid";
 static const char *echo_name = "com.android.ipc-unittest.srv.echo";
@@ -46,7 +67,7 @@
 static const char* receiver_name = "com.android.trusty.memref.receiver";
 static const size_t memref_chunk_size = 4096;
 
-static const char* _sopts = "hsvDS:t:r:m:b:";
+static const char* _sopts = "hsvDS:t:r:m:b:B:";
 /* clang-format off */
 static const struct option _lopts[] =  {
     {"help",    no_argument,       0, 'h'},
@@ -57,6 +78,8 @@
     {"repeat",  required_argument, 0, 'r'},
     {"burst",   required_argument, 0, 'b'},
     {"msgsize", required_argument, 0, 'm'},
+    {"test",    required_argument, 0, 't'},
+    {"bench",   required_argument, 0, 'B'},
     {0, 0, 0, 0}
 };
 /* clang-format on */
@@ -74,6 +97,7 @@
         "  -m, --msgsize size    max message size\n"
         "  -v, --variable        variable message size\n"
         "  -s, --silent          silent\n"
+        "  -B, --bench           Run as Benchmark N times\n"
         "\n";
 
 static const char* usage_long =
@@ -96,12 +120,34 @@
         "   send-fd      - transmit dma_buf to trusty, use as shm\n"
         "\n";
 
-static uint opt_repeat  = 1;
-static uint opt_msgsize = 32;
-static uint opt_msgburst = 32;
-static bool opt_variable = false;
-static bool opt_silent = false;
-static char* srv_name = NULL;
+struct tipc_test_params {
+    uint repeat;
+    uint msgsize;
+    uint msgburst;
+    bool variable;
+    bool silent;
+    uint bench;
+    char* srv_name;
+    char* dev_name;
+    char* test_name;
+};
+typedef int (*tipc_test_func_t)(const struct tipc_test_params*);
+
+struct tipc_test_def {
+    char* test_name;
+    tipc_test_func_t func;
+};
+
+static void init_params(struct tipc_test_params* params) {
+    params->repeat = 1;
+    params->msgsize = 32;
+    params->msgburst = 32;
+    params->variable = false;
+    params->silent = false;
+    params->bench = 0;
+    params->srv_name = NULL;
+    params->test_name = NULL;
+}
 
 static void print_usage_and_exit(const char *prog, int code, bool verbose)
 {
@@ -110,8 +156,7 @@
     exit(code);
 }
 
-static void parse_options(int argc, char **argv)
-{
+static void parse_options(int argc, char** argv, struct tipc_test_params* params) {
     int c;
     int oidx = 0;
 
@@ -121,35 +166,39 @@
 
         switch (c) {
             case 'D':
-                dev_name = strdup(optarg);
+                params->dev_name = strdup(optarg);
                 break;
 
             case 'S':
-                srv_name = strdup(optarg);
+                params->srv_name = strdup(optarg);
                 break;
 
             case 't':
-                test_name = strdup(optarg);
+                params->test_name = strdup(optarg);
                 break;
 
             case 'v':
-                opt_variable = true;
+                params->variable = true;
                 break;
 
             case 'r':
-                opt_repeat = atoi(optarg);
+                params->repeat = atoi(optarg);
                 break;
 
             case 'm':
-                opt_msgsize = atoi(optarg);
+                params->msgsize = atoi(optarg);
                 break;
 
             case 'b':
-                opt_msgburst = atoi(optarg);
+                params->msgburst = atoi(optarg);
                 break;
 
             case 's':
-                opt_silent = true;
+                params->silent = true;
+                break;
+
+            case 'B':
+                params->bench = atoi(optarg);
                 break;
 
             case 'h':
@@ -162,32 +211,31 @@
     }
 }
 
-static int connect_test(uint repeat)
-{
+static int connect_test(const struct tipc_test_params* params) {
     uint i;
     int echo_fd;
     int dsink_fd;
     int custom_fd;
 
-    if (!opt_silent) {
-        printf("%s: repeat = %u\n", __func__, repeat);
+    if (!params->silent) {
+        printf("%s: repeat = %u\n", __func__, params->repeat);
     }
 
-    for (i = 0; i < repeat; i++) {
-        if (srv_name) {
-            custom_fd = tipc_connect(dev_name, srv_name);
+    for (i = 0; i < params->repeat; i++) {
+        if (params->srv_name) {
+            custom_fd = tipc_connect(params->dev_name, params->srv_name);
             if (custom_fd < 0) {
-                fprintf(stderr, "Failed to connect to '%s' service\n", srv_name);
+                fprintf(stderr, "Failed to connect to '%s' service\n", params->srv_name);
             }
             if (custom_fd >= 0) {
                 tipc_close(custom_fd);
             }
         } else {
-            echo_fd = tipc_connect(dev_name, echo_name);
+            echo_fd = tipc_connect(params->dev_name, echo_name);
             if (echo_fd < 0) {
                 fprintf(stderr, "Failed to connect to '%s' service\n", "echo");
             }
-            dsink_fd = tipc_connect(dev_name, datasink_name);
+            dsink_fd = tipc_connect(params->dev_name, datasink_name);
             if (dsink_fd < 0) {
                 fprintf(stderr, "Failed to connect to '%s' service\n", "datasink");
             }
@@ -201,79 +249,75 @@
         }
     }
 
-    if (!opt_silent) {
+    if (!params->silent) {
         printf("%s: done\n", __func__);
     }
 
     return 0;
 }
 
-static int connect_foo(uint repeat)
-{
+static int connect_foo(const struct tipc_test_params* params) {
     uint i;
     int fd;
 
-    if (!opt_silent) {
-        printf("%s: repeat = %u\n", __func__, repeat);
+    if (!params->silent) {
+        printf("%s: repeat = %u\n", __func__, params->repeat);
     }
 
-    for (i = 0; i < repeat; i++) {
-        fd = tipc_connect(dev_name, "foo");
+    for (i = 0; i < params->repeat; i++) {
+        fd = tipc_connect(params->dev_name, "foo");
         if (fd >= 0) {
             fprintf(stderr, "succeeded to connect to '%s' service\n", "foo");
             tipc_close(fd);
         }
     }
 
-    if (!opt_silent) {
+    if (!params->silent) {
         printf("%s: done\n", __func__);
     }
 
     return 0;
 }
 
-
-static int closer1_test(uint repeat)
-{
+static int closer1_test(const struct tipc_test_params* params) {
     uint i;
     int fd;
 
-    if (!opt_silent) {
-        printf("%s: repeat = %u\n", __func__, repeat);
+    if (!params->silent) {
+        printf("%s: repeat = %u\n", __func__, params->repeat);
     }
 
-    for (i = 0; i < repeat; i++) {
-        fd = tipc_connect(dev_name, closer1_name);
+    for (i = 0; i < params->repeat; i++) {
+        fd = tipc_connect(params->dev_name, closer1_name);
         if (fd < 0) {
             fprintf(stderr, "Failed to connect to '%s' service\n", "closer1");
             continue;
         }
-        if (!opt_silent) {
+        if (!params->silent) {
             printf("%s: connected\n", __func__);
         }
         tipc_close(fd);
     }
 
-    if (!opt_silent) {
+    if (!params->silent) {
         printf("%s: done\n", __func__);
     }
 
     return 0;
 }
 
-static int closer2_test(uint repeat)
-{
+static int closer2_test(const struct tipc_test_params* params) {
     uint i;
     int fd;
 
-    if (!opt_silent) {
-        printf("%s: repeat = %u\n", __func__, repeat);
+    if (!params->silent) {
+        printf("%s: repeat = %u\n", __func__, params->repeat);
     }
 
-    for (i = 0; i < repeat; i++) {
-        fd = tipc_connect(dev_name, closer2_name);
+    for (i = 0; i < params->repeat; i++) {
+        fd = tipc_connect(params->dev_name, closer2_name);
         if (fd < 0) {
-            if (!opt_silent) {
+            if (!params->silent) {
                 printf("failed to connect to '%s' service\n", "closer2");
             }
         } else {
@@ -283,38 +327,37 @@
         }
     }
 
-    if (!opt_silent) {
+    if (!params->silent) {
         printf("%s: done\n", __func__);
     }
 
     return 0;
 }
 
-static int closer3_test(uint repeat)
-{
+static int closer3_test(const struct tipc_test_params* params) {
     uint i, j;
     ssize_t rc;
     int fd[4];
     char buf[64];
 
-    if (!opt_silent) {
-        printf("%s: repeat = %u\n", __func__, repeat);
+    if (!params->silent) {
+        printf("%s: repeat = %u\n", __func__, params->repeat);
     }
 
-    for (i = 0; i < repeat; i++) {
+    for (i = 0; i < params->repeat; i++) {
         /* open 4 connections to closer3 service */
         for (j = 0; j < 4; j++) {
-            fd[j] = tipc_connect(dev_name, closer3_name);
+            fd[j] = tipc_connect(params->dev_name, closer3_name);
             if (fd[j] < 0) {
                 fprintf(stderr, "fd[%d]: failed to connect to '%s' service\n", j, "closer3");
             } else {
-                if (!opt_silent) {
+                if (!params->silent) {
                     printf("%s: fd[%d]=%d: connected\n", __func__, j, fd[j]);
                 }
                 memset(buf, i + j, sizeof(buf));
                 rc = write(fd[j], buf, sizeof(buf));
                 if (rc != sizeof(buf)) {
-                    if (!opt_silent) {
+                    if (!params->silent) {
                         printf("%s: fd[%d]=%d: write returned  = %zd\n", __func__, j, fd[j], rc);
                     }
                     perror("closer3_test: write");
@@ -330,7 +373,7 @@
             if (fd[j] < 0) continue;
             rc = write(fd[j], buf, sizeof(buf));
             if (rc != sizeof(buf)) {
-                if (!opt_silent) {
+                if (!params->silent) {
                     printf("%s: fd[%d]=%d: write returned = %zd\n", __func__, j, fd[j], rc);
                 }
                 perror("closer3_test: write");
@@ -345,38 +388,36 @@
         }
     }
 
-    if (!opt_silent) {
+    if (!params->silent) {
         printf("%s: done\n", __func__);
     }
 
     return 0;
 }
 
-
-static int echo_test(uint repeat, uint msgsz, bool var)
-{
+static int echo_test(const struct tipc_test_params* params) {
     uint i;
     ssize_t rc;
     size_t msg_len;
     int echo_fd = -1;
-    char tx_buf[msgsz];
-    char rx_buf[msgsz];
+    char tx_buf[params->msgsize];
+    char rx_buf[params->msgsize];
 
-    if (!opt_silent) {
-        printf("%s: repeat %u: msgsz %u: variable %s\n", __func__, repeat, msgsz,
-               var ? "true" : "false");
+    if (!params->silent) {
+        printf("%s: repeat %u: params->msgsize %u: variable %s\n", __func__, params->repeat,
+               params->msgsize, params->variable ? "true" : "false");
     }
 
-    echo_fd = tipc_connect(dev_name, echo_name);
+    echo_fd = tipc_connect(params->dev_name, echo_name);
     if (echo_fd < 0) {
         fprintf(stderr, "Failed to connect to service\n");
         return echo_fd;
     }
 
-    for (i = 0; i < repeat; i++) {
-        msg_len = msgsz;
-        if (opt_variable && msgsz) {
-            msg_len = rand() % msgsz;
+    for (i = 0; i < params->repeat; i++) {
+        msg_len = params->msgsize;
+        if (params->variable && params->msgsize) {
+            msg_len = rand() % params->msgsize;
         }
 
         memset(tx_buf, i + 1, msg_len);
@@ -406,37 +447,37 @@
 
     tipc_close(echo_fd);
 
-    if (!opt_silent) {
+    if (!params->silent) {
         printf("%s: done\n", __func__);
     }
 
     return 0;
 }
 
-static int burst_write_test(uint repeat, uint msgburst, uint msgsz, bool var)
-{
+static int burst_write_test(const struct tipc_test_params* params) {
     int fd;
     uint i, j;
     ssize_t rc;
     size_t msg_len;
-    char tx_buf[msgsz];
+    char tx_buf[params->msgsize];
 
-    if (!opt_silent) {
-        printf("%s: repeat %u: burst %u: msgsz %u: variable %s\n", __func__, repeat, msgburst,
-               msgsz, var ? "true" : "false");
+    if (!params->silent) {
+        printf("%s: repeat %u: burst %u: params->msgsize %u: variable %s\n", __func__,
+               params->repeat, params->msgburst, params->msgsize,
+               params->variable ? "true" : "false");
     }
 
-    for (i = 0; i < repeat; i++) {
-        fd = tipc_connect(dev_name, datasink_name);
+    for (i = 0; i < params->repeat; i++) {
+        fd = tipc_connect(params->dev_name, datasink_name);
         if (fd < 0) {
             fprintf(stderr, "Failed to connect to '%s' service\n", "datasink");
             break;
         }
 
-        for (j = 0; j < msgburst; j++) {
-            msg_len = msgsz;
-            if (var && msgsz) {
-                msg_len = rand() % msgsz;
+        for (j = 0; j < params->msgburst; j++) {
+            msg_len = params->msgsize;
+            if (params->variable && params->msgsize) {
+                msg_len = rand() % params->msgsize;
             }
 
             memset(tx_buf, i + 1, msg_len);
@@ -450,23 +491,21 @@
         tipc_close(fd);
     }
 
-    if (!opt_silent) {
+    if (!params->silent) {
         printf("%s: done\n", __func__);
     }
 
     return 0;
 }
 
-
-static int _wait_for_msg(int fd, uint msgsz, int timeout)
-{
+static int _wait_for_msg(int fd, int timeout, const struct tipc_test_params* params) {
     int rc;
     fd_set rfds;
     uint msgcnt = 0;
-    char rx_buf[msgsz];
+    char rx_buf[params->msgsize];
     struct timeval tv;
 
-    if (!opt_silent) {
+    if (!params->silent) {
         printf("waiting (%d) for msg\n", timeout);
     }
 
@@ -480,7 +519,7 @@
         rc = select(fd + 1, &rfds, NULL, NULL, &tv);
 
         if (rc == 0) {
-            if (!opt_silent) {
+            if (!params->silent) {
                 printf("select timedout\n");
             }
             break;
@@ -502,42 +541,40 @@
         }
     }
 
-    if (!opt_silent) {
+    if (!params->silent) {
         printf("got %u messages\n", msgcnt);
     }
 
     return 0;
 }
 
-
-static int select_test(uint repeat, uint msgburst, uint msgsz)
-{
+static int select_test(const struct tipc_test_params* params) {
     int fd;
     uint i, j;
     ssize_t rc;
-    char tx_buf[msgsz];
+    char tx_buf[params->msgsize];
 
-    if (!opt_silent) {
-        printf("%s: repeat %u\n", __func__, repeat);
+    if (!params->silent) {
+        printf("%s: repeat %u\n", __func__, params->repeat);
     }
 
-    fd = tipc_connect(dev_name, echo_name);
+    fd = tipc_connect(params->dev_name, echo_name);
     if (fd < 0) {
         fprintf(stderr, "Failed to connect to '%s' service\n", "echo");
         return fd;
     }
 
-    for (i = 0; i < repeat; i++) {
-        _wait_for_msg(fd, msgsz, 1);
+    for (i = 0; i < params->repeat; i++) {
+        _wait_for_msg(fd, 1, params);
 
-        if (!opt_silent) {
-            printf("sending burst: %u msg\n", msgburst);
+        if (!params->silent) {
+            printf("sending burst: %u msg\n", params->msgburst);
         }
 
-        for (j = 0; j < msgburst; j++) {
-            memset(tx_buf, i + j, msgsz);
-            rc = write(fd, tx_buf, msgsz);
-            if ((size_t)rc != msgsz) {
+        for (j = 0; j < params->msgburst; j++) {
+            memset(tx_buf, i + j, params->msgsize);
+            rc = write(fd, tx_buf, params->msgsize);
+            if ((size_t)rc != params->msgsize) {
                 perror("burst_test: write");
                 break;
             }
@@ -546,37 +583,36 @@
 
     tipc_close(fd);
 
-    if (!opt_silent) {
+    if (!params->silent) {
         printf("%s: done\n", __func__);
     }
 
     return 0;
 }
 
-static int blocked_read_test(uint repeat)
-{
+static int blocked_read_test(const struct tipc_test_params* params) {
     int fd;
     uint i;
     ssize_t rc;
     char rx_buf[512];
 
-    if (!opt_silent) {
-        printf("%s: repeat %u\n", __func__, repeat);
+    if (!params->silent) {
+        printf("%s: repeat %u\n", __func__, params->repeat);
     }
 
-    fd = tipc_connect(dev_name, echo_name);
+    fd = tipc_connect(params->dev_name, echo_name);
     if (fd < 0) {
         fprintf(stderr, "Failed to connect to '%s' service\n", "echo");
         return fd;
     }
 
-    for (i = 0; i < repeat; i++) {
+    for (i = 0; i < params->repeat; i++) {
         rc = read(fd, rx_buf, sizeof(rx_buf));
         if (rc < 0) {
             perror("select_test: read");
             break;
         } else {
-            if (!opt_silent) {
+            if (!params->silent) {
                 printf("got %zd bytes\n", rc);
             }
         }
@@ -584,15 +620,14 @@
 
     tipc_close(fd);
 
-    if (!opt_silent) {
+    if (!params->silent) {
         printf("%s: done\n", __func__);
     }
 
     return 0;
 }
 
-static int ta2ta_ipc_test(void)
-{
+static int ta2ta_ipc_test(const struct tipc_test_params* params) {
     enum test_message_header {
         TEST_PASSED = 0,
         TEST_FAILED = 1,
@@ -604,11 +639,11 @@
     int ret;
     unsigned char rx_buf[256];
 
-    if (!opt_silent) {
+    if (!params->silent) {
         printf("%s:\n", __func__);
     }
 
-    fd = tipc_connect(dev_name, main_ctrl_name);
+    fd = tipc_connect(params->dev_name, main_ctrl_name);
     if (fd < 0) {
         fprintf(stderr, "Failed to connect to '%s' service\n", "main_ctrl");
         return fd;
@@ -658,13 +693,12 @@
            uuid->clock_seq_and_node[7]);
 }
 
-static int dev_uuid_test(void)
-{
+static int dev_uuid_test(const struct tipc_test_params* params) {
     int fd;
     ssize_t rc;
     uuid_t uuid;
 
-    fd = tipc_connect(dev_name, uuid_name);
+    fd = tipc_connect(params->dev_name, uuid_name);
     if (fd < 0) {
         fprintf(stderr, "Failed to connect to '%s' service\n", "uuid");
         return fd;
@@ -677,7 +711,7 @@
     } else if (rc != sizeof(uuid)) {
         fprintf(stderr, "unexpected uuid size (%d vs. %d)\n", (int)rc, (int)sizeof(uuid));
     } else {
-        print_uuid(dev_name, &uuid);
+        print_uuid(params->dev_name, &uuid);
     }
 
     tipc_close(fd);
@@ -685,61 +719,58 @@
     return 0;
 }
 
-static int ta_access_test(void)
-{
+static int ta_access_test(const struct tipc_test_params* params) {
     int fd;
 
-    if (!opt_silent) {
+    if (!params->silent) {
         printf("%s:\n", __func__);
     }
 
-    fd = tipc_connect(dev_name, ta_only_name);
+    fd = tipc_connect(params->dev_name, ta_only_name);
     if (fd >= 0) {
         fprintf(stderr, "Succeed to connect to '%s' service\n", "ta_only");
         tipc_close(fd);
     }
 
-    fd = tipc_connect(dev_name, ns_only_name);
+    fd = tipc_connect(params->dev_name, ns_only_name);
     if (fd < 0) {
         fprintf(stderr, "Failed to connect to '%s' service\n", "ns_only");
         return fd;
     }
     tipc_close(fd);
 
-    if (!opt_silent) {
+    if (!params->silent) {
         printf("%s: done\n", __func__);
     }
 
     return 0;
 }
 
-
-static int writev_test(uint repeat, uint msgsz, bool var)
-{
+static int writev_test(const struct tipc_test_params* params) {
     uint i;
     ssize_t rc;
     size_t msg_len;
     int echo_fd = -1;
-    char tx0_buf[msgsz];
-    char tx1_buf[msgsz];
-    char rx_buf[msgsz];
+    char tx0_buf[params->msgsize];
+    char tx1_buf[params->msgsize];
+    char rx_buf[params->msgsize];
     struct iovec iovs[2] = {{tx0_buf, 0}, {tx1_buf, 0}};
 
-    if (!opt_silent) {
-        printf("%s: repeat %u: msgsz %u: variable %s\n", __func__, repeat, msgsz,
-               var ? "true" : "false");
+    if (!params->silent) {
+        printf("%s: repeat %u: params->msgsize %u: variable %s\n", __func__, params->repeat,
+               params->msgsize, params->variable ? "true" : "false");
     }
 
-    echo_fd = tipc_connect(dev_name, echo_name);
+    echo_fd = tipc_connect(params->dev_name, echo_name);
     if (echo_fd < 0) {
         fprintf(stderr, "Failed to connect to service\n");
         return echo_fd;
     }
 
-    for (i = 0; i < repeat; i++) {
-        msg_len = msgsz;
-        if (opt_variable && msgsz) {
-            msg_len = rand() % msgsz;
+    for (i = 0; i < params->repeat; i++) {
+        msg_len = params->msgsize;
+        if (params->variable && params->msgsize) {
+            msg_len = rand() % params->msgsize;
         }
 
         iovs[0].iov_len = msg_len / 3;
@@ -786,39 +817,38 @@
 
     tipc_close(echo_fd);
 
-    if (!opt_silent) {
+    if (!params->silent) {
         printf("%s: done\n", __func__);
     }
 
     return 0;
 }
 
-static int readv_test(uint repeat, uint msgsz, bool var)
-{
+static int readv_test(const struct tipc_test_params* params) {
     uint i;
     ssize_t rc;
     size_t msg_len;
     int echo_fd = -1;
-    char tx_buf[msgsz];
-    char rx0_buf[msgsz];
-    char rx1_buf[msgsz];
+    char tx_buf[params->msgsize];
+    char rx0_buf[params->msgsize];
+    char rx1_buf[params->msgsize];
     struct iovec iovs[2] = {{rx0_buf, 0}, {rx1_buf, 0}};
 
-    if (!opt_silent) {
-        printf("%s: repeat %u: msgsz %u: variable %s\n", __func__, repeat, msgsz,
-               var ? "true" : "false");
+    if (!params->silent) {
+        printf("%s: repeat %u: params->msgsize %u: variable %s\n", __func__, params->repeat,
+               params->msgsize, params->variable ? "true" : "false");
     }
 
-    echo_fd = tipc_connect(dev_name, echo_name);
+    echo_fd = tipc_connect(params->dev_name, echo_name);
     if (echo_fd < 0) {
         fprintf(stderr, "Failed to connect to service\n");
         return echo_fd;
     }
 
-    for (i = 0; i < repeat; i++) {
-        msg_len = msgsz;
-        if (opt_variable && msgsz) {
-            msg_len = rand() % msgsz;
+    for (i = 0; i < params->repeat; i++) {
+        msg_len = params->msgsize;
+        if (params->variable && params->msgsize) {
+            msg_len = rand() % params->msgsize;
         }
 
         iovs[0].iov_len = msg_len / 3;
@@ -865,14 +895,14 @@
 
     tipc_close(echo_fd);
 
-    if (!opt_silent) {
+    if (!params->silent) {
         printf("%s: done\n", __func__);
     }
 
     return 0;
 }
 
-static int send_fd_test(void) {
+static int send_fd_test(const struct tipc_test_params* params) {
     int ret;
     int dma_buf = -1;
     int fd = -1;
@@ -881,7 +911,7 @@
 
     const size_t num_chunks = 10;
 
-    fd = tipc_connect(dev_name, receiver_name);
+    fd = tipc_connect(params->dev_name, receiver_name);
     if (fd < 0) {
         fprintf(stderr, "Failed to connect to test support TA - is it missing?\n");
         ret = -1;
@@ -948,6 +978,73 @@
     return ret;
 }
 
+uint64_t get_time_us(void) {
+    struct timespec spec;
+
+    clock_gettime(CLOCK_MONOTONIC, &spec);
+    return spec.tv_sec * 1000000 + spec.tv_nsec / 1000;
+}
+
+static const struct tipc_test_def tipc_tests[] = {
+        {"connect", connect_test},
+        {"connect_foo", connect_foo},
+        {"burst_write", burst_write_test},
+        {"select", select_test},
+        {"blocked_read", blocked_read_test},
+        {"closer1", closer1_test},
+        {"closer2", closer2_test},
+        {"closer3", closer3_test},
+        {"echo", echo_test},
+        {"ta2ta-ipc", ta2ta_ipc_test},
+        {"dev-uuid", dev_uuid_test},
+        {"ta-access", ta_access_test},
+        {"writev", writev_test},
+        {"readv", readv_test},
+        {"send-fd", send_fd_test},
+};
+
+tipc_test_func_t get_test_function(const struct tipc_test_params* params) {
+    for (size_t i = 0; i < sizeof(tipc_tests) / sizeof(tipc_tests[0]); i++) {
+        if (strcmp(params->test_name, tipc_tests[i].test_name) == 0) {
+            return tipc_tests[i].func;
+        }
+    }
+    fprintf(stderr, "Unrecognized test name '%s'\n", params->test_name);
+    exit(1);
+}
+
+static int run_as_bench(const struct tipc_test_params* params) {
+    int rc = 0;
+    int64_t min = INT64_MAX;
+    int64_t max = 0;
+    int64_t avg = 0;
+    int64_t cold = 0;
+
+    uint64_t start;
+    uint64_t end;
+
+    tipc_test_func_t test = get_test_function(params);
+
+    for (size_t i = 0; (i < params->bench + 1) && rc == 0; ++i) {
+        start = get_time_us();
+        rc |= test(params);
+        end = get_time_us();
+        int64_t t = end - start;
+
+        if (i == 0) {
+            cold = t;
+        } else {
+            min = (t < min) ? t : min;
+            max = (t > max) ? t : max;
+            avg += t;
+        }
+    }
+    avg /= params->bench;
+
+    fprintf(stderr, BENCH_RESULT_TPL, params->test_name, min, max, avg, cold, min, max, avg, cold);
+    return rc;
+}
+
 int main(int argc, char **argv)
 {
     int rc = 0;
@@ -955,52 +1052,25 @@
     if (argc <= 1) {
         print_usage_and_exit(argv[0], EXIT_FAILURE, false);
     }
+    struct tipc_test_params params;
+    init_params(&params);
+    parse_options(argc, argv, &params);
 
-    parse_options(argc, argv);
-
-    if (!dev_name) {
-        dev_name = TIPC_DEFAULT_DEVNAME;
+    if (!params.dev_name) {
+        params.dev_name = TIPC_DEFAULT_DEVNAME;
     }
 
-    if (!test_name) {
+    if (!params.test_name) {
         fprintf(stderr, "need a Test to run\n");
         print_usage_and_exit(argv[0], EXIT_FAILURE, true);
     }
 
-    if (strcmp(test_name, "connect") == 0) {
-        rc = connect_test(opt_repeat);
-    } else if (strcmp(test_name, "connect_foo") == 0) {
-        rc = connect_foo(opt_repeat);
-    } else if (strcmp(test_name, "burst_write") == 0) {
-        rc = burst_write_test(opt_repeat, opt_msgburst, opt_msgsize, opt_variable);
-    } else if (strcmp(test_name, "select") == 0) {
-        rc = select_test(opt_repeat, opt_msgburst, opt_msgsize);
-    } else if (strcmp(test_name, "blocked_read") == 0) {
-        rc = blocked_read_test(opt_repeat);
-    } else if (strcmp(test_name, "closer1") == 0) {
-        rc = closer1_test(opt_repeat);
-    } else if (strcmp(test_name, "closer2") == 0) {
-        rc = closer2_test(opt_repeat);
-    } else if (strcmp(test_name, "closer3") == 0) {
-        rc = closer3_test(opt_repeat);
-    } else if (strcmp(test_name, "echo") == 0) {
-        rc = echo_test(opt_repeat, opt_msgsize, opt_variable);
-    } else if (strcmp(test_name, "ta2ta-ipc") == 0) {
-        rc = ta2ta_ipc_test();
-    } else if (strcmp(test_name, "dev-uuid") == 0) {
-        rc = dev_uuid_test();
-    } else if (strcmp(test_name, "ta-access") == 0) {
-        rc = ta_access_test();
-    } else if (strcmp(test_name, "writev") == 0) {
-        rc = writev_test(opt_repeat, opt_msgsize, opt_variable);
-    } else if (strcmp(test_name, "readv") == 0) {
-        rc = readv_test(opt_repeat, opt_msgsize, opt_variable);
-    } else if (strcmp(test_name, "send-fd") == 0) {
-        rc = send_fd_test();
+    if (params.bench > 0) {
+        rc = run_as_bench(&params);
+        params.bench = 0;
     } else {
-        fprintf(stderr, "Unrecognized test name '%s'\n", test_name);
-        print_usage_and_exit(argv[0], EXIT_FAILURE, true);
+        tipc_test_func_t test = get_test_function(&params);
+        rc = test(&params);
     }
-
     return rc == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
 }
diff --git a/trusty/secretkeeper/Android.bp b/trusty/secretkeeper/Android.bp
new file mode 100644
index 0000000..f6b740a
--- /dev/null
+++ b/trusty/secretkeeper/Android.bp
@@ -0,0 +1,96 @@
+//
+// 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_binary {
+    name: "android.hardware.security.secretkeeper.trusty",
+    relative_install_path: "hw",
+    vendor: true,
+    init_rc: ["android.hardware.security.secretkeeper.trusty.rc"],
+    vintf_fragments: ["android.hardware.security.secretkeeper.trusty.xml"],
+    srcs: [
+        "src/hal_main.rs",
+    ],
+    rustlibs: [
+        "libandroid_logger",
+        "libbinder_rs",
+        "libauthgraph_hal",
+        "libtrusty-rs",
+        "liblibc",
+        "liblog_rust",
+        "libsecretkeeper_hal",
+    ],
+    defaults: [
+        "secretkeeper_use_latest_hal_aidl_rust",
+    ],
+    prefer_rlib: true,
+}
+
+cc_defaults {
+    name: "trusty_secretkeeper_fuzz_defaults",
+    srcs: [":trusty_tipc_fuzzer"],
+    fuzz_config: {
+        cc: [
+            "alanstokes@google.com",
+            "drysdale@google.com",
+            "shikhapanwar@google.com",
+        ],
+        componentid: 867125,
+        // TODO: add Secretkeeper hotlist
+        // hotlists: [""],
+    },
+}
+
+cc_fuzz {
+    name: "trusty_secretkeeper_sk_fuzzer",
+    defaults: [
+        "trusty_fuzzer_defaults",
+        "trusty_secretkeeper_fuzz_defaults",
+    ],
+    cflags: [
+        "-DTRUSTY_APP_PORT=\"com.android.trusty.secretkeeper\"",
+        "-DTRUSTY_APP_UUID=\"4582bf12-1f7d-4830-9be5-36e6bd91c2c6\"",
+        "-DTRUSTY_APP_FILENAME=\"secretkeeper_app.syms.elf\"",
+    ],
+}
+
+cc_fuzz {
+    name: "trusty_secretkeeper_ag_fuzzer",
+    defaults: [
+        "trusty_fuzzer_defaults",
+        "trusty_secretkeeper_fuzz_defaults",
+    ],
+    cflags: [
+        "-DTRUSTY_APP_PORT=\"com.android.trusty.secretkeeper.authgraph\"",
+        "-DTRUSTY_APP_UUID=\"4582bf12-1f7d-4830-9be5-36e6bd91c2c6\"",
+        "-DTRUSTY_APP_FILENAME=\"secretkeeper_app.syms.elf\"",
+    ],
+}
+
+cc_fuzz {
+    name: "trusty_secretkeeper_bl_fuzzer",
+    defaults: [
+        "trusty_fuzzer_defaults",
+        "trusty_secretkeeper_fuzz_defaults",
+    ],
+    cflags: [
+        "-DTRUSTY_APP_PORT=\"com.android.trusty.secretkeeper.bootloader\"",
+        "-DTRUSTY_APP_UUID=\"4582bf12-1f7d-4830-9be5-36e6bd91c2c6\"",
+        "-DTRUSTY_APP_FILENAME=\"secretkeeper_app.syms.elf\"",
+    ],
+}
diff --git a/trusty/secretkeeper/android.hardware.security.secretkeeper.trusty.rc b/trusty/secretkeeper/android.hardware.security.secretkeeper.trusty.rc
new file mode 100644
index 0000000..3be03ad
--- /dev/null
+++ b/trusty/secretkeeper/android.hardware.security.secretkeeper.trusty.rc
@@ -0,0 +1,4 @@
+service vendor.secretkeeper.trusty /vendor/bin/hw/android.hardware.security.secretkeeper.trusty
+    class hal
+    user nobody
+    group drmrpc
\ No newline at end of file
diff --git a/trusty/secretkeeper/android.hardware.security.secretkeeper.trusty.xml b/trusty/secretkeeper/android.hardware.security.secretkeeper.trusty.xml
new file mode 100644
index 0000000..2ac152b
--- /dev/null
+++ b/trusty/secretkeeper/android.hardware.security.secretkeeper.trusty.xml
@@ -0,0 +1,7 @@
+<manifest version="1.0" type="device">
+    <hal format="aidl">
+        <name>android.hardware.security.secretkeeper</name>
+        <version>1</version>
+        <fqname>ISecretkeeper/default</fqname>
+    </hal>
+</manifest>
diff --git a/trusty/secretkeeper/src/hal_main.rs b/trusty/secretkeeper/src/hal_main.rs
new file mode 100644
index 0000000..9439c36
--- /dev/null
+++ b/trusty/secretkeeper/src/hal_main.rs
@@ -0,0 +1,141 @@
+//
+// 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.
+
+//! This module implements the HAL service for Secretkeeper in Trusty.
+use authgraph_hal::{channel::SerializedChannel};
+use secretkeeper_hal::SecretkeeperService;
+use android_hardware_security_secretkeeper::aidl::android::hardware::security::secretkeeper::ISecretkeeper::{
+    ISecretkeeper, BpSecretkeeper,
+};
+use log::{error, info};
+use std::{
+    ffi::CString,
+    panic,
+    sync::{Arc, Mutex},
+};
+use trusty::DEFAULT_DEVICE;
+
+const SK_TIPC_SERVICE_PORT: &str = "com.android.trusty.secretkeeper";
+const AG_TIPC_SERVICE_PORT: &str = "com.android.trusty.secretkeeper.authgraph";
+
+static SERVICE_INSTANCE: &str = "default";
+
+/// Local error type for failures in the HAL service.
+#[derive(Debug, Clone)]
+struct HalServiceError(String);
+
+#[derive(Debug)]
+struct TipcChannel {
+    channel: Arc<Mutex<trusty::TipcChannel>>,
+}
+
+impl TipcChannel {
+    fn new(channel: trusty::TipcChannel) -> Self {
+        Self { channel: Arc::new(Mutex::new(channel)) }
+    }
+}
+
+impl SerializedChannel for TipcChannel {
+    const MAX_SIZE: usize = 4000;
+    fn execute(&self, req_data: &[u8]) -> binder::Result<Vec<u8>> {
+        // Hold lock across both request and response.
+        let mut channel = self.channel.lock().unwrap();
+        channel.send(req_data).map_err(|e| {
+            binder::Status::new_exception(
+                binder::ExceptionCode::TRANSACTION_FAILED,
+                Some(
+                    &CString::new(format!(
+                        "Failed to send the request via tipc channel because of {:?}",
+                        e
+                    ))
+                    .unwrap(),
+                ),
+            )
+        })?;
+        // TODO: cope with fragmentation and reassembly
+        let mut rsp_data = Vec::new();
+        channel.recv(&mut rsp_data).map_err(|e| {
+            binder::Status::new_exception(
+                binder::ExceptionCode::TRANSACTION_FAILED,
+                Some(
+                    &CString::new(format!(
+                        "Failed to receive the response via tipc channel because of {:?}",
+                        e
+                    ))
+                    .unwrap(),
+                ),
+            )
+        })?;
+        Ok(rsp_data)
+    }
+}
+
+fn main() {
+    if let Err(e) = inner_main() {
+        panic!("HAL service failed: {:?}", e);
+    }
+}
+
+fn inner_main() -> Result<(), HalServiceError> {
+    // Initialize Android logging.
+    android_logger::init_once(
+        android_logger::Config::default()
+            .with_tag("secretkeeper-hal-trusty")
+            .with_min_level(log::Level::Info)
+            .with_log_id(android_logger::LogId::System),
+    );
+    // Redirect panic messages to logcat.
+    panic::set_hook(Box::new(|panic_info| {
+        error!("{}", panic_info);
+    }));
+
+    info!("Trusty Secretkeeper HAL service is starting.");
+
+    info!("Starting thread pool now.");
+    binder::ProcessState::start_thread_pool();
+
+    // Create connections to the TA.
+    let ag_connection = trusty::TipcChannel::connect(DEFAULT_DEVICE, AG_TIPC_SERVICE_PORT)
+        .map_err(|e| {
+            HalServiceError(format!(
+                "Failed to connect to Trusty port {AG_TIPC_SERVICE_PORT} because of {:?}.",
+                e
+            ))
+        })?;
+    let ag_tipc_channel = TipcChannel::new(ag_connection);
+
+    let sk_connection = trusty::TipcChannel::connect(DEFAULT_DEVICE, SK_TIPC_SERVICE_PORT)
+        .map_err(|e| {
+            HalServiceError(format!(
+                "Failed to connect to Trusty port {SK_TIPC_SERVICE_PORT} because of {:?}.",
+                e
+            ))
+        })?;
+    let sk_tipc_channel = TipcChannel::new(sk_connection);
+
+    // Register the AIDL service
+    let service = SecretkeeperService::new_as_binder(sk_tipc_channel, ag_tipc_channel);
+    let service_name =
+        format!("{}/{}", <BpSecretkeeper as ISecretkeeper>::get_descriptor(), SERVICE_INSTANCE);
+    binder::add_service(&service_name, service.as_binder()).map_err(|e| {
+        HalServiceError(format!("Failed to register service {} because of {:?}.", service_name, e))
+    })?;
+
+    info!("Successfully registered Secretkeeper HAL service.");
+    info!("Joining thread pool now.");
+    binder::ProcessState::join_thread_pool();
+    info!("Secretkeeper HAL service is terminating."); // should not reach here
+    Ok(())
+}
diff --git a/trusty/trusty-base.mk b/trusty/trusty-base.mk
index 1986c73..d645c3e 100644
--- a/trusty/trusty-base.mk
+++ b/trusty/trusty-base.mk
@@ -35,8 +35,16 @@
     LOCAL_KEYMINT_PRODUCT_PACKAGE := android.hardware.security.keymint-service.trusty
 endif
 
+# TODO(b/306364873): move this to be flag-controlled?
+ifeq ($(SECRETKEEPER_ENABLED),)
+    LOCAL_SECRETKEEPER_PRODUCT_PACKAGE :=
+else
+    LOCAL_SECRETKEEPER_PRODUCT_PACKAGE := android.hardware.security.secretkeeper.trusty
+endif
+
 PRODUCT_PACKAGES += \
 	$(LOCAL_KEYMINT_PRODUCT_PACKAGE) \
+	$(LOCAL_SECRETKEEPER_PRODUCT_PACKAGE) \
 	android.hardware.gatekeeper-service.trusty \
 	trusty_apploader \