Add internal API to bind devices to VFIO

An app can specify devices to be assigned. VirtualizationService will
handle unbinding devices and binding them to VFIO.
VirtualizationService will also assemble a DTBO for VM.

Bug: 287379025
Test: TH
Change-Id: Icd720aa5ebde6dc4b6fa2b36cc22af27183462a9
diff --git a/virtualizationservice/Android.bp b/virtualizationservice/Android.bp
index 6b39ff9..0732c04 100644
--- a/virtualizationservice/Android.bp
+++ b/virtualizationservice/Android.bp
@@ -36,6 +36,7 @@
         "libstatslog_virtualization_rust",
         "libtombstoned_client_rust",
         "libvsock",
+        "liblazy_static",
     ],
     apex_available: ["com.android.virt"],
 }
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl b/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
index 947aa86..3546355 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
@@ -65,4 +65,12 @@
      * Get a list of assignable devices.
      */
     AssignableDevice[] getAssignableDevices();
+
+    /**
+     * Bind given devices to vfio driver.
+     *
+     * @param devices paths of sysfs nodes of devices to assign.
+     * @return a file descriptor containing DTBO for VM.
+     */
+    ParcelFileDescriptor bindDevicesToVfioDriver(in String[] devices);
 }
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index 4d0b823..2dbb6e2 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -33,19 +33,22 @@
 use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::IVirtualMachineService::VM_TOMBSTONES_SERVICE_PORT;
 use anyhow::{anyhow, ensure, Context, Result};
 use binder::{self, BinderFeatures, ExceptionCode, Interface, LazyServiceGuard, Status, Strong};
+use lazy_static::lazy_static;
 use libc::VMADDR_CID_HOST;
 use log::{error, info, warn};
 use rustutils::system_properties;
-use std::collections::HashMap;
-use std::fs::{create_dir, remove_dir_all, set_permissions, Permissions};
+use std::collections::{HashMap, HashSet};
+use std::fs::{canonicalize, create_dir, remove_dir_all, set_permissions, File, Permissions};
 use std::io::{Read, Write};
+use std::os::fd::FromRawFd;
 use std::os::unix::fs::PermissionsExt;
 use std::os::unix::raw::{pid_t, uid_t};
-use std::path::PathBuf;
+use std::path::{Path, PathBuf};
 use std::sync::{Arc, Mutex, Weak};
 use tombstoned_client::{DebuggerdDumpType, TombstonedConnection};
 use vsock::{VsockListener, VsockStream};
-use nix::unistd::{chown, Uid};
+use nix::fcntl::OFlag;
+use nix::unistd::{chown, pipe2, Uid};
 
 /// The unique ID of a VM used (together with a port number) for vsock communication.
 pub type Cid = u32;
@@ -181,6 +184,75 @@
             node: "/sys/bus/platform/devices/16d00000.eh".to_owned(),
         }])
     }
+
+    fn bindDevicesToVfioDriver(&self, devices: &[String]) -> binder::Result<ParcelFileDescriptor> {
+        check_use_custom_virtual_machine()?;
+
+        let mut set = HashSet::new();
+        for device in devices.iter() {
+            if !set.insert(device) {
+                return Err(Status::new_exception_str(
+                    ExceptionCode::ILLEGAL_ARGUMENT,
+                    Some(format!("duplicated device {device}")),
+                ));
+            }
+            bind_device(device)?;
+        }
+
+        // TODO(b/278008182): create a file descriptor containing DTBO for devices.
+        let (raw_read, raw_write) = pipe2(OFlag::O_CLOEXEC).map_err(|e| {
+            Status::new_exception_str(
+                ExceptionCode::SERVICE_SPECIFIC,
+                Some(format!("can't create fd for DTBO: {e:?}")),
+            )
+        })?;
+        // SAFETY: We are the sole owner of this FD as we just created it, and it is valid and open.
+        let read_fd = unsafe { File::from_raw_fd(raw_read) };
+        // SAFETY: We are the sole owner of this FD as we just created it, and it is valid and open.
+        let _write_fd = unsafe { File::from_raw_fd(raw_write) };
+
+        Ok(ParcelFileDescriptor::new(read_fd))
+    }
+}
+
+lazy_static! {
+    static ref SYSFS_PLATFORM_DEVICES: &'static Path = Path::new("/sys/devices/platform/");
+    static ref VFIO_PLATFORM_DRIVER: &'static Path =
+        Path::new("/sys/bus/platform/drivers/vfio-platform");
+}
+
+fn bind_device(device: &str) -> binder::Result<()> {
+    // Check platform device exists
+    let dev_sysfs_path = canonicalize(device).map_err(|e| {
+        Status::new_exception_str(
+            ExceptionCode::SERVICE_SPECIFIC,
+            Some(format!("can't canonicalize: {e:?}")),
+        )
+    })?;
+    if !dev_sysfs_path.starts_with(*SYSFS_PLATFORM_DEVICES) {
+        return Err(Status::new_exception_str(
+            ExceptionCode::ILLEGAL_ARGUMENT,
+            Some(format!("{device} is not a platform device")),
+        ));
+    }
+
+    // Check platform device is bound to VFIO driver
+    let dev_driver_path = canonicalize(dev_sysfs_path.join("driver")).map_err(|e| {
+        Status::new_exception_str(
+            ExceptionCode::SERVICE_SPECIFIC,
+            Some(format!("can't canonicalize: {e:?}")),
+        )
+    })?;
+    if dev_driver_path != *VFIO_PLATFORM_DRIVER {
+        // TODO(b/278008182): unbind driver and bind to VFIO
+        return Err(Status::new_exception_str(
+            ExceptionCode::UNSUPPORTED_OPERATION,
+            Some("not implemented".to_owned()),
+        ));
+    }
+
+    // already bound to VFIO driver
+    Ok(())
 }
 
 #[derive(Debug, Default)]