Implement LLNDK for AVF

libavf will be a new LLNDK serving stable C APIs for AVF. For now it
supports creating a raw config, setting part of fields of raw configs,
spawning/connecting to virtmgr, and then creating a VM.

Bug: 369588412
Test: run a VM with libavf
Change-Id: If0ef5ec4e4121a872e65e862698f674e0352c372
diff --git a/libs/libavf/Android.bp b/libs/libavf/Android.bp
new file mode 100644
index 0000000..e143709
--- /dev/null
+++ b/libs/libavf/Android.bp
@@ -0,0 +1,58 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_bindgen {
+    name: "libavf_bindgen",
+    wrapper_src: "include/android/virtualization.h",
+    crate_name: "avf_bindgen",
+    defaults: ["avf_build_flags_rust"],
+    source_stem: "bindings",
+    bindgen_flags: ["--default-enum-style rust"],
+    apex_available: ["com.android.virt"],
+}
+
+rust_defaults {
+    name: "libavf.default",
+    crate_name: "avf",
+    defaults: ["avf_build_flags_rust"],
+    srcs: ["src/lib.rs"],
+    edition: "2021",
+    rustlibs: [
+        "libvmclient",
+        "android.system.virtualizationcommon-rust",
+        "android.system.virtualizationservice-rust",
+        "libavf_bindgen",
+        "libbinder_rs",
+        "liblibc",
+        "liblog_rust",
+        "librpcbinder_rs",
+    ],
+    apex_available: ["com.android.virt"],
+}
+
+rust_ffi_static {
+    name: "libavf_impl",
+    defaults: ["libavf.default"],
+    export_include_dirs: ["include"],
+}
+
+cc_library {
+    name: "libavf",
+    llndk: {
+        symbol_file: "libavf.map.txt",
+        moved_to_apex: true,
+    },
+    whole_static_libs: ["libavf_impl"],
+    shared_libs: [
+        "libbinder_ndk",
+        "libbinder_rpc_unstable",
+        "liblog",
+    ],
+    export_static_lib_headers: ["libavf_impl"],
+    apex_available: ["com.android.virt"],
+    version_script: "libavf.map.txt",
+    stubs: {
+        symbol_file: "libavf.map.txt",
+    },
+}
diff --git a/libs/libavf/include/android/virtualization.h b/libs/libavf/include/android/virtualization.h
new file mode 100644
index 0000000..f33ee75
--- /dev/null
+++ b/libs/libavf/include/android/virtualization.h
@@ -0,0 +1,326 @@
+/*
+ * 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.
+ */
+#pragma once
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+__BEGIN_DECLS
+
+/**
+ * Represents a handle on a virtual machine raw config.
+ */
+typedef struct AVirtualMachineRawConfig AVirtualMachineRawConfig;
+
+/**
+ * Create a new virtual machine raw config object with no properties.
+ *
+ * This only creates the raw config object. `name` and `kernel` must be set with
+ * calls to {@link AVirtualMachineRawConfig_setName} and {@link AVirtualMachineRawConfig_setKernel}.
+ * Other properties, set by {@link AVirtualMachineRawConfig_setMemoryMib},
+ * {@link AVirtualMachineRawConfig_setInitRd}, {@link AVirtualMachineRawConfig_addDisk},
+ * {@link AVirtualMachineRawConfig_setProtectedVm}, and {@link AVirtualMachineRawConfig_setBalloon}
+ * are optional.
+ *
+ * The caller takes ownership of the returned raw config object, and is responsible for creating a
+ * VM by calling {@link AVirtualMachine_createRaw} or releasing it by calling
+ * {@link AVirtualMachineRawConfig_destroy}.
+ *
+ * \return A new virtual machine raw config object.
+ */
+AVirtualMachineRawConfig* AVirtualMachineRawConfig_create();
+
+/**
+ * Destroy a virtual machine config object.
+ *
+ * \param config a virtual machine config object.
+ *
+ * `AVirtualMachineRawConfig_destroy` does nothing if `config` is null. A destroyed config object
+ * must not be reused.
+ */
+void AVirtualMachineRawConfig_destroy(AVirtualMachineRawConfig* config);
+
+/**
+ * Set a name of a virtual machine.
+ *
+ * \param config a virtual machine config object.
+ * \param name a pointer to a null-terminated string for the name.
+ *
+ * \return If successful, it returns 0.
+ */
+int AVirtualMachineRawConfig_setName(AVirtualMachineRawConfig* config, const char* name);
+
+/**
+ * Set an instance ID of a virtual machine.
+ *
+ * \param config a virtual machine config object.
+ * \param instanceId a pointer to a 64-byte buffer for the instance ID.
+ *
+ * \return If successful, it returns 0.
+ */
+int AVirtualMachineRawConfig_setInstanceId(AVirtualMachineRawConfig* config,
+                                           const int8_t* instanceId);
+
+/**
+ * Set a kernel image of a virtual machine.
+ *
+ * \param config a virtual machine config object.
+ * \param fd a readable file descriptor containing the kernel image, or -1 to unset.
+ *   `AVirtualMachineRawConfig_setKernel` takes ownership of `fd`.
+ *
+ * \return If successful, it returns 0.
+ */
+int AVirtualMachineRawConfig_setKernel(AVirtualMachineRawConfig* config, int fd);
+
+/**
+ * Set an init rd of a virtual machine.
+ *
+ * \param config a virtual machine config object.
+ * \param fd a readable file descriptor containing the kernel image, or -1 to unset.
+ *   `AVirtualMachineRawConfig_setInitRd` takes ownership of `fd`.
+ *
+ * \return If successful, it returns 0.
+ */
+int AVirtualMachineRawConfig_setInitRd(AVirtualMachineRawConfig* config, int fd);
+
+/**
+ * Add a disk for a virtual machine.
+ *
+ * \param config a virtual machine config object.
+ * \param fd a readable file descriptor containing the disk image.
+ * `AVirtualMachineRawConfig_addDisk` takes ownership of `fd`.
+ *
+ * \return If successful, it returns 0. If `fd` is invalid, it returns -EINVAL.
+ */
+int AVirtualMachineRawConfig_addDisk(AVirtualMachineRawConfig* config, int fd);
+
+/**
+ * Set how much memory will be given to a virtual machine.
+ *
+ * \param config a virtual machine config object.
+ * \param memoryMib the amount of RAM to give the virtual machine, in MiB. 0 or negative to use the
+ *   default.
+ *
+ * \return If successful, it returns 0.
+ */
+int AVirtualMachineRawConfig_setMemoryMib(AVirtualMachineRawConfig* config, int32_t memoryMib);
+
+/**
+ * Set whether a virtual machine is protected or not.
+ *
+ * \param config a virtual machine config object.
+ * \param protectedVm whether the virtual machine should be protected.
+ *
+ * \return If successful, it returns 0.
+ */
+int AVirtualMachineRawConfig_setProtectedVm(AVirtualMachineRawConfig* config, bool protectedVm);
+
+/**
+ * Set whether a virtual machine uses memory ballooning or not.
+ *
+ * \param config a virtual machine config object.
+ * \param balloon whether the virtual machine should use memory ballooning.
+ *
+ * \return If successful, it returns 0.
+ */
+int AVirtualMachineRawConfig_setBalloon(AVirtualMachineRawConfig* config, bool balloon);
+
+/**
+ * Set whether to use an alternate, hypervisor-specific authentication method
+ * for protected VMs. You don't want to use this.
+ *
+ * \return If successful, it returns 0. It returns `-ENOTSUP` if the hypervisor doesn't have an
+ *   alternate auth mode.
+ */
+int AVirtualMachineRawConfig_setHypervisorSpecificAuthMethod(AVirtualMachineRawConfig* config,
+                                                             bool enable);
+
+/**
+ * Use the specified fd as the backing memfd for a range of the guest
+ * physical memory.
+ *
+ * \param config a virtual machine config object.
+ * \param fd a memfd
+ * \param rangeStart range start IPA
+ * \param rangeEnd range end IPA
+ *
+ * \return If successful, it returns 0. It returns `-ENOTSUP` if the hypervisor doesn't support
+ *   backing memfd.
+ */
+int AVirtualMachineRawConfig_addCustomMemoryBackingFile(AVirtualMachineRawConfig* config, int fd,
+                                                        size_t rangeStart, size_t rangeEnd);
+
+/**
+ * Represents a handle on a virtualization service, responsible for managing virtual machines.
+ */
+typedef struct AVirtualizationService AVirtualizationService;
+
+/**
+ * Spawn a new instance of `virtmgr`, a child process that will host the `VirtualizationService`
+ * service, and connect to the child process.
+ *
+ * The caller takes ownership of the returned service object, and is responsible for releasing it
+ * by calling {@link AVirtualizationService_destroy}.
+ *
+ * \param early set to true when running a service for early virtual machines. See
+ *   [`early_vm.md`](../../../../docs/early_vm.md) for more details on early virtual machines.
+ * \param service an out parameter that will be set to the service handle.
+ *
+ * \return
+ *   - If successful, it sets `service` and returns 0.
+ *   - If it fails to spawn `virtmgr`, it leaves `service` untouched and returns a negative value
+ *     representing the OS error code.
+ *   - If it fails to connect to the spawned `virtmgr`, it leaves `service` untouched and returns
+ *     `-ECONNREFUSED`.
+ */
+int AVirtualizationService_create(AVirtualizationService** service, bool early);
+
+/**
+ * Destroy a VirtualizationService object.
+ *
+ * `AVirtualizationService_destroy` does nothing if `service` is null. A destroyed service object
+ * must not be reused.
+ *
+ * \param service a handle on a virtualization service.
+ */
+void AVirtualizationService_destroy(AVirtualizationService* service);
+
+/**
+ * Represents a handle on a virtual machine.
+ */
+typedef struct AVirtualMachine AVirtualMachine;
+
+/**
+ * The reason why a virtual machine stopped.
+ * @see AVirtualMachine_waitForStop
+ */
+enum StopReason : int32_t {
+    /**
+     * VirtualizationService died.
+     */
+    VIRTUALIZATION_SERVICE_DIED = 1,
+    /**
+     * There was an error waiting for the virtual machine.
+     */
+    INFRASTRUCTURE_ERROR = 2,
+    /**
+     * The virtual machine was killed.
+     */
+    KILLED = 3,
+    /**
+     * The virtual machine stopped for an unknown reason.
+     */
+    UNKNOWN = 4,
+    /**
+     * The virtual machine requested to shut down.
+     */
+    SHUTDOWN = 5,
+    /**
+     * crosvm had an error starting the virtual machine.
+     */
+    START_FAILED = 6,
+    /**
+     * The virtual machine requested to reboot, possibly as the result of a kernel panic.
+     */
+    REBOOT = 7,
+    /**
+     * The virtual machine or crosvm crashed.
+     */
+    CRASH = 8,
+    /**
+     * The pVM firmware failed to verify the VM because the public key doesn't match.
+     */
+    PVM_FIRMWARE_PUBLIC_KEY_MISMATCH = 9,
+    /**
+     * The pVM firmware failed to verify the VM because the instance image changed.
+     */
+    PVM_FIRMWARE_INSTANCE_IMAGE_CHANGED = 10,
+    /**
+     * The virtual machine was killed due to hangup.
+     */
+    HANGUP = 11,
+    /**
+     * VirtualizationService sent a stop reason which was not recognised by the client library.
+     */
+    UNRECOGNISED = 0,
+};
+
+/**
+ * Create a virtual machine with given raw `config`.
+ *
+ * The created virtual machine is in stopped state. To run the created virtual machine, call
+ * {@link AVirtualMachine_start}.
+ *
+ * The caller takes ownership of the returned virtual machine object, and is responsible for
+ * releasing it by calling {@link AVirtualMachine_destroy}.
+ *
+ * \param service a handle on a virtualization service.
+ * \param config a virtual machine config object. Ownership will always be transferred from the
+ *   caller, even if unsuccessful. `config` must not be reused.
+ * \param consoleOutFd a writable file descriptor for the console output, or -1. Ownership will
+ *   always be transferred from the caller, even if unsuccessful.
+ * \param consoleInFd a readable file descriptor for the console input, or -1. Ownership will always
+ *   be transferred from the caller, even if unsuccessful.
+ * \param logFd a writable file descriptor for the log output, or -1. Ownership will always be
+ *   transferred from the caller, even if unsuccessful.
+ * \param vm an out parameter that will be set to the virtual machine handle.
+ *
+ * \return If successful, it sets `vm` and returns 0. Otherwise, it leaves `vm` untouched and
+ *   returns `-EIO`.
+ */
+int AVirtualMachine_createRaw(const AVirtualizationService* service,
+                              AVirtualMachineRawConfig* config, int consoleOutFd, int consoleInFd,
+                              int logFd, AVirtualMachine** vm);
+
+/**
+ * Start a virtual machine.
+ *
+ * \param vm a handle on a virtual machine.
+ *
+ * \return If successful, it returns 0. Otherwise, it returns `-EIO`.
+ */
+int AVirtualMachine_start(AVirtualMachine* vm);
+
+/**
+ * Stop a virtual machine.
+ *
+ * \param vm a handle on a virtual machine.
+ *
+ * \return If successful, it returns 0. Otherwise, it returns `-EIO`.
+ */
+int AVirtualMachine_stop(AVirtualMachine* vm);
+
+/**
+ * Wait until a virtual machine stops.
+ *
+ * \param vm a handle on a virtual machine.
+ *
+ * \return The reason why the virtual machine stopped.
+ */
+enum StopReason AVirtualMachine_waitForStop(AVirtualMachine* vm);
+
+/**
+ * Destroy a virtual machine.
+ *
+ * `AVirtualMachine_destroy` does nothing if `vm` is null. A destroyed virtual machine must not be
+ * reused.
+ *
+ * \param vm a handle on a virtual machine.
+ */
+void AVirtualMachine_destroy(AVirtualMachine* vm);
+
+__END_DECLS
diff --git a/libs/libavf/libavf.map.txt b/libs/libavf/libavf.map.txt
new file mode 100644
index 0000000..ecb4cc9
--- /dev/null
+++ b/libs/libavf/libavf.map.txt
@@ -0,0 +1,24 @@
+LIBAVF {
+  global:
+    AVirtualMachineRawConfig_create; # apex llndk
+    AVirtualMachineRawConfig_destroy; # apex llndk
+    AVirtualMachineRawConfig_setName; # apex llndk
+    AVirtualMachineRawConfig_setInstanceId; # apex llndk
+    AVirtualMachineRawConfig_setKernel; # apex llndk
+    AVirtualMachineRawConfig_setInitRd; # apex llndk
+    AVirtualMachineRawConfig_addDisk; # apex llndk
+    AVirtualMachineRawConfig_setMemoryMib; # apex llndk
+    AVirtualMachineRawConfig_setProtectedVm; # apex llndk
+    AVirtualMachineRawConfig_setBalloon; # apex llndk
+    AVirtualMachineRawConfig_setHypervisorSpecificAuthMethod; # apex llndk
+    AVirtualMachineRawConfig_addCustomMemoryBackingFile; # apex llndk
+    AVirtualizationService_create; # apex llndk
+    AVirtualizationService_destroy; # apex llndk
+    AVirtualMachine_createRaw; # apex llndk
+    AVirtualMachine_start; # apex llndk
+    AVirtualMachine_stop; # apex llndk
+    AVirtualMachine_waitForStop; # apex llndk
+    AVirtualMachine_destroy; # apex llndk
+  local:
+    *;
+};
diff --git a/libs/libavf/src/lib.rs b/libs/libavf/src/lib.rs
new file mode 100644
index 0000000..0a8f891
--- /dev/null
+++ b/libs/libavf/src/lib.rs
@@ -0,0 +1,413 @@
+// 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.
+
+//! Stable C library for AVF.
+
+use std::ffi::CStr;
+use std::fs::File;
+use std::os::fd::FromRawFd;
+use std::os::raw::{c_char, c_int};
+use std::ptr;
+
+use android_system_virtualizationservice::{
+    aidl::android::system::virtualizationservice::{
+        DiskImage::DiskImage, IVirtualizationService::IVirtualizationService,
+        VirtualMachineConfig::VirtualMachineConfig,
+        VirtualMachineRawConfig::VirtualMachineRawConfig,
+    },
+    binder::{ParcelFileDescriptor, Strong},
+};
+use avf_bindgen::StopReason;
+use vmclient::{DeathReason, VirtualizationService, VmInstance};
+
+/// Create a new virtual machine config object with no properties.
+#[no_mangle]
+pub extern "C" fn AVirtualMachineRawConfig_create() -> *mut VirtualMachineRawConfig {
+    let config = Box::new(VirtualMachineRawConfig {
+        platformVersion: "~1.0".to_owned(),
+        ..Default::default()
+    });
+    Box::into_raw(config)
+}
+
+/// Destroy a virtual machine config object.
+///
+/// # Safety
+/// `config` must be a pointer returned by `AVirtualMachineRawConfig_create`. `config` must not be
+/// used after deletion.
+#[no_mangle]
+pub unsafe extern "C" fn AVirtualMachineRawConfig_destroy(config: *mut VirtualMachineRawConfig) {
+    if !config.is_null() {
+        // SAFETY: `config` is assumed to be a valid, non-null pointer returned by
+        // AVirtualMachineRawConfig_create. It's the only reference to the object.
+        unsafe {
+            let _ = Box::from_raw(config);
+        }
+    }
+}
+
+/// Set a name of a virtual machine.
+///
+/// # Safety
+/// `config` must be a pointer returned by `AVirtualMachineRawConfig_create`.
+#[no_mangle]
+pub unsafe extern "C" fn AVirtualMachineRawConfig_setName(
+    config: *mut VirtualMachineRawConfig,
+    name: *const c_char,
+) -> c_int {
+    // SAFETY: `config` is assumed to be a valid, non-null pointer returned by
+    // AVirtualMachineRawConfig_create. It's the only reference to the object.
+    let config = unsafe { &mut *config };
+    // SAFETY: `name` is assumed to be a pointer to a valid C string.
+    config.name = unsafe { CStr::from_ptr(name) }.to_string_lossy().into_owned();
+    0
+}
+
+/// Set an instance ID of a virtual machine.
+///
+/// # Safety
+/// `config` must be a pointer returned by `AVirtualMachineRawConfig_create`. `instanceId` must be a
+/// valid, non-null pointer to 64-byte data.
+#[no_mangle]
+pub unsafe extern "C" fn AVirtualMachineRawConfig_setInstanceId(
+    config: *mut VirtualMachineRawConfig,
+    instance_id: *const u8,
+) -> c_int {
+    // SAFETY: `config` is assumed to be a valid, non-null pointer returned by
+    // AVirtualMachineRawConfig_create. It's the only reference to the object.
+    let config = unsafe { &mut *config };
+    // SAFETY: `instanceId` is assumed to be a valid pointer to 64 bytes of memory. `config`
+    // is assumed to be a valid object returned by AVirtuaMachineConfig_create.
+    // Both never overlap.
+    unsafe {
+        ptr::copy_nonoverlapping(instance_id, config.instanceId.as_mut_ptr(), 64);
+    }
+    0
+}
+
+/// Set a kernel image of a virtual machine.
+///
+/// # Safety
+/// `config` must be a pointer returned by `AVirtualMachineRawConfig_create`. `fd` must be a valid
+/// file descriptor or -1. `AVirtualMachineRawConfig_setKernel` takes ownership of `fd` and `fd`
+/// will be closed upon `AVirtualMachineRawConfig_delete`.
+#[no_mangle]
+pub unsafe extern "C" fn AVirtualMachineRawConfig_setKernel(
+    config: *mut VirtualMachineRawConfig,
+    fd: c_int,
+) -> c_int {
+    let file = get_file_from_fd(fd);
+    // SAFETY: `config` is assumed to be a valid, non-null pointer returned by
+    // AVirtualMachineRawConfig_create. It's the only reference to the object.
+    let config = unsafe { &mut *config };
+    config.kernel = file.map(ParcelFileDescriptor::new);
+    0
+}
+
+/// Set an init rd of a virtual machine.
+///
+/// # Safety
+/// `config` must be a pointer returned by `AVirtualMachineRawConfig_create`. `fd` must be a valid
+/// file descriptor or -1. `AVirtualMachineRawConfig_setInitRd` takes ownership of `fd` and `fd`
+/// will be closed upon `AVirtualMachineRawConfig_delete`.
+#[no_mangle]
+pub unsafe extern "C" fn AVirtualMachineRawConfig_setInitRd(
+    config: *mut VirtualMachineRawConfig,
+    fd: c_int,
+) -> c_int {
+    let file = get_file_from_fd(fd);
+    // SAFETY: `config` is assumed to be a valid, non-null pointer returned by
+    // AVirtualMachineRawConfig_create. It's the only reference to the object.
+    let config = unsafe { &mut *config };
+    config.initrd = file.map(ParcelFileDescriptor::new);
+    0
+}
+
+/// Add a disk for a virtual machine.
+///
+/// # Safety
+/// `config` must be a pointer returned by `AVirtualMachineRawConfig_create`. `fd` must be a valid
+/// file descriptor. `AVirtualMachineRawConfig_addDisk` takes ownership of `fd` and `fd` will be
+/// closed upon `AVirtualMachineRawConfig_delete`.
+#[no_mangle]
+pub unsafe extern "C" fn AVirtualMachineRawConfig_addDisk(
+    config: *mut VirtualMachineRawConfig,
+    fd: c_int,
+    writable: bool,
+) -> c_int {
+    let file = get_file_from_fd(fd);
+    // SAFETY: `config` is assumed to be a valid, non-null pointer returned by
+    // AVirtualMachineRawConfig_create. It's the only reference to the object.
+    let config = unsafe { &mut *config };
+    match file {
+        // partition not supported yet
+        None => -libc::EINVAL,
+        Some(file) => {
+            config.disks.push(DiskImage {
+                image: Some(ParcelFileDescriptor::new(file)),
+                writable,
+                ..Default::default()
+            });
+            0
+        }
+    }
+}
+
+/// Set how much memory will be given to a virtual machine.
+///
+/// # Safety
+/// `config` must be a pointer returned by `AVirtualMachineRawConfig_create`.
+#[no_mangle]
+pub unsafe extern "C" fn AVirtualMachineRawConfig_setMemoryMib(
+    config: *mut VirtualMachineRawConfig,
+    memory_mib: i32,
+) -> c_int {
+    // SAFETY: `config` is assumed to be a valid, non-null pointer returned by
+    // AVirtualMachineRawConfig_create. It's the only reference to the object.
+    let config = unsafe { &mut *config };
+    config.memoryMib = memory_mib;
+    0
+}
+
+/// Set whether a virtual machine is protected or not.
+///
+/// # Safety
+/// `config` must be a pointer returned by `AVirtualMachineRawConfig_create`.
+#[no_mangle]
+pub unsafe extern "C" fn AVirtualMachineRawConfig_setProtectedVm(
+    config: *mut VirtualMachineRawConfig,
+    protected_vm: bool,
+) -> c_int {
+    // SAFETY: `config` is assumed to be a valid, non-null pointer returned by
+    // AVirtualMachineRawConfig_create. It's the only reference to the object.
+    let config = unsafe { &mut *config };
+    config.protectedVm = protected_vm;
+    0
+}
+
+/// Set whether a virtual machine uses memory ballooning or not.
+///
+/// # Safety
+/// `config` must be a pointer returned by `AVirtualMachineRawConfig_create`.
+#[no_mangle]
+pub unsafe extern "C" fn AVirtualMachineRawConfig_setBalloon(
+    config: *mut VirtualMachineRawConfig,
+    balloon: bool,
+) -> c_int {
+    // SAFETY: `config` is assumed to be a valid, non-null pointer returned by
+    // AVirtualMachineRawConfig_create. It's the only reference to the object.
+    let config = unsafe { &mut *config };
+    config.noBalloon = !balloon;
+    0
+}
+
+/// NOT IMPLEMENTED.
+///
+/// # Returns
+/// It always returns `-ENOTSUP`.
+#[no_mangle]
+pub extern "C" fn AVirtualMachineRawConfig_setHypervisorSpecificAuthMethod(
+    _config: *mut VirtualMachineRawConfig,
+    _enable: bool,
+) -> c_int {
+    -libc::ENOTSUP
+}
+
+/// NOT IMPLEMENTED.
+///
+/// # Returns
+/// It always returns `-ENOTSUP`.
+#[no_mangle]
+pub extern "C" fn AVirtualMachineRawConfig_addCustomMemoryBackingFile(
+    _config: *mut VirtualMachineRawConfig,
+    _fd: c_int,
+    _range_start: usize,
+    _range_end: usize,
+) -> c_int {
+    -libc::ENOTSUP
+}
+
+/// Spawn a new instance of `virtmgr`, a child process that will host the `VirtualizationService`
+/// AIDL service, and connect to the child process.
+///
+/// # Safety
+/// `service_ptr` must be a valid, non-null pointer to a mutable raw pointer.
+#[no_mangle]
+pub unsafe extern "C" fn AVirtualizationService_create(
+    service_ptr: *mut *mut Strong<dyn IVirtualizationService>,
+    early: bool,
+) -> c_int {
+    let virtmgr =
+        if early { VirtualizationService::new_early() } else { VirtualizationService::new() };
+    let virtmgr = match virtmgr {
+        Ok(virtmgr) => virtmgr,
+        Err(e) => return -e.raw_os_error().unwrap_or(libc::EIO),
+    };
+    match virtmgr.connect() {
+        Ok(service) => {
+            // SAFETY: `service` is assumed to be a valid, non-null pointer to a mutable raw
+            // pointer. `service` is the only reference here and `config` takes
+            // ownership.
+            unsafe {
+                *service_ptr = Box::into_raw(Box::new(service));
+            }
+            0
+        }
+        Err(_) => -libc::ECONNREFUSED,
+    }
+}
+
+/// Destroy a VirtualizationService object.
+///
+/// # Safety
+/// `service` must be a pointer returned by `AVirtualizationService_create` or
+/// `AVirtualizationService_create_early`. `service` must not be reused after deletion.
+#[no_mangle]
+pub unsafe extern "C" fn AVirtualizationService_destroy(
+    service: *mut Strong<dyn IVirtualizationService>,
+) {
+    if !service.is_null() {
+        // SAFETY: `service` is assumed to be a valid, non-null pointer returned by
+        // `AVirtualizationService_create`. It's the only reference to the object.
+        unsafe {
+            let _ = Box::from_raw(service);
+        }
+    }
+}
+
+/// Create a virtual machine with given `config`.
+///
+/// # Safety
+/// `config` must be a pointer returned by `AVirtualMachineRawConfig_create`. `service` must be a
+/// pointer returned by `AVirtualMachineRawConfig_create`. `vm_ptr` must be a valid, non-null
+/// pointer to a mutable raw pointer. `console_out_fd`, `console_in_fd`, and `log_fd` must be a
+/// valid file descriptor or -1. `AVirtualMachine_create` takes ownership of `console_out_fd`,
+/// `console_in_fd`, and `log_fd`, and taken file descriptors must not be reused.
+#[no_mangle]
+pub unsafe extern "C" fn AVirtualMachine_createRaw(
+    service: *const Strong<dyn IVirtualizationService>,
+    config: *mut VirtualMachineRawConfig,
+    console_out_fd: c_int,
+    console_in_fd: c_int,
+    log_fd: c_int,
+    vm_ptr: *mut *mut VmInstance,
+) -> c_int {
+    // SAFETY: `service` is assumed to be a valid, non-null pointer returned by
+    // `AVirtualizationService_create` or `AVirtualizationService_create_early`. It's the only
+    // reference to the object.
+    let service = unsafe { &*service };
+
+    // SAFETY: `config` is assumed to be a valid, non-null pointer returned by
+    // `AVirtualMachineRawConfig_create`. It's the only reference to the object.
+    let config = unsafe { *Box::from_raw(config) };
+    let config = VirtualMachineConfig::RawConfig(config);
+
+    let console_out = get_file_from_fd(console_out_fd);
+    let console_in = get_file_from_fd(console_in_fd);
+    let log = get_file_from_fd(log_fd);
+
+    match VmInstance::create(service.as_ref(), &config, console_out, console_in, log, None, None) {
+        Ok(vm) => {
+            // SAFETY: `vm_ptr` is assumed to be a valid, non-null pointer to a mutable raw pointer.
+            // `vm` is the only reference here and `vm_ptr` takes ownership.
+            unsafe {
+                *vm_ptr = Box::into_raw(Box::new(vm));
+            }
+            0
+        }
+        Err(_) => -libc::EIO,
+    }
+}
+
+/// Start a virtual machine.
+///
+/// # Safety
+/// `vm` must be a pointer returned by `AVirtualMachine_createRaw`.
+#[no_mangle]
+pub unsafe extern "C" fn AVirtualMachine_start(vm: *const VmInstance) -> c_int {
+    // SAFETY: `vm` is assumed to be a valid, non-null pointer returned by
+    // `AVirtualMachine_createRaw`. It's the only reference to the object.
+    let vm = unsafe { &*vm };
+    match vm.start() {
+        Ok(_) => 0,
+        Err(_) => -libc::EIO,
+    }
+}
+
+/// Stop a virtual machine.
+///
+/// # Safety
+/// `vm` must be a pointer returned by `AVirtualMachine_create`.
+#[no_mangle]
+pub unsafe extern "C" fn AVirtualMachine_stop(vm: *const VmInstance) -> c_int {
+    // SAFETY: `vm` is assumed to be a valid, non-null pointer returned by
+    // `AVirtualMachine_createRaw`. It's the only reference to the object.
+    let vm = unsafe { &*vm };
+    match vm.stop() {
+        Ok(_) => 0,
+        Err(_) => -libc::EIO,
+    }
+}
+
+/// Wait until a virtual machine stops.
+///
+/// # Safety
+/// `vm` must be a pointer returned by `AVirtualMachine_createRaw`.
+#[no_mangle]
+pub unsafe extern "C" fn AVirtualMachine_waitForStop(vm: *const VmInstance) -> StopReason {
+    // SAFETY: `vm` is assumed to be a valid, non-null pointer returned by
+    // AVirtualMachine_create. It's the only reference to the object.
+    let vm = unsafe { &*vm };
+    match vm.wait_for_death() {
+        DeathReason::VirtualizationServiceDied => StopReason::VIRTUALIZATION_SERVICE_DIED,
+        DeathReason::InfrastructureError => StopReason::INFRASTRUCTURE_ERROR,
+        DeathReason::Killed => StopReason::KILLED,
+        DeathReason::Unknown => StopReason::UNKNOWN,
+        DeathReason::Shutdown => StopReason::SHUTDOWN,
+        DeathReason::StartFailed => StopReason::START_FAILED,
+        DeathReason::Reboot => StopReason::REBOOT,
+        DeathReason::Crash => StopReason::CRASH,
+        DeathReason::PvmFirmwarePublicKeyMismatch => StopReason::PVM_FIRMWARE_PUBLIC_KEY_MISMATCH,
+        DeathReason::PvmFirmwareInstanceImageChanged => {
+            StopReason::PVM_FIRMWARE_INSTANCE_IMAGE_CHANGED
+        }
+        DeathReason::Hangup => StopReason::HANGUP,
+        _ => StopReason::UNRECOGNISED,
+    }
+}
+
+/// Destroy a virtual machine.
+///
+/// # Safety
+/// `vm` must be a pointer returned by `AVirtualMachine_createRaw`. `vm` must not be reused after
+/// deletion.
+#[no_mangle]
+pub unsafe extern "C" fn AVirtualMachine_destroy(vm: *mut VmInstance) {
+    if !vm.is_null() {
+        // SAFETY: `vm` is assumed to be a valid, non-null pointer returned by
+        // AVirtualMachine_create. It's the only reference to the object.
+        unsafe {
+            let _ = Box::from_raw(vm);
+        }
+    }
+}
+
+fn get_file_from_fd(fd: i32) -> Option<File> {
+    if fd == -1 {
+        None
+    } else {
+        // SAFETY: transferring ownership of `fd` from the caller
+        Some(unsafe { File::from_raw_fd(fd) })
+    }
+}
diff --git a/libs/libvmclient/src/lib.rs b/libs/libvmclient/src/lib.rs
index 13630c0..c0baea5 100644
--- a/libs/libvmclient/src/lib.rs
+++ b/libs/libvmclient/src/lib.rs
@@ -243,6 +243,11 @@
         self.vm.start()
     }
 
+    /// Stops the VM.
+    pub fn stop(&self) -> BinderResult<()> {
+        self.vm.stop()
+    }
+
     /// Returns the CID used for vsock connections to the VM.
     pub fn cid(&self) -> i32 {
         self.cid