Merge "Merge 25Q1 (ab/BP1A.250305.020) to AOSP main" into main
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/Application.kt b/android/TerminalApp/java/com/android/virtualization/terminal/Application.kt
index 9f4909d..c427337 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/Application.kt
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/Application.kt
@@ -97,14 +97,6 @@
             super.onStop(owner)
         }
 
-        override fun onDestroy(owner: LifecycleOwner) {
-            if (vmLauncherService != null) {
-                this@Application.unbindService(connection)
-                vmLauncherService = null
-            }
-            super.onDestroy(owner)
-        }
-
         fun bindToVmLauncherService() {
             val intent = Intent(this@Application, VmLauncherService::class.java)
             this@Application.bindService(intent, connection, 0) // No BIND_AUTO_CREATE
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/Logger.kt b/android/TerminalApp/java/com/android/virtualization/terminal/Logger.kt
index 547f1a7..d02491b 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/Logger.kt
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/Logger.kt
@@ -30,6 +30,7 @@
 import java.nio.file.Files
 import java.nio.file.Path
 import java.nio.file.StandardOpenOption
+import java.time.LocalDateTime
 import java.util.concurrent.ExecutorService
 import libcore.io.Streams
 
@@ -37,14 +38,21 @@
  * Forwards VM's console output to a file on the Android side, and VM's log output to Android logd.
  */
 internal object Logger {
-    fun setup(vm: VirtualMachine, path: Path, executor: ExecutorService) {
+    fun setup(vm: VirtualMachine, dir: Path, executor: ExecutorService) {
+        val tag = vm.name
+
         if (vm.config.debugLevel != VirtualMachineConfig.DEBUG_LEVEL_FULL) {
+            Log.i(tag, "Logs are not captured. Non-debuggable VM.")
             return
         }
 
         try {
+            Files.createDirectories(dir)
+            deleteOldLogs(dir, 10)
+            val logPath = dir.resolve(LocalDateTime.now().toString() + ".txt")
             val console = vm.getConsoleOutput()
-            val file = Files.newOutputStream(path, StandardOpenOption.CREATE)
+            val file =
+                Files.newOutputStream(logPath, StandardOpenOption.CREATE)
             executor.submit<Int?> {
                 console.use { console ->
                     LineBufferedOutputStream(file).use { fileOutput ->
@@ -54,7 +62,7 @@
             }
 
             val log = vm.getLogOutput()
-            executor.submit<Unit> { log.use { writeToLogd(it, vm.name) } }
+            executor.submit<Unit> { log.use { writeToLogd(it, tag) } }
         } catch (e: VirtualMachineException) {
             throw RuntimeException(e)
         } catch (e: IOException) {
@@ -62,12 +70,32 @@
         }
     }
 
+    fun deleteOldLogs(dir: Path, numLogsToKeep: Long) {
+        Files.list(dir)
+            .filter { Files.isRegularFile(it) }
+            .sorted(
+                Comparator.comparingLong { f: Path ->
+                        // for some reason, type inference didn't work here!
+                        Files.getLastModifiedTime(f).toMillis()
+                    }
+                    .reversed()
+            )
+            .skip(numLogsToKeep)
+            .forEach {
+                try {
+                    Files.delete(it)
+                } catch (e: IOException) {
+                    // don't bother
+                }
+            }
+    }
+
     @Throws(IOException::class)
-    private fun writeToLogd(input: InputStream?, vmName: String?) {
+    private fun writeToLogd(input: InputStream?, tag: String?) {
         val reader = BufferedReader(InputStreamReader(input))
         reader
             .useLines { lines -> lines.takeWhile { !Thread.interrupted() } }
-            .forEach { Log.d(vmName, it) }
+            .forEach { Log.d(tag, it) }
     }
 
     private class LineBufferedOutputStream(out: OutputStream?) : BufferedOutputStream(out) {
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherService.kt b/android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherService.kt
index eee0ada..aa1898f 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherService.kt
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherService.kt
@@ -188,10 +188,7 @@
         val displaySize = intent.getParcelableExtra(EXTRA_DISPLAY_INFO, DisplayInfo::class.java)
 
         customImageConfigBuilder.setAudioConfig(
-            AudioConfig.Builder()
-                .setUseSpeaker(true)
-                .setUseMicrophone(true)
-                .build()
+            AudioConfig.Builder().setUseSpeaker(true).setUseMicrophone(true).build()
         )
         if (overrideConfigIfNecessary(customImageConfigBuilder, displaySize)) {
             configBuilder.setCustomImageConfig(customImageConfigBuilder.build())
@@ -219,8 +216,8 @@
             resultReceiver?.send(if (success) RESULT_STOP else RESULT_ERROR, null)
             stopSelf()
         }
-        val logPath = getFileStreamPath(virtualMachine!!.name + ".log").toPath()
-        Logger.setup(virtualMachine!!, logPath, executorService!!)
+        val logDir = getFileStreamPath(virtualMachine!!.name + ".log").toPath()
+        Logger.setup(virtualMachine!!, logDir, executorService!!)
 
         val notification =
             intent.getParcelableExtra<Notification?>(EXTRA_NOTIFICATION, Notification::class.java)
diff --git a/guest/forwarder_guest_launcher/debian/service b/guest/forwarder_guest_launcher/debian/service
index ad57a26..7812d67 100644
--- a/guest/forwarder_guest_launcher/debian/service
+++ b/guest/forwarder_guest_launcher/debian/service
@@ -5,7 +5,7 @@
 After=virtiofs_internal.service
 
 [Service]
-ExecStart=/usr/bin/bash -c '/usr/bin/forwarder_guest_launcher --grpc_port $(cat /mnt/internal/debian_service_port)'
+ExecStart=/usr/bin/bash -c '/usr/bin/forwarder_guest_launcher --grpc-port-file /mnt/internal/debian_service_port'
 Type=simple
 Restart=on-failure
 RestartSec=1
diff --git a/guest/forwarder_guest_launcher/src/main.rs b/guest/forwarder_guest_launcher/src/main.rs
index f4c8ca9..3cb557a 100644
--- a/guest/forwarder_guest_launcher/src/main.rs
+++ b/guest/forwarder_guest_launcher/src/main.rs
@@ -52,10 +52,9 @@
 #[derive(Parser)]
 /// Flags for running command
 pub struct Args {
-    /// grpc port number
+    /// path to a file where grpc port number is written
     #[arg(long)]
-    #[arg(alias = "grpc_port")]
-    grpc_port: String,
+    grpc_port_file: String,
 }
 
 async fn process_forwarding_request_queue(
@@ -163,11 +162,23 @@
 
 #[tokio::main]
 async fn main() -> Result<(), Box<dyn std::error::Error>> {
-    env_logger::init();
+    env_logger::builder().filter_level(log::LevelFilter::Debug).init();
     debug!("Starting forwarder_guest_launcher");
     let args = Args::parse();
     let gateway_ip_addr = netdev::get_default_gateway()?.ipv4[0];
-    let addr = format!("https://{}:{}", gateway_ip_addr.to_string(), args.grpc_port);
+
+    // Wait for `grpc_port_file` becomes available.
+    const GRPC_PORT_MAX_RETRY_COUNT: u32 = 10;
+    for _ in 0..GRPC_PORT_MAX_RETRY_COUNT {
+        if std::path::Path::new(&args.grpc_port_file).exists() {
+            break;
+        }
+        debug!("{} does not exist. Wait 1 second", args.grpc_port_file);
+        tokio::time::sleep(std::time::Duration::from_secs(1)).await;
+    }
+    let grpc_port = std::fs::read_to_string(&args.grpc_port_file)?.trim().to_string();
+
+    let addr = format!("https://{}:{}", gateway_ip_addr.to_string(), grpc_port);
     let channel = Endpoint::from_shared(addr)?.connect().await?;
     let client = DebianServiceClient::new(channel);
 
diff --git a/guest/shutdown_runner/Cargo.toml b/guest/shutdown_runner/Cargo.toml
index 0b44baa..92f8762 100644
--- a/guest/shutdown_runner/Cargo.toml
+++ b/guest/shutdown_runner/Cargo.toml
@@ -7,6 +7,7 @@
 [dependencies]
 anyhow = "1.0.94"
 clap = { version = "4.5.20", features = ["derive"] }
+env_logger = "0.11.5"
 log = "0.4.22"
 netdev = "0.31.0"
 prost = "0.13.3"
diff --git a/guest/shutdown_runner/debian/service b/guest/shutdown_runner/debian/service
index 2668930..a5249d0 100644
--- a/guest/shutdown_runner/debian/service
+++ b/guest/shutdown_runner/debian/service
@@ -4,7 +4,7 @@
 After=virtiofs_internal.service
 
 [Service]
-ExecStart=/usr/bin/bash -c '/usr/bin/shutdown_runner --grpc_port $(cat /mnt/internal/debian_service_port)'
+ExecStart=/usr/bin/bash -c '/usr/bin/shutdown_runner --grpc-port-file /mnt/internal/debian_service_port'
 Type=simple
 Restart=on-failure
 RestartSec=1
diff --git a/guest/shutdown_runner/src/main.rs b/guest/shutdown_runner/src/main.rs
index 19e9883..4043002 100644
--- a/guest/shutdown_runner/src/main.rs
+++ b/guest/shutdown_runner/src/main.rs
@@ -1,3 +1,17 @@
+// Copyright 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.
+
 use api::debian_service_client::DebianServiceClient;
 use api::ShutdownQueueOpeningRequest;
 use std::process::Command;
@@ -12,18 +26,28 @@
 #[derive(Parser)]
 /// Flags for running command
 pub struct Args {
-    /// grpc port number
+    /// Path to a file where grpc port number is written
     #[arg(long)]
-    #[arg(alias = "grpc_port")]
-    grpc_port: String,
+    grpc_port_file: String,
 }
 
 #[tokio::main]
 async fn main() -> Result<(), Box<dyn std::error::Error>> {
+    env_logger::builder().filter_level(log::LevelFilter::Debug).init();
     let args = Args::parse();
     let gateway_ip_addr = netdev::get_default_gateway()?.ipv4[0];
 
-    let server_addr = format!("http://{}:{}", gateway_ip_addr.to_string(), args.grpc_port);
+    // Wait for `grpc_port_file` becomes available.
+    const GRPC_PORT_MAX_RETRY_COUNT: u32 = 10;
+    for _ in 0..GRPC_PORT_MAX_RETRY_COUNT {
+        if std::path::Path::new(&args.grpc_port_file).exists() {
+            break;
+        }
+        debug!("{} does not exist. Wait 1 second", args.grpc_port_file);
+        tokio::time::sleep(std::time::Duration::from_secs(1)).await;
+    }
+    let grpc_port = std::fs::read_to_string(&args.grpc_port_file)?.trim().to_string();
+    let server_addr = format!("http://{}:{}", gateway_ip_addr.to_string(), grpc_port);
 
     debug!("connect to grpc server {}", server_addr);
 
diff --git a/guest/trusty/security_vm/vm/Android.bp b/guest/trusty/security_vm/vm/Android.bp
index 35d7313..6fa0c32 100644
--- a/guest/trusty/security_vm/vm/Android.bp
+++ b/guest/trusty/security_vm/vm/Android.bp
@@ -29,6 +29,7 @@
     ],
     visibility: [
         "//packages/modules/Virtualization/guest/trusty/test_vm/vm",
+        "//packages/modules/Virtualization/guest/trusty/test_vm_os/vm",
         "//vendor:__subpackages__",
     ],
 }
diff --git a/guest/trusty/test_vm/Android.bp b/guest/trusty/test_vm/Android.bp
index 06b7d9d..676e231 100644
--- a/guest/trusty/test_vm/Android.bp
+++ b/guest/trusty/test_vm/Android.bp
@@ -34,6 +34,30 @@
     filename: "trusty-test_vm-config.json",
 }
 
+prebuilt_etc {
+    name: "trusty_vm_launcher_sh",
+    enabled: false,
+    arch: {
+        arm64: {
+            enabled: true,
+        },
+    },
+    src: "trusty-vm-launcher.sh",
+    filename: "trusty-vm-launcher.sh",
+}
+
+prebuilt_etc {
+    name: "trusty_wait_ready_sh",
+    enabled: false,
+    arch: {
+        arm64: {
+            enabled: true,
+        },
+    },
+    src: "trusty-wait-ready.sh",
+    filename: "trusty-wait-ready.sh",
+}
+
 sh_test {
     // VTS tests for all Trusted HALs defined
     // under hardware/interfaces/security/see
diff --git a/guest/trusty/test_vm_os/Android.bp b/guest/trusty/test_vm_os/Android.bp
new file mode 100644
index 0000000..ab0d5d8
--- /dev/null
+++ b/guest/trusty/test_vm_os/Android.bp
@@ -0,0 +1,63 @@
+// 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"],
+    default_team: "trendy_team_trusty",
+}
+
+prebuilt_etc {
+    name: "test_vm_os.trusty_test_vm_config",
+    enabled: false,
+    installable: false,
+    arch: {
+        arm64: {
+            src: "trusty-test_vm-config-arm64.json",
+            enabled: true,
+        },
+        x86_64: {
+            src: "trusty-test_vm-config-x86_64.json",
+            enabled: true,
+        },
+    },
+    filename: "trusty-test_vm-config.json",
+}
+
+sh_test {
+    name: "TrustyVmUnitTests",
+    src: "trusty-ut-ctrl.sh",
+    enabled: false,
+    arch: {
+        arm64: {
+            enabled: true,
+        },
+        x86_64: {
+            enabled: true,
+        },
+    },
+    filename_from_src: true,
+    data: [
+        ":trusty_test_vm_os_elf",
+        ":test_vm_os.trusty_test_vm_config",
+        "trusty-vm-launcher.sh",
+        "trusty-wait-ready.sh",
+        ":trusty-ut-ctrl.system",
+    ],
+    // TODO(b/378367793) use the AndroidTest.xml generated from the trusty
+    // test-map for test_vm payload
+    test_config_template: "AndroidTest.xml",
+    test_suites: [
+        "general-tests",
+    ],
+}
diff --git a/guest/trusty/test_vm_os/AndroidTest.xml b/guest/trusty/test_vm_os/AndroidTest.xml
new file mode 100644
index 0000000..be5c467
--- /dev/null
+++ b/guest/trusty/test_vm_os/AndroidTest.xml
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2025 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.
+  -->
+    <configuration description="Runs {MODULE}">
+    <!-- object type="module_controller" class="com.android.tradefed.testtype.suite.module.CommandSuccessModuleController" -->
+        <!--Skip the test when trusty VM is not enabled. -->
+        <!--option name="run-command" value="getprop trusty.test_vm.nonsecure_vm_ready | grep 1" /-->
+    <!--/object-->
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
+    <!-- Target Preparers - Run Shell Commands -->
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+        <option name="cleanup" value="true" />
+        <option name="push-file" key="trusty-ut-ctrl.system" value="/data/local/tmp/trusty_test_vm_os/trusty-ut-ctrl" />
+        <option name="push-file" key="trusty-ut-ctrl.sh" value="/data/local/tmp/trusty_test_vm_os/trusty-ut-ctrl.sh" />
+        <option name="push-file" key="trusty-vm-launcher.sh" value="/data/local/tmp/trusty_test_vm_os/trusty-vm-launcher.sh" />
+        <option name="push-file" key="trusty-wait-ready.sh" value="/data/local/tmp/trusty_test_vm_os/trusty-wait-ready.sh" />
+        <option name="push-file" key="trusty-test_vm-config.json" value="/data/local/tmp/trusty_test_vm_os/trusty-test_vm-config.json" />
+        <option name="push-file" key="trusty_test_vm_os.elf" value="/data/local/tmp/trusty_test_vm_os/trusty_test_vm_os.elf" />
+    </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="throw-if-cmd-fail" value="true" />
+        <!--Note: the first run-command shall not expect the background command to have started -->
+        <option name="run-bg-command" value="sh /data/local/tmp/trusty_test_vm_os/trusty-vm-launcher.sh" />
+        <option name="run-command" value="sh /data/local/tmp/trusty_test_vm_os/trusty-wait-ready.sh" />
+        <option name="run-command" value="start storageproxyd_test_vm_os" />
+        <option name="teardown-command" value="stop storageproxyd_test_vm_os" />
+        <option name="teardown-command" value="killall storageproxyd_test_vm_os || true" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.binary.ExecutableTargetTest" >
+        <option name="parse-gtest" value="true" />
+        <option name="abort-if-device-lost" value="true"/>
+        <option name="abort-if-root-lost" value="true" />
+        <option name="per-binary-timeout" value="10m" />
+        <option name="test-command-line" key="com.android.kernel.mmutest" value="/data/local/tmp/trusty_test_vm_os/trusty-ut-ctrl.sh com.android.kernel.mmutest"/>
+        <option name="test-command-line" key="com.android.kernel.threadtest" value="/data/local/tmp/trusty_test_vm_os/trusty-ut-ctrl.sh com.android.kernel.threadtest"/>
+        <option name="test-command-line" key="com.android.kernel.iovectest" value="/data/local/tmp/trusty_test_vm_os/trusty-ut-ctrl.sh com.android.kernel.iovectest"/>
+        <!--TODO(b/400064847) enable kernel.timertest when Trusty VM supports more than 2 VCPU"/-->
+        <!--option name="test-command-line" key="com.android.kernel.timertest" value="/data/local/tmp/trusty_test_vm_os/trusty-ut-ctrl.sh com.android.kernel.timertest"/-->
+        <option name="test-command-line" key="com.android.kernel.btitest" value="/data/local/tmp/trusty_test_vm_os/trusty-ut-ctrl.sh com.android.kernel.btitest"/>
+        <option name="test-command-line" key="com.android.kernel.cachetest" value="/data/local/tmp/trusty_test_vm_os/trusty-ut-ctrl.sh com.android.kernel.cachetest"/>
+        <option name="test-command-line" key="com.android.kernel.console-unittest" value="/data/local/tmp/trusty_test_vm_os/trusty-ut-ctrl.sh com.android.kernel.console-unittest"/>
+        <option name="test-command-line" key="com.android.kernel.dpc-unittest" value="/data/local/tmp/trusty_test_vm_os/trusty-ut-ctrl.sh com.android.kernel.dpc-unittest"/>
+        <option name="test-command-line" key="com.android.kernel.iovectest" value="/data/local/tmp/trusty_test_vm_os/trusty-ut-ctrl.sh com.android.kernel.iovectest"/>
+        <option name="test-command-line" key="com.android.kernel.ktipc.test" value="/data/local/tmp/trusty_test_vm_os/trusty-ut-ctrl.sh com.android.kernel.ktipc.test"/>
+        <option name="test-command-line" key="com.android.kernel.memorytest" value="/data/local/tmp/trusty_test_vm_os/trusty-ut-ctrl.sh com.android.kernel.memorytest"/>
+        <option name="test-command-line" key="com.android.kernel.pactest" value="/data/local/tmp/trusty_test_vm_os/trusty-ut-ctrl.sh com.android.kernel.pactest"/>
+        <option name="test-command-line" key="com.android.kernel.uirq-unittest" value="/data/local/tmp/trusty_test_vm_os/trusty-ut-ctrl.sh com.android.kernel.uirq-unittest"/>
+        <option name="test-command-line" key="com.android.kernel.usercopy-unittest" value="/data/local/tmp/trusty_test_vm_os/trusty-ut-ctrl.sh com.android.kernel.usercopy-unittest"/>
+        <option name="test-command-line" key="com.android.kernel.userscstest" value="/data/local/tmp/trusty_test_vm_os/trusty-ut-ctrl.sh com.android.kernel.userscstest"/>
+        <option name="test-command-line" key="com.android.manifesttest" value="/data/local/tmp/trusty_test_vm_os/trusty-ut-ctrl.sh com.android.manifesttest"/>
+        <option name="test-command-line" key="com.android.memref.test" value="/data/local/tmp/trusty_test_vm_os/trusty-ut-ctrl.sh com.android.memref.test"/>
+        <option name="test-command-line" key="com.android.trusty.rust.memref.test" value="/data/local/tmp/trusty_test_vm_os/trusty-ut-ctrl.sh com.android.trusty.rust.memref.test"/>
+        <option name="test-command-line" key="com.android.timer-unittest" value="/data/local/tmp/trusty_test_vm_os/trusty-ut-ctrl.sh com.android.timer-unittest"/>
+        <option name="test-command-line" key="com.android.ipc-unittest.ctrl" value="/data/local/tmp/trusty_test_vm_os/trusty-ut-ctrl.sh com.android.ipc-unittest.ctrl"/>
+        <!--option name="test-command-line" key="com.android.trusty.cfitest" value="/data/local/tmp/trusty_test_vm_os/trusty-ut-ctrl.sh com.android.trusty.cfitest"/-->
+        <option name="test-command-line" key="com.android.trusty.crashtest" value="/data/local/tmp/trusty_test_vm_os/trusty-ut-ctrl.sh com.android.trusty.crashtest"/>
+        <option name="test-command-line" key="com.android.trusty.dlmalloctest" value="/data/local/tmp/trusty_test_vm_os/trusty-ut-ctrl.sh com.android.trusty.dlmalloctest"/>
+        <option name="test-command-line" key="com.android.trusty.rust.tipc.test" value="/data/local/tmp/trusty_test_vm_os/trusty-ut-ctrl.sh com.android.trusty.rust.tipc.test"/>
+        <option name="test-command-line" key="com.android.uirq-unittest" value="/data/local/tmp/trusty_test_vm_os/trusty-ut-ctrl.sh com.android.uirq-unittest"/>
+    </test>
+    <test class="com.android.tradefed.testtype.binary.ExecutableTargetTest" >
+        <option name="parse-gtest" value="true" />
+        <!--option name="abort-if-device-lost" value="true" /-->
+        <!--option name="abort-if-root-lost" value="true" /-->
+        <option name="per-binary-timeout" value="40m" />
+        <option name="test-command-line" key="com.android.trusty.rust.binder_rpc_test.test" value="/data/local/tmp/trusty_test_vm_os/trusty-ut-ctrl.sh com.android.trusty.rust.binder_rpc_test.test"/>
+        <option name="test-command-line" key="com.android.trusty.binder.test" value="/data/local/tmp/trusty_test_vm_os/trusty-ut-ctrl.sh com.android.trusty.binder.test"/>
+    </test>
+    </configuration>
diff --git a/guest/trusty/test_vm_os/README.md b/guest/trusty/test_vm_os/README.md
new file mode 100644
index 0000000..4d65d9f
--- /dev/null
+++ b/guest/trusty/test_vm_os/README.md
@@ -0,0 +1,7 @@
+## test_vm_os
+
+The Trusty test_vm_os is meant to test the Trusty OS as a VM,
+its payload ought to include the test TAs for different test types:
+- Trusty kernel OS test
+- Trusty/Binder IPC tests
+- Trusty user-space tests for service TAs (DT tree for example)
diff --git a/guest/trusty/test_vm_os/TEST_MAPPING b/guest/trusty/test_vm_os/TEST_MAPPING
new file mode 100644
index 0000000..1506720
--- /dev/null
+++ b/guest/trusty/test_vm_os/TEST_MAPPING
@@ -0,0 +1,9 @@
+{
+  "trusty_test_vm_presubmit": [
+  ],
+  "trusty_test_vm_postsubmit": [
+    {
+        "name": "TrustyVMOS_UnitTests"
+    }
+  ]
+}
diff --git a/guest/trusty/test_vm_os/trusty-test_vm-config-arm64.json b/guest/trusty/test_vm_os/trusty-test_vm-config-arm64.json
new file mode 100644
index 0000000..9d60892
--- /dev/null
+++ b/guest/trusty/test_vm_os/trusty-test_vm-config-arm64.json
@@ -0,0 +1,8 @@
+{
+    "name": "trusty_test_vm",
+    "kernel": "/data/local/tmp/trusty_test_vm_os/trusty_test_vm_os.elf",
+    "platform_version": "1.0",
+    "cpu_topology": "one_cpu",
+    "memory_mib": 112,
+    "protected": true
+}
diff --git a/guest/trusty/test_vm_os/trusty-test_vm-config-x86_64.json b/guest/trusty/test_vm_os/trusty-test_vm-config-x86_64.json
new file mode 100644
index 0000000..5270ac7
--- /dev/null
+++ b/guest/trusty/test_vm_os/trusty-test_vm-config-x86_64.json
@@ -0,0 +1,7 @@
+{
+    "name": "trusty_test_vm",
+    "kernel": "/data/local/tmp/trusty_test_vm_os/trusty_test_vm_os.elf",
+    "platform_version": "1.0",
+    "cpu_topology": "one_cpu",
+    "memory_mib": 112
+}
diff --git a/guest/trusty/test_vm_os/trusty-ut-ctrl.sh b/guest/trusty/test_vm_os/trusty-ut-ctrl.sh
new file mode 100644
index 0000000..860236b
--- /dev/null
+++ b/guest/trusty/test_vm_os/trusty-ut-ctrl.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+#
+# 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.
+
+/data/local/tmp/trusty_test_vm_os/trusty-ut-ctrl -D VSOCK:${2:-$(getprop trusty.test_vm_os.vm_cid)}:1 $1
diff --git a/guest/trusty/test_vm_os/trusty-vm-launcher.sh b/guest/trusty/test_vm_os/trusty-vm-launcher.sh
new file mode 100755
index 0000000..497b188
--- /dev/null
+++ b/guest/trusty/test_vm_os/trusty-vm-launcher.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+# Copyright 2024 Google Inc. All rights reserved.
+#
+# 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.
+
+/apex/com.android.virt/bin/vm run /data/local/tmp/trusty_test_vm_os/trusty-test_vm-config.json
diff --git a/guest/trusty/test_vm_os/trusty-wait-ready.sh b/guest/trusty/test_vm_os/trusty-wait-ready.sh
new file mode 100755
index 0000000..0aed284
--- /dev/null
+++ b/guest/trusty/test_vm_os/trusty-wait-ready.sh
@@ -0,0 +1,39 @@
+#!/bin/bash
+set -euo pipefail
+
+function get_cid {
+    local max_cid
+    max_cid=$(/apex/com.android.virt/bin/vm list | awk 'BEGIN { FS="[:,]" } /cid/ { print $2; }' | sort -n | tail -1)
+
+    # return the value trimmed from whitespaces
+    echo "${max_cid}" | xargs
+}
+
+function wait_for_cid {
+    TIMES=${1:-20}
+    X=0
+    local init_cid
+    init_cid=$(get_cid)
+    while [ "$TIMES" -eq 0 ] || [ "$TIMES" -gt "$X" ]
+    do
+      local cid
+      cid=$(get_cid)
+      echo "wait_for_cid: retry $(( X++ )) / $TIMES : init_cid=$init_cid cid=$cid";
+      if [ "$cid" -gt "$init_cid" ]
+      then
+        break
+      else
+        sleep 2
+      fi
+    done
+    setprop trusty.test_vm_os.vm_cid "$cid"
+}
+
+# This script is expected to be started before the trusty_test_vm is started
+# wait_for_cid gets the max cid and wait for it to be updated as an indication
+# that the trusty_test_vm has properly started.
+# wait_for_cid polls for the CID change at 2 seconds intervals
+# the input argument is the max number of retries (20 by default)
+wait_for_cid "$@"
+
+echo trusty.test_vm_os.vm_cid="$(getprop trusty.test_vm_os.vm_cid)"
diff --git a/guest/trusty/test_vm_os/vm/Android.bp b/guest/trusty/test_vm_os/vm/Android.bp
new file mode 100644
index 0000000..2e81828
--- /dev/null
+++ b/guest/trusty/test_vm_os/vm/Android.bp
@@ -0,0 +1,114 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+prebuilt_etc {
+    name: "trusty_test_vm_os_elf",
+    system_ext_specific: true,
+    filename: "trusty_test_vm_os.elf",
+    src: select((os(), arch()), {
+        ("android", "arm64"): ":trusty_test_vm_os_signed",
+        ("android", "x86_64"): ":trusty_test_vm_os_unsigned",
+        (default, default): ":empty_file",
+    }),
+}
+
+cc_binary {
+    name: "trusty_test_vm_os_signed",
+    srcs: [
+        ":trusty_test_vm_os_signed_bin_obj",
+    ],
+    // reuse the common trusty_vm_sections linker script
+    linker_scripts: [
+        ":trusty_vm_sections.ld",
+    ],
+    ldflags: [
+        // Prevent the `trusty_test_vm_os_signed_bin_obj` segment from being garbage collected.
+        "-Wl,--no-gc-sections",
+        // Prevent the build ID segments from being added, as it would corrupt the integrity
+        // of the original signed image.
+        "-Wl,--build-id=none",
+        // Use a standard page size of 4096, smaller than the default 16384, to avoid padding
+        // with extra bytes.
+        "-Wl,-z,max-page-size=4096",
+    ],
+    nocrt: true,
+    no_libcrt: true,
+    static_executable: true,
+    system_shared_libs: [],
+    enabled: false,
+    target: {
+        android_arm64: {
+            enabled: true,
+        },
+    },
+    strip: {
+        none: true,
+    },
+}
+
+cc_genrule {
+    name: "test_vm_os.S",
+    enabled: false,
+    arch: {
+        arm64: {
+            srcs: [":trusty_test_vm_os_signed_bin"],
+            enabled: true,
+        },
+    },
+    out: ["test_vm_os.S"],
+    cmd: "(" +
+        "    echo '.section .vm_payload_signed.bin';" +
+        "    echo '.globl vm_payload_signed';" +
+        "    echo 'vm_payload_signed:';" +
+        "    echo '.incbin \"'$(in)'\"';" +
+        ") > $(out)",
+    visibility: ["//visibility:private"],
+}
+
+cc_object {
+    name: "trusty_test_vm_os_signed_bin_obj",
+    srcs: [
+        ":test_vm_os.S",
+    ],
+    crt: false,
+    static_libs: ["trusty_test_vm_os_signed_bin"],
+    system_shared_libs: [],
+    enabled: false,
+    target: {
+        android_arm64: {
+            enabled: true,
+        },
+    },
+    visibility: ["//visibility:private"],
+}
+
+// python -c "import hashlib; print(hashlib.sha256(b'trusty_test_vm_os_salt').hexdigest())"
+trusty_test_vm_os_salt = "74706b35d927b14539a73e14e6e91a2d3be5d46a12c02cf4084bcef5ffee6e4a"
+
+TRUSTY_TEST_VM_OS_VERSION = 1
+
+avb_add_hash_footer {
+    name: "trusty_test_vm_os_signed_bin",
+    filename: "trusty_test_vm_os_signed.bin",
+    partition_name: "boot",
+    private_key: ":trusty_vm_sign_key",
+    salt: trusty_test_vm_os_salt,
+    rollback_index: TRUSTY_TEST_VM_OS_VERSION,
+    props: [
+        {
+            name: "com.android.virt.cap",
+            value: "trusty_security_vm",
+        },
+    ],
+    src: ":trusty_test_vm_os_unsigned",
+    enabled: false,
+    arch: {
+        arm64: {
+            enabled: true,
+        },
+        x86_64: {
+            enabled: true,
+        },
+    },
+}