blob: b4360a552a88af0a16819b9aa344238be8d82c62 [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 <sys/stat.h>
#include <sys/types.h>
#include <cassert>
#include <string>
#include <vector>
#include <android-base/strings.h>
#include <android-base/logging.h>
#include <vulkan/vulkan.h>
#include "common/libs/utils/environment.h"
#include "common/libs/utils/network.h"
#include "common/libs/utils/subprocess.h"
#include "common/libs/utils/files.h"
#include "host/libs/config/cuttlefish_config.h"
#include "host/libs/config/known_paths.h"
#include "host/libs/vm_manager/qemu_manager.h"
namespace cuttlefish {
namespace vm_manager {
namespace {
std::string GetControlSocketPath(const CuttlefishConfig& config,
const std::string& socket_name) {
return config.ForDefaultInstance().PerInstanceInternalPath(
socket_name.c_str());
}
SharedFD AddTapFdParameter(Command* crosvm_cmd,
const std::string& tap_name) {
auto tap_fd = OpenTapInterface(tap_name);
if (tap_fd->IsOpen()) {
crosvm_cmd->AddParameter("--tap-fd=", tap_fd);
} else {
LOG(ERROR) << "Unable to connect to " << tap_name << ": "
<< tap_fd->StrError();
}
return tap_fd;
}
bool ReleaseDhcpLeases(const std::string& lease_path, SharedFD tap_fd) {
auto lease_file_fd = SharedFD::Open(lease_path, O_RDONLY);
if (!lease_file_fd->IsOpen()) {
LOG(ERROR) << "Could not open leases file \"" << lease_path << '"';
return false;
}
bool success = true;
auto dhcp_leases = ParseDnsmasqLeases(lease_file_fd);
for (auto& lease : dhcp_leases) {
std::uint8_t dhcp_server_ip[] = {192, 168, 96, (std::uint8_t) (ForCurrentInstance(1) * 4 - 3)};
if (!ReleaseDhcp4(tap_fd, lease.mac_address, lease.ip_address, dhcp_server_ip)) {
LOG(ERROR) << "Failed to release " << lease;
success = false;
} else {
LOG(INFO) << "Successfully dropped " << lease;
}
}
return success;
}
bool Stop(const std::string& socket_name) {
auto config = CuttlefishConfig::Get();
Command command(config->crosvm_binary());
command.AddParameter("stop");
command.AddParameter(GetControlSocketPath(*config, socket_name));
auto process = command.Start();
return process.Wait() == 0;
}
} // namespace
bool CrosvmManager::IsSupported() {
#ifdef __ANDROID__
return true;
#else
return HostSupportsQemuCli();
#endif
}
std::vector<std::string> CrosvmManager::ConfigureGpuMode(
const std::string& gpu_mode) {
// 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.
if (gpu_mode == kGpuModeGuestSwiftshader) {
return {
"androidboot.cpuvulkan.version=" + std::to_string(VK_API_VERSION_1_2),
"androidboot.hardware.gralloc=minigbm",
"androidboot.hardware.hwcomposer=ranchu",
"androidboot.hardware.egl=angle",
"androidboot.hardware.vulkan=pastel",
};
}
if (gpu_mode == kGpuModeDrmVirgl) {
return {
"androidboot.cpuvulkan.version=0",
"androidboot.hardware.gralloc=minigbm",
"androidboot.hardware.hwcomposer=drm_minigbm",
"androidboot.hardware.egl=mesa",
};
}
if (gpu_mode == kGpuModeGfxStream) {
return {
"androidboot.cpuvulkan.version=0",
"androidboot.hardware.gralloc=minigbm",
"androidboot.hardware.hwcomposer=ranchu",
"androidboot.hardware.egl=emulation",
"androidboot.hardware.vulkan=ranchu",
"androidboot.hardware.gltransport=virtio-gpu-asg",
};
}
return {};
}
std::string CrosvmManager::ConfigureBootDevices(int num_disks) {
// TODO There is no way to control this assignment with crosvm (yet)
if (HostArch() == Arch::X86_64) {
// crosvm has an additional PCI device for an ISA bridge
return ConfigureMultipleBootDevices("pci0000:00/0000:00:", 1, 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";
}
}
constexpr auto crosvm_socket = "crosvm_control.sock";
constexpr auto crosvm_for_ap_socket = "crosvm_for_ap_control.sock";
std::vector<Command> CrosvmManager::StartCommands(
const CuttlefishConfig& config) {
auto instance = config.ForDefaultInstance();
Command crosvm_cmd(config.crosvm_binary(), [](Subprocess* proc) {
auto stopped = Stop(crosvm_socket);
if (stopped) {
return StopperResult::kStopSuccess;
}
LOG(WARNING) << "Failed to stop VMM nicely, attempting to KILL";
return KillSubprocess(proc) == StopperResult::kStopSuccess
? StopperResult::kStopCrash
: StopperResult::kStopFailure;
});
bool use_ap_instance =
!config.ap_rootfs_image().empty() && !config.ap_kernel_image().empty();
Command ap_cmd(config.crosvm_binary(), [](Subprocess* proc) {
auto stopped = Stop(crosvm_for_ap_socket);
if (stopped) {
return StopperResult::kStopSuccess;
}
LOG(WARNING) << "Failed to stop VMM for AP nicely, attempting to KILL";
return KillSubprocess(proc) == StopperResult::kStopSuccess
? StopperResult::kStopCrash
: StopperResult::kStopFailure;
});
int hvc_num = 0;
int serial_num = 0;
auto add_hvc_sink = [&crosvm_cmd, &hvc_num]() {
crosvm_cmd.AddParameter("--serial=hardware=virtio-console,num=", ++hvc_num,
",type=sink");
};
auto add_serial_sink = [&crosvm_cmd, &serial_num]() {
crosvm_cmd.AddParameter("--serial=hardware=serial,num=", ++serial_num,
",type=sink");
};
auto add_hvc_console = [&crosvm_cmd, &hvc_num](const std::string& output) {
crosvm_cmd.AddParameter("--serial=hardware=virtio-console,num=", ++hvc_num,
",type=file,path=", output, ",console=true");
};
auto add_serial_console_ro = [&crosvm_cmd,
&serial_num](const std::string& output) {
crosvm_cmd.AddParameter("--serial=hardware=serial,num=", ++serial_num,
",type=file,path=", output, ",earlycon=true");
};
auto add_serial_console = [&crosvm_cmd, &serial_num](
const std::string& output,
const std::string& input) {
crosvm_cmd.AddParameter("--serial=hardware=serial,num=", ++serial_num,
",type=file,path=", output, ",input=", input,
",earlycon=true");
};
auto add_hvc_ro = [&crosvm_cmd, &hvc_num](const std::string& output) {
crosvm_cmd.AddParameter("--serial=hardware=virtio-console,num=", ++hvc_num,
",type=file,path=", output);
};
auto add_hvc = [&crosvm_cmd, &hvc_num](const std::string& output,
const std::string& input) {
crosvm_cmd.AddParameter("--serial=hardware=virtio-console,num=", ++hvc_num,
",type=file,path=", output, ",input=", input);
};
crosvm_cmd.AddParameter("run");
ap_cmd.AddParameter("run");
if (!config.smt()) {
crosvm_cmd.AddParameter("--no-smt");
}
if (config.vhost_net()) {
crosvm_cmd.AddParameter("--vhost-net");
}
if (!config.vhost_user_mac80211_hwsim().empty()) {
crosvm_cmd.AddParameter("--vhost-user-mac80211-hwsim=",
config.vhost_user_mac80211_hwsim());
ap_cmd.AddParameter("--vhost-user-mac80211-hwsim=",
config.vhost_user_mac80211_hwsim());
}
if (config.protected_vm()) {
crosvm_cmd.AddParameter("--protected-vm");
}
if (config.gdb_port() > 0) {
CHECK(config.cpus() == 1) << "CPUs must be 1 for crosvm gdb mode";
crosvm_cmd.AddParameter("--gdb=", config.gdb_port());
}
auto gpu_mode = config.gpu_mode();
if (gpu_mode == kGpuModeGuestSwiftshader) {
crosvm_cmd.AddParameter("--gpu=2D");
} else if (gpu_mode == kGpuModeDrmVirgl || gpu_mode == kGpuModeGfxStream) {
crosvm_cmd.AddParameter(gpu_mode == kGpuModeGfxStream ?
"--gpu=gfxstream," : "--gpu=",
"egl=true,surfaceless=true,glx=false,gles=true");
}
for (const auto& display_config : config.display_configs()) {
crosvm_cmd.AddParameter("--gpu-display=", "width=", display_config.width,
",", "height=", display_config.height);
}
crosvm_cmd.AddParameter("--wayland-sock=", instance.frames_socket_path());
// crosvm_cmd.AddParameter("--null-audio");
crosvm_cmd.AddParameter("--mem=", config.memory_mb());
crosvm_cmd.AddParameter("--cpus=", config.cpus());
auto disk_num = instance.virtual_disk_paths().size();
CHECK_GE(VmManager::kMaxDisks, disk_num)
<< "Provided too many disks (" << disk_num << "), maximum "
<< VmManager::kMaxDisks << "supported";
for (const auto& disk : instance.virtual_disk_paths()) {
crosvm_cmd.AddParameter(config.protected_vm() ? "--disk=" :
"--rwdisk=", disk);
}
crosvm_cmd.AddParameter("--socket=",
GetControlSocketPath(config, crosvm_socket));
ap_cmd.AddParameter("--socket=",
GetControlSocketPath(config, crosvm_for_ap_socket));
if (config.enable_vnc_server() || config.enable_webrtc()) {
auto touch_type_parameter =
config.enable_webrtc() ? "--multi-touch=" : "--single-touch=";
auto display_configs = config.display_configs();
CHECK_GE(display_configs.size(), 1);
for (int i = 0; i < display_configs.size(); ++i) {
auto display_config = display_configs[i];
crosvm_cmd.AddParameter(touch_type_parameter,
instance.touch_socket_path(i), ":",
display_config.width, ":", display_config.height);
}
crosvm_cmd.AddParameter("--keyboard=", instance.keyboard_socket_path());
}
if (config.enable_webrtc()) {
crosvm_cmd.AddParameter("--switches=", instance.switches_socket_path());
}
AddTapFdParameter(&crosvm_cmd, instance.mobile_tap_name());
AddTapFdParameter(&crosvm_cmd, instance.ethernet_tap_name());
SharedFD wifi_tap;
if (config.vhost_user_mac80211_hwsim().empty()) {
wifi_tap = AddTapFdParameter(&crosvm_cmd, instance.wifi_tap_name());
} else if (use_ap_instance) {
wifi_tap = AddTapFdParameter(&ap_cmd, instance.wifi_tap_name());
}
if (FileExists(instance.access_kregistry_path())) {
crosvm_cmd.AddParameter("--rw-pmem-device=",
instance.access_kregistry_path());
}
if (FileExists(instance.pstore_path())) {
crosvm_cmd.AddParameter("--pstore=path=", instance.pstore_path(),
",size=", FileSize(instance.pstore_path()));
}
if (config.enable_sandbox()) {
const bool seccomp_exists = DirectoryExists(config.seccomp_policy_dir());
const std::string& var_empty_dir = kCrosvmVarEmptyDir;
const bool var_empty_available = DirectoryExists(var_empty_dir);
if (!var_empty_available || !seccomp_exists) {
LOG(FATAL) << var_empty_dir << " is not an existing, empty directory."
<< "seccomp-policy-dir, " << config.seccomp_policy_dir()
<< " does not exist " << std::endl;
return {};
}
crosvm_cmd.AddParameter("--seccomp-policy-dir=", config.seccomp_policy_dir());
ap_cmd.AddParameter("--seccomp-policy-dir=", config.seccomp_policy_dir());
} else {
crosvm_cmd.AddParameter("--disable-sandbox");
ap_cmd.AddParameter("--disable-sandbox");
}
if (instance.vsock_guest_cid() >= 2) {
crosvm_cmd.AddParameter("--cid=", instance.vsock_guest_cid());
}
// Use a virtio-console instance for the main kernel console. All
// messages will switch from earlycon to virtio-console after the driver
// is loaded, and crosvm will append to the kernel log automatically
add_hvc_console(instance.kernel_log_pipe_name());
if (config.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 (config.kgdb() || config.use_bootloader()) {
add_serial_console(instance.console_out_pipe_name(),
instance.console_in_pipe_name());
// 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
add_hvc_sink();
} else {
add_serial_sink();
add_hvc(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 (config.kgdb() || config.use_bootloader()) {
add_serial_console_ro(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
add_hvc_sink();
}
SharedFD log_out_rd, log_out_wr;
if (!SharedFD::Pipe(&log_out_rd, &log_out_wr)) {
LOG(ERROR) << "Failed to create log pipe for crosvm's stdout/stderr: "
<< log_out_rd->StrError();
return {};
}
crosvm_cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdOut, log_out_wr);
crosvm_cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdErr, log_out_wr);
Command log_tee_cmd(HostBinaryPath("log_tee"));
log_tee_cmd.AddParameter("--process_name=crosvm");
log_tee_cmd.AddParameter("--log_fd_in=", log_out_rd);
// Serial port for logcat, redirected to a pipe
add_hvc_ro(instance.logcat_pipe_name());
add_hvc(instance.PerInstanceInternalPath("keymaster_fifo_vm.out"),
instance.PerInstanceInternalPath("keymaster_fifo_vm.in"));
add_hvc(instance.PerInstanceInternalPath("gatekeeper_fifo_vm.out"),
instance.PerInstanceInternalPath("gatekeeper_fifo_vm.in"));
if (config.enable_host_bluetooth()) {
add_hvc(instance.PerInstanceInternalPath("bt_fifo_vm.out"),
instance.PerInstanceInternalPath("bt_fifo_vm.in"));
} else {
add_hvc_sink();
}
if (config.enable_gnss_grpc_proxy()) {
add_hvc(instance.PerInstanceInternalPath("gnsshvc_fifo_vm.out"),
instance.PerInstanceInternalPath("gnsshvc_fifo_vm.in"));
} else {
add_hvc_sink();
}
for (auto i = 0; i < VmManager::kMaxDisks - disk_num; i++) {
add_hvc_sink();
}
CHECK(hvc_num + disk_num == VmManager::kMaxDisks + VmManager::kDefaultNumHvcs)
<< "HVC count (" << hvc_num << ") + disk count (" << disk_num << ") "
<< "is not the expected total of "
<< VmManager::kMaxDisks + VmManager::kDefaultNumHvcs << " devices";
if (config.enable_audio()) {
crosvm_cmd.AddParameter("--sound=",
config.ForDefaultInstance().audio_server_path());
}
// TODO(b/162071003): virtiofs crashes without sandboxing, this should be fixed
if (config.enable_sandbox()) {
// Set up directory shared with virtiofs
crosvm_cmd.AddParameter("--shared-dir=", instance.PerInstancePath(kSharedDirName),
":shared:type=fs");
}
// This needs to be the last parameter
crosvm_cmd.AddParameter("--bios=", config.bootloader());
// Only run the leases workaround if we are not using the new network
// bridge architecture - in that case, we have a wider DHCP address
// space and stale leases should be much less of an issue
if (!FileExists("/var/run/cuttlefish-dnsmasq-cvd-wbr.leases") &&
(config.vhost_user_mac80211_hwsim().empty() || use_ap_instance)) {
// TODO(schuffelen): QEMU also needs this and this is not the best place for
// this code. Find a better place to put it.
auto lease_file =
ForCurrentInstance("/var/run/cuttlefish-dnsmasq-cvd-wbr-") + ".leases";
if (!ReleaseDhcpLeases(lease_file, wifi_tap)) {
LOG(ERROR) << "Failed to release wifi DHCP leases. Connecting to the wifi "
<< "network may not work.";
}
}
ap_cmd.AddParameter("--root=", config.ap_rootfs_image());
ap_cmd.AddParameter(config.ap_kernel_image());
std::vector<Command> ret;
ret.push_back(std::move(crosvm_cmd));
if (use_ap_instance) {
ret.push_back(std::move(ap_cmd));
}
ret.push_back(std::move(log_tee_cmd));
return ret;
}
} // namespace vm_manager
} // namespace cuttlefish