blob: 83a957cf9ef8073b285f74b57f89034a90b99fb4 [file] [log] [blame] [edit]
/*
* Copyright (C) 2018 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/qemu_manager.h"
#include <string.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/un.h>
#include <sys/wait.h>
#include <unistd.h>
#include <cstdlib>
#include <iomanip>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
#include <android-base/strings.h>
#include <android-base/logging.h>
#include <vulkan/vulkan.h>
#include "common/libs/utils/files.h"
#include "common/libs/utils/result.h"
#include "common/libs/utils/subprocess.h"
#include "host/libs/config/command_source.h"
#include "host/libs/config/cuttlefish_config.h"
#include "host/libs/vm_manager/vhost_user.h"
namespace cuttlefish {
namespace vm_manager {
namespace {
std::string GetMonitorPath(const CuttlefishConfig& config) {
return config.ForDefaultInstance().PerInstanceInternalUdsPath(
"qemu_monitor.sock");
}
StopperResult Stop() {
auto config = CuttlefishConfig::Get();
auto monitor_path = GetMonitorPath(*config);
auto monitor_sock = SharedFD::SocketLocalClient(
monitor_path.c_str(), false, SOCK_STREAM);
if (!monitor_sock->IsOpen()) {
LOG(ERROR) << "The connection to qemu is closed, is it still running?";
return StopperResult::kStopFailure;
}
char msg[] = "{\"execute\":\"qmp_capabilities\"}{\"execute\":\"quit\"}";
ssize_t len = sizeof(msg) - 1;
while (len > 0) {
int tmp = monitor_sock->Write(msg, len);
if (tmp < 0) {
LOG(ERROR) << "Error writing to socket: " << monitor_sock->StrError();
return StopperResult::kStopFailure;
}
len -= tmp;
}
// Log the reply
char buff[1000];
while ((len = monitor_sock->Read(buff, sizeof(buff) - 1)) > 0) {
buff[len] = '\0';
LOG(INFO) << "From qemu monitor: " << buff;
}
return StopperResult::kStopSuccess;
}
Result<std::pair<int, int>> GetQemuVersion(const std::string& qemu_binary) {
Command qemu_version_cmd(qemu_binary);
qemu_version_cmd.AddParameter("-version");
std::string qemu_version_input, qemu_version_output, qemu_version_error;
cuttlefish::SubprocessOptions options;
options.Verbose(false);
int qemu_version_ret = cuttlefish::RunWithManagedStdio(
std::move(qemu_version_cmd), &qemu_version_input, &qemu_version_output,
&qemu_version_error, std::move(options));
CF_EXPECT(qemu_version_ret == 0,
qemu_binary << " -version returned unexpected response "
<< qemu_version_output << ". Stderr was "
<< qemu_version_error);
// Snip around the extra text we don't care about
qemu_version_output.erase(0, std::string("QEMU emulator version ").length());
auto space_pos = qemu_version_output.find(" ", 0);
if (space_pos != std::string::npos) {
qemu_version_output.resize(space_pos);
}
auto qemu_version_bits = android::base::Split(qemu_version_output, ".");
return {{std::stoi(qemu_version_bits[0]), std::stoi(qemu_version_bits[1])}};
}
} // namespace
QemuManager::QemuManager(Arch arch) : arch_(arch) {}
bool QemuManager::IsSupported() {
return HostSupportsQemuCli();
}
Result<std::unordered_map<std::string, std::string>>
QemuManager::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<std::string, std::string> bootconfig_args;
auto gpu_mode = instance.gpu_mode();
if (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"},
// OpenGL ES 3.1
{"androidboot.opengles.version", "196609"},
};
} else if (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
// OpenGL ES 3.0
{"androidboot.opengles.version", "196608"},
};
} else if (gpu_mode == kGpuModeGfxstream ||
gpu_mode == kGpuModeGfxstreamGuestAngle ||
gpu_mode == kGpuModeGfxstreamGuestAngleHostSwiftShader) {
const bool uses_angle =
gpu_mode == kGpuModeGfxstreamGuestAngle ||
gpu_mode == kGpuModeGfxstreamGuestAngleHostSwiftShader;
const std::string gles_impl = uses_angle ? "angle" : "emulation";
const std::string gltransport =
(instance.guest_android_version() == "11.0.0") ? "virtio-gpu-pipe"
: "virtio-gpu-asg";
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", gltransport},
{"androidboot.opengles.version", "196609"}, // OpenGL ES 3.1
};
} else if (instance.gpu_mode() == kGpuModeNone) {
return {};
} else {
return CF_ERR("Unhandled 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>>
QemuManager::ConfigureBootDevices(
const CuttlefishConfig::InstanceSpecific& instance) {
const int num_disks = instance.virtual_disk_paths().size();
const int num_gpu = instance.hwcomposer() != kHwComposerNone;
switch (arch_) {
case Arch::Arm:
return {{{"androidboot.boot_devices", "3f000000.pcie"}}};
case Arch::Arm64:
#ifdef __APPLE__
return {{{"androidboot.boot_devices", "3f000000.pcie"}}};
#else
return {{{"androidboot.boot_devices", "4010000000.pcie"}}};
#endif
case Arch::RiscV64:
return {{{"androidboot.boot_devices", "soc/30000000.pci"}}};
case Arch::X86:
case Arch::X86_64: {
// QEMU has additional PCI devices for an ISA bridge and PIIX4
// virtio_gpu precedes the first console or disk
// TODO(schuffelen): Simplify this logic when crosvm uses multiport
int pci_offset = 3 + num_gpu - VmManager::kDefaultNumHvcs;
return ConfigureMultipleBootDevices("pci0000:00/0000:00:", pci_offset,
num_disks);
}
}
}
Result<std::vector<MonitorCommand>> QemuManager::StartCommands(
const CuttlefishConfig& config,
std::vector<VmmDependencyCommand*>& dependency_commands) {
std::vector<MonitorCommand> commands;
auto instance = config.ForDefaultInstance();
std::string qemu_binary = instance.qemu_binary_dir();
switch (arch_) {
case Arch::Arm:
qemu_binary += "/qemu-system-arm";
break;
case Arch::Arm64:
qemu_binary += "/qemu-system-aarch64";
break;
case Arch::RiscV64:
qemu_binary += "/qemu-system-riscv64";
break;
case Arch::X86:
qemu_binary += "/qemu-system-i386";
break;
case Arch::X86_64:
qemu_binary += "/qemu-system-x86_64";
break;
}
auto qemu_version = CF_EXPECT(GetQemuVersion(qemu_binary));
Command qemu_cmd(qemu_binary, KillSubprocessFallback(Stop));
qemu_cmd.AddPrerequisite([&dependency_commands]() -> Result<void> {
for (auto dependencyCommand : dependency_commands) {
CF_EXPECT(dependencyCommand->WaitForAvailability());
}
return {};
});
int hvc_num = 0;
int serial_num = 0;
auto add_hvc_sink = [&qemu_cmd, &hvc_num]() {
qemu_cmd.AddParameter("-chardev");
qemu_cmd.AddParameter("null,id=hvc", hvc_num);
qemu_cmd.AddParameter("-device");
qemu_cmd.AddParameter("virtconsole,bus=virtio-serial.0,chardev=hvc",
hvc_num);
hvc_num++;
};
auto add_serial_sink = [&qemu_cmd, &serial_num]() {
qemu_cmd.AddParameter("-chardev");
qemu_cmd.AddParameter("null,id=serial", serial_num);
qemu_cmd.AddParameter("-serial");
qemu_cmd.AddParameter("chardev:serial", serial_num);
serial_num++;
};
auto add_serial_console_ro = [&qemu_cmd,
&serial_num](const std::string& output) {
qemu_cmd.AddParameter("-chardev");
qemu_cmd.AddParameter("file,id=serial", serial_num, ",path=", output,
",append=on");
qemu_cmd.AddParameter("-serial");
qemu_cmd.AddParameter("chardev:serial", serial_num);
serial_num++;
};
auto add_serial_console = [&qemu_cmd,
&serial_num](const std::string& prefix) {
qemu_cmd.AddParameter("-chardev");
qemu_cmd.AddParameter("pipe,id=serial", serial_num, ",path=", prefix);
qemu_cmd.AddParameter("-serial");
qemu_cmd.AddParameter("chardev:serial", serial_num);
serial_num++;
};
auto add_hvc_ro = [&qemu_cmd, &hvc_num](const std::string& output) {
qemu_cmd.AddParameter("-chardev");
qemu_cmd.AddParameter("file,id=hvc", hvc_num, ",path=", output,
",append=on");
qemu_cmd.AddParameter("-device");
qemu_cmd.AddParameter("virtconsole,bus=virtio-serial.0,chardev=hvc",
hvc_num);
hvc_num++;
};
auto add_hvc = [&qemu_cmd, &hvc_num](const std::string& prefix) {
qemu_cmd.AddParameter("-chardev");
qemu_cmd.AddParameter("pipe,id=hvc", hvc_num, ",path=", prefix);
qemu_cmd.AddParameter("-device");
qemu_cmd.AddParameter("virtconsole,bus=virtio-serial.0,chardev=hvc",
hvc_num);
hvc_num++;
};
auto add_hvc_serial = [&qemu_cmd, &hvc_num](const std::string& prefix) {
qemu_cmd.AddParameter("-chardev");
qemu_cmd.AddParameter("serial,id=hvc", hvc_num, ",path=", prefix);
qemu_cmd.AddParameter("-device");
qemu_cmd.AddParameter("virtconsole,bus=virtio-serial.0,chardev=hvc",
hvc_num);
hvc_num++;
};
bool is_arm = arch_ == Arch::Arm || arch_ == Arch::Arm64;
bool is_x86 = arch_ == Arch::X86 || arch_ == Arch::X86_64;
bool is_riscv64 = arch_ == Arch::RiscV64;
auto access_kregistry_size_bytes = 0;
if (FileExists(instance.access_kregistry_path())) {
access_kregistry_size_bytes = FileSize(instance.access_kregistry_path());
CF_EXPECT((access_kregistry_size_bytes & (1024 * 1024 - 1)) == 0,
instance.access_kregistry_path()
<< " file size (" << access_kregistry_size_bytes
<< ") not a multiple of 1MB");
}
auto hwcomposer_pmem_size_bytes = 0;
if (instance.hwcomposer() != kHwComposerNone) {
if (FileExists(instance.hwcomposer_pmem_path())) {
hwcomposer_pmem_size_bytes = FileSize(instance.hwcomposer_pmem_path());
CF_EXPECT((hwcomposer_pmem_size_bytes & (1024 * 1024 - 1)) == 0,
instance.hwcomposer_pmem_path()
<< " file size (" << hwcomposer_pmem_size_bytes
<< ") not a multiple of 1MB");
}
}
auto pstore_size_bytes = 0;
if (FileExists(instance.pstore_path())) {
pstore_size_bytes = FileSize(instance.pstore_path());
CF_EXPECT((pstore_size_bytes & (1024 * 1024 - 1)) == 0,
instance.pstore_path() << " file size (" << pstore_size_bytes
<< ") not a multiple of 1MB");
}
qemu_cmd.AddParameter("-name");
qemu_cmd.AddParameter("guest=", instance.instance_name(), ",debug-threads=on");
qemu_cmd.AddParameter("-machine");
std::string machine = is_x86 ? "pc,nvdimm=on" : "virt";
if (IsHostCompatible(arch_)) {
#ifdef __linux__
machine += ",accel=kvm";
#elif defined(__APPLE__)
machine += ",accel=hvf";
#else
#error "Unknown OS"
#endif
if (is_arm) {
machine += ",gic-version=3";
}
} else if (is_arm) {
// QEMU doesn't support GICv3 with TCG yet
machine += ",gic-version=2";
CF_EXPECT(instance.cpus() <= 8, "CPUs must be no more than 8 with GICv2");
}
if (instance.mte()) {
machine += ",mte=on";
}
qemu_cmd.AddParameter(machine, ",usb=off,dump-guest-core=off");
qemu_cmd.AddParameter("-m");
auto maxmem = instance.memory_mb() +
(access_kregistry_size_bytes / 1024 / 1024) +
(hwcomposer_pmem_size_bytes / 1024 / 1024) +
(is_x86 ? pstore_size_bytes / 1024 / 1024 : 0);
auto slots = is_x86 ? ",slots=2" : "";
qemu_cmd.AddParameter("size=", instance.memory_mb(), "M",
",maxmem=", maxmem, "M", slots);
qemu_cmd.AddParameter("-overcommit");
qemu_cmd.AddParameter("mem-lock=off");
// Assume SMT is always 2 threads per core, which is how most hardware
// today is configured, and the way crosvm does it
qemu_cmd.AddParameter("-smp");
if (instance.smt()) {
CF_EXPECT(instance.cpus() % 2 == 0,
"CPUs must be a multiple of 2 in SMT mode");
qemu_cmd.AddParameter(instance.cpus(), ",cores=",
instance.cpus() / 2, ",threads=2");
} else {
qemu_cmd.AddParameter(instance.cpus(), ",cores=",
instance.cpus(), ",threads=1");
}
qemu_cmd.AddParameter("-uuid");
qemu_cmd.AddParameter(instance.uuid());
qemu_cmd.AddParameter("-no-user-config");
qemu_cmd.AddParameter("-nodefaults");
qemu_cmd.AddParameter("-no-shutdown");
qemu_cmd.AddParameter("-rtc");
qemu_cmd.AddParameter("base=utc");
qemu_cmd.AddParameter("-boot");
qemu_cmd.AddParameter("strict=on");
qemu_cmd.AddParameter("-chardev");
qemu_cmd.AddParameter("socket,id=charmonitor,path=", GetMonitorPath(config),
",server=on,wait=off");
qemu_cmd.AddParameter("-mon");
qemu_cmd.AddParameter("chardev=charmonitor,id=monitor,mode=control");
auto gpu_mode = instance.gpu_mode();
if (gpu_mode == kGpuModeDrmVirgl) {
qemu_cmd.AddParameter("-display");
qemu_cmd.AddParameter("egl-headless");
qemu_cmd.AddParameter("-vnc");
qemu_cmd.AddParameter("127.0.0.1:", instance.qemu_vnc_server_port());
} else if (gpu_mode == kGpuModeGuestSwiftshader ||
gpu_mode == kGpuModeGfxstream ||
gpu_mode == kGpuModeGfxstreamGuestAngle ||
gpu_mode == kGpuModeGfxstreamGuestAngleHostSwiftShader) {
qemu_cmd.AddParameter("-vnc");
qemu_cmd.AddParameter("127.0.0.1:", instance.qemu_vnc_server_port());
} else {
qemu_cmd.AddParameter("-display");
qemu_cmd.AddParameter("none");
}
if (instance.hwcomposer() != kHwComposerNone) {
auto display_configs = instance.display_configs();
CF_EXPECT(display_configs.size() >= 1);
auto display_config = display_configs[0];
qemu_cmd.AddParameter("-device");
std::string gpu_device;
if (gpu_mode == kGpuModeGuestSwiftshader || qemu_version.first < 6) {
gpu_device = "virtio-gpu-pci";
} else if (gpu_mode == kGpuModeDrmVirgl) {
gpu_device = "virtio-gpu-gl-pci";
} else if (gpu_mode == kGpuModeGfxstream) {
gpu_device =
"virtio-gpu-rutabaga,x-gfxstream-gles=on,gfxstream-vulkan=on,"
"x-gfxstream-composer=on,hostmem=256M";
} else if (gpu_mode == kGpuModeGfxstreamGuestAngle ||
gpu_mode == kGpuModeGfxstreamGuestAngleHostSwiftShader) {
gpu_device =
"virtio-gpu-rutabaga,gfxstream-vulkan=on,"
"x-gfxstream-composer=on,hostmem=256M";
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");
qemu_cmd.AddEnvironmentVariable("VK_DRIVER_FILES",
swiftshader_icd_json);
qemu_cmd.AddEnvironmentVariable("VK_ICD_FILENAMES",
swiftshader_icd_json);
}
}
qemu_cmd.AddParameter(
gpu_device, ",id=gpu0",
fmt::format(",addr={:0>2x}.0", VmManager::kGpuPciSlotNum),
",xres=", display_config.width, ",yres=", display_config.height);
}
if (!instance.console()) {
// In kgdb mode, earlycon is an interactive console, and so early
// dmesg will go there instead of the kernel.log. On QEMU, we do this
// bit of logic up before the hvc console is set up, so the command line
// flags appear in the right order and "append=on" does the right thing
if (instance.enable_kernel_log() &&
(instance.kgdb() || instance.use_bootloader())) {
add_serial_console_ro(instance.kernel_log_pipe_name());
}
}
qemu_cmd.AddParameter("-device");
qemu_cmd.AddParameter(
"virtio-serial-pci-non-transitional,max_ports=31,id=virtio-serial");
// /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 above on a legacy serial port, it will control
// the main log until the virtio-console takes over.
// (Note that QEMU does not automatically generate console= parameters for
// the bootloader/kernel cmdline, so the control of whether this pipe is
// actually managed by the kernel as a console is handled elsewhere.)
add_hvc_ro(instance.kernel_log_pipe_name());
// /dev/hvc1 = serial console
if (instance.console()) {
if (instance.kgdb() || instance.use_bootloader()) {
add_serial_console(instance.console_pipe_prefix());
// 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_pipe_prefix());
}
} else {
if (instance.kgdb() || instance.use_bootloader()) {
// The add_serial_console_ro() call above was applied by the time we reach
// this code, so we don't need another add_serial_*() call
}
// 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();
}
// /dev/hvc2 = serial logging
// Serial port for logcat, redirected to a pipe
add_hvc_ro(instance.logcat_pipe_name());
// /dev/hvc3 = keymaster (C++ implementation)
add_hvc(instance.PerInstanceInternalPath("keymaster_fifo_vm"));
// /dev/hvc4 = gatekeeper
add_hvc(instance.PerInstanceInternalPath("gatekeeper_fifo_vm"));
// /dev/hvc5 = bt
if (config.enable_host_bluetooth()) {
add_hvc(instance.PerInstanceInternalPath("bt_fifo_vm"));
} else {
add_hvc_sink();
}
// /dev/hvc6 = gnss
// /dev/hvc7 = location
if (instance.enable_gnss_grpc_proxy()) {
add_hvc(instance.PerInstanceInternalPath("gnsshvc_fifo_vm"));
add_hvc(instance.PerInstanceInternalPath("locationhvc_fifo_vm"));
} else {
for (auto i = 0; i < 2; i++) {
add_hvc_sink();
}
}
/* Added one for confirmation UI.
*
* b/237452165
*
* Confirmation UI is not supported with QEMU for now. In order
* to not conflict with confirmation UI-related configurations used
* w/ Crosvm, we should add one generic avc.
*
* confui_fifo_vm.{in/out} are created along with the streamer process,
* which is not created w/ QEMU.
*/
// /dev/hvc8 = confirmationui
add_hvc_sink();
// /dev/hvc9 = uwb
if (config.enable_host_uwb()) {
add_hvc(instance.PerInstanceInternalPath("uwb_fifo_vm"));
} else {
add_hvc_sink();
}
// /dev/hvc10 = oemlock
add_hvc(instance.PerInstanceInternalPath("oemlock_fifo_vm"));
// /dev/hvc11 = keymint (Rust implementation)
add_hvc(instance.PerInstanceInternalPath("keymint_fifo_vm"));
// /dev/hvc12 = nfc
if (config.enable_host_nfc()) {
add_hvc(instance.PerInstanceInternalPath("nfc_fifo_vm"));
} else {
add_hvc_sink();
}
// sensors_fifo_vm.{in/out} are created along with the streamer process,
// which is not created w/ QEMU.
// /dev/hvc13 = sensors
add_hvc_sink();
// /dev/hvc14 = MCU CONTROL
if (instance.mcu()["control"]["type"].asString() == "serial") {
auto path = instance.PerInstanceInternalPath("mcu");
path += "/" + instance.mcu()["control"]["path"].asString();
add_hvc_serial(path);
} else {
add_hvc_sink();
}
// /dev/hvc15 = MCU UART
if (instance.mcu()["uart0"]["type"].asString() == "serial") {
auto path = instance.PerInstanceInternalPath("mcu");
path += "/" + instance.mcu()["uart0"]["path"].asString();
add_hvc_serial(path);
} else {
add_hvc_sink();
}
auto disk_num = instance.virtual_disk_paths().size();
for (auto i = 0; i < VmManager::kMaxDisks - disk_num; i++) {
add_hvc_sink();
}
CF_EXPECT(
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");
CF_EXPECT(VmManager::kMaxDisks >= disk_num,
"Provided too many disks (" << disk_num << "), maximum "
<< VmManager::kMaxDisks << "supported");
auto readonly = instance.protected_vm() ? ",readonly" : "";
size_t i = 0;
for (const auto& disk : instance.virtual_disk_paths()) {
if (instance.vhost_user_block()) {
auto block = CF_EXPECT(VhostUserBlockDevice(config, i, disk));
commands.emplace_back(std::move(block.device_cmd));
commands.emplace_back(std::move(block.device_logs_cmd));
auto socket_path = std::move(block.socket_path);
qemu_cmd.AddPrerequisite([socket_path]() -> Result<void> {
#ifdef __linux__
return WaitForUnixSocketListeningWithoutConnect(socket_path,
/*timeoutSec=*/30);
#else
return CF_ERR("Unhandled check if vhost user block ready.");
#endif
});
qemu_cmd.AddParameter("-chardev");
qemu_cmd.AddParameter("socket,id=vhost-user-block-", i,
",path=", socket_path);
qemu_cmd.AddParameter("-device");
qemu_cmd.AddParameter(
"vhost-user-blk-pci-non-transitional,chardev=vhost-user-block-", i);
} else {
qemu_cmd.AddParameter("-drive");
qemu_cmd.AddParameter("file=", disk, ",if=none,id=drive-virtio-disk", i,
",aio=threads", readonly);
qemu_cmd.AddParameter("-device");
qemu_cmd.AddParameter(
#ifdef __APPLE__
"virtio-blk-pci-non-transitional,drive=drive-virtio-disk", i,
#else
"virtio-blk-pci-non-transitional,scsi=off,drive=drive-virtio-disk", i,
#endif
",id=virtio-disk", i, (i == 0 ? ",bootindex=1" : ""));
}
++i;
}
if (is_x86 && FileExists(instance.pstore_path())) {
// QEMU will assign the NVDIMM (ramoops pstore region) 150000000-1501fffff
// As we will pass this to ramoops, define this region first so it is always
// located at this address. This is currently x86 only.
qemu_cmd.AddParameter("-object");
qemu_cmd.AddParameter("memory-backend-file,id=objpmem0,share=on,mem-path=",
instance.pstore_path(), ",size=", pstore_size_bytes);
qemu_cmd.AddParameter("-device");
qemu_cmd.AddParameter("nvdimm,memdev=objpmem0,id=ramoops");
}
// QEMU does not implement virtio-pmem-pci for ARM64 or RISC-V yet; restore
// this when the device has been added
if (is_x86) {
if (access_kregistry_size_bytes > 0) {
qemu_cmd.AddParameter("-object");
qemu_cmd.AddParameter(
"memory-backend-file,id=objpmem1,share=on,mem-path=",
instance.access_kregistry_path(),
",size=", access_kregistry_size_bytes);
qemu_cmd.AddParameter("-device");
qemu_cmd.AddParameter(
"virtio-pmem-pci,disable-legacy=on,memdev=objpmem1,id=pmem0");
}
if (hwcomposer_pmem_size_bytes > 0) {
qemu_cmd.AddParameter("-object");
qemu_cmd.AddParameter(
"memory-backend-file,id=objpmem2,share=on,mem-path=",
instance.hwcomposer_pmem_path(),
",size=", hwcomposer_pmem_size_bytes);
qemu_cmd.AddParameter("-device");
qemu_cmd.AddParameter(
"virtio-pmem-pci,disable-legacy=on,memdev=objpmem2,id=pmem1");
}
}
qemu_cmd.AddParameter("-object");
qemu_cmd.AddParameter("rng-random,id=objrng0,filename=/dev/urandom");
qemu_cmd.AddParameter("-device");
qemu_cmd.AddParameter("virtio-rng-pci-non-transitional,rng=objrng0,id=rng0,",
"max-bytes=1024,period=2000");
qemu_cmd.AddParameter("-device");
qemu_cmd.AddParameter("virtio-mouse-pci,disable-legacy=on");
qemu_cmd.AddParameter("-device");
qemu_cmd.AddParameter("virtio-keyboard-pci,disable-legacy=on");
// device padding for unsupported "switches" input
qemu_cmd.AddParameter("-device");
qemu_cmd.AddParameter("virtio-keyboard-pci,disable-legacy=on");
auto vhost_net = instance.vhost_net() ? ",vhost=on" : "";
qemu_cmd.AddParameter("-device");
qemu_cmd.AddParameter("virtio-balloon-pci-non-transitional,id=balloon0");
switch (instance.external_network_mode()) {
case ExternalNetworkMode::kTap:
qemu_cmd.AddParameter("-netdev");
qemu_cmd.AddParameter(
"tap,id=hostnet0,ifname=", instance.mobile_tap_name(),
",script=no,downscript=no", vhost_net);
qemu_cmd.AddParameter("-netdev");
qemu_cmd.AddParameter(
"tap,id=hostnet1,ifname=", instance.ethernet_tap_name(),
",script=no,downscript=no", vhost_net);
if (!config.virtio_mac80211_hwsim()) {
qemu_cmd.AddParameter("-netdev");
qemu_cmd.AddParameter(
"tap,id=hostnet2,ifname=", instance.wifi_tap_name(),
",script=no,downscript=no", vhost_net);
}
break;
case cuttlefish::ExternalNetworkMode::kSlirp: {
const std::string net =
fmt::format("{}/{}", instance.ril_ipaddr(), instance.ril_prefixlen());
const std::string& host = instance.ril_gateway();
qemu_cmd.AddParameter("-netdev");
// TODO(schuffelen): `dns` needs to match the first `nameserver` in
// `/etc/resolv.conf`. Implement something that generalizes beyond gLinux.
qemu_cmd.AddParameter("user,id=hostnet0,net=", net, ",host=", host,
",dns=127.0.0.1");
qemu_cmd.AddParameter("-netdev");
qemu_cmd.AddParameter("user,id=hostnet1,net=10.0.1.1/24,dns=8.8.4.4");
if (!config.virtio_mac80211_hwsim()) {
qemu_cmd.AddParameter("-netdev");
qemu_cmd.AddParameter("user,id=hostnet2,net=10.0.2.1/24,dns=1.1.1.1");
}
break;
}
default:
return CF_ERRF("Unexpected net mode {}",
instance.external_network_mode());
}
// The ordering of virtio-net devices is important. Make sure any change here
// is reflected in ethprime u-boot variable
qemu_cmd.AddParameter("-device");
qemu_cmd.AddParameter(
"virtio-net-pci-non-transitional,netdev=hostnet0,id=net0,mac=",
instance.mobile_mac());
qemu_cmd.AddParameter("-device");
qemu_cmd.AddParameter("virtio-net-pci-non-transitional,netdev=hostnet1,id=net1,mac=",
instance.ethernet_mac());
if (!config.virtio_mac80211_hwsim()) {
qemu_cmd.AddParameter("-device");
qemu_cmd.AddParameter("virtio-net-pci-non-transitional,netdev=hostnet2,id=net2,mac=",
instance.wifi_mac());
}
if (is_x86 || is_arm) {
qemu_cmd.AddParameter("-cpu");
qemu_cmd.AddParameter(IsHostCompatible(arch_) ? "host" : "max");
}
// Explicitly enable the optional extensions of interest, in case the default
// behavior changes upstream.
if (is_riscv64) {
qemu_cmd.AddParameter("-cpu");
qemu_cmd.AddParameter("rv64",
",v=true,elen=64,vlen=128",
",zba=true,zbb=true,zbs=true");
}
qemu_cmd.AddParameter("-msg");
qemu_cmd.AddParameter("timestamp=on");
#ifdef __linux__
qemu_cmd.AddParameter("-device");
qemu_cmd.AddParameter("vhost-vsock-pci-non-transitional,guest-cid=",
instance.vsock_guest_cid());
#endif
qemu_cmd.AddParameter("-device");
qemu_cmd.AddParameter("AC97,audiodev=audio_none");
qemu_cmd.AddParameter("-audiodev");
qemu_cmd.AddParameter("driver=none,id=audio_none");
qemu_cmd.AddParameter("-device");
qemu_cmd.AddParameter("qemu-xhci,id=xhci");
qemu_cmd.AddParameter("-L");
qemu_cmd.AddParameter(HostQemuBiosPath());
if (is_riscv64) {
qemu_cmd.AddParameter("-kernel");
qemu_cmd.AddParameter(instance.bootloader());
} else if (is_arm) {
qemu_cmd.AddParameter("-bios");
qemu_cmd.AddParameter(instance.bootloader());
} else {
qemu_cmd.AddParameter("-drive");
qemu_cmd.AddParameter("if=pflash,format=raw,readonly=on,file=",
instance.bootloader());
qemu_cmd.AddParameter("-drive");
qemu_cmd.AddParameter("if=pflash,format=raw,file=", instance.pflash_path());
}
if (instance.gdb_port() > 0) {
qemu_cmd.AddParameter("-S");
qemu_cmd.AddParameter("-gdb");
qemu_cmd.AddParameter("tcp::", instance.gdb_port());
}
commands.emplace_back(std::move(qemu_cmd), true);
return commands;
}
} // namespace vm_manager
} // namespace cuttlefish