Merge "Stop VmLauncherService when vm is finished" into main
diff --git a/android/virtmgr/src/aidl.rs b/android/virtmgr/src/aidl.rs
index 87d7a88..e2b2804 100644
--- a/android/virtmgr/src/aidl.rs
+++ b/android/virtmgr/src/aidl.rs
@@ -59,7 +59,7 @@
     Key::Key, PubKey::PubKey, SessionIdSignature::SessionIdSignature, SessionInfo::SessionInfo,
     SessionInitiationInfo::SessionInitiationInfo,
 };
-use anyhow::{anyhow, bail, Context, Result};
+use anyhow::{anyhow, bail, ensure, Context, Result};
 use apkverify::{HashAlgorithm, V4Signature};
 use avflog::LogResult;
 use binder::{
@@ -76,7 +76,7 @@
 use rustutils::system_properties;
 use semver::VersionReq;
 use serde::Deserialize;
-use std::collections::HashSet;
+use std::collections::{HashSet, HashMap};
 use std::convert::TryInto;
 use std::fs;
 use std::ffi::CStr;
@@ -2007,22 +2007,21 @@
 }
 
 // KEEP IN SYNC WITH early_vms.xsd
-#[derive(Debug, Deserialize, PartialEq)]
+#[derive(Clone, Debug, Deserialize, PartialEq)]
 struct EarlyVm {
-    #[allow(dead_code)]
     name: String,
-    #[allow(dead_code)]
     cid: i32,
-    #[allow(dead_code)]
     path: String,
 }
 
 #[derive(Debug, Default, Deserialize)]
 struct EarlyVms {
-    #[allow(dead_code)]
     early_vm: Vec<EarlyVm>,
 }
 
+static EARLY_VMS_CACHE: LazyLock<Mutex<HashMap<String, Vec<EarlyVm>>>> =
+    LazyLock::new(|| Mutex::new(HashMap::new()));
+
 fn range_for_partition(partition: &str) -> Result<Range<Cid>> {
     match partition {
         "system" => Ok(100..200),
@@ -2031,7 +2030,7 @@
     }
 }
 
