/*crosvm * Copyright (C) 2019 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. */ #include "host/libs/vm_manager/crosvm_manager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "common/libs/utils/environment.h" #include "common/libs/utils/files.h" #include "common/libs/utils/json.h" #include "common/libs/utils/network.h" #include "common/libs/utils/result.h" #include "common/libs/utils/subprocess.h" #include "host/libs/command_util/snapshot_utils.h" #include "host/libs/config/cuttlefish_config.h" #include "host/libs/config/known_paths.h" #include "host/libs/vm_manager/crosvm_builder.h" #include "host/libs/vm_manager/qemu_manager.h" namespace cuttlefish { namespace vm_manager { constexpr auto kTouchpadDefaultPrefix = "Crosvm_Virtio_Multitouch_Touchpad_"; bool CrosvmManager::IsSupported() { #ifdef __ANDROID__ return true; #else return HostSupportsQemuCli(); #endif } Result> CrosvmManager::ConfigureGraphics( const CuttlefishConfig::InstanceSpecific& instance) { // Override the default HAL search paths in all cases. We do this because // the HAL search path allows for fallbacks, and fallbacks in conjunction // with properties lead to non-deterministic behavior while loading the // HALs. std::unordered_map bootconfig_args; if (instance.gpu_mode() == kGpuModeGuestSwiftshader) { bootconfig_args = { {"androidboot.cpuvulkan.version", std::to_string(VK_API_VERSION_1_2)}, {"androidboot.hardware.gralloc", "minigbm"}, {"androidboot.hardware.hwcomposer", instance.hwcomposer()}, {"androidboot.hardware.hwcomposer.display_finder_mode", "drm"}, {"androidboot.hardware.hwcomposer.display_framebuffer_format", instance.guest_uses_bgra_framebuffers() ? "bgra" : "rgba"}, {"androidboot.hardware.egl", "angle"}, {"androidboot.hardware.vulkan", "pastel"}, {"androidboot.opengles.version", "196609"}, // OpenGL ES 3.1 }; } else if (instance.gpu_mode() == kGpuModeDrmVirgl) { bootconfig_args = { {"androidboot.cpuvulkan.version", "0"}, {"androidboot.hardware.gralloc", "minigbm"}, {"androidboot.hardware.hwcomposer", "ranchu"}, {"androidboot.hardware.hwcomposer.mode", "client"}, {"androidboot.hardware.hwcomposer.display_finder_mode", "drm"}, {"androidboot.hardware.hwcomposer.display_framebuffer_format", instance.guest_uses_bgra_framebuffers() ? "bgra" : "rgba"}, {"androidboot.hardware.egl", "mesa"}, // No "hardware" Vulkan support, yet {"androidboot.opengles.version", "196608"}, // OpenGL ES 3.0 }; } else if (instance.gpu_mode() == kGpuModeGfxstream || instance.gpu_mode() == kGpuModeGfxstreamGuestAngle || instance.gpu_mode() == kGpuModeGfxstreamGuestAngleHostSwiftShader) { const bool uses_angle = instance.gpu_mode() == kGpuModeGfxstreamGuestAngle || instance.gpu_mode() == kGpuModeGfxstreamGuestAngleHostSwiftShader; const std::string gles_impl = uses_angle ? "angle" : "emulation"; const std::string gfxstream_transport = instance.gpu_gfxstream_transport(); CF_EXPECT(gfxstream_transport == "virtio-gpu-asg" || gfxstream_transport == "virtio-gpu-pipe", "Invalid Gfxstream transport option: \"" << gfxstream_transport << "\""); bootconfig_args = { {"androidboot.cpuvulkan.version", "0"}, {"androidboot.hardware.gralloc", "minigbm"}, {"androidboot.hardware.hwcomposer", instance.hwcomposer()}, {"androidboot.hardware.hwcomposer.display_finder_mode", "drm"}, {"androidboot.hardware.hwcomposer.display_framebuffer_format", instance.guest_uses_bgra_framebuffers() ? "bgra" : "rgba"}, {"androidboot.hardware.egl", gles_impl}, {"androidboot.hardware.vulkan", "ranchu"}, {"androidboot.hardware.gltransport", gfxstream_transport}, {"androidboot.opengles.version", "196609"}, // OpenGL ES 3.1 }; } else if (instance.gpu_mode() == kGpuModeCustom) { bootconfig_args = { {"androidboot.cpuvulkan.version", "0"}, {"androidboot.hardware.gralloc", "minigbm"}, {"androidboot.hardware.hwcomposer", instance.hwcomposer()}, {"androidboot.hardware.hwcomposer.display_finder_mode", "drm"}, {"androidboot.hardware.hwcomposer.display_framebuffer_format", instance.guest_uses_bgra_framebuffers() ? "bgra" : "rgba"}, {"androidboot.hardware.egl", "angle"}, {"androidboot.hardware.vulkan", instance.guest_vulkan_driver()}, {"androidboot.hardware.gltransport", "virtio-gpu-asg"}, {"androidboot.opengles.version", "196609"}, // OpenGL ES 3.1 }; } else if (instance.gpu_mode() == kGpuModeNone) { return {}; } else { return CF_ERR("Unknown GPU mode " << instance.gpu_mode()); } if (!instance.gpu_angle_feature_overrides_enabled().empty()) { bootconfig_args["androidboot.hardware.angle_feature_overrides_enabled"] = instance.gpu_angle_feature_overrides_enabled(); } if (!instance.gpu_angle_feature_overrides_disabled().empty()) { bootconfig_args["androidboot.hardware.angle_feature_overrides_disabled"] = instance.gpu_angle_feature_overrides_disabled(); } return bootconfig_args; } Result> CrosvmManager::ConfigureBootDevices( const CuttlefishConfig::InstanceSpecific& instance) { const int num_disks = instance.virtual_disk_paths().size(); const bool has_gpu = instance.hwcomposer() != kHwComposerNone; // TODO There is no way to control this assignment with crosvm (yet) if (HostArch() == Arch::X86_64) { int num_gpu_pcis = has_gpu ? 1 : 0; if (instance.gpu_mode() != kGpuModeNone && !instance.enable_gpu_vhost_user()) { // crosvm has an additional PCI device for an ISA bridge when running // with a gpu and without vhost user gpu. num_gpu_pcis += 1; } // virtio_gpu and virtio_wl precedes the first console or disk return ConfigureMultipleBootDevices("pci0000:00/0000:00:", 1 + num_gpu_pcis, num_disks); } else { // On ARM64 crosvm, block devices are on their own bridge, so we don't // need to calculate it, and the path is always the same return {{{"androidboot.boot_devices", "10000.pci"}}}; } } std::string ToSingleLineString(const Json::Value& value) { Json::StreamWriterBuilder builder; builder["indentation"] = ""; return Json::writeString(builder, value); } void MaybeConfigureVulkanIcd(const CuttlefishConfig& config, Command* command) { const auto& gpu_mode = config.ForDefaultInstance().gpu_mode(); if (gpu_mode == kGpuModeGfxstreamGuestAngleHostSwiftShader) { // See https://github.com/KhronosGroup/Vulkan-Loader. const std::string swiftshader_icd_json = HostUsrSharePath("vulkan/icd.d/vk_swiftshader_icd.json"); command->AddEnvironmentVariable("VK_DRIVER_FILES", swiftshader_icd_json); command->AddEnvironmentVariable("VK_ICD_FILENAMES", swiftshader_icd_json); } } Result CrosvmPathForVhostUserGpu(const CuttlefishConfig& config) { const auto& instance = config.ForDefaultInstance(); switch (HostArch()) { case Arch::Arm64: return HostBinaryPath("aarch64-linux-gnu/crosvm"); case Arch::X86: case Arch::X86_64: return instance.crosvm_binary(); default: break; } return CF_ERR("Unhandled host arch " << HostArchStr() << " for vhost user gpu crosvm"); } struct VhostUserDeviceCommands { Command device_cmd; Command device_logs_cmd; }; Result BuildVhostUserGpu( const CuttlefishConfig& config, Command* main_crosvm_cmd) { const auto& instance = config.ForDefaultInstance(); if (!instance.enable_gpu_vhost_user()) { return CF_ERR("Attempting to build vhost user gpu when not enabled?"); } auto gpu_device_socket_path = instance.PerInstanceInternalUdsPath("vhost-user-gpu-socket"); auto gpu_device_logs_path = instance.PerInstanceInternalPath("crosvm_vhost_user_gpu.fifo"); auto gpu_device_logs = CF_EXPECT(SharedFD::Fifo(gpu_device_logs_path, 0666)); Command gpu_device_logs_cmd(HostBinaryPath("log_tee")); gpu_device_logs_cmd.AddParameter("--process_name=crosvm_gpu"); gpu_device_logs_cmd.AddParameter("--log_fd_in=", gpu_device_logs); gpu_device_logs_cmd.SetStopper(KillSubprocessFallback([](Subprocess* proc) { // Ask nicely so that log_tee gets a chance to process all the logs. // TODO: b/335934714 - Make sure the process actually exits bool res = kill(proc->pid(), SIGINT) == 0; return res ? StopperResult::kStopSuccess : StopperResult::kStopFailure; })); const std::string crosvm_path = CF_EXPECT(CrosvmPathForVhostUserGpu(config)); CrosvmBuilder gpu_device_cmd; // NOTE: The "main" crosvm process returns a kCrosvmVmResetExitCode when the // guest exits but the "gpu" crosvm just exits cleanly with 0 after the "main" // crosvm disconnects. gpu_device_cmd.ApplyProcessRestarter(crosvm_path, /*first_time_argument=*/"", /*exit_code=*/0); gpu_device_cmd.Cmd().AddParameter("device"); gpu_device_cmd.Cmd().AddParameter("gpu"); const auto& gpu_mode = instance.gpu_mode(); CF_EXPECT( gpu_mode == kGpuModeGfxstream || gpu_mode == kGpuModeGfxstreamGuestAngle || gpu_mode == kGpuModeGfxstreamGuestAngleHostSwiftShader, "GPU mode " << gpu_mode << " not yet supported with vhost user gpu."); const std::string gpu_pci_address = fmt::format("00:{:0>2x}.0", VmManager::kGpuPciSlotNum); // Why does this need JSON instead of just following the normal flags style... Json::Value gpu_params_json; gpu_params_json["pci-address"] = gpu_pci_address; if (gpu_mode == kGpuModeGfxstream) { gpu_params_json["context-types"] = "gfxstream-gles:gfxstream-vulkan"; gpu_params_json["egl"] = true; gpu_params_json["gles"] = true; } else if (gpu_mode == kGpuModeGfxstreamGuestAngle || gpu_mode == kGpuModeGfxstreamGuestAngleHostSwiftShader) { gpu_params_json["context-types"] = "gfxstream-vulkan"; gpu_params_json["egl"] = false; gpu_params_json["gles"] = false; } gpu_params_json["glx"] = false; gpu_params_json["surfaceless"] = true; gpu_params_json["external-blob"] = instance.enable_gpu_external_blob(); gpu_params_json["system-blob"] = instance.enable_gpu_system_blob(); if (instance.hwcomposer() != kHwComposerNone) { // "displays": [ // { // "mode": { // "windowed": [ // 720, // 1280 // ] // }, // "dpi": [ // 320, // 320 // ], // "refresh-rate": 60 // } // ] Json::Value displays(Json::arrayValue); for (const auto& display_config : instance.display_configs()) { Json::Value display_mode_windowed(Json::arrayValue); display_mode_windowed[0] = display_config.width; display_mode_windowed[1] = display_config.height; Json::Value display_mode; display_mode["windowed"] = display_mode_windowed; Json::Value display_dpi(Json::arrayValue); display_dpi[0] = display_config.dpi; display_dpi[1] = display_config.dpi; Json::Value display; display["mode"] = display_mode; display["dpi"] = display_dpi; display["refresh-rate"] = display_config.refresh_rate_hz; displays.append(display); } gpu_params_json["displays"] = displays; gpu_device_cmd.Cmd().AddParameter("--wayland-sock=", instance.frames_socket_path()); } // Connect device to main crosvm: gpu_device_cmd.Cmd().AddParameter("--socket=", gpu_device_socket_path); main_crosvm_cmd->AddPrerequisite([gpu_device_socket_path]() -> Result { #ifdef __linux__ return WaitForUnixSocketListeningWithoutConnect(gpu_device_socket_path, /*timeoutSec=*/30); #else return CF_ERR("Unhandled check if vhost user gpu ready."); #endif }); main_crosvm_cmd->AddParameter( "--vhost-user=gpu,pci-address=", gpu_pci_address, ",socket=", gpu_device_socket_path); gpu_device_cmd.Cmd().AddParameter("--params"); gpu_device_cmd.Cmd().AddParameter(ToSingleLineString(gpu_params_json)); MaybeConfigureVulkanIcd(config, &gpu_device_cmd.Cmd()); gpu_device_cmd.Cmd().RedirectStdIO(Subprocess::StdIOChannel::kStdOut, gpu_device_logs); gpu_device_cmd.Cmd().RedirectStdIO(Subprocess::StdIOChannel::kStdErr, gpu_device_logs); return VhostUserDeviceCommands{ .device_cmd = std::move(gpu_device_cmd.Cmd()), .device_logs_cmd = std::move(gpu_device_logs_cmd), }; } Result ConfigureGpu(const CuttlefishConfig& config, Command* crosvm_cmd) { const auto& instance = config.ForDefaultInstance(); const auto& gpu_mode = instance.gpu_mode(); const std::string gles_string = gpu_mode == kGpuModeGfxstreamGuestAngle || gpu_mode == kGpuModeGfxstreamGuestAngleHostSwiftShader ? ",gles=false" : ",gles=true"; // 256MB so it is small enough for a 32-bit kernel. const bool target_is_32bit = instance.target_arch() == Arch::Arm || instance.target_arch() == Arch::X86; const std::string gpu_pci_bar_size = target_is_32bit ? ",pci-bar-size=268435456" : ""; const std::string gpu_udmabuf_string = instance.enable_gpu_udmabuf() ? ",udmabuf=true" : ""; const std::string gpu_renderer_features = instance.gpu_renderer_features(); const std::string gpu_renderer_features_param = !gpu_renderer_features.empty() ? ",renderer-features=\"" + gpu_renderer_features + "\"" : ""; const std::string gpu_common_string = fmt::format(",pci-address=00:{:0>2x}.0", VmManager::kGpuPciSlotNum) + gpu_udmabuf_string + gpu_pci_bar_size; const std::string gpu_common_3d_string = gpu_common_string + ",egl=true,surfaceless=true,glx=false" + gles_string + gpu_renderer_features_param; if (gpu_mode == kGpuModeGuestSwiftshader) { crosvm_cmd->AddParameter("--gpu=backend=2D", gpu_common_string); } else if (gpu_mode == kGpuModeDrmVirgl) { crosvm_cmd->AddParameter("--gpu=backend=virglrenderer,context-types=virgl2", gpu_common_3d_string); } else if (gpu_mode == kGpuModeGfxstream) { crosvm_cmd->AddParameter( "--gpu=context-types=gfxstream-gles:gfxstream-vulkan:gfxstream-" "composer", gpu_common_3d_string); } else if (gpu_mode == kGpuModeGfxstreamGuestAngle || gpu_mode == kGpuModeGfxstreamGuestAngleHostSwiftShader) { crosvm_cmd->AddParameter( "--gpu=context-types=gfxstream-vulkan:gfxstream-composer", gpu_common_3d_string); } else if (gpu_mode == kGpuModeCustom) { const std::string gpu_context_types = "--gpu=context-types=" + instance.gpu_context_types(); crosvm_cmd->AddParameter(gpu_context_types, gpu_common_string); } MaybeConfigureVulkanIcd(config, crosvm_cmd); if (instance.hwcomposer() != kHwComposerNone) { for (const auto& display_config : instance.display_configs()) { const auto display_w = std::to_string(display_config.width); const auto display_h = std::to_string(display_config.height); const auto display_dpi = std::to_string(display_config.dpi); const auto display_rr = std::to_string(display_config.refresh_rate_hz); const auto display_params = android::base::Join( std::vector{ "mode=windowed[" + display_w + "," + display_h + "]", "dpi=[" + display_dpi + "," + display_dpi + "]", "refresh-rate=" + display_rr, }, ","); crosvm_cmd->AddParameter("--gpu-display=", display_params); } crosvm_cmd->AddParameter("--wayland-sock=", instance.frames_socket_path()); } return {}; } Result> CrosvmManager::StartCommands( const CuttlefishConfig& config, std::vector& dependencyCommands) { auto instance = config.ForDefaultInstance(); auto environment = config.ForDefaultEnvironment(); CrosvmBuilder crosvm_cmd; crosvm_cmd.Cmd().AddPrerequisite([&dependencyCommands]() -> Result { for (auto dependencyCommand : dependencyCommands) { CF_EXPECT(dependencyCommand->WaitForAvailability()); } return {}; }); // Add "--restore_path=" if there is a snapshot // path supplied. // // Use the process_restarter "-first_time_argument" flag to only do this for // the first invocation. If the guest requests a restart, we don't want crosvm // to restore again. It should reboot normally. std::string first_time_argument; if (IsRestoring(config)) { const std::string snapshot_dir_path = config.snapshot_path(); auto meta_info_json = CF_EXPECT(LoadMetaJson(snapshot_dir_path)); const std::vector selectors{kGuestSnapshotField, instance.id()}; const auto guest_snapshot_dir_suffix = CF_EXPECT(GetValue(meta_info_json, selectors)); // guest_snapshot_dir_suffix is a relative to // the snapshot_path const auto restore_path = snapshot_dir_path + "/" + guest_snapshot_dir_suffix + "/" + kGuestSnapshotBase; first_time_argument = "--restore=" + restore_path; } crosvm_cmd.ApplyProcessRestarter(instance.crosvm_binary(), first_time_argument, kCrosvmVmResetExitCode); crosvm_cmd.Cmd().AddParameter("run"); crosvm_cmd.AddControlSocket(instance.CrosvmSocketPath(), instance.crosvm_binary()); if (!instance.smt()) { crosvm_cmd.Cmd().AddParameter("--no-smt"); } // Disable USB passthrough. It isn't needed for any key use cases and it is // not compatible with crosvm suspend-resume support yet (b/266622743). // TODO: Allow it to be turned back on using a flag. if (!instance.enable_usb()) { crosvm_cmd.Cmd().AddParameter("--no-usb"); } crosvm_cmd.Cmd().AddParameter("--core-scheduling=false"); if (instance.vhost_net()) { crosvm_cmd.Cmd().AddParameter("--vhost-net"); } if (config.virtio_mac80211_hwsim() && !environment.vhost_user_mac80211_hwsim().empty()) { crosvm_cmd.Cmd().AddParameter("--vhost-user=mac80211-hwsim,socket=", environment.vhost_user_mac80211_hwsim()); } if (instance.protected_vm()) { crosvm_cmd.Cmd().AddParameter("--protected-vm"); } if (!instance.crosvm_use_balloon()) { crosvm_cmd.Cmd().AddParameter("--no-balloon"); } if (!instance.crosvm_use_rng()) { crosvm_cmd.Cmd().AddParameter("--no-rng"); } if (instance.gdb_port() > 0) { CF_EXPECT(instance.cpus() == 1, "CPUs must be 1 for crosvm gdb mode"); crosvm_cmd.Cmd().AddParameter("--gdb=", instance.gdb_port()); } std::optional vhost_user_gpu; if (instance.enable_gpu_vhost_user()) { vhost_user_gpu.emplace( CF_EXPECT(BuildVhostUserGpu(config, &crosvm_cmd.Cmd()))); } else { CF_EXPECT(ConfigureGpu(config, &crosvm_cmd.Cmd())); } if (instance.hwcomposer() != kHwComposerNone) { const bool pmem_disabled = instance.mte() || !instance.use_pmem(); if (!pmem_disabled && FileExists(instance.hwcomposer_pmem_path())) { crosvm_cmd.Cmd().AddParameter("--rw-pmem-device=", instance.hwcomposer_pmem_path()); } } const auto gpu_capture_enabled = !instance.gpu_capture_binary().empty(); // crosvm_cmd.Cmd().AddParameter("--null-audio"); crosvm_cmd.Cmd().AddParameter("--mem=", instance.memory_mb()); crosvm_cmd.Cmd().AddParameter("--cpus=", instance.cpus()); if (instance.mte()) { crosvm_cmd.Cmd().AddParameter("--mte"); } auto disk_num = instance.virtual_disk_paths().size(); CF_EXPECT(VmManager::kMaxDisks >= disk_num, "Provided too many disks (" << disk_num << "), maximum " << VmManager::kMaxDisks << "supported"); for (const auto& disk : instance.virtual_disk_paths()) { if (instance.protected_vm()) { crosvm_cmd.AddReadOnlyDisk(disk); } else { crosvm_cmd.AddReadWriteDisk(disk); } } if (instance.enable_webrtc()) { bool is_chromeos = instance.boot_flow() == CuttlefishConfig::InstanceSpecific::BootFlow::ChromeOs || instance.boot_flow() == CuttlefishConfig::InstanceSpecific::BootFlow::ChromeOsDisk; auto touch_type_parameter = is_chromeos ? "single-touch" : "multi-touch"; auto display_configs = instance.display_configs(); CF_EXPECT(display_configs.size() >= 1); int touch_idx = 0; for (auto& display_config : display_configs) { crosvm_cmd.Cmd().AddParameter( "--input=", touch_type_parameter, "[path=", instance.touch_socket_path(touch_idx++), ",width=", display_config.width, ",height=", display_config.height, "]"); } auto touchpad_configs = instance.touchpad_configs(); for (int i = 0; i < touchpad_configs.size(); ++i) { auto touchpad_config = touchpad_configs[i]; crosvm_cmd.Cmd().AddParameter( "--input=", touch_type_parameter, "[path=", instance.touch_socket_path(touch_idx++), ",width=", touchpad_config.width, ",height=", touchpad_config.height, ",name=", kTouchpadDefaultPrefix, i, "]"); } crosvm_cmd.Cmd().AddParameter("--input=rotary[path=", instance.rotary_socket_path(), "]"); crosvm_cmd.Cmd().AddParameter("--input=keyboard[path=", instance.keyboard_socket_path(), "]"); crosvm_cmd.Cmd().AddParameter("--input=switches[path=", instance.switches_socket_path(), "]"); } SharedFD wifi_tap; // GPU capture can only support named files and not file descriptors due to // having to pass arguments to crosvm via a wrapper script. #ifdef __linux__ if (!gpu_capture_enabled) { // The PCI ordering of tap devices is important. Make sure any change here // is reflected in ethprime u-boot variable. // TODO(b/218364216, b/322862402): Crosvm occupies 32 PCI devices first and only then uses PCI // functions which may break order. The final solution is going to be a PCI allocation strategy // that will guarantee the ordering. For now, hardcode PCI network devices to unoccupied // functions. const pci::Address mobile_pci = pci::Address(0, VmManager::kNetPciDeviceNum, 1); const pci::Address ethernet_pci = pci::Address(0, VmManager::kNetPciDeviceNum, 2); crosvm_cmd.AddTap(instance.mobile_tap_name(), instance.mobile_mac(), mobile_pci); crosvm_cmd.AddTap(instance.ethernet_tap_name(), instance.ethernet_mac(), ethernet_pci); if (!config.virtio_mac80211_hwsim() && environment.enable_wifi()) { wifi_tap = crosvm_cmd.AddTap(instance.wifi_tap_name()); } } #endif const bool pmem_disabled = instance.mte() || !instance.use_pmem(); if (!pmem_disabled && FileExists(instance.access_kregistry_path())) { crosvm_cmd.Cmd().AddParameter("--rw-pmem-device=", instance.access_kregistry_path()); } if (!pmem_disabled && FileExists(instance.pstore_path())) { crosvm_cmd.Cmd().AddParameter("--pstore=path=", instance.pstore_path(), ",size=", FileSize(instance.pstore_path())); } if (instance.enable_sandbox()) { const bool seccomp_exists = DirectoryExists(instance.seccomp_policy_dir()); const std::string& var_empty_dir = kCrosvmVarEmptyDir; const bool var_empty_available = DirectoryExists(var_empty_dir); CF_EXPECT(var_empty_available && seccomp_exists, var_empty_dir << " is not an existing, empty directory." << "seccomp-policy-dir, " << instance.seccomp_policy_dir() << " does not exist"); crosvm_cmd.Cmd().AddParameter("--seccomp-policy-dir=", instance.seccomp_policy_dir()); } else { crosvm_cmd.Cmd().AddParameter("--disable-sandbox"); } if (instance.vsock_guest_cid() >= 2) { if (instance.vhost_user_vsock()) { auto param = fmt::format("/tmp/vsock_{}_{}/vhost.socket,max-queue-size=256", instance.vsock_guest_cid(), std::to_string(getuid())); crosvm_cmd.Cmd().AddParameter("--vhost-user=vsock,socket=", param); } else { crosvm_cmd.Cmd().AddParameter("--cid=", instance.vsock_guest_cid()); } } // /dev/hvc0 = kernel console // If kernel log is enabled, the virtio-console port will be specified as // a true console for Linux, and kernel messages will be printed there. // Otherwise, the port will still be set up for bootloader and userspace // messages, but the kernel will not print anything here. This keeps our // kernel log event features working. If an alternative "earlycon" boot // console is configured below on a legacy serial port, it will control // the main log until the virtio-console takes over. crosvm_cmd.AddHvcReadOnly(instance.kernel_log_pipe_name(), instance.enable_kernel_log()); // /dev/hvc1 = serial console if (instance.console()) { // stdin is the only currently supported way to write data to a serial port // in crosvm. A file (named pipe) is used here instead of stdout to ensure // only the serial port output is received by the console forwarder as // crosvm may print other messages to stdout. if (instance.kgdb() || instance.use_bootloader()) { crosvm_cmd.AddSerialConsoleReadWrite(instance.console_out_pipe_name(), instance.console_in_pipe_name(), instance.enable_kernel_log()); // In kgdb mode, we have the interactive console on ttyS0 (both Android's // console and kdb), so we can disable the virtio-console port usually // allocated to Android's serial console, and redirect it to a sink. This // ensures that that the PCI device assignments (and thus sepolicy) don't // have to change crosvm_cmd.AddHvcSink(); } else { crosvm_cmd.AddSerialSink(); crosvm_cmd.AddHvcReadWrite(instance.console_out_pipe_name(), instance.console_in_pipe_name()); } } else { // Use an 8250 UART (ISA or platform device) for earlycon, as the // virtio-console driver may not be available for early messages // In kgdb mode, earlycon is an interactive console, and so early // dmesg will go there instead of the kernel.log if (instance.enable_kernel_log() && (instance.kgdb() || instance.use_bootloader())) { crosvm_cmd.AddSerialConsoleReadOnly(instance.kernel_log_pipe_name()); } // as above, create a fake virtio-console 'sink' port when the serial // console is disabled, so the PCI device ID assignments don't move // around crosvm_cmd.AddHvcSink(); } auto crosvm_logs_path = instance.PerInstanceInternalPath("crosvm.fifo"); auto crosvm_logs = CF_EXPECT(SharedFD::Fifo(crosvm_logs_path, 0666)); Command crosvm_log_tee_cmd(HostBinaryPath("log_tee")); crosvm_log_tee_cmd.AddParameter("--process_name=crosvm"); crosvm_log_tee_cmd.AddParameter("--log_fd_in=", crosvm_logs); crosvm_log_tee_cmd.SetStopper(KillSubprocessFallback([](Subprocess* proc) { // Ask nicely so that log_tee gets a chance to process all the logs. bool res = kill(proc->pid(), SIGINT) == 0; // TODO: b/335934714 - Make sure the process actually exits return res ? StopperResult::kStopSuccess : StopperResult::kStopFailure; })); // /dev/hvc2 = serial logging // Serial port for logcat, redirected to a pipe crosvm_cmd.AddHvcReadOnly(instance.logcat_pipe_name()); // /dev/hvc3 = keymaster (C++ implementation) crosvm_cmd.AddHvcReadWrite( instance.PerInstanceInternalPath("keymaster_fifo_vm.out"), instance.PerInstanceInternalPath("keymaster_fifo_vm.in")); // /dev/hvc4 = gatekeeper crosvm_cmd.AddHvcReadWrite( instance.PerInstanceInternalPath("gatekeeper_fifo_vm.out"), instance.PerInstanceInternalPath("gatekeeper_fifo_vm.in")); // /dev/hvc5 = bt if (config.enable_host_bluetooth()) { crosvm_cmd.AddHvcReadWrite( instance.PerInstanceInternalPath("bt_fifo_vm.out"), instance.PerInstanceInternalPath("bt_fifo_vm.in")); } else { crosvm_cmd.AddHvcSink(); } // /dev/hvc6 = gnss // /dev/hvc7 = location if (instance.enable_gnss_grpc_proxy()) { crosvm_cmd.AddHvcReadWrite( instance.PerInstanceInternalPath("gnsshvc_fifo_vm.out"), instance.PerInstanceInternalPath("gnsshvc_fifo_vm.in")); crosvm_cmd.AddHvcReadWrite( instance.PerInstanceInternalPath("locationhvc_fifo_vm.out"), instance.PerInstanceInternalPath("locationhvc_fifo_vm.in")); } else { for (auto i = 0; i < 2; i++) { crosvm_cmd.AddHvcSink(); } } // /dev/hvc8 = confirmationui crosvm_cmd.AddHvcReadWrite( instance.PerInstanceInternalPath("confui_fifo_vm.out"), instance.PerInstanceInternalPath("confui_fifo_vm.in")); // /dev/hvc9 = uwb if (config.enable_host_uwb()) { crosvm_cmd.AddHvcReadWrite( instance.PerInstanceInternalPath("uwb_fifo_vm.out"), instance.PerInstanceInternalPath("uwb_fifo_vm.in")); } else { crosvm_cmd.AddHvcSink(); } // /dev/hvc10 = oemlock crosvm_cmd.AddHvcReadWrite( instance.PerInstanceInternalPath("oemlock_fifo_vm.out"), instance.PerInstanceInternalPath("oemlock_fifo_vm.in")); // /dev/hvc11 = keymint (Rust implementation) crosvm_cmd.AddHvcReadWrite( instance.PerInstanceInternalPath("keymint_fifo_vm.out"), instance.PerInstanceInternalPath("keymint_fifo_vm.in")); // /dev/hvc12 = NFC if (config.enable_host_nfc()) { crosvm_cmd.AddHvcReadWrite( instance.PerInstanceInternalPath("nfc_fifo_vm.out"), instance.PerInstanceInternalPath("nfc_fifo_vm.in")); } else { crosvm_cmd.AddHvcSink(); } // /dev/hvc13 = sensors crosvm_cmd.AddHvcReadWrite( instance.PerInstanceInternalPath("sensors_fifo_vm.out"), instance.PerInstanceInternalPath("sensors_fifo_vm.in")); // /dev/hvc14 = MCU CONTROL if (instance.mcu()["control"]["type"].asString() == "serial") { auto path = instance.PerInstanceInternalPath("mcu"); path += "/" + instance.mcu()["control"]["path"].asString(); crosvm_cmd.AddHvcReadWrite(path, path); } else { crosvm_cmd.AddHvcSink(); } // /dev/hvc15 = MCU UART if (instance.mcu()["uart0"]["type"].asString() == "serial") { auto path = instance.PerInstanceInternalPath("mcu"); path += "/" + instance.mcu()["uart0"]["path"].asString(); crosvm_cmd.AddHvcReadWrite(path, path); } else { crosvm_cmd.AddHvcSink(); } for (auto i = 0; i < VmManager::kMaxDisks - disk_num; i++) { crosvm_cmd.AddHvcSink(); } CF_EXPECT(crosvm_cmd.HvcNum() + disk_num == VmManager::kMaxDisks + VmManager::kDefaultNumHvcs, "HVC count (" << crosvm_cmd.HvcNum() << ") + disk count (" << disk_num << ") is not the expected total of " << VmManager::kMaxDisks + VmManager::kDefaultNumHvcs << " devices"); if (instance.enable_audio()) { crosvm_cmd.Cmd().AddParameter("--sound=", instance.audio_server_path()); } // TODO(b/162071003): virtiofs crashes without sandboxing, this should be // fixed if (instance.enable_virtiofs()) { CF_EXPECT(instance.enable_sandbox(), "virtiofs is currently not supported without sandboxing"); // Set up directory shared with virtiofs crosvm_cmd.Cmd().AddParameter( "--shared-dir=", instance.PerInstancePath(kSharedDirName), ":shared:type=fs"); } if (instance.target_arch() == Arch::X86_64) { crosvm_cmd.Cmd().AddParameter("--pflash=", instance.pflash_path()); } // This needs to be the last parameter crosvm_cmd.Cmd().AddParameter("--bios=", instance.bootloader()); std::vector commands; if (vhost_user_gpu) { // The vhost user gpu crosvm command should be added before the main // crosvm command so that the main crosvm command can use a prerequisite // to wait for the communication socket to be ready. commands.emplace_back(std::move(vhost_user_gpu->device_cmd)); commands.emplace_back(std::move(vhost_user_gpu->device_logs_cmd)); } // log_tee must be added before crosvm_cmd to ensure all of crosvm's logs are // captured during shutdown. Processes are stopped in reverse order. commands.emplace_back(std::move(crosvm_log_tee_cmd)); if (gpu_capture_enabled) { const std::string gpu_capture_basename = cpp_basename(instance.gpu_capture_binary()); auto gpu_capture_logs_path = instance.PerInstanceInternalPath("gpu_capture.fifo"); auto gpu_capture_logs = CF_EXPECT(SharedFD::Fifo(gpu_capture_logs_path, 0666)); Command gpu_capture_log_tee_cmd(HostBinaryPath("log_tee")); gpu_capture_log_tee_cmd.AddParameter("--process_name=", gpu_capture_basename); gpu_capture_log_tee_cmd.AddParameter("--log_fd_in=", gpu_capture_logs); Command gpu_capture_command(instance.gpu_capture_binary()); if (gpu_capture_basename == "ngfx") { // Crosvm depends on command line arguments being passed as multiple // arguments but ngfx only allows a single `--args`. To work around this, // create a wrapper script that launches crosvm with all of the arguments // and pass this wrapper script to ngfx. const std::string crosvm_wrapper_path = instance.PerInstanceInternalPath("crosvm_wrapper.sh"); const std::string crosvm_wrapper_content = crosvm_cmd.Cmd().AsBashScript(crosvm_logs_path); CF_EXPECT(android::base::WriteStringToFile(crosvm_wrapper_content, crosvm_wrapper_path)); CF_EXPECT(MakeFileExecutable(crosvm_wrapper_path)); gpu_capture_command.AddParameter("--exe=", crosvm_wrapper_path); gpu_capture_command.AddParameter("--launch-detached"); gpu_capture_command.AddParameter("--verbose"); gpu_capture_command.AddParameter("--activity=Frame Debugger"); } else { // TODO(natsu): renderdoc return CF_ERR( "Unhandled GPU capture binary: " << instance.gpu_capture_binary()); } gpu_capture_command.RedirectStdIO(Subprocess::StdIOChannel::kStdOut, gpu_capture_logs); gpu_capture_command.RedirectStdIO(Subprocess::StdIOChannel::kStdErr, gpu_capture_logs); commands.emplace_back(std::move(gpu_capture_log_tee_cmd)); commands.emplace_back(std::move(gpu_capture_command)); } else { crosvm_cmd.Cmd().RedirectStdIO(Subprocess::StdIOChannel::kStdOut, crosvm_logs); crosvm_cmd.Cmd().RedirectStdIO(Subprocess::StdIOChannel::kStdErr, crosvm_logs); commands.emplace_back(std::move(crosvm_cmd.Cmd()), true); } return commands; } Result CrosvmManager::WaitForRestoreComplete(SharedFD stop_fd) const { auto instance = CF_EXPECT(CuttlefishConfig::Get())->ForDefaultInstance(); // Wait for the control socket to exist. It is created early in crosvm's // startup sequence, but the process may not even have been exec'd by CF at // this point. while (!FileExists(instance.CrosvmSocketPath())) { std::vector poll = {{.fd = stop_fd, .events = POLLIN}}; const int result = SharedFD::Poll(poll, 50 /* ms */); // Check for errors. CF_EXPECT(result >= 0, "failed to wait on stop_fd: " << strerror(errno)); // Check if pipe became readable or closed. if (result > 0) { return false; } } // Ask crosvm to resume the VM. crosvm promises to not complete this command // until the vCPUs are started (even if it was never suspended to begin // with). auto infop = CF_EXPECT(Execute( std::vector{ instance.crosvm_binary(), "resume", instance.CrosvmSocketPath(), "--full", }, SubprocessOptions(), WEXITED)); CF_EXPECT_EQ(infop.si_code, CLD_EXITED); CF_EXPECTF(infop.si_status == 0, "crosvm resume returns non zero code {}", infop.si_status); return true; } } // namespace vm_manager } // namespace cuttlefish