blob: 213f4fdd65cc1d6db78ddcf56692370bfec66df7 [file] [log] [blame] [edit]
// Copyright 2020 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "vm_tools/concierge/vm_builder.h"
#include <optional>
#include <string>
#include <utility>
#include <base/strings/string_number_conversions.h>
#include <base/json/json_writer.h>
#include <base/strings/string_util.h>
#include <base/logging.h>
#include <base/values.h>
#include <re2/re2.h>
#include "base/files/file_path.h"
#include "base/strings/string_split.h"
#include "vm_tools/concierge/pci_utils.h"
#include "vm_tools/concierge/vm_util.h"
namespace vm_tools::concierge {
namespace {
// Path to the default wayland socket.
constexpr char kWaylandSocket[] = "/run/chrome/wayland-0";
constexpr char kVirglRenderServerPath[] = "/usr/libexec/virgl_render_server";
// Path to scudo allocator for GWP-ASan enablement in venus render server
#if defined(__x86_64__)
constexpr char kScudoPath[] =
"/usr/lib64/libclang_rt.scudo_standalone-x86_64.so";
#elif defined(__aarch64__)
constexpr char kScudoPath[] =
"/usr/lib64/libclang_rt.scudo_standalone-aarch64.so";
#elif defined(__arm__)
constexpr char kScudoPath[] = "/usr/lib/libclang_rt.scudo_standalone-armhf.so";
#else
static_assert(
!USE_CROSVM_VENUS_GWP_ASAN,
"scudo library *MUST* be available if concierge is built expecting it");
#endif
// Custom parameter key to override the o_direct= disk parameter for specific
// Nth entry.
constexpr char kKeyToOverrideODirectN[] = "O_DIRECT_N";
// Custom parameter key to override the multiple_workers= disk parameter.
constexpr char kKeyToOverrideBlockMultipleWorkers[] = "BLOCK_MULTIPLE_WORKERS";
// Custom parameter key to override the async executor for the disk devices.
constexpr char kKeyToOverrideIoBlockAsyncExecutor[] = "BLOCK_ASYNC_EXECUTOR";
// Custom parameter key to override the kernel path
constexpr char kKeyToOverrideKernelPath[] = "KERNEL_PATH";
constexpr char kUringAsyncExecutorString[] = "uring";
constexpr char kEpollAsyncExecutorString[] = "epoll";
std::string BooleanParameter(const char* parameter, bool value) {
std::string result = base::StrCat({parameter, value ? "true" : "false"});
return result;
}
std::string AsyncExecutorToString(VmBuilder::AsyncExecutor async_executor) {
switch (async_executor) {
case VmBuilder::AsyncExecutor::kUring:
return kUringAsyncExecutorString;
case VmBuilder::AsyncExecutor::kEpoll:
return kEpollAsyncExecutorString;
}
}
} // namespace
// Convert a string to the corresponding AsyncExecutor. This returns nullopt if
// the given string is unknown.
std::optional<VmBuilder::AsyncExecutor> StringToAsyncExecutor(
const std::string& async_executor) {
if (async_executor == kUringAsyncExecutorString) {
return std::optional{VmBuilder::AsyncExecutor::kUring};
} else if (async_executor == kEpollAsyncExecutorString) {
return std::optional{VmBuilder::AsyncExecutor::kEpoll};
} else {
LOG(ERROR) << "Failed to convert unknown string to AsyncExecutor"
<< async_executor;
return std::nullopt;
}
}
VmBuilder::VmBuilder() = default;
VmBuilder::VmBuilder(VmBuilder&&) = default;
VmBuilder& VmBuilder::operator=(VmBuilder&& other) = default;
VmBuilder::~VmBuilder() = default;
VmBuilder& VmBuilder::SetKernel(base::FilePath kernel) {
kernel_ = std::move(kernel);
return *this;
}
VmBuilder& VmBuilder::SetInitrd(base::FilePath initrd) {
initrd_ = std::move(initrd);
return *this;
}
VmBuilder& VmBuilder::SetBios(base::FilePath bios) {
bios_ = std::move(bios);
return *this;
}
VmBuilder& VmBuilder::SetPflash(base::FilePath pflash) {
pflash_ = std::move(pflash);
return *this;
}
VmBuilder& VmBuilder::SetRootfs(const struct Rootfs& rootfs) {
rootfs_ = rootfs;
return *this;
}
VmBuilder& VmBuilder::SetCpus(int32_t cpus) {
cpus_ = cpus;
return *this;
}
VmBuilder& VmBuilder::SetVmCpuArgs(const struct VmCpuArgs& vm_cpu_args) {
vm_cpu_args_ = vm_cpu_args;
return *this;
}
VmBuilder& VmBuilder::SetVsockCid(uint32_t vsock_cid) {
vsock_cid_ = vsock_cid;
return *this;
}
VmBuilder& VmBuilder::AppendDisk(VmBuilder::Disk disk) {
disks_.push_back(std::move(disk));
return *this;
}
VmBuilder& VmBuilder::AppendPmemDevice(VmBuilder::PmemDevice pmem_device) {
pmem_devices_.push_back(std::move(pmem_device));
return *this;
}
VmBuilder& VmBuilder::SetMemory(const std::string& memory_in_mb) {
memory_in_mib_ = memory_in_mb;
return *this;
}
VmBuilder& VmBuilder::SetBalloonBias(const std::string& balloon_bias_mib) {
balloon_bias_mib_ = balloon_bias_mib;
return *this;
}
VmBuilder& VmBuilder::EnableWorkingSetReporting(bool enable) {
enable_working_set_reporting_ = enable;
return *this;
}
VmBuilder& VmBuilder::SetSyslogTag(const std::string& syslog_tag) {
syslog_tag_ = syslog_tag;
return *this;
}
VmBuilder& VmBuilder::SetSocketPath(const std::string& socket_path) {
vm_socket_path_ = socket_path;
return *this;
}
VmBuilder& VmBuilder::AppendTapFd(base::ScopedFD fd) {
tap_fds_.push_back(std::move(fd));
return *this;
}
VmBuilder& VmBuilder::AppendKernelParam(const std::string& param) {
kernel_params_.push_back(param);
return *this;
}
VmBuilder& VmBuilder::AppendOemString(const std::string& string) {
oem_strings_.push_back(string);
return *this;
}
VmBuilder& VmBuilder::AppendAudioDevice(const std::string& params) {
audio_devices_.push_back(AudioDevice{.params = params});
return *this;
}
VmBuilder& VmBuilder::AppendSerialDevice(const std::string& device) {
serial_devices_.push_back(device);
return *this;
}
VmBuilder& VmBuilder::SetWaylandSocket(const std::string& socket) {
// The "true" socket, which is the visual one, must be set first.
DCHECK(wayland_sockets_.empty());
if (socket.empty()) {
// We want the empty string to mean "use the default socket", since that is
// the behaviour we want if the user does not set the wayland socket in the
// VirtualMachineSpec proto.
wayland_sockets_.push_back(kWaylandSocket);
} else {
wayland_sockets_.push_back(socket);
}
return *this;
}
VmBuilder& VmBuilder::AddExtraWaylandSocket(const std::string& socket) {
// Additional sockets must only be added after the "true" socket, since the
// first socket provided to the VM will always be interpreted as the visual
// one.
DCHECK(!wayland_sockets_.empty());
wayland_sockets_.push_back(socket);
return *this;
}
VmBuilder& VmBuilder::AppendSharedDir(SharedDataParam shared_dir) {
shared_dirs_.push_back(std::move(shared_dir));
return *this;
}
VmBuilder& VmBuilder::AppendCustomParam(const std::string& key,
const std::string& value) {
custom_params_.emplace_back(key, value);
return *this;
}
VmBuilder& VmBuilder::EnableGpu(bool enable) {
enable_gpu_ = enable;
return *this;
}
VmBuilder& VmBuilder::EnableDGpuPassthrough(bool enable) {
enable_dgpu_passthrough_ = enable;
return *this;
}
VmBuilder& VmBuilder::EnableVulkan(bool enable) {
enable_vulkan_ = enable;
return *this;
}
VmBuilder& VmBuilder::EnableBigGl(bool enable) {
#if USE_BIG_GL
enable_big_gl_ = enable;
#else
LOG_IF(WARNING, enable) << "Big GL is not supported on this board";
enable_big_gl_ = false;
#endif
return *this;
}
VmBuilder& VmBuilder::EnableRenderServer(bool enable) {
enable_render_server_ = enable;
return *this;
}
VmBuilder& VmBuilder::SetGpuCachePath(base::FilePath gpu_cache_path) {
gpu_cache_path_ = std::move(gpu_cache_path);
return *this;
}
VmBuilder& VmBuilder::SetGpuCacheSize(std::string gpu_cache_size_str) {
gpu_cache_size_str_ = std::move(gpu_cache_size_str);
return *this;
}
VmBuilder& VmBuilder::SetRenderServerCachePath(
base::FilePath render_server_cache_path) {
render_server_cache_path_ = std::move(render_server_cache_path);
return *this;
}
VmBuilder& VmBuilder::SetPrecompiledCachePath(
base::FilePath precompiled_cache_path) {
precompiled_cache_path_ = std::move(precompiled_cache_path);
return *this;
}
VmBuilder& VmBuilder::SetFozDbListPath(base::FilePath foz_db_list_path) {
foz_db_list_path_ = std::move(foz_db_list_path);
return *this;
}
VmBuilder& VmBuilder::SetRenderServerCacheSize(
std::string render_server_cache_size_str) {
render_server_cache_size_str_ = std::move(render_server_cache_size_str);
return *this;
}
VmBuilder& VmBuilder::EnableGpuContextTypeDefaults() {
enabled_gpu_context_types_.reset();
return *this;
}
VmBuilder& VmBuilder::EnableGpuContextTypeVirgl(bool enable) {
enabled_gpu_context_types_.set(GpuContextType::GPU_CONTEXT_TYPE_VIRGL,
enable);
return *this;
}
VmBuilder& VmBuilder::EnableGpuContextTypeVenus(bool enable) {
enabled_gpu_context_types_.set(GpuContextType::GPU_CONTEXT_TYPE_VENUS,
enable);
return *this;
}
VmBuilder& VmBuilder::EnableGpuContextTypeCrossDomain(bool enable) {
enabled_gpu_context_types_.set(GpuContextType::GPU_CONTEXT_TYPE_CROSS_DOMAIN,
enable);
return *this;
}
VmBuilder& VmBuilder::EnableGpuContextTypeDrm(bool enable) {
enabled_gpu_context_types_.set(GpuContextType::GPU_CONTEXT_TYPE_DRM, enable);
return *this;
}
VmBuilder& VmBuilder::EnableVtpmProxy(bool enable) {
enable_vtpm_proxy_ = enable;
return *this;
}
VmBuilder& VmBuilder::EnableVideoDecoder(bool enable) {
enable_video_decoder_ = enable;
return *this;
}
VmBuilder& VmBuilder::EnableVideoEncoder(bool enable) {
enable_video_encoder_ = enable;
return *this;
}
VmBuilder& VmBuilder::EnableBattery(bool enable) {
enable_battery_ = enable;
return *this;
}
VmBuilder& VmBuilder::EnableSmt(bool enable) {
enable_smt_ = enable;
return *this;
}
VmBuilder& VmBuilder::EnableDelayRt(bool enable) {
enable_delay_rt_ = enable;
return *this;
}
VmBuilder& VmBuilder::EnablePerVmCoreScheduling(bool enable) {
enable_per_vm_core_scheduling_ = enable;
return *this;
}
VmBuilder& VmBuilder::EnablePvClock(bool enable) {
enable_pvclock_ = enable;
return *this;
}
VmBuilder& VmBuilder::EnableODirectN(int n, bool enable) {
disks_.at(n).o_direct = enable;
return *this;
}
VmBuilder& VmBuilder::EnableMultipleWorkers(bool enable) {
for (auto& d : disks_) {
d.multiple_workers = enable;
}
return *this;
}
VmBuilder& VmBuilder::SetBlockAsyncExecutor(AsyncExecutor executor) {
for (auto& d : disks_) {
d.async_executor = executor;
}
return *this;
}
VmBuilder& VmBuilder::SetBlockSize(size_t block_size) {
for (auto& d : disks_) {
d.block_size = block_size;
}
return *this;
}
VmBuilder& VmBuilder::SetBlockSizeN(size_t n, size_t block_size) {
disks_.at(n).block_size = block_size;
return *this;
}
VmBuilder& VmBuilder::SetVmmSwapDir(base::FilePath vmm_swap_dir) {
vmm_swap_dir_ = std::move(vmm_swap_dir);
return *this;
}
bool VmBuilder::ProcessCustomParameters(
const CustomParametersForDev& devparams) {
for (auto value : devparams.ObtainSpecialParameters(kKeyToOverrideODirectN)) {
int64_t n;
if (!base::StringToInt64(value, &n)) {
LOG(ERROR) << "Unknown value for " << kKeyToOverrideODirectN << ": "
<< value;
return false;
}
EnableODirectN(n, true);
/* block size for DM-verity root file system */
SetBlockSizeN(n, 4096);
}
if (devparams.ObtainSpecialParameter(kKeyToOverrideBlockMultipleWorkers)
.value_or("false") == "true") {
EnableMultipleWorkers(true);
}
const auto block_async_executor =
devparams.ObtainSpecialParameter(kKeyToOverrideIoBlockAsyncExecutor);
if (block_async_executor) {
const auto executor_enum =
StringToAsyncExecutor(block_async_executor.value());
if (!executor_enum.has_value()) {
LOG(ERROR) << "Unknown value for BLOCK_ASYNC_EXECUTOR: "
<< block_async_executor.value();
return false;
}
SetBlockAsyncExecutor(executor_enum.value());
}
if (auto kernel_override =
devparams.ObtainSpecialParameter(kKeyToOverrideKernelPath);
kernel_override) {
kernel_ = base::FilePath(kernel_override.value());
}
return true;
}
std::optional<base::StringPairs> VmBuilder::BuildVmArgs(
CustomParametersForDev* devparams) && {
if (devparams) {
if (!ProcessCustomParameters(*devparams)) {
return std::nullopt;
}
}
base::StringPairs post_run_args = BuildRunParams();
// Early-return when BuildRunParams() failed.
if (post_run_args.empty())
return std::nullopt;
base::StringPairs pre_run_args = BuildPreRunParams();
std::vector<std::string> pre_crosvm_args;
if (devparams) {
devparams->Apply(post_run_args);
devparams->AppendPrerunParams(pre_run_args);
pre_crosvm_args = devparams->ObtainPrecrosvmParams();
}
base::StringPairs args;
for (const auto& arg : pre_crosvm_args) {
args.emplace_back(arg, "");
}
args.emplace_back(kCrosvmBin, "");
args.insert(args.end(), pre_run_args.begin(), pre_run_args.end());
args.emplace_back("run", "");
args.insert(args.end(), post_run_args.begin(), post_run_args.end());
// Kernel should be at the end.
if (!kernel_.empty())
args.emplace_back(kernel_.value(), "");
return args;
}
base::StringPairs VmBuilder::BuildPreRunParams() const {
base::StringPairs args;
if (!syslog_tag_.empty())
args.emplace_back("--syslog-tag", syslog_tag_);
return args;
}
base::StringPairs VmBuilder::BuildRunParams() const {
base::StringPairs args = {{"--cpus", std::to_string(cpus_)}};
if (!memory_in_mib_.empty())
args.emplace_back("--mem", memory_in_mib_);
if (!balloon_bias_mib_.empty())
args.emplace_back("--balloon-bias-mib", balloon_bias_mib_);
if (enable_working_set_reporting_)
args.emplace_back("--balloon-ws-reporting", "");
for (const auto& tap_fd : tap_fds_)
args.emplace_back(
"--net", "packed-queue=true,tap-fd=" + std::to_string(tap_fd.get()));
if (vsock_cid_.has_value())
args.emplace_back("--cid", std::to_string(vsock_cid_.value()));
if (!vm_socket_path_.empty())
args.emplace_back("--socket", vm_socket_path_);
for (const auto& w : wayland_sockets_)
args.emplace_back("--wayland-sock", w);
for (const auto& s : serial_devices_)
args.emplace_back("--serial", s);
if (enable_smt_.has_value()) {
if (!enable_smt_.value())
args.emplace_back("--no-smt", "");
}
if (enable_delay_rt_)
args.emplace_back("--delay-rt", "");
if (enable_per_vm_core_scheduling_)
args.emplace_back("--per-vm-core-scheduling", "");
if (kernel_params_.size() > 0)
args.emplace_back("--params", base::JoinString(kernel_params_, " "));
for (const std::string& s : oem_strings_)
args.emplace_back("--oem-strings", s);
if (rootfs_.has_value()) {
const auto& rootfs = rootfs_.value();
std::string rootfs_arg = rootfs.path.value();
rootfs_arg += BooleanParameter(",ro=", !rootfs.writable);
rootfs_arg += BooleanParameter(",root=", true);
if (rootfs.device.find("pmem") != std::string::npos) {
// TODO(davidriley): Re-add rootflags=dax once guest kernel has fix for
// b/169339326.
args.emplace_back("--pmem", rootfs_arg);
} else {
args.emplace_back("--block", rootfs_arg);
}
}
for (const auto& dev : audio_devices_) {
args.emplace_back("--virtio-snd", dev.params);
}
for (const auto& d : disks_) {
auto disk_args = d.GetCrosvmArgs();
args.insert(std::end(args), std::begin(disk_args), std::end(disk_args));
}
for (const auto& pmem_device : pmem_devices_) {
auto device_args = pmem_device.GetCrosvmArgs();
args.insert(std::end(args), std::begin(device_args), std::end(device_args));
}
if (enable_gpu_) {
std::string gpu_arg = "vulkan=";
gpu_arg += enable_vulkan_ ? "true" : "false";
// some of these are related to other options (e.g. use_vulkan_, big_gl_)
// but we treat them as orthogonal, requiring callers to manage their
// desired context types explicitly to keep a 1:1 mapping between VmBuilder
// options and the VMM's options.
if (enabled_gpu_context_types_.any()) {
gpu_arg += ",context-types=";
if (enabled_gpu_context_types_.test(GPU_CONTEXT_TYPE_VIRGL)) {
gpu_arg += ":virgl:virgl2";
}
if (enabled_gpu_context_types_.test(GPU_CONTEXT_TYPE_VENUS)) {
gpu_arg += ":venus";
}
if (enabled_gpu_context_types_.test(GPU_CONTEXT_TYPE_CROSS_DOMAIN)) {
gpu_arg += ":cross-domain";
}
if (enabled_gpu_context_types_.test(GPU_CONTEXT_TYPE_DRM)) {
gpu_arg += ":drm";
}
}
if (enable_big_gl_) {
gpu_arg += ",gles=false";
}
if (!gpu_cache_path_.empty()) {
gpu_arg += ",cache-path=" + gpu_cache_path_.value();
}
if (!gpu_cache_size_str_.empty()) {
gpu_arg += ",cache-size=" + gpu_cache_size_str_;
}
gpu_arg += ",fixed-blob-mapping=";
if constexpr (USE_CROSVM_FIXED_BLOB_MAPPING) {
gpu_arg += "true";
} else {
gpu_arg += "false";
}
args.emplace_back("--gpu", gpu_arg);
if (enable_render_server_) {
std::string render_server_arg = "path=";
render_server_arg += kVirglRenderServerPath;
if (!render_server_cache_path_.empty()) {
render_server_arg += ",cache-path=" + render_server_cache_path_.value();
}
if (!render_server_cache_size_str_.empty()) {
render_server_arg += ",cache-size=" + render_server_cache_size_str_;
}
if (!foz_db_list_path_.empty()) {
render_server_arg += ",foz-db-list-path=" + foz_db_list_path_.value();
}
if (!precompiled_cache_path_.empty()) {
render_server_arg +=
",precompiled-cache-path=" + precompiled_cache_path_.value();
}
if constexpr (USE_CROSVM_VENUS_GWP_ASAN) {
render_server_arg += ",ld-preload-path=" + std::string(kScudoPath);
}
args.emplace_back("--gpu-render-server", render_server_arg);
}
}
if (enable_dgpu_passthrough_) {
std::vector<base::FilePath> dgpu_devices = GetPciDevicesList(
pci_utils::PciDeviceType::PCI_DEVICE_TYPE_DGPU_PASSTHROUGH);
for (const auto& dgpu_device : dgpu_devices) {
std::string dgpu_pt_arg =
base::StringPrintf("%s,iommu=viommu", dgpu_device.value().c_str());
args.emplace_back("--vfio", std::move(dgpu_pt_arg));
}
// TODO(b/297293470): These options appear to break borealis'
// suspend+resume. The below disables them if there are no dgpu devices but
// they will probably still be broken with dgpu.
if (!dgpu_devices.empty()) {
args.emplace_back("--s2idle", "");
args.emplace_back("--ac-adapter", "");
}
}
if (enable_vtpm_proxy_)
args.emplace_back("--vtpm-proxy", "");
if (enable_video_decoder_)
args.emplace_back("--video-decoder", "libvda");
if (enable_video_encoder_)
args.emplace_back("--video-encoder", "libvda");
if (enable_battery_)
args.emplace_back("--battery", "type=goldfish");
if (enable_pvclock_)
args.emplace_back("--pvclock", "");
for (const auto& shared_dir : shared_dirs_)
args.emplace_back("--shared-dir", shared_dir.to_string());
for (const auto& custom_param : custom_params_)
args.emplace_back(custom_param.first, custom_param.second);
if (!initrd_.empty())
args.emplace_back("-i", initrd_.value());
if (!bios_.empty())
args.emplace_back("--bios", bios_.value());
if (!pflash_.empty())
args.emplace_back("--pflash", "path=" + pflash_.value());
if (!vmm_swap_dir_.empty()) {
args.emplace_back("--swap", vmm_swap_dir_.value());
}
if (vm_cpu_args_.has_value()) {
if (!vm_cpu_args_->cpu_affinity.empty())
args.emplace_back("--cpu-affinity", vm_cpu_args_->cpu_affinity);
if (!vm_cpu_args_->cpu_capacity.empty())
args.emplace_back("--cpu-capacity",
base::JoinString(vm_cpu_args_->cpu_capacity, ","));
if (!vm_cpu_args_->cpu_clusters.empty()) {
for (const auto& cluster : vm_cpu_args_->cpu_clusters) {
auto cpu_list = base::JoinString(cluster, ",");
args.emplace_back("--cpu-cluster", cpu_list);
}
}
}
return args;
}
base::StringPairs VmBuilder::Disk::GetCrosvmArgs() const {
std::string first = "--block";
std::string readonly_arg = BooleanParameter(",ro=", !writable);
std::string sparse_arg;
if (sparse) {
sparse_arg = BooleanParameter(",sparse=", sparse.value());
}
std::string o_direct_arg;
if (o_direct) {
o_direct_arg = BooleanParameter(",o_direct=", o_direct.value());
}
std::string multiple_workers_arg;
if (multiple_workers) {
multiple_workers_arg =
BooleanParameter(",multiple-workers=", multiple_workers.value());
}
std::string block_size_arg;
if (block_size) {
block_size_arg = ",block_size=" + std::to_string(block_size.value());
}
std::string async_executor_arg;
if (async_executor) {
async_executor_arg = base::StrCat(
{",async_executor=", AsyncExecutorToString(async_executor.value())});
}
std::string block_id_arg;
if (block_id) {
// Virtio_blk can't handle more than 20 chars:
// https://docs.oasis-open.org/virtio/virtio/v1.2/csd01/virtio-v1.2-csd01.html#x1-2850006
CHECK_LE(block_id.value().size(), 20);
block_id_arg = base::StrCat({",id=", block_id.value()});
}
std::string second = base::StrCat(
{path.value(), readonly_arg, sparse_arg, o_direct_arg,
multiple_workers_arg, block_size_arg, async_executor_arg, block_id_arg});
base::StringPairs result = {{std::move(first), std::move(second)}};
return result;
}
base::StringPairs VmBuilder::PmemDevice::GetCrosvmArgs() const {
std::string first = "--pmem";
std::string read_only_arg = writable ? ",ro=false" : ",ro=true";
std::string vma_size_arg;
if (vma_size) {
vma_size_arg = ",vma-size=" + std::to_string(vma_size.value());
}
std::string swap_interval_arg;
if (swap_interval_ms) {
swap_interval_arg =
",swap-interval-ms=" + std::to_string(swap_interval_ms.value());
}
std::string second =
base::StrCat({path, read_only_arg, vma_size_arg, swap_interval_arg});
base::StringPairs result = {{std::move(first), std::move(second)}};
return result;
}
} // namespace vm_tools::concierge