-fn find_early_vm(xml_path: &Path, cid_range: &Range<Cid>, name: &str) -> Result<EarlyVm> {
+fn get_early_vms_in_path(xml_path: &Path) -> Result<Vec<EarlyVm>> {
     if !xml_path.exists() {
         bail!("{} doesn't exist", xml_path.display());
     }
@@ -2043,35 +2042,74 @@
     let early_vms: EarlyVms = serde_xml_rs::from_str(&xml)
         .with_context(|| format!("Can't parse {}", xml_path.display()))?;
 
-    let mut found_vm: Option<EarlyVm> = None;
+    Ok(early_vms.early_vm)
+}
 
-    for early_vm in early_vms.early_vm {
+fn validate_cid_range(early_vms: &[EarlyVm], cid_range: &Range<Cid>) -> Result<()> {
+    for early_vm in early_vms {
+        let cid = early_vm
+            .cid
+            .try_into()
+            .with_context(|| format!("VM '{}' uses Invalid CID {}", early_vm.name, early_vm.cid))?;
+
+        ensure!(
+            cid_range.contains(&cid),
+            "VM '{}' uses CID {cid} which is out of range. Available CIDs: {cid_range:?}",
+            early_vm.name
+        );
+    }
+    Ok(())
+}
+
+fn get_early_vms_in_partition(partition: &str) -> Result<Vec<EarlyVm>> {
+    let mut cache = EARLY_VMS_CACHE.lock().unwrap();
+
+    if let Some(result) = cache.get(partition) {
+        return Ok(result.clone());
+    }
+
+    let pattern = format!("/{partition}/etc/avf/early_vms*.xml");
+    let mut early_vms = Vec::new();
+    for entry in glob::glob(&pattern).with_context(|| format!("Failed to glob {}", &pattern))? {
+        match entry {
+            Ok(path) => early_vms.extend(get_early_vms_in_path(&path)?),
+            Err(e) => error!("Error while globbing (but continuing) {}: {}", &pattern, e),
+        }
+    }
+
+    validate_cid_range(&early_vms, &range_for_partition(partition)?)
+        .with_context(|| format!("CID validation for {partition} failed"))?;
+
+    cache.insert(partition.to_owned(), early_vms.clone());
+
+    Ok(early_vms)
+}
+
+fn find_early_vm<'a>(early_vms: &'a [EarlyVm], name: &str) -> Result<&'a EarlyVm> {
+    let mut found_vm: Option<&EarlyVm> = None;
+
+    for early_vm in early_vms {
         if early_vm.name != name {
             continue;
         }
 
-        let cid = early_vm
-            .cid
-            .try_into()
-            .with_context(|| format!("Invalid CID value {}", early_vm.cid))?;
-
-        if !cid_range.contains(&cid) {
-            bail!("VM '{}' uses CID {cid} which is out of range. Available CIDs for '{}': {cid_range:?}", xml_path.display(), early_vm.name);
-        }
-
         if found_vm.is_some() {
-            bail!("Multiple VMs named {name} are found in {}", xml_path.display());
+            bail!("Multiple VMs named '{name}' are found");
         }
 
         found_vm = Some(early_vm);
     }
 
-    found_vm.ok_or_else(|| anyhow!("Can't find {name} in {}", xml_path.display()))
+    found_vm.ok_or_else(|| anyhow!("Can't find a VM named '{name}'"))
 }
 
 fn find_early_vm_for_partition(partition: &str, name: &str) -> Result<EarlyVm> {
-    let cid_range = range_for_partition(partition)?;
-    find_early_vm(Path::new(&format!("/{partition}/etc/avf/early_vms.xml")), &cid_range, name)
+    let early_vms = get_early_vms_in_partition(partition)
+        .with_context(|| format!("Failed to get early VMs from {partition}"))?;
+
+    Ok(find_early_vm(&early_vms, name)
+        .with_context(|| format!("Failed to find early VM '{name}' in {partition}"))?
+        .clone())
 }
 
 #[cfg(test)]
@@ -2314,6 +2352,87 @@
                 <path>/system/bin/vm_demo_native_early</path>
             </early_vm>
             <early_vm>
