blob: 0b2cd993a0ad5db7bb81c62493941b5a803d2cd3 [file] [log] [blame]
/*
* 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 <signal.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <cassert>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/strings.h>
#include <json/json.h>
#include <vulkan/vulkan.h>
#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<std::unordered_map<std::string, std::string>>
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 properities lead to non-deterministic behavior while loading the
// HALs.
std::unordered_map<std::string, std::string> 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.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.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.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() == 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<std::unordered_map<std::string, std::string>>
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<std::string> 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<VhostUserDeviceCommands> 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_socket = SharedFD::SocketLocalServer(
gpu_device_socket_path.c_str(), false, SOCK_STREAM, 0777);
CF_EXPECT(gpu_device_socket->IsOpen(),
"Failed to create socket for crosvm vhost user gpu's control"
<< gpu_device_socket->StrError());
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([](Subprocess* proc) {
// Ask nicely so that log_tee gets a chance to process all the logs.
int rval = kill(proc->pid(), SIGINT);
if (rval != 0) {
LOG(ERROR) << "Failed to stop log_tee nicely, attempting to KILL";
return KillSubprocess(proc) == StopperResult::kStopSuccess
? StopperResult::kStopCrash
: StopperResult::kStopFailure;
}
return StopperResult::kStopSuccess;
});
const std::string crosvm_path = CF_EXPECT(CrosvmPathForVhostUserGpu(config));
Command gpu_device_cmd(crosvm_path);
gpu_device_cmd.AddParameter("device");
gpu_device_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.AddParameter("--wayland-sock=",
instance.frames_socket_path());
}
// Connect device to main crosvm:
gpu_device_cmd.AddParameter("--socket=", gpu_device_socket_path);
main_crosvm_cmd->AddParameter(
"--vhost-user=gpu,pci-address=", gpu_pci_address,
",socket=", gpu_device_socket_path);
gpu_device_cmd.AddParameter("--params");
gpu_device_cmd.AddParameter(ToSingleLineString(gpu_params_json));
MaybeConfigureVulkanIcd(config, &gpu_device_cmd);
gpu_device_cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdOut,
gpu_device_logs);
gpu_device_cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdErr,
gpu_device_logs);
return VhostUserDeviceCommands{
.device_cmd = std::move(gpu_device_cmd),
.device_logs_cmd = std::move(gpu_device_logs_cmd),
};
}
Result<void> 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_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;
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);
}
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<std::string>{
"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<std::vector<MonitorCommand>> CrosvmManager::StartCommands(
const CuttlefishConfig& config,
std::vector<VmmDependencyCommand*>& dependencyCommands) {
auto instance = config.ForDefaultInstance();
auto environment = config.ForDefaultEnvironment();
CrosvmBuilder crosvm_cmd;
crosvm_cmd.Cmd().AddPrerequisite([&dependencyCommands]() -> Result<void> {
for (auto dependencyCommand : dependencyCommands) {
CF_EXPECT(dependencyCommand->WaitForAvailability());
}
return {};
});
// Add "--restore_path=<guest snapshot directory>" 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<std::string> selectors{kGuestSnapshotField,
instance.id()};
const auto guest_snapshot_dir_suffix =
CF_EXPECT(GetValue<std::string>(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.
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<VhostUserDeviceCommands> 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(
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(
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("--rotary=",
instance.rotary_socket_path());
crosvm_cmd.Cmd().AddParameter("--keyboard=",
instance.keyboard_socket_path());
crosvm_cmd.Cmd().AddParameter("--switches=",
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 ordering of tap devices is important. Make sure any change here
// is reflected in ethprime u-boot variable
crosvm_cmd.AddTap(instance.mobile_tap_name(), instance.mobile_mac());
crosvm_cmd.AddTap(instance.ethernet_tap_name(), instance.ethernet_mac());
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([](Subprocess* proc) {
// Ask nicely so that log_tee gets a chance to process all the logs.
int rval = kill(proc->pid(), SIGINT);
if (rval != 0) {
LOG(ERROR) << "Failed to stop log_tee nicely, attempting to KILL";
return KillSubprocess(proc) == StopperResult::kStopSuccess
? StopperResult::kStopCrash
: StopperResult::kStopFailure;
}
return StopperResult::kStopSuccess;
});
// /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");
}
// This needs to be the last parameter
crosvm_cmd.Cmd().AddParameter("--bios=", instance.bootloader());
// 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.
std::vector<MonitorCommand> commands;
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);
}
if (vhost_user_gpu) {
commands.emplace_back(std::move(vhost_user_gpu->device_cmd));
commands.emplace_back(std::move(vhost_user_gpu->device_logs_cmd));
}
return commands;
}
} // namespace vm_manager
} // namespace cuttlefish