Merge "Start using virtmgr for running VMs"
diff --git a/compos/tests/AndroidTest.xml b/compos/tests/AndroidTest.xml
index f9e6837..4b414f1 100644
--- a/compos/tests/AndroidTest.xml
+++ b/compos/tests/AndroidTest.xml
@@ -16,6 +16,10 @@
<configuration description="Tests for CompOS">
<option name="config-descriptor:metadata" key="mainline-param" value="com.google.android.art.apex" />
+ <!-- Only run tests if the device under test is SDK version 33 (Android 13) or above. -->
+ <object type="module_controller"
+ class="com.android.tradefed.testtype.suite.module.Sdk33ModuleController" />
+
<target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer">
<option name="force-root" value="true" />
</target_preparer>
diff --git a/javalib/api/system-current.txt b/javalib/api/system-current.txt
index 592a751..30e437b 100644
--- a/javalib/api/system-current.txt
+++ b/javalib/api/system-current.txt
@@ -3,19 +3,19 @@
public class VirtualMachine implements java.lang.AutoCloseable {
method public void clearCallback();
- method public void close();
- method @NonNull public android.os.IBinder connectToVsockServer(@IntRange(from=android.system.virtualmachine.VirtualMachine.MIN_VSOCK_PORT, to=android.system.virtualmachine.VirtualMachine.MAX_VSOCK_PORT) long) throws android.system.virtualmachine.VirtualMachineException;
- method @NonNull public android.os.ParcelFileDescriptor connectVsock(@IntRange(from=android.system.virtualmachine.VirtualMachine.MIN_VSOCK_PORT, to=android.system.virtualmachine.VirtualMachine.MAX_VSOCK_PORT) long) throws android.system.virtualmachine.VirtualMachineException;
- method @NonNull public android.system.virtualmachine.VirtualMachineConfig getConfig();
- method @NonNull public java.io.InputStream getConsoleOutput() throws android.system.virtualmachine.VirtualMachineException;
- method @NonNull public java.io.InputStream getLogOutput() throws android.system.virtualmachine.VirtualMachineException;
+ method @WorkerThread public void close();
+ method @NonNull @WorkerThread public android.os.IBinder connectToVsockServer(@IntRange(from=android.system.virtualmachine.VirtualMachine.MIN_VSOCK_PORT, to=android.system.virtualmachine.VirtualMachine.MAX_VSOCK_PORT) long) throws android.system.virtualmachine.VirtualMachineException;
+ method @NonNull @WorkerThread public android.os.ParcelFileDescriptor connectVsock(@IntRange(from=android.system.virtualmachine.VirtualMachine.MIN_VSOCK_PORT, to=android.system.virtualmachine.VirtualMachine.MAX_VSOCK_PORT) long) throws android.system.virtualmachine.VirtualMachineException;
+ method @NonNull @WorkerThread public android.system.virtualmachine.VirtualMachineConfig getConfig();
+ method @NonNull @WorkerThread public java.io.InputStream getConsoleOutput() throws android.system.virtualmachine.VirtualMachineException;
+ method @NonNull @WorkerThread public java.io.InputStream getLogOutput() throws android.system.virtualmachine.VirtualMachineException;
method @NonNull public String getName();
- method public int getStatus();
- method @RequiresPermission(android.system.virtualmachine.VirtualMachine.MANAGE_VIRTUAL_MACHINE_PERMISSION) public void run() throws android.system.virtualmachine.VirtualMachineException;
+ method @WorkerThread public int getStatus();
+ method @RequiresPermission(android.system.virtualmachine.VirtualMachine.MANAGE_VIRTUAL_MACHINE_PERMISSION) @WorkerThread public void run() throws android.system.virtualmachine.VirtualMachineException;
method public void setCallback(@NonNull java.util.concurrent.Executor, @NonNull android.system.virtualmachine.VirtualMachineCallback);
- method @NonNull public android.system.virtualmachine.VirtualMachineConfig setConfig(@NonNull android.system.virtualmachine.VirtualMachineConfig) throws android.system.virtualmachine.VirtualMachineException;
- method public void stop() throws android.system.virtualmachine.VirtualMachineException;
- method @NonNull public android.system.virtualmachine.VirtualMachineDescriptor toDescriptor() throws android.system.virtualmachine.VirtualMachineException;
+ method @NonNull @WorkerThread public android.system.virtualmachine.VirtualMachineConfig setConfig(@NonNull android.system.virtualmachine.VirtualMachineConfig) throws android.system.virtualmachine.VirtualMachineException;
+ method @WorkerThread public void stop() throws android.system.virtualmachine.VirtualMachineException;
+ method @NonNull @WorkerThread public android.system.virtualmachine.VirtualMachineDescriptor toDescriptor() throws android.system.virtualmachine.VirtualMachineException;
field public static final String MANAGE_VIRTUAL_MACHINE_PERMISSION = "android.permission.MANAGE_VIRTUAL_MACHINE";
field public static final long MAX_VSOCK_PORT = 4294967295L; // 0xffffffffL
field public static final long MIN_VSOCK_PORT = 1024L; // 0x400L
@@ -91,12 +91,12 @@
}
public class VirtualMachineManager {
- method @NonNull @RequiresPermission(android.system.virtualmachine.VirtualMachine.MANAGE_VIRTUAL_MACHINE_PERMISSION) public android.system.virtualmachine.VirtualMachine create(@NonNull String, @NonNull android.system.virtualmachine.VirtualMachineConfig) throws android.system.virtualmachine.VirtualMachineException;
- method public void delete(@NonNull String) throws android.system.virtualmachine.VirtualMachineException;
- method @Nullable public android.system.virtualmachine.VirtualMachine get(@NonNull String) throws android.system.virtualmachine.VirtualMachineException;
+ method @NonNull @RequiresPermission(android.system.virtualmachine.VirtualMachine.MANAGE_VIRTUAL_MACHINE_PERMISSION) @WorkerThread public android.system.virtualmachine.VirtualMachine create(@NonNull String, @NonNull android.system.virtualmachine.VirtualMachineConfig) throws android.system.virtualmachine.VirtualMachineException;
+ method @WorkerThread public void delete(@NonNull String) throws android.system.virtualmachine.VirtualMachineException;
+ method @Nullable @WorkerThread public android.system.virtualmachine.VirtualMachine get(@NonNull String) throws android.system.virtualmachine.VirtualMachineException;
method public int getCapabilities();
- method @NonNull public android.system.virtualmachine.VirtualMachine getOrCreate(@NonNull String, @NonNull android.system.virtualmachine.VirtualMachineConfig) throws android.system.virtualmachine.VirtualMachineException;
- method @NonNull public android.system.virtualmachine.VirtualMachine importFromDescriptor(@NonNull String, @NonNull android.system.virtualmachine.VirtualMachineDescriptor) throws android.system.virtualmachine.VirtualMachineException;
+ method @NonNull @WorkerThread public android.system.virtualmachine.VirtualMachine getOrCreate(@NonNull String, @NonNull android.system.virtualmachine.VirtualMachineConfig) throws android.system.virtualmachine.VirtualMachineException;
+ method @NonNull @WorkerThread public android.system.virtualmachine.VirtualMachine importFromDescriptor(@NonNull String, @NonNull android.system.virtualmachine.VirtualMachineDescriptor) throws android.system.virtualmachine.VirtualMachineException;
field public static final int CAPABILITY_NON_PROTECTED_VM = 2; // 0x2
field public static final int CAPABILITY_PROTECTED_VM = 1; // 0x1
}
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachine.java b/javalib/src/android/system/virtualmachine/VirtualMachine.java
index 34fb379..3295681 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachine.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachine.java
@@ -52,6 +52,7 @@
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.TestApi;
+import android.annotation.WorkerThread;
import android.content.ComponentCallbacks2;
import android.content.Context;
import android.content.res.Configuration;
@@ -107,6 +108,15 @@
* received using {@link #setCallback}. The app can communicate with the VM using {@link
* #connectToVsockServer} or {@link #connectVsock}.
*
+ * <p>The payload code running inside the VM has access to a set of native APIs; see the <a
+ * href="https://cs.android.com/android/platform/superproject/+/master:packages/modules/Virtualization/vm_payload/README.md">README
+ * file</a> for details.
+ *
+ * <p>Each VM has a unique secret, computed from the APK that contains the code running in it, the
+ * VM configuration, and a random per-instance salt. The secret can be accessed by the payload code
+ * running inside the VM (using {@code AVmPayload_getVmInstanceSecret}) but is not made available
+ * outside it.
+ *
* @hide
*/
@SystemApi
@@ -520,8 +530,8 @@
// Once we explicitly delete a VM it must remain permanently in the deleted state;
// if a new VM is created with the same name (and files) that's unrelated.
mWasDeleted = true;
- deleteVmDirectory(context, name);
}
+ deleteVmDirectory(context, name);
}
static void deleteVmDirectory(Context context, String name) throws VirtualMachineException {
@@ -577,13 +587,16 @@
/**
* Returns the currently selected config of this virtual machine. There can be multiple virtual
* machines sharing the same config. Even in that case, the virtual machines are completely
- * isolated from each other; one cannot share its secret to another virtual machine even if they
- * share the same config. It is also possible that a virtual machine can switch its config,
- * which can be done by calling {@link #setConfig(VirtualMachineConfig)}.
+ * isolated from each other; they have different secrets. It is also possible that a virtual
+ * machine can change its config, which can be done by calling {@link
+ * #setConfig(VirtualMachineConfig)}.
+ *
+ * <p>NOTE: This method may block and should not be called on the main thread.
*
* @hide
*/
@SystemApi
+ @WorkerThread
@NonNull
public VirtualMachineConfig getConfig() {
synchronized (mLock) {
@@ -594,9 +607,12 @@
/**
* Returns the current status of this virtual machine.
*
+ * <p>NOTE: This method may block and should not be called on the main thread.
+ *
* @hide
*/
@SystemApi
+ @WorkerThread
@Status
public int getStatus() {
IVirtualMachine virtualMachine;
@@ -735,11 +751,14 @@
* registering a callback using {@link #setCallback(Executor, VirtualMachineCallback)} before
* calling {@code run()}.
*
+ * <p>NOTE: This method may block and should not be called on the main thread.
+ *
* @throws VirtualMachineException if the virtual machine is not stopped or could not be
* started.
* @hide
*/
@SystemApi
+ @WorkerThread
@RequiresPermission(MANAGE_VIRTUAL_MACHINE_PERMISSION)
public void run() throws VirtualMachineException {
synchronized (mLock) {
@@ -878,10 +897,13 @@
/**
* Returns the stream object representing the console output from the virtual machine.
*
+ * <p>NOTE: This method may block and should not be called on the main thread.
+ *
* @throws VirtualMachineException if the stream could not be created.
* @hide
*/
@SystemApi
+ @WorkerThread
@NonNull
public InputStream getConsoleOutput() throws VirtualMachineException {
synchronized (mLock) {
@@ -893,10 +915,13 @@
/**
* Returns the stream object representing the log output from the virtual machine.
*
+ * <p>NOTE: This method may block and should not be called on the main thread.
+ *
* @throws VirtualMachineException if the stream could not be created.
* @hide
*/
@SystemApi
+ @WorkerThread
@NonNull
public InputStream getLogOutput() throws VirtualMachineException {
synchronized (mLock) {
@@ -910,11 +935,14 @@
* computer; the machine halts immediately. Software running on the virtual machine is not
* notified of the event. A stopped virtual machine can be re-started by calling {@link #run()}.
*
+ * <p>NOTE: This method may block and should not be called on the main thread.
+ *
* @throws VirtualMachineException if the virtual machine is not running or could not be
* stopped.
* @hide
*/
@SystemApi
+ @WorkerThread
public void stop() throws VirtualMachineException {
synchronized (mLock) {
if (mVirtualMachine == null) {
@@ -934,10 +962,13 @@
/**
* Stops this virtual machine, if it is running.
*
+ * <p>NOTE: This method may block and should not be called on the main thread.
+ *
* @see #stop()
* @hide
*/
@SystemApi
+ @WorkerThread
@Override
public void close() {
synchronized (mLock) {
@@ -988,12 +1019,15 @@
* <p>The new config must be {@link VirtualMachineConfig#isCompatibleWith compatible with} the
* existing config.
*
+ * <p>NOTE: This method may block and should not be called on the main thread.
+ *
* @return the old config
* @throws VirtualMachineException if the virtual machine is not stopped, or the new config is
* incompatible.
* @hide
*/
@SystemApi
+ @WorkerThread
@NonNull
public VirtualMachineConfig setConfig(@NonNull VirtualMachineConfig newConfig)
throws VirtualMachineException {
@@ -1022,11 +1056,14 @@
* VirtualMachineCallback#onPayloadReady(VirtualMachine)}, it can use this method to establish a
* connection to the guest VM.
*
+ * <p>NOTE: This method may block and should not be called on the main thread.
+ *
* @throws VirtualMachineException if the virtual machine is not running or the connection
* failed.
* @hide
*/
@SystemApi
+ @WorkerThread
@NonNull
public IBinder connectToVsockServer(
@IntRange(from = MIN_VSOCK_PORT, to = MAX_VSOCK_PORT) long port)
@@ -1045,10 +1082,13 @@
/**
* Opens a vsock connection to the VM on the given port.
*
+ * <p>NOTE: This method may block and should not be called on the main thread.
+ *
* @throws VirtualMachineException if connecting fails.
* @hide
*/
@SystemApi
+ @WorkerThread
@NonNull
public ParcelFileDescriptor connectVsock(
@IntRange(from = MIN_VSOCK_PORT, to = MAX_VSOCK_PORT) long port)
@@ -1094,12 +1134,15 @@
* VirtualMachineManager#importFromDescriptor} is called. It is recommended that the VM not be
* started until that operation is complete.
*
+ * <p>NOTE: This method may block and should not be called on the main thread.
+ *
* @return a {@link VirtualMachineDescriptor} instance that represents the VM's state.
* @throws VirtualMachineException if the virtual machine is not stopped, or the state could not
* be captured.
* @hide
*/
@SystemApi
+ @WorkerThread
@NonNull
public VirtualMachineDescriptor toDescriptor() throws VirtualMachineException {
synchronized (mLock) {
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java b/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
index 75e5414..7555dec 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
@@ -349,9 +349,10 @@
/**
* Tests if this config is compatible with other config. Being compatible means that the configs
- * can be interchangeably used for the same virtual machine. Compatible changes includes the
- * number of CPUs and the size of the RAM. All other changes (e.g. using a different payload,
- * change of the debug mode, etc.) are considered as incompatible.
+ * can be interchangeably used for the same virtual machine; they do not change the VM identity
+ * or secrets. Such changes include varying the number of CPUs or the size of the RAM. Changes
+ * that would alter the identity of the VM (e.g. using a different payload or changing the debug
+ * mode) are considered incompatible.
*
* @hide
*/
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineManager.java b/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
index 5b30617..6aa8133 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
@@ -24,6 +24,7 @@
import android.annotation.RequiresFeature;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
+import android.annotation.WorkerThread;
import android.content.Context;
import android.content.pm.PackageManager;
import android.sysprop.HypervisorProperties;
@@ -123,12 +124,15 @@
* the name and the config are the same as a deleted one. The new virtual machine will initially
* be stopped.
*
+ * <p>NOTE: This method may block and should not be called on the main thread.
+ *
* @throws VirtualMachineException if the VM cannot be created, or there is an existing VM with
* the given name.
* @hide
*/
@SystemApi
@NonNull
+ @WorkerThread
@RequiresPermission(VirtualMachine.MANAGE_VIRTUAL_MACHINE_PERMISSION)
public VirtualMachine create(@NonNull String name, @NonNull VirtualMachineConfig config)
throws VirtualMachineException {
@@ -154,12 +158,15 @@
* machine instance. Multiple calls to get() passing the same name will get the same object
* returned, until the virtual machine is deleted (via {@link #delete}) and then recreated.
*
+ * <p>NOTE: This method may block and should not be called on the main thread.
+ *
* @see #getOrCreate
* @throws VirtualMachineException if the virtual machine exists but could not be successfully
* retrieved.
* @hide
*/
@SystemApi
+ @WorkerThread
@Nullable
public VirtualMachine get(@NonNull String name) throws VirtualMachineException {
synchronized (sCreateLock) {
@@ -186,11 +193,14 @@
*
* <p>The new virtual machine will be in the same state as the descriptor indicates.
*
+ * <p>NOTE: This method may block and should not be called on the main thread.
+ *
* @throws VirtualMachineException if the VM cannot be imported.
* @hide
*/
@NonNull
@SystemApi
+ @WorkerThread
public VirtualMachine importFromDescriptor(
@NonNull String name, @NonNull VirtualMachineDescriptor vmDescriptor)
throws VirtualMachineException {
@@ -205,10 +215,13 @@
* Returns an existing {@link VirtualMachine} if it exists, or create a new one. The config
* parameter is used only when a new virtual machine is created.
*
+ * <p>NOTE: This method may block and should not be called on the main thread.
+ *
* @throws VirtualMachineException if the virtual machine could not be created or retrieved.
* @hide
*/
@SystemApi
+ @WorkerThread
@NonNull
public VirtualMachine getOrCreate(@NonNull String name, @NonNull VirtualMachineConfig config)
throws VirtualMachineException {
@@ -229,11 +242,14 @@
* with the same name is different from an already deleted virtual machine even if it has the
* same config.
*
+ * <p>NOTE: This method may block and should not be called on the main thread.
+ *
* @throws VirtualMachineException if the virtual machine does not exist, is not stopped, or
* cannot be deleted.
* @hide
*/
@SystemApi
+ @WorkerThread
public void delete(@NonNull String name) throws VirtualMachineException {
synchronized (sCreateLock) {
VirtualMachine vm = getVmByName(name);
diff --git a/libs/fdtpci/Android.bp b/libs/fdtpci/Android.bp
new file mode 100644
index 0000000..f368b08
--- /dev/null
+++ b/libs/fdtpci/Android.bp
@@ -0,0 +1,18 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_library_rlib {
+ name: "libfdtpci",
+ edition: "2021",
+ no_stdlibs: true,
+ host_supported: false,
+ crate_name: "fdtpci",
+ srcs: ["src/lib.rs"],
+ rustlibs: [
+ "liblibfdt",
+ "liblog_rust_nostd",
+ "libvirtio_drivers",
+ ],
+ apex_available: ["com.android.virt"],
+}
diff --git a/libs/fdtpci/TEST_MAPPING b/libs/fdtpci/TEST_MAPPING
new file mode 100644
index 0000000..c315b4a
--- /dev/null
+++ b/libs/fdtpci/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "avf-presubmit": [
+ {
+ "name": "vmbase_example.integration_test"
+ }
+ ]
+}
diff --git a/libs/fdtpci/src/lib.rs b/libs/fdtpci/src/lib.rs
new file mode 100644
index 0000000..a63e05b
--- /dev/null
+++ b/libs/fdtpci/src/lib.rs
@@ -0,0 +1,231 @@
+// Copyright 2022, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Library for working with (VirtIO) PCI devices discovered from a device tree.
+
+#![no_std]
+
+use core::{
+ ffi::CStr,
+ fmt::{self, Display, Formatter},
+ ops::Range,
+};
+use libfdt::{AddressRange, Fdt, FdtError, FdtNode};
+use log::debug;
+use virtio_drivers::pci::bus::{Cam, PciRoot};
+
+/// PCI MMIO configuration region size.
+const PCI_CFG_SIZE: usize = 0x100_0000;
+
+/// An error parsing a PCI node from an FDT.
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub enum PciError {
+ /// Error getting PCI node from FDT.
+ FdtErrorPci(FdtError),
+ /// Failed to find PCI bus in FDT.
+ FdtNoPci,
+ /// Error getting `reg` property from PCI node.
+ FdtErrorReg(FdtError),
+ /// PCI node missing `reg` property.
+ FdtMissingReg,
+ /// Empty `reg property on PCI node.
+ FdtRegEmpty,
+ /// PCI `reg` property missing size.
+ FdtRegMissingSize,
+ /// PCI CAM size reported by FDT is not what we expected.
+ CamWrongSize(usize),
+ /// Error getting `ranges` property from PCI node.
+ FdtErrorRanges(FdtError),
+ /// PCI node missing `ranges` property.
+ FdtMissingRanges,
+ /// Bus address is not equal to CPU physical address in `ranges` property.
+ RangeAddressMismatch {
+ /// A bus address from the `ranges` property.
+ bus_address: u64,
+ /// The corresponding CPU physical address from the `ranges` property.
+ cpu_physical: u64,
+ },
+ /// No suitable PCI memory range found.
+ NoSuitableRange,
+}
+
+impl Display for PciError {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ match self {
+ Self::FdtErrorPci(e) => write!(f, "Error getting PCI node from FDT: {}", e),
+ Self::FdtNoPci => write!(f, "Failed to find PCI bus in FDT."),
+ Self::FdtErrorReg(e) => write!(f, "Error getting reg property from PCI node: {}", e),
+ Self::FdtMissingReg => write!(f, "PCI node missing reg property."),
+ Self::FdtRegEmpty => write!(f, "Empty reg property on PCI node."),
+ Self::FdtRegMissingSize => write!(f, "PCI reg property missing size."),
+ Self::CamWrongSize(cam_size) => write!(
+ f,
+ "FDT says PCI CAM is {} bytes but we expected {}.",
+ cam_size, PCI_CFG_SIZE
+ ),
+ Self::FdtErrorRanges(e) => {
+ write!(f, "Error getting ranges property from PCI node: {}", e)
+ }
+ Self::FdtMissingRanges => write!(f, "PCI node missing ranges property."),
+ Self::RangeAddressMismatch { bus_address, cpu_physical } => {
+ write!(
+ f,
+ "bus address {:#018x} != CPU physical address {:#018x}",
+ bus_address, cpu_physical
+ )
+ }
+ Self::NoSuitableRange => write!(f, "No suitable PCI memory range found."),
+ }
+ }
+}
+
+/// Information about the PCI bus parsed from the device tree.
+#[derive(Debug)]
+pub struct PciInfo {
+ /// The MMIO range used by the memory-mapped PCI CAM.
+ pub cam_range: Range<usize>,
+ /// The MMIO range from which 32-bit PCI BARs should be allocated.
+ pub bar_range: Range<u32>,
+}
+
+impl PciInfo {
+ /// Finds the PCI node in the FDT, parses its properties and validates it.
+ pub fn from_fdt(fdt: &Fdt) -> Result<Self, PciError> {
+ let pci_node = pci_node(fdt)?;
+
+ let cam_range = parse_cam_range(&pci_node)?;
+ let bar_range = parse_ranges(&pci_node)?;
+
+ Ok(Self { cam_range, bar_range })
+ }
+
+ /// Returns the `PciRoot` for the memory-mapped CAM found in the FDT. The CAM should be mapped
+ /// before this is called, by calling [`PciInfo::map`].
+ ///
+ /// # Safety
+ ///
+ /// To prevent concurrent access, only one `PciRoot` should exist in the program. Thus this
+ /// method must only be called once, and there must be no other `PciRoot` constructed using the
+ /// same CAM.
+ pub unsafe fn make_pci_root(&self) -> PciRoot {
+ PciRoot::new(self.cam_range.start as *mut u8, Cam::MmioCam)
+ }
+}
+
+/// Finds an FDT node with compatible=pci-host-cam-generic.
+fn pci_node(fdt: &Fdt) -> Result<FdtNode, PciError> {
+ fdt.compatible_nodes(CStr::from_bytes_with_nul(b"pci-host-cam-generic\0").unwrap())
+ .map_err(PciError::FdtErrorPci)?
+ .next()
+ .ok_or(PciError::FdtNoPci)
+}
+
+/// Parses the "reg" property of the given PCI FDT node to find the MMIO CAM range.
+fn parse_cam_range(pci_node: &FdtNode) -> Result<Range<usize>, PciError> {
+ let pci_reg = pci_node
+ .reg()
+ .map_err(PciError::FdtErrorReg)?
+ .ok_or(PciError::FdtMissingReg)?
+ .next()
+ .ok_or(PciError::FdtRegEmpty)?;
+ let cam_addr = pci_reg.addr as usize;
+ let cam_size = pci_reg.size.ok_or(PciError::FdtRegMissingSize)? as usize;
+ debug!("Found PCI CAM at {:#x}-{:#x}", cam_addr, cam_addr + cam_size);
+ // Check that the CAM is the size we expect, so we don't later try accessing it beyond its
+ // bounds. If it is a different size then something is very wrong and we shouldn't continue to
+ // access it; maybe there is some new version of PCI we don't know about.
+ if cam_size != PCI_CFG_SIZE {
+ return Err(PciError::CamWrongSize(cam_size));
+ }
+
+ Ok(cam_addr..cam_addr + cam_size)
+}
+
+/// Parses the "ranges" property of the given PCI FDT node, and returns the largest suitable range
+/// to use for non-prefetchable 32-bit memory BARs.
+fn parse_ranges(pci_node: &FdtNode) -> Result<Range<u32>, PciError> {
+ let mut memory_address = 0;
+ let mut memory_size = 0;
+
+ for AddressRange { addr: (flags, bus_address), parent_addr: cpu_physical, size } in pci_node
+ .ranges::<(u32, u64), u64, u64>()
+ .map_err(PciError::FdtErrorRanges)?
+ .ok_or(PciError::FdtMissingRanges)?
+ {
+ let flags = PciMemoryFlags(flags);
+ let prefetchable = flags.prefetchable();
+ let range_type = flags.range_type();
+ debug!(
+ "range: {:?} {}prefetchable bus address: {:#018x} CPU physical address: {:#018x} size: {:#018x}",
+ range_type,
+ if prefetchable { "" } else { "non-" },
+ bus_address,
+ cpu_physical,
+ size,
+ );
+
+ // Use a 64-bit range for 32-bit memory, if it is low enough, because crosvm doesn't
+ // currently provide any 32-bit ranges.
+ if !prefetchable
+ && matches!(range_type, PciRangeType::Memory32 | PciRangeType::Memory64)
+ && size > memory_size.into()
+ && bus_address + size < u32::MAX.into()
+ {
+ if bus_address != cpu_physical {
+ return Err(PciError::RangeAddressMismatch { bus_address, cpu_physical });
+ }
+ memory_address = u32::try_from(cpu_physical).unwrap();
+ memory_size = u32::try_from(size).unwrap();
+ }
+ }
+
+ if memory_size == 0 {
+ return Err(PciError::NoSuitableRange);
+ }
+
+ Ok(memory_address..memory_address + memory_size)
+}
+
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+struct PciMemoryFlags(u32);
+
+impl PciMemoryFlags {
+ pub fn prefetchable(self) -> bool {
+ self.0 & 0x80000000 != 0
+ }
+
+ pub fn range_type(self) -> PciRangeType {
+ PciRangeType::from((self.0 & 0x3000000) >> 24)
+ }
+}
+
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+enum PciRangeType {
+ ConfigurationSpace,
+ IoSpace,
+ Memory32,
+ Memory64,
+}
+
+impl From<u32> for PciRangeType {
+ fn from(value: u32) -> Self {
+ match value {
+ 0 => Self::ConfigurationSpace,
+ 1 => Self::IoSpace,
+ 2 => Self::Memory32,
+ 3 => Self::Memory64,
+ _ => panic!("Tried to convert invalid range type {}", value),
+ }
+ }
+}
diff --git a/pvmfw/Android.bp b/pvmfw/Android.bp
index 2912c91..ed3ef8d 100644
--- a/pvmfw/Android.bp
+++ b/pvmfw/Android.bp
@@ -15,9 +15,9 @@
"libaarch64_paging",
"libbuddy_system_allocator",
"libdice_nostd",
+ "libfdtpci",
"liblibfdt",
"liblog_rust_nostd",
- "libpvmfw_avb_nostd",
"libpvmfw_embedded_key",
"libtinyvec_nostd",
"libvirtio_drivers",
diff --git a/pvmfw/avb/Android.bp b/pvmfw/avb/Android.bp
index 65259a5..3026d20 100644
--- a/pvmfw/avb/Android.bp
+++ b/pvmfw/avb/Android.bp
@@ -10,6 +10,9 @@
rustlibs: [
"libavb_bindgen",
],
+ whole_static_libs: [
+ "libavb",
+ ],
}
rust_library_rlib {
@@ -25,4 +28,31 @@
name: "libpvmfw_avb.test",
defaults: ["libpvmfw_avb_nostd_defaults"],
test_suites: ["general-tests"],
+ data: [
+ ":avb_testkey_rsa2048_pub_bin",
+ ":avb_testkey_rsa4096_pub_bin",
+ ":microdroid_kernel_signed",
+ ":unsigned_test_image",
+ ],
+ rustlibs: [
+ "libanyhow",
+ ],
+ enabled: false,
+ arch: {
+ // Microdroid kernel is only available in these architectures.
+ arm64: {
+ enabled: true,
+ },
+ x86_64: {
+ enabled: true,
+ },
+ },
+}
+
+// Generates a 16KB unsigned image for testing.
+genrule {
+ name: "unsigned_test_image",
+ tools: ["avbtool"],
+ out: ["unsigned_test.img"],
+ cmd: "$(location avbtool) generate_test_image --image_size 16384 --output $(out)",
}
diff --git a/pvmfw/avb/src/lib.rs b/pvmfw/avb/src/lib.rs
index eb1f918..1f39076 100644
--- a/pvmfw/avb/src/lib.rs
+++ b/pvmfw/avb/src/lib.rs
@@ -15,6 +15,8 @@
//! A library implementing the payload verification for pvmfw with libavb
#![cfg_attr(not(test), no_std)]
+// For usize.checked_add_signed(isize), available in Rust 1.66.0
+#![feature(mixed_integer_ops)]
mod verify;
diff --git a/pvmfw/avb/src/verify.rs b/pvmfw/avb/src/verify.rs
index 7f3ba3d..d5f7283 100644
--- a/pvmfw/avb/src/verify.rs
+++ b/pvmfw/avb/src/verify.rs
@@ -14,11 +14,19 @@
//! This module handles the pvmfw payload verification.
-use avb_bindgen::AvbSlotVerifyResult;
-use core::fmt;
+use avb_bindgen::{
+ avb_slot_verify, AvbHashtreeErrorMode, AvbIOResult, AvbOps, AvbSlotVerifyFlags,
+ AvbSlotVerifyResult,
+};
+use core::{
+ ffi::{c_char, c_void, CStr},
+ fmt,
+ ptr::{self, NonNull},
+ slice,
+};
/// Error code from AVB image verification.
-#[derive(Clone, Debug)]
+#[derive(Clone, Debug, PartialEq, Eq)]
pub enum AvbImageVerifyError {
/// AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_ARGUMENT
InvalidArgument,
@@ -82,31 +90,383 @@
}
}
+enum AvbIOError {
+ /// AVB_IO_RESULT_ERROR_OOM,
+ #[allow(dead_code)]
+ Oom,
+ /// AVB_IO_RESULT_ERROR_IO,
+ #[allow(dead_code)]
+ Io,
+ /// AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION,
+ NoSuchPartition,
+ /// AVB_IO_RESULT_ERROR_RANGE_OUTSIDE_PARTITION,
+ RangeOutsidePartition,
+ /// AVB_IO_RESULT_ERROR_NO_SUCH_VALUE,
+ NoSuchValue,
+ /// AVB_IO_RESULT_ERROR_INVALID_VALUE_SIZE,
+ InvalidValueSize,
+ /// AVB_IO_RESULT_ERROR_INSUFFICIENT_SPACE,
+ #[allow(dead_code)]
+ InsufficientSpace,
+}
+
+impl From<AvbIOError> for AvbIOResult {
+ fn from(error: AvbIOError) -> Self {
+ match error {
+ AvbIOError::Oom => AvbIOResult::AVB_IO_RESULT_ERROR_OOM,
+ AvbIOError::Io => AvbIOResult::AVB_IO_RESULT_ERROR_IO,
+ AvbIOError::NoSuchPartition => AvbIOResult::AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION,
+ AvbIOError::RangeOutsidePartition => {
+ AvbIOResult::AVB_IO_RESULT_ERROR_RANGE_OUTSIDE_PARTITION
+ }
+ AvbIOError::NoSuchValue => AvbIOResult::AVB_IO_RESULT_ERROR_NO_SUCH_VALUE,
+ AvbIOError::InvalidValueSize => AvbIOResult::AVB_IO_RESULT_ERROR_INVALID_VALUE_SIZE,
+ AvbIOError::InsufficientSpace => AvbIOResult::AVB_IO_RESULT_ERROR_INSUFFICIENT_SPACE,
+ }
+ }
+}
+
+fn to_avb_io_result(result: Result<(), AvbIOError>) -> AvbIOResult {
+ result.map_or_else(|e| e.into(), |_| AvbIOResult::AVB_IO_RESULT_OK)
+}
+
+extern "C" fn read_is_device_unlocked(
+ _ops: *mut AvbOps,
+ out_is_unlocked: *mut bool,
+) -> AvbIOResult {
+ if let Err(e) = is_not_null(out_is_unlocked) {
+ return e.into();
+ }
+ // SAFETY: It is safe as the raw pointer `out_is_unlocked` is a valid pointer.
+ unsafe {
+ *out_is_unlocked = false;
+ }
+ AvbIOResult::AVB_IO_RESULT_OK
+}
+
+extern "C" fn read_from_partition(
+ ops: *mut AvbOps,
+ partition: *const c_char,
+ offset: i64,
+ num_bytes: usize,
+ buffer: *mut c_void,
+ out_num_read: *mut usize,
+) -> AvbIOResult {
+ to_avb_io_result(try_read_from_partition(
+ ops,
+ partition,
+ offset,
+ num_bytes,
+ buffer,
+ out_num_read,
+ ))
+}
+
+fn try_read_from_partition(
+ ops: *mut AvbOps,
+ partition: *const c_char,
+ offset: i64,
+ num_bytes: usize,
+ buffer: *mut c_void,
+ out_num_read: *mut usize,
+) -> Result<(), AvbIOError> {
+ let ops = as_avbops_ref(ops)?;
+ let partition = ops.as_ref().get_partition(partition)?;
+ let buffer = to_nonnull(buffer)?;
+ // SAFETY: It is safe to copy the requested number of bytes to `buffer` as `buffer`
+ // is created to point to the `num_bytes` of bytes in memory.
+ let buffer_slice = unsafe { slice::from_raw_parts_mut(buffer.as_ptr() as *mut u8, num_bytes) };
+ copy_data_to_dst(partition, offset, buffer_slice)?;
+ let out_num_read = to_nonnull(out_num_read)?;
+ // SAFETY: The raw pointer `out_num_read` was created to point to a valid a `usize`
+ // and we checked it is nonnull.
+ unsafe {
+ *out_num_read.as_ptr() = buffer_slice.len();
+ }
+ Ok(())
+}
+
+fn copy_data_to_dst(src: &[u8], offset: i64, dst: &mut [u8]) -> Result<(), AvbIOError> {
+ let start = to_copy_start(offset, src.len()).ok_or(AvbIOError::InvalidValueSize)?;
+ let end = start.checked_add(dst.len()).ok_or(AvbIOError::InvalidValueSize)?;
+ dst.copy_from_slice(src.get(start..end).ok_or(AvbIOError::RangeOutsidePartition)?);
+ Ok(())
+}
+
+fn to_copy_start(offset: i64, len: usize) -> Option<usize> {
+ usize::try_from(offset)
+ .ok()
+ .or_else(|| isize::try_from(offset).ok().and_then(|v| len.checked_add_signed(v)))
+}
+
+extern "C" fn get_size_of_partition(
+ ops: *mut AvbOps,
+ partition: *const c_char,
+ out_size_num_bytes: *mut u64,
+) -> AvbIOResult {
+ to_avb_io_result(try_get_size_of_partition(ops, partition, out_size_num_bytes))
+}
+
+fn try_get_size_of_partition(
+ ops: *mut AvbOps,
+ partition: *const c_char,
+ out_size_num_bytes: *mut u64,
+) -> Result<(), AvbIOError> {
+ let ops = as_avbops_ref(ops)?;
+ let partition = ops.as_ref().get_partition(partition)?;
+ let partition_size =
+ u64::try_from(partition.len()).map_err(|_| AvbIOError::InvalidValueSize)?;
+ let out_size_num_bytes = to_nonnull(out_size_num_bytes)?;
+ // SAFETY: The raw pointer `out_size_num_bytes` was created to point to a valid a `u64`
+ // and we checked it is nonnull.
+ unsafe {
+ *out_size_num_bytes.as_ptr() = partition_size;
+ }
+ Ok(())
+}
+
+extern "C" fn read_rollback_index(
+ _ops: *mut AvbOps,
+ _rollback_index_location: usize,
+ _out_rollback_index: *mut u64,
+) -> AvbIOResult {
+ // Rollback protection is not yet implemented, but
+ // this method is required by `avb_slot_verify()`.
+ AvbIOResult::AVB_IO_RESULT_OK
+}
+
+extern "C" fn get_unique_guid_for_partition(
+ _ops: *mut AvbOps,
+ _partition: *const c_char,
+ _guid_buf: *mut c_char,
+ _guid_buf_size: usize,
+) -> AvbIOResult {
+ // This method is required by `avb_slot_verify()`.
+ AvbIOResult::AVB_IO_RESULT_OK
+}
+
+extern "C" fn validate_public_key_for_partition(
+ ops: *mut AvbOps,
+ partition: *const c_char,
+ public_key_data: *const u8,
+ public_key_length: usize,
+ public_key_metadata: *const u8,
+ public_key_metadata_length: usize,
+ out_is_trusted: *mut bool,
+ out_rollback_index_location: *mut u32,
+) -> AvbIOResult {
+ to_avb_io_result(try_validate_public_key_for_partition(
+ ops,
+ partition,
+ public_key_data,
+ public_key_length,
+ public_key_metadata,
+ public_key_metadata_length,
+ out_is_trusted,
+ out_rollback_index_location,
+ ))
+}
+
+#[allow(clippy::too_many_arguments)]
+fn try_validate_public_key_for_partition(
+ ops: *mut AvbOps,
+ partition: *const c_char,
+ public_key_data: *const u8,
+ public_key_length: usize,
+ _public_key_metadata: *const u8,
+ _public_key_metadata_length: usize,
+ out_is_trusted: *mut bool,
+ _out_rollback_index_location: *mut u32,
+) -> Result<(), AvbIOError> {
+ is_not_null(public_key_data)?;
+ // SAFETY: It is safe to create a slice with the given pointer and length as
+ // `public_key_data` is a valid pointer and it points to an array of length
+ // `public_key_length`.
+ let public_key = unsafe { slice::from_raw_parts(public_key_data, public_key_length) };
+ let ops = as_avbops_ref(ops)?;
+ // Verifies the public key for the known partitions only.
+ ops.as_ref().get_partition(partition)?;
+ let trusted_public_key = ops.as_ref().trusted_public_key;
+ let out_is_trusted = to_nonnull(out_is_trusted)?;
+ // SAFETY: It is safe as the raw pointer `out_is_trusted` is a nonnull pointer.
+ unsafe {
+ *out_is_trusted.as_ptr() = public_key == trusted_public_key;
+ }
+ Ok(())
+}
+
+fn as_avbops_ref<'a>(ops: *mut AvbOps) -> Result<&'a AvbOps, AvbIOError> {
+ let ops = to_nonnull(ops)?;
+ // SAFETY: It is safe as the raw pointer `ops` is a nonnull pointer.
+ unsafe { Ok(ops.as_ref()) }
+}
+
+fn to_nonnull<T>(p: *mut T) -> Result<NonNull<T>, AvbIOError> {
+ NonNull::new(p).ok_or(AvbIOError::NoSuchValue)
+}
+
+fn is_not_null<T>(ptr: *const T) -> Result<(), AvbIOError> {
+ if ptr.is_null() {
+ Err(AvbIOError::NoSuchValue)
+ } else {
+ Ok(())
+ }
+}
+
+struct Payload<'a> {
+ kernel: &'a [u8],
+ trusted_public_key: &'a [u8],
+}
+
+impl<'a> AsRef<Payload<'a>> for AvbOps {
+ fn as_ref(&self) -> &Payload<'a> {
+ let payload = self.user_data as *const Payload;
+ // SAFETY: It is safe to cast the `AvbOps.user_data` to Payload as we have saved a
+ // pointer to a valid value of Payload in user_data when creating AvbOps, and
+ // assume that the Payload isn't used beyond the lifetime of the AvbOps that it
+ // belongs to.
+ unsafe { &*payload }
+ }
+}
+
+impl<'a> Payload<'a> {
+ const KERNEL_PARTITION_NAME: &[u8] = b"bootloader\0";
+
+ fn kernel_partition_name(&self) -> &CStr {
+ CStr::from_bytes_with_nul(Self::KERNEL_PARTITION_NAME).unwrap()
+ }
+
+ fn get_partition(&self, partition_name: *const c_char) -> Result<&[u8], AvbIOError> {
+ is_not_null(partition_name)?;
+ // SAFETY: It is safe as the raw pointer `partition_name` is a nonnull pointer.
+ let partition_name = unsafe { CStr::from_ptr(partition_name) };
+ match partition_name.to_bytes_with_nul() {
+ Self::KERNEL_PARTITION_NAME => Ok(self.kernel),
+ _ => Err(AvbIOError::NoSuchPartition),
+ }
+ }
+}
+
/// Verifies the payload (signed kernel + initrd) against the trusted public key.
-pub fn verify_payload(_public_key: &[u8]) -> Result<(), AvbImageVerifyError> {
- // TODO(b/256148034): Verify the kernel image with avb_slot_verify()
- // let result = unsafe {
- // avb_slot_verify(
- // &mut avb_ops,
- // requested_partitions.as_ptr(),
- // ab_suffix.as_ptr(),
- // flags,
- // hashtree_error_mode,
- // null_mut(),
- // )
- // };
- let result = AvbSlotVerifyResult::AVB_SLOT_VERIFY_RESULT_OK;
+pub fn verify_payload(kernel: &[u8], trusted_public_key: &[u8]) -> Result<(), AvbImageVerifyError> {
+ let mut payload = Payload { kernel, trusted_public_key };
+ let mut avb_ops = AvbOps {
+ user_data: &mut payload as *mut _ as *mut c_void,
+ ab_ops: ptr::null_mut(),
+ atx_ops: ptr::null_mut(),
+ read_from_partition: Some(read_from_partition),
+ get_preloaded_partition: None,
+ write_to_partition: None,
+ validate_vbmeta_public_key: None,
+ read_rollback_index: Some(read_rollback_index),
+ write_rollback_index: None,
+ read_is_device_unlocked: Some(read_is_device_unlocked),
+ get_unique_guid_for_partition: Some(get_unique_guid_for_partition),
+ get_size_of_partition: Some(get_size_of_partition),
+ read_persistent_value: None,
+ write_persistent_value: None,
+ validate_public_key_for_partition: Some(validate_public_key_for_partition),
+ };
+ // NULL is needed to mark the end of the array.
+ let requested_partitions: [*const c_char; 2] =
+ [payload.kernel_partition_name().as_ptr(), ptr::null()];
+ let ab_suffix = CStr::from_bytes_with_nul(b"\0").unwrap();
+
+ // SAFETY: It is safe to call `avb_slot_verify()` as the pointer arguments (`ops`,
+ // `requested_partitions` and `ab_suffix`) passed to the method are all valid and
+ // initialized. The last argument `out_data` is allowed to be null so that nothing
+ // will be written to it.
+ let result = unsafe {
+ avb_slot_verify(
+ &mut avb_ops,
+ requested_partitions.as_ptr(),
+ ab_suffix.as_ptr(),
+ AvbSlotVerifyFlags::AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION,
+ AvbHashtreeErrorMode::AVB_HASHTREE_ERROR_MODE_RESTART_AND_INVALIDATE,
+ /*out_data=*/ ptr::null_mut(),
+ )
+ };
to_avb_verify_result(result)
}
#[cfg(test)]
mod tests {
use super::*;
+ use anyhow::Result;
+ use std::fs;
- // TODO(b/256148034): Test verification succeeds with valid payload later.
+ const PUBLIC_KEY_RSA2048_PATH: &str = "data/testkey_rsa2048_pub.bin";
+ const PUBLIC_KEY_RSA4096_PATH: &str = "data/testkey_rsa4096_pub.bin";
+
+ /// This test uses the Microdroid payload compiled on the fly to check that
+ /// the latest payload can be verified successfully.
#[test]
- fn verification_succeeds_with_placeholder_input() {
- let fake_public_key = [0u8; 2];
- assert!(verify_payload(&fake_public_key).is_ok());
+ fn latest_valid_payload_is_verified_successfully() -> Result<()> {
+ let kernel = load_latest_signed_kernel()?;
+ let public_key = fs::read(PUBLIC_KEY_RSA4096_PATH)?;
+
+ assert_eq!(Ok(()), verify_payload(&kernel, &public_key));
+ Ok(())
+ }
+
+ #[test]
+ fn payload_with_empty_public_key_fails_verification() -> Result<()> {
+ assert_payload_verification_fails(
+ &load_latest_signed_kernel()?,
+ /*trusted_public_key=*/ &[0u8; 0],
+ AvbImageVerifyError::PublicKeyRejected,
+ )
+ }
+
+ #[test]
+ fn payload_with_an_invalid_public_key_fails_verification() -> Result<()> {
+ assert_payload_verification_fails(
+ &load_latest_signed_kernel()?,
+ /*trusted_public_key=*/ &[0u8; 512],
+ AvbImageVerifyError::PublicKeyRejected,
+ )
+ }
+
+ #[test]
+ fn payload_with_a_different_valid_public_key_fails_verification() -> Result<()> {
+ assert_payload_verification_fails(
+ &load_latest_signed_kernel()?,
+ &fs::read(PUBLIC_KEY_RSA2048_PATH)?,
+ AvbImageVerifyError::PublicKeyRejected,
+ )
+ }
+
+ #[test]
+ fn unsigned_kernel_fails_verification() -> Result<()> {
+ assert_payload_verification_fails(
+ &fs::read("unsigned_test.img")?,
+ &fs::read(PUBLIC_KEY_RSA4096_PATH)?,
+ AvbImageVerifyError::Io,
+ )
+ }
+
+ #[test]
+ fn tampered_kernel_fails_verification() -> Result<()> {
+ let mut kernel = load_latest_signed_kernel()?;
+ kernel[1] = !kernel[1]; // Flip the bits
+
+ assert_payload_verification_fails(
+ &kernel,
+ &fs::read(PUBLIC_KEY_RSA4096_PATH)?,
+ AvbImageVerifyError::Verification,
+ )
+ }
+
+ fn assert_payload_verification_fails(
+ kernel: &[u8],
+ trusted_public_key: &[u8],
+ expected_error: AvbImageVerifyError,
+ ) -> Result<()> {
+ assert_eq!(Err(expected_error), verify_payload(kernel, trusted_public_key));
+ Ok(())
+ }
+
+ fn load_latest_signed_kernel() -> Result<Vec<u8>> {
+ Ok(fs::read("microdroid_kernel")?)
}
}
diff --git a/pvmfw/src/entry.rs b/pvmfw/src/entry.rs
index 1b35c79..e979a95 100644
--- a/pvmfw/src/entry.rs
+++ b/pvmfw/src/entry.rs
@@ -47,6 +47,7 @@
/// The provided ramdisk was invalid.
InvalidRamdisk,
/// Failed to verify the payload.
+ #[allow(dead_code)]
PayloadVerificationError,
}
diff --git a/pvmfw/src/heap.rs b/pvmfw/src/heap.rs
index bfa8320..eab3bc4 100644
--- a/pvmfw/src/heap.rs
+++ b/pvmfw/src/heap.rs
@@ -14,6 +14,14 @@
//! Heap implementation.
+use core::alloc::GlobalAlloc as _;
+use core::alloc::Layout;
+use core::ffi::c_void;
+use core::mem;
+use core::num::NonZeroUsize;
+use core::ptr;
+use core::ptr::NonNull;
+
use buddy_system_allocator::LockedHeap;
#[global_allocator]
@@ -24,3 +32,32 @@
pub unsafe fn init() {
HEAP_ALLOCATOR.lock().init(HEAP.as_mut_ptr() as usize, HEAP.len());
}
+
+#[no_mangle]
+unsafe extern "C" fn malloc(size: usize) -> *mut c_void {
+ malloc_(size).map_or(ptr::null_mut(), |p| p.cast::<c_void>().as_ptr())
+}
+
+#[no_mangle]
+unsafe extern "C" fn free(ptr: *mut c_void) {
+ if let Some(ptr) = NonNull::new(ptr).map(|p| p.cast::<usize>().as_ptr().offset(-1)) {
+ if let Some(size) = NonZeroUsize::new(*ptr) {
+ if let Some(layout) = malloc_layout(size) {
+ HEAP_ALLOCATOR.dealloc(ptr as *mut u8, layout);
+ }
+ }
+ }
+}
+
+unsafe fn malloc_(size: usize) -> Option<NonNull<usize>> {
+ let size = NonZeroUsize::new(size)?.checked_add(mem::size_of::<usize>())?;
+ let ptr = HEAP_ALLOCATOR.alloc(malloc_layout(size)?);
+ let ptr = NonNull::new(ptr)?.cast::<usize>().as_ptr();
+ *ptr = size.get();
+ NonNull::new(ptr.offset(1))
+}
+
+fn malloc_layout(size: NonZeroUsize) -> Option<Layout> {
+ const ALIGN: usize = mem::size_of::<u64>();
+ Layout::from_size_align(size.get(), ALIGN).ok()
+}
diff --git a/pvmfw/src/main.rs b/pvmfw/src/main.rs
index 7d64bf0..4d1ddfe 100644
--- a/pvmfw/src/main.rs
+++ b/pvmfw/src/main.rs
@@ -34,15 +34,15 @@
mod smccc;
use crate::{
- avb::PUBLIC_KEY,
+ avb::PUBLIC_KEY, // Keep the public key here otherwise the signing script will be broken.
entry::RebootReason,
memory::MemoryTracker,
- pci::{find_virtio_devices, PciError, PciInfo},
+ pci::{find_virtio_devices, map_mmio},
};
use dice::bcc;
+use fdtpci::{PciError, PciInfo};
use libfdt::Fdt;
use log::{debug, error, info, trace};
-use pvmfw_avb::verify_payload;
fn main(
fdt: &Fdt,
@@ -54,6 +54,7 @@
info!("pVM firmware");
debug!("FDT: {:?}", fdt as *const libfdt::Fdt);
debug!("Signed kernel: {:?} ({:#x} bytes)", signed_kernel.as_ptr(), signed_kernel.len());
+ debug!("AVB public key: addr={:?}, size={:#x} ({1})", PUBLIC_KEY.as_ptr(), PUBLIC_KEY.len());
if let Some(rd) = ramdisk {
debug!("Ramdisk: {:?} ({:#x} bytes)", rd.as_ptr(), rd.len());
} else {
@@ -64,16 +65,12 @@
// Set up PCI bus for VirtIO devices.
let pci_info = PciInfo::from_fdt(fdt).map_err(handle_pci_error)?;
debug!("PCI: {:#x?}", pci_info);
- pci_info.map(memory)?;
+ map_mmio(&pci_info, memory)?;
// Safety: This is the only place where we call make_pci_root, and this main function is only
// called once.
let mut pci_root = unsafe { pci_info.make_pci_root() };
find_virtio_devices(&mut pci_root).map_err(handle_pci_error)?;
- verify_payload(PUBLIC_KEY).map_err(|e| {
- error!("Failed to verify the payload: {e}");
- RebootReason::PayloadVerificationError
- })?;
info!("Starting payload...");
Ok(())
}
diff --git a/pvmfw/src/pci.rs b/pvmfw/src/pci.rs
index 301ecfc..e9ac45b 100644
--- a/pvmfw/src/pci.rs
+++ b/pvmfw/src/pci.rs
@@ -14,225 +14,26 @@
//! Functions to scan the PCI bus for VirtIO devices.
-use crate::{
- entry::RebootReason,
- memory::{MemoryRange, MemoryTracker},
-};
-use core::{
- ffi::CStr,
- fmt::{self, Display, Formatter},
- ops::Range,
-};
-use libfdt::{AddressRange, Fdt, FdtError, FdtNode};
+use crate::{entry::RebootReason, memory::MemoryTracker};
+use fdtpci::{PciError, PciInfo};
use log::{debug, error};
-use virtio_drivers::pci::{
- bus::{Cam, PciRoot},
- virtio_device_type,
-};
+use virtio_drivers::pci::{bus::PciRoot, virtio_device_type};
-/// PCI MMIO configuration region size.
-const PCI_CFG_SIZE: usize = 0x100_0000;
+/// Maps the CAM and BAR range in the page table and MMIO guard.
+pub fn map_mmio(pci_info: &PciInfo, memory: &mut MemoryTracker) -> Result<(), RebootReason> {
+ memory.map_mmio_range(pci_info.cam_range.clone()).map_err(|e| {
+ error!("Failed to map PCI CAM: {}", e);
+ RebootReason::InternalError
+ })?;
-#[derive(Clone, Debug, Eq, PartialEq)]
-pub enum PciError {
- FdtErrorPci(FdtError),
- FdtNoPci,
- FdtErrorReg(FdtError),
- FdtMissingReg,
- FdtRegEmpty,
- FdtRegMissingSize,
- CamWrongSize(usize),
- FdtErrorRanges(FdtError),
- FdtMissingRanges,
- RangeAddressMismatch { bus_address: u64, cpu_physical: u64 },
- NoSuitableRange,
-}
-
-impl Display for PciError {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- match self {
- Self::FdtErrorPci(e) => write!(f, "Error getting PCI node from FDT: {}", e),
- Self::FdtNoPci => write!(f, "Failed to find PCI bus in FDT."),
- Self::FdtErrorReg(e) => write!(f, "Error getting reg property from PCI node: {}", e),
- Self::FdtMissingReg => write!(f, "PCI node missing reg property."),
- Self::FdtRegEmpty => write!(f, "Empty reg property on PCI node."),
- Self::FdtRegMissingSize => write!(f, "PCI reg property missing size."),
- Self::CamWrongSize(cam_size) => write!(
- f,
- "FDT says PCI CAM is {} bytes but we expected {}.",
- cam_size, PCI_CFG_SIZE
- ),
- Self::FdtErrorRanges(e) => {
- write!(f, "Error getting ranges property from PCI node: {}", e)
- }
- Self::FdtMissingRanges => write!(f, "PCI node missing ranges property."),
- Self::RangeAddressMismatch { bus_address, cpu_physical } => {
- write!(
- f,
- "bus address {:#018x} != CPU physical address {:#018x}",
- bus_address, cpu_physical
- )
- }
- Self::NoSuitableRange => write!(f, "No suitable PCI memory range found."),
- }
- }
-}
-
-/// Information about the PCI bus parsed from the device tree.
-#[derive(Debug)]
-pub struct PciInfo {
- /// The MMIO range used by the memory-mapped PCI CAM.
- cam_range: MemoryRange,
- /// The MMIO range from which 32-bit PCI BARs should be allocated.
- bar_range: Range<u32>,
-}
-
-impl PciInfo {
- /// Finds the PCI node in the FDT, parses its properties and validates it.
- pub fn from_fdt(fdt: &Fdt) -> Result<Self, PciError> {
- let pci_node = pci_node(fdt)?;
-
- let cam_range = parse_cam_range(&pci_node)?;
- let bar_range = parse_ranges(&pci_node)?;
-
- Ok(Self { cam_range, bar_range })
- }
-
- /// Maps the CAM and BAR range in the page table and MMIO guard.
- pub fn map(&self, memory: &mut MemoryTracker) -> Result<(), RebootReason> {
- memory.map_mmio_range(self.cam_range.clone()).map_err(|e| {
- error!("Failed to map PCI CAM: {}", e);
+ memory
+ .map_mmio_range(pci_info.bar_range.start as usize..pci_info.bar_range.end as usize)
+ .map_err(|e| {
+ error!("Failed to map PCI MMIO range: {}", e);
RebootReason::InternalError
})?;
- memory.map_mmio_range(self.bar_range.start as usize..self.bar_range.end as usize).map_err(
- |e| {
- error!("Failed to map PCI MMIO range: {}", e);
- RebootReason::InternalError
- },
- )?;
-
- Ok(())
- }
-
- /// Returns the `PciRoot` for the memory-mapped CAM found in the FDT. The CAM should be mapped
- /// before this is called, by calling [`PciInfo::map`].
- ///
- /// # Safety
- ///
- /// To prevent concurrent access, only one `PciRoot` should exist in the program. Thus this
- /// method must only be called once, and there must be no other `PciRoot` constructed using the
- /// same CAM.
- pub unsafe fn make_pci_root(&self) -> PciRoot {
- PciRoot::new(self.cam_range.start as *mut u8, Cam::MmioCam)
- }
-}
-
-/// Finds an FDT node with compatible=pci-host-cam-generic.
-fn pci_node(fdt: &Fdt) -> Result<FdtNode, PciError> {
- fdt.compatible_nodes(CStr::from_bytes_with_nul(b"pci-host-cam-generic\0").unwrap())
- .map_err(PciError::FdtErrorPci)?
- .next()
- .ok_or(PciError::FdtNoPci)
-}
-
-/// Parses the "reg" property of the given PCI FDT node to find the MMIO CAM range.
-fn parse_cam_range(pci_node: &FdtNode) -> Result<MemoryRange, PciError> {
- let pci_reg = pci_node
- .reg()
- .map_err(PciError::FdtErrorReg)?
- .ok_or(PciError::FdtMissingReg)?
- .next()
- .ok_or(PciError::FdtRegEmpty)?;
- let cam_addr = pci_reg.addr as usize;
- let cam_size = pci_reg.size.ok_or(PciError::FdtRegMissingSize)? as usize;
- debug!("Found PCI CAM at {:#x}-{:#x}", cam_addr, cam_addr + cam_size);
- // Check that the CAM is the size we expect, so we don't later try accessing it beyond its
- // bounds. If it is a different size then something is very wrong and we shouldn't continue to
- // access it; maybe there is some new version of PCI we don't know about.
- if cam_size != PCI_CFG_SIZE {
- return Err(PciError::CamWrongSize(cam_size));
- }
-
- Ok(cam_addr..cam_addr + cam_size)
-}
-
-/// Parses the "ranges" property of the given PCI FDT node, and returns the largest suitable range
-/// to use for non-prefetchable 32-bit memory BARs.
-fn parse_ranges(pci_node: &FdtNode) -> Result<Range<u32>, PciError> {
- let mut memory_address = 0;
- let mut memory_size = 0;
-
- for AddressRange { addr: (flags, bus_address), parent_addr: cpu_physical, size } in pci_node
- .ranges::<(u32, u64), u64, u64>()
- .map_err(PciError::FdtErrorRanges)?
- .ok_or(PciError::FdtMissingRanges)?
- {
- let flags = PciMemoryFlags(flags);
- let prefetchable = flags.prefetchable();
- let range_type = flags.range_type();
- debug!(
- "range: {:?} {}prefetchable bus address: {:#018x} CPU physical address: {:#018x} size: {:#018x}",
- range_type,
- if prefetchable { "" } else { "non-" },
- bus_address,
- cpu_physical,
- size,
- );
-
- // Use a 64-bit range for 32-bit memory, if it is low enough, because crosvm doesn't
- // currently provide any 32-bit ranges.
- if !prefetchable
- && matches!(range_type, PciRangeType::Memory32 | PciRangeType::Memory64)
- && size > memory_size.into()
- && bus_address + size < u32::MAX.into()
- {
- if bus_address != cpu_physical {
- return Err(PciError::RangeAddressMismatch { bus_address, cpu_physical });
- }
- memory_address = u32::try_from(cpu_physical).unwrap();
- memory_size = u32::try_from(size).unwrap();
- }
- }
-
- if memory_size == 0 {
- return Err(PciError::NoSuitableRange);
- }
-
- Ok(memory_address..memory_address + memory_size)
-}
-
-#[derive(Copy, Clone, Debug, Eq, PartialEq)]
-struct PciMemoryFlags(u32);
-
-impl PciMemoryFlags {
- pub fn prefetchable(self) -> bool {
- self.0 & 0x80000000 != 0
- }
-
- pub fn range_type(self) -> PciRangeType {
- PciRangeType::from((self.0 & 0x3000000) >> 24)
- }
-}
-
-#[derive(Copy, Clone, Debug, Eq, PartialEq)]
-enum PciRangeType {
- ConfigurationSpace,
- IoSpace,
- Memory32,
- Memory64,
-}
-
-impl From<u32> for PciRangeType {
- fn from(value: u32) -> Self {
- match value {
- 0 => Self::ConfigurationSpace,
- 1 => Self::IoSpace,
- 2 => Self::Memory32,
- 3 => Self::Memory64,
- _ => panic!("Tried to convert invalid range type {}", value),
- }
- }
+ Ok(())
}
/// Finds VirtIO PCI devices.
diff --git a/tests/aidl/com/android/microdroid/testservice/ITestService.aidl b/tests/aidl/com/android/microdroid/testservice/ITestService.aidl
index c936e1b..7ee1f01 100644
--- a/tests/aidl/com/android/microdroid/testservice/ITestService.aidl
+++ b/tests/aidl/com/android/microdroid/testservice/ITestService.aidl
@@ -49,4 +49,10 @@
/** Returns a mask of effective capabilities that the process running the payload binary has. */
String[] getEffectiveCapabilities();
+
+ /* write the content into the specified file. */
+ void writeToFile(String content, String path);
+
+ /* get the content of the specified file. */
+ String readFromFile(String path);
}
diff --git a/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java b/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
index 9aed34d..536f663 100644
--- a/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
+++ b/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
@@ -126,6 +126,12 @@
}
}
+ protected enum EncryptedStoreOperation {
+ NONE,
+ READ,
+ WRITE,
+ }
+
public abstract static class VmEventListener implements VirtualMachineCallback {
private ExecutorService mExecutorService = Executors.newSingleThreadExecutor();
private OptionalLong mVcpuStartedNanoTime = OptionalLong.empty();
diff --git a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
index 160b679..897879b 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -113,6 +113,7 @@
private static final int MIN_MEM_ARM64 = 150;
private static final int MIN_MEM_X86_64 = 196;
+ private static final String EXAMPLE_STRING = "Literally any string!! :)";
@Test
@CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
@@ -1142,6 +1143,29 @@
assertThat(testResults.mEffectiveCapabilities).isEmpty();
}
+ @Test
+ @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
+ public void encryptedStorageIsPersistent() throws Exception {
+ assumeSupportedKernel();
+
+ VirtualMachineConfig config =
+ newVmConfigBuilder()
+ .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
+ .setMemoryMib(minMemoryRequired())
+ .setEncryptedStorageKib(4096)
+ .setDebugLevel(DEBUG_LEVEL_FULL)
+ .build();
+ VirtualMachine vm = forceCreateNewVirtualMachine("test_vm_a", config);
+ TestResults testResults = runVmTestService(vm, EncryptedStoreOperation.WRITE);
+ assertThat(testResults.mException).isNull();
+
+ // Re-run the same VM & verify the file persisted. Note, the previous `runVmTestService`
+ // stopped the VM
+ testResults = runVmTestService(vm, EncryptedStoreOperation.READ);
+ assertThat(testResults.mException).isNull();
+ assertThat(testResults.mFileContent).isEqualTo(EXAMPLE_STRING);
+ }
+
private void assertFileContentsAreEqualInTwoVms(String fileName, String vmName1, String vmName2)
throws IOException {
File file1 = getVmFile(vmName1, fileName);
@@ -1197,9 +1221,15 @@
String mApkContentsPath;
String mEncryptedStoragePath;
String[] mEffectiveCapabilities;
+ String mFileContent;
}
private TestResults runVmTestService(VirtualMachine vm) throws Exception {
+ return runVmTestService(vm, EncryptedStoreOperation.NONE);
+ }
+
+ private TestResults runVmTestService(VirtualMachine vm, EncryptedStoreOperation mode)
+ throws Exception {
CompletableFuture<Boolean> payloadStarted = new CompletableFuture<>();
CompletableFuture<Boolean> payloadReady = new CompletableFuture<>();
TestResults testResults = new TestResults();
@@ -1222,6 +1252,14 @@
testService.getEncryptedStoragePath();
testResults.mEffectiveCapabilities =
testService.getEffectiveCapabilities();
+ if (mode == EncryptedStoreOperation.WRITE) {
+ testService.writeToFile(
+ /*content*/ EXAMPLE_STRING,
+ /*path*/ "/mnt/encryptedstore/test_file");
+ } else if (mode == EncryptedStoreOperation.READ) {
+ testResults.mFileContent =
+ testService.readFromFile("/mnt/encryptedstore/test_file");
+ }
} catch (Exception e) {
testResults.mException = e;
}
diff --git a/tests/testapk/src/native/testbinary.cpp b/tests/testapk/src/native/testbinary.cpp
index da408e4..4ba502a 100644
--- a/tests/testapk/src/native/testbinary.cpp
+++ b/tests/testapk/src/native/testbinary.cpp
@@ -232,6 +232,29 @@
return ScopedAStatus::fromServiceSpecificErrorWithMessage(-1, message.c_str());
}
}
+
+ ScopedAStatus writeToFile(const std::string& content, const std::string& path) override {
+ if (!android::base::WriteStringToFile(content, path)) {
+ std::string msg = "Failed to write " + content + " to file " + path +
+ ". Errono: " + std::to_string(errno);
+ return ScopedAStatus::fromExceptionCodeWithMessage(EX_SERVICE_SPECIFIC,
+ msg.c_str());
+ }
+ // TODO(b/264520098): Remove sync() once TestService supports quit() method
+ // and Microdroid manager flushes filesystem caches on shutdown.
+ sync();
+ return ScopedAStatus::ok();
+ }
+
+ ScopedAStatus readFromFile(const std::string& path, std::string* out) override {
+ if (!android::base::ReadFileToString(path, out)) {
+ std::string msg =
+ "Failed to read " + path + " to string. Errono: " + std::to_string(errno);
+ return ScopedAStatus::fromExceptionCodeWithMessage(EX_SERVICE_SPECIFIC,
+ msg.c_str());
+ }
+ return ScopedAStatus::ok();
+ }
};
auto testService = ndk::SharedRefBase::make<TestService>();
diff --git a/vmbase/example/Android.bp b/vmbase/example/Android.bp
index fbad8f4..94eb21a 100644
--- a/vmbase/example/Android.bp
+++ b/vmbase/example/Android.bp
@@ -12,6 +12,7 @@
"libaarch64_paging",
"libbuddy_system_allocator",
"libdice_nostd",
+ "libfdtpci",
"liblibfdt",
"liblog_rust_nostd",
"libvirtio_drivers",
diff --git a/vmbase/example/src/main.rs b/vmbase/example/src/main.rs
index 888f273..ec28a11 100644
--- a/vmbase/example/src/main.rs
+++ b/vmbase/example/src/main.rs
@@ -28,18 +28,16 @@
bionic_tls, dtb_range, print_addresses, rodata_range, stack_chk_guard, text_range,
writable_region, DEVICE_REGION,
};
-use crate::pci::{check_pci, pci_node, PciMemory32Allocator};
+use crate::pci::{check_pci, get_bar_region};
use aarch64_paging::{idmap::IdMap, paging::Attributes};
use alloc::{vec, vec::Vec};
use buddy_system_allocator::LockedHeap;
use core::ffi::CStr;
+use fdtpci::PciInfo;
use libfdt::Fdt;
use log::{debug, info, trace, LevelFilter};
use vmbase::{logger, main, println};
-/// PCI MMIO configuration region size.
-const AARCH64_PCI_CFG_SIZE: u64 = 0x1000000;
-
static INITIALISED_DATA: [u32; 4] = [1, 2, 3, 4];
static mut ZEROED_DATA: [u32; 10] = [0; 10];
static mut MUTABLE_DATA: [u32; 4] = [1, 2, 3, 4];
@@ -73,16 +71,8 @@
info!("FDT passed verification.");
check_fdt(fdt);
- let pci_node = pci_node(fdt);
- // Parse reg property to find CAM.
- let pci_reg = pci_node.reg().unwrap().unwrap().next().unwrap();
- debug!("Found PCI CAM at {:#x}-{:#x}", pci_reg.addr, pci_reg.addr + pci_reg.size.unwrap());
- // Check that the CAM is the size we expect, so we don't later try accessing it beyond its
- // bounds. If it is a different size then something is very wrong and we shouldn't continue to
- // access it; maybe there is some new version of PCI we don't know about.
- assert_eq!(pci_reg.size.unwrap(), AARCH64_PCI_CFG_SIZE);
- // Parse ranges property to find memory ranges from which to allocate PCI BARs.
- let pci_allocator = PciMemory32Allocator::for_pci_ranges(&pci_node);
+ let pci_info = PciInfo::from_fdt(fdt).unwrap();
+ debug!("Found PCI CAM at {:#x}-{:#x}", pci_info.cam_range.start, pci_info.cam_range.end);
modify_fdt(fdt);
@@ -125,10 +115,7 @@
)
.unwrap();
idmap
- .map_range(
- &pci_allocator.get_region(),
- Attributes::DEVICE_NGNRE | Attributes::EXECUTE_NEVER,
- )
+ .map_range(&get_bar_region(&pci_info), Attributes::DEVICE_NGNRE | Attributes::EXECUTE_NEVER)
.unwrap();
info!("Activating IdMap...");
@@ -139,7 +126,8 @@
check_data();
check_dice();
- check_pci(pci_reg);
+ let mut pci_root = unsafe { pci_info.make_pci_root() };
+ check_pci(&mut pci_root);
}
fn check_stack_guard() {
diff --git a/vmbase/example/src/pci.rs b/vmbase/example/src/pci.rs
index bd5b5ba..a204b90 100644
--- a/vmbase/example/src/pci.rs
+++ b/vmbase/example/src/pci.rs
@@ -16,14 +16,11 @@
use aarch64_paging::paging::MemoryRegion;
use alloc::alloc::{alloc, dealloc, Layout};
-use core::{ffi::CStr, mem::size_of};
-use libfdt::{AddressRange, Fdt, FdtNode, Reg};
+use core::mem::size_of;
+use fdtpci::PciInfo;
use log::{debug, info};
use virtio_drivers::{
- pci::{
- bus::{Cam, PciRoot},
- virtio_device_type, PciTransport,
- },
+ pci::{bus::PciRoot, virtio_device_type, PciTransport},
DeviceType, Hal, PhysAddr, Transport, VirtAddr, VirtIOBlk, PAGE_SIZE,
};
@@ -33,24 +30,14 @@
/// The size in sectors of the test block device we expect.
const EXPECTED_SECTOR_COUNT: usize = 4;
-/// Finds an FDT node with compatible=pci-host-cam-generic.
-pub fn pci_node(fdt: &Fdt) -> FdtNode {
- fdt.compatible_nodes(CStr::from_bytes_with_nul(b"pci-host-cam-generic\0").unwrap())
- .unwrap()
- .next()
- .unwrap()
-}
-
-pub fn check_pci(reg: Reg<u64>) {
- let mut pci_root = unsafe { PciRoot::new(reg.addr as *mut u8, Cam::MmioCam) };
+pub fn check_pci(pci_root: &mut PciRoot) {
let mut checked_virtio_device_count = 0;
for (device_function, info) in pci_root.enumerate_bus(0) {
let (status, command) = pci_root.get_status_command(device_function);
info!("Found {} at {}, status {:?} command {:?}", info, device_function, status, command);
if let Some(virtio_type) = virtio_device_type(&info) {
info!(" VirtIO {:?}", virtio_type);
- let mut transport =
- PciTransport::new::<HalImpl>(&mut pci_root, device_function).unwrap();
+ let mut transport = PciTransport::new::<HalImpl>(pci_root, device_function).unwrap();
info!(
"Detected virtio PCI device with device type {:?}, features {:#018x}",
transport.device_type(),
@@ -88,89 +75,9 @@
}
}
-#[derive(Copy, Clone, Debug, Eq, PartialEq)]
-struct PciMemoryFlags(u32);
-
-impl PciMemoryFlags {
- pub fn prefetchable(self) -> bool {
- self.0 & 0x80000000 != 0
- }
-
- pub fn range_type(self) -> PciRangeType {
- PciRangeType::from((self.0 & 0x3000000) >> 24)
- }
-}
-
-/// Allocates 32-bit memory addresses for PCI BARs.
-pub struct PciMemory32Allocator {
- start: u32,
- end: u32,
-}
-
-impl PciMemory32Allocator {
- /// Creates a new allocator based on the ranges property of the given PCI node.
- pub fn for_pci_ranges(pci_node: &FdtNode) -> Self {
- let mut memory_32_address = 0;
- let mut memory_32_size = 0;
- for AddressRange { addr: (flags, bus_address), parent_addr: cpu_physical, size } in pci_node
- .ranges::<(u32, u64), u64, u64>()
- .expect("Error getting ranges property from PCI node")
- .expect("PCI node missing ranges property.")
- {
- let flags = PciMemoryFlags(flags);
- let prefetchable = flags.prefetchable();
- let range_type = flags.range_type();
- info!(
- "range: {:?} {}prefetchable bus address: {:#018x} host physical address: {:#018x} size: {:#018x}",
- range_type,
- if prefetchable { "" } else { "non-" },
- bus_address,
- cpu_physical,
- size,
- );
- if !prefetchable
- && ((range_type == PciRangeType::Memory32 && size > memory_32_size.into())
- || (range_type == PciRangeType::Memory64
- && size > memory_32_size.into()
- && bus_address + size < u32::MAX.into()))
- {
- // Use the 64-bit range for 32-bit memory, if it is low enough.
- assert_eq!(bus_address, cpu_physical);
- memory_32_address = u32::try_from(cpu_physical).unwrap();
- memory_32_size = u32::try_from(size).unwrap();
- }
- }
- if memory_32_size == 0 {
- panic!("No PCI memory regions found.");
- }
-
- Self { start: memory_32_address, end: memory_32_address + memory_32_size }
- }
-
- /// Gets a memory region covering the address space from which this allocator will allocate.
- pub fn get_region(&self) -> MemoryRegion {
- MemoryRegion::new(self.start as usize, self.end as usize)
- }
-}
-
-#[derive(Copy, Clone, Debug, Eq, PartialEq)]
-enum PciRangeType {
- ConfigurationSpace,
- IoSpace,
- Memory32,
- Memory64,
-}
-
-impl From<u32> for PciRangeType {
- fn from(value: u32) -> Self {
- match value {
- 0 => Self::ConfigurationSpace,
- 1 => Self::IoSpace,
- 2 => Self::Memory32,
- 3 => Self::Memory64,
- _ => panic!("Tried to convert invalid range type {}", value),
- }
- }
+/// Gets the memory region in which BARs are allocated.
+pub fn get_bar_region(pci_info: &PciInfo) -> MemoryRegion {
+ MemoryRegion::new(pci_info.bar_range.start as usize, pci_info.bar_range.end as usize)
}
struct HalImpl;
diff --git a/vmbase/src/bionic.rs b/vmbase/src/bionic.rs
index 8b3a076..b4a2f7b 100644
--- a/vmbase/src/bionic.rs
+++ b/vmbase/src/bionic.rs
@@ -14,6 +14,11 @@
//! Low-level compatibility layer between baremetal Rust and Bionic C functions.
+use core::ffi::c_char;
+use core::ffi::c_int;
+use core::ffi::CStr;
+
+use crate::eprintln;
use crate::linker;
/// Reference to __stack_chk_guard.
@@ -23,3 +28,37 @@
extern "C" fn __stack_chk_fail() -> ! {
panic!("stack guard check failed");
}
+
+/// Called from C to cause abnormal program termination.
+#[no_mangle]
+extern "C" fn abort() -> ! {
+ panic!("C code called abort()")
+}
+
+/// Error number set and read by C functions.
+pub static mut ERRNO: c_int = 0;
+
+#[no_mangle]
+unsafe extern "C" fn __errno() -> *mut c_int {
+ &mut ERRNO as *mut _
+}
+
+/// Reports a fatal error detected by Bionic.
+///
+/// # Safety
+///
+/// Input strings `prefix` and `format` must be properly NULL-terminated.
+///
+/// # Note
+///
+/// This Rust functions is missing the last argument of its C/C++ counterpart, a va_list.
+#[no_mangle]
+unsafe extern "C" fn async_safe_fatal_va_list(prefix: *const c_char, format: *const c_char) {
+ let prefix = CStr::from_ptr(prefix);
+ let format = CStr::from_ptr(format);
+
+ if let (Ok(prefix), Ok(format)) = (prefix.to_str(), format.to_str()) {
+ // We don't bother with printf formatting.
+ eprintln!("FATAL BIONIC ERROR: {prefix}: \"{format}\" (unformatted)");
+ }
+}