+                <name>vm_demo_native_early_2</name>
+                <cid>456</cid>
+                <path>/system/bin/vm_demo_native_early_2</path>
+            </early_vm>
+        </early_vms>
+        "#,
+        )?;
+
+        let cid_range = 100..1000;
+
+        let early_vms = get_early_vms_in_path(&xml_path)?;
+        validate_cid_range(&early_vms, &cid_range)?;
+
+        let test_cases = [
+            (
+                "vm_demo_native_early",
+                EarlyVm {
+                    name: "vm_demo_native_early".to_owned(),
+                    cid: 123,
+                    path: "/system/bin/vm_demo_native_early".to_owned(),
+                },
+            ),
+            (
+                "vm_demo_native_early_2",
+                EarlyVm {
+                    name: "vm_demo_native_early_2".to_owned(),
+                    cid: 456,
+                    path: "/system/bin/vm_demo_native_early_2".to_owned(),
+                },
+            ),
+        ];
+
+        for (name, expected) in test_cases {
+            let result = find_early_vm(&early_vms, name)?;
+            assert_eq!(result, &expected);
+        }
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_invalid_cid_validation() -> Result<()> {
+        let tmp_dir = tempfile::TempDir::new()?;
+        let xml_path = tmp_dir.path().join("early_vms.xml");
+
+        let cid_range = 100..1000;
+
+        for cid in [-1, 999999] {
+            std::fs::write(
+                &xml_path,
+                format!(
+                    r#"<?xml version="1.0" encoding="utf-8"?>
+        <early_vms>
+            <early_vm>
+                <name>vm_demo_invalid_cid</name>
+                <cid>{cid}</cid>
+                <path>/system/bin/vm_demo_invalid_cid</path>
+            </early_vm>
+        </early_vms>
+        "#
+                ),
+            )?;
+
+            let early_vms = get_early_vms_in_path(&xml_path)?;
+            assert!(validate_cid_range(&early_vms, &cid_range).is_err(), "should fail");
+        }
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_duplicated_early_vms() -> Result<()> {
+        let tmp_dir = tempfile::TempDir::new()?;
+        let tmp_dir_path = tmp_dir.path().to_owned();
+        let xml_path = tmp_dir_path.join("early_vms.xml");
+
+        std::fs::write(
+            &xml_path,
+            br#"<?xml version="1.0" encoding="utf-8"?>
+        <early_vms>
+            <early_vm>
                 <name>vm_demo_duplicated_name</name>
                 <cid>456</cid>
                 <path>/system/bin/vm_demo_duplicated_name_1</path>
@@ -2323,42 +2442,16 @@
                 <cid>789</cid>
                 <path>/system/bin/vm_demo_duplicated_name_2</path>
             </early_vm>
-            <early_vm>
-                <name>vm_demo_invalid_cid_1</name>
-                <cid>-1</cid>
-                <path>/system/bin/vm_demo_invalid_cid_1</path>
-            </early_vm>
-            <early_vm>
-                <name>vm_demo_invalid_cid_2</name>
-                <cid>999999</cid>
-                <path>/system/bin/vm_demo_invalid_cid_2</path>
-            </early_vm>
         </early_vms>
         "#,
         )?;
 
         let cid_range = 100..1000;
 
-        let result = find_early_vm(&xml_path, &cid_range, "vm_demo_native_early")?;
-        let expected = EarlyVm {
-            name: "vm_demo_native_early".to_owned(),
-            cid: 123,
-            path: "/system/bin/vm_demo_native_early".to_owned(),
-        };
-        assert_eq!(result, expected);
+        let early_vms = get_early_vms_in_path(&xml_path)?;
+        validate_cid_range(&early_vms, &cid_range)?;
 
-        assert!(
-            find_early_vm(&xml_path, &cid_range, "vm_demo_duplicated_name").is_err(),
-            "should fail"
-        );
-        assert!(
-            find_early_vm(&xml_path, &cid_range, "vm_demo_invalid_cid_1").is_err(),
-            "should fail"
-        );
-        assert!(
-            find_early_vm(&xml_path, &cid_range, "vm_demo_invalid_cid_2").is_err(),
-            "should fail"
-        );
+        assert!(find_early_vm(&early_vms, "vm_demo_duplicated_name").is_err(), "should fail");
 
         Ok(())
     }
diff --git a/android/virtmgr/src/crosvm.rs b/android/virtmgr/src/crosvm.rs
index b28834a..94379a9 100644
--- a/android/virtmgr/src/crosvm.rs
+++ b/android/virtmgr/src/crosvm.rs
@@ -975,7 +975,11 @@
     if config.protected {
         match system_properties::read(SYSPROP_CUSTOM_PVMFW_PATH)? {
             Some(pvmfw_path) if !pvmfw_path.is_empty() => {
-                command.arg("--protected-vm-with-firmware").arg(pvmfw_path)
+                if pvmfw_path == "none" {
+                    command.arg("--protected-vm-without-firmware")
+                } else {
+                    command.arg("--protected-vm-with-firmware").arg(pvmfw_path)
+                }
             }
             _ => command.arg("--protected-vm"),
         };
diff --git a/build/microdroid/Android.bp b/build/microdroid/Android.bp
index d5d8108..d9e39f0 100644
--- a/build/microdroid/Android.bp
+++ b/build/microdroid/Android.bp
@@ -139,7 +139,10 @@
             ],
         },
     },
-    linker_config_src: "linker.config.json",
+    linkerconfig: {
+        gen_linker_config: true,
+        linker_config_srcs: ["linker.config.json"],
+    },
     base_dir: "system",
     dirs: microdroid_rootdirs + select(release_flag("RELEASE_AVF_ENABLE_DICE_CHANGES"), {
         true: ["microdroid_resources"],
diff --git a/docs/early_vm.md b/docs/early_vm.md
index 44b71ff..3f21f11 100644
--- a/docs/early_vm.md
+++ b/docs/early_vm.md
@@ -8,8 +8,9 @@
 
 To run an early VM, clients must follow these steps.
 
-1) Early VMs need to be defined in `{partition}/etc/avf/early_vms.xml`. The
-schema for this file is defined in [`early_vms.xsd`](../android/virtmgr/early_vms.xsd).
+1) Early VMs must be defined in XML files located at
+`{partition}/etc/avf/early_vms*.xml`. Schema for these files is defined in
+[`early_vms.xsd`](../android/virtmgr/early_vms.xsd).
 
 ```early_vms.xml
 <early_vms>
@@ -25,6 +26,9 @@
 connection with `early_virtmgr` and create a VM named `vm_demo_native_early`,
 which will be assigned the static CID 123.
 
+Multiple XML files matching the glob pattern
+`{partition}/etc/avf/early_vms*.xml` can be used to define early VMs.
+
 2) The client must have the following three or four capabilities.
 
 * `IPC_LOCK`
diff --git a/guest/pvmfw/README.md b/guest/pvmfw/README.md
index 3ffa3f0..50fe3d3 100644
--- a/guest/pvmfw/README.md
+++ b/guest/pvmfw/README.md
@@ -487,3 +487,19 @@
 Note: `adb root` is required to set the system property.
 
 [bcc.dat]: https://cs.android.com/android/platform/superproject/main/+/main:packages/modules/Virtualization/tests/pvmfw/assets/bcc.dat
+
+### Running pVM without pvmfw
+
+Sometimes, it might be useful to start a pVM without pvmfw, e.g. when debugging
+early pVM boot issues. You can achieve that by setting `hypervisor.pvmfw.path`
+propety to the value `none`:
+
+```shell
+adb shell 'setprop hypervisor.pvmfw.path "none"'
+```
+
+Then run a protected VM:
+
+```shell
+adb shell /apex/com.android.virt/bin/vm run-microdroid --protected
+```
diff --git a/guest/pvmfw/src/device_assignment.rs b/guest/pvmfw/src/device_assignment.rs
index da9462b..9b55cff 100644
--- a/guest/pvmfw/src/device_assignment.rs
+++ b/guest/pvmfw/src/device_assignment.rs
@@ -784,14 +784,16 @@
             if reg.size != phys_reg.size {
                 return Err(DeviceAssignmentError::InvalidRegSize(reg.size, phys_reg.size));
             }
-            let expected_token = phys_reg.addr;
-            // If this call returns successfully, hyp has mapped the MMIO region at `reg`.
-            let token = hypervisor.get_phys_mmio_token(reg.addr, reg.size).map_err(|e| {
-                error!("Hypervisor error while requesting MMIO token: {e}");
-                DeviceAssignmentError::InvalidReg(reg.addr)
-            })?;
-            if token != expected_token {
-                return Err(DeviceAssignmentError::InvalidRegToken(token, expected_token));
+            for offset in (0..reg.size).step_by(granule) {
+                let expected_token = phys_reg.addr + offset;
+                // If this call returns successfully, hyp has mapped the MMIO granule.
+                let token = hypervisor.get_phys_mmio_token(reg.addr + offset).map_err(|e| {
+                    error!("Hypervisor error while requesting MMIO token: {e}");
+                    DeviceAssignmentError::InvalidReg(reg.addr)
+                })?;
+                if token != expected_token {
+                    return Err(DeviceAssignmentError::InvalidRegToken(token, expected_token));
+                }
             }
         }
 
@@ -1143,7 +1145,7 @@
 #[cfg(test)]
 trait DeviceAssigningHypervisor {
     /// Returns MMIO token.
-    fn get_phys_mmio_token(&self, base_ipa: u64, size: u64) -> MockHypervisorResult<u64>;
+    fn get_phys_mmio_token(&self, base_ipa: u64) -> MockHypervisorResult<u64>;
 
     /// Returns DMA token as a tuple of (phys_iommu_id, phys_sid).
     fn get_phys_iommu_token(&self, pviommu_id: u64, vsid: u64) -> MockHypervisorResult<(u64, u64)>;
@@ -1206,7 +1208,7 @@
     }
 
     impl DeviceAssigningHypervisor for MockHypervisor {
-        fn get_phys_mmio_token(&self, base_ipa: u64, _size: u64) -> MockHypervisorResult<u64> {
+        fn get_phys_mmio_token(&self, base_ipa: u64) -> MockHypervisorResult<u64> {
             let token = self.get_mmio_token(base_ipa);
 
             Ok(*token.ok_or(MockHypervisorError::FailedGetPhysMmioToken)?)
diff --git a/guest/trusty/security_vm/launcher/Android.bp b/guest/trusty/security_vm/launcher/Android.bp
index ff628fd..e482e02 100644
--- a/guest/trusty/security_vm/launcher/Android.bp
+++ b/guest/trusty/security_vm/launcher/Android.bp
@@ -18,3 +18,40 @@
         false: false,
     }),
 }
+
+prebuilt_etc {
+    name: "lk_trusty.elf",
+    system_ext_specific: true,
+    relative_install_path: "vm/trusty_vm",
+    filename: "lk_trusty.elf",
+    arch: {
+        x86_64: {
+            src: ":trusty_security_vm_signed",
+        },
+    },
+    src: ":empty_file",
+}
+
+filegroup {
+    name: "trusty_vm_sign_key",
+    srcs: [":avb_testkey_rsa4096"],
+}
+
+// python -c "import hashlib; print(hashlib.sha256(b'trusty_security_vm_salt').hexdigest())"
+trusty_security_vm_salt = "75a71e967c1a1e0f805cca20465e7acf83e6a04e567a67c426d8b5a94f8d61c5"
+
+avb_add_hash_footer {
+    name: "trusty_security_vm_signed",
+    filename: "trusty_security_vm_signed",
+    partition_name: "boot",
+    private_key: ":trusty_vm_sign_key",
+    salt: trusty_security_vm_salt,
+    src: ":empty_file",
+    enabled: false,
+    arch: {
+        x86_64: {
+            src: ":trusty-test-lk.elf",
+            enabled: true,
+        },
+    },
+}
diff --git a/libs/libvmbase/src/hyp/hypervisor/common.rs b/libs/libvmbase/src/hyp/hypervisor/common.rs
index de0fe12..8f0e4dc 100644
--- a/libs/libvmbase/src/hyp/hypervisor/common.rs
+++ b/libs/libvmbase/src/hyp/hypervisor/common.rs
@@ -69,7 +69,7 @@
 /// Device assigning hypervisor
 pub trait DeviceAssigningHypervisor {
     /// Returns MMIO token.
-    fn get_phys_mmio_token(&self, base_ipa: u64, size: u64) -> Result<u64>;
+    fn get_phys_mmio_token(&self, base_ipa: u64) -> Result<u64>;
 
     /// Returns DMA token as a tuple of (phys_iommu_id, phys_sid).
     fn get_phys_iommu_token(&self, pviommu_id: u64, vsid: u64) -> Result<(u64, u64)>;
diff --git a/libs/libvmbase/src/hyp/hypervisor/kvm.rs b/libs/libvmbase/src/hyp/hypervisor/kvm.rs
index e496f09..7ed829e 100644
--- a/libs/libvmbase/src/hyp/hypervisor/kvm.rs
+++ b/libs/libvmbase/src/hyp/hypervisor/kvm.rs
@@ -173,10 +173,9 @@
 }
 
 impl DeviceAssigningHypervisor for ProtectedKvmHypervisor {
-    fn get_phys_mmio_token(&self, base_ipa: u64, size: u64) -> Result<u64> {
+    fn get_phys_mmio_token(&self, base_ipa: u64) -> Result<u64> {
         let mut args = [0u64; 17];
         args[0] = base_ipa;
-        args[1] = size;
 
         let ret = checked_hvc64_expect_results(VENDOR_HYP_KVM_DEV_REQ_MMIO_FUNC_ID, args)?;
         Ok(ret[0])