1 /*crosvm
2 * Copyright (C) 2019 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "host/libs/vm_manager/crosvm_manager.h"
18
19 #include <poll.h>
20 #include <signal.h>
21 #include <sys/stat.h>
22 #include <sys/types.h>
23
24 #include <cassert>
25 #include <map>
26 #include <string>
27 #include <unordered_map>
28 #include <utility>
29 #include <vector>
30
31 #include <android-base/file.h>
32 #include <android-base/logging.h>
33 #include <android-base/strings.h>
34 #include <json/json.h>
35 #include <vulkan/vulkan.h>
36
37 #include "common/libs/utils/environment.h"
38 #include "common/libs/utils/files.h"
39 #include "common/libs/utils/json.h"
40 #include "common/libs/utils/network.h"
41 #include "common/libs/utils/result.h"
42 #include "common/libs/utils/subprocess.h"
43 #include "host/libs/command_util/snapshot_utils.h"
44 #include "host/libs/config/cuttlefish_config.h"
45 #include "host/libs/config/known_paths.h"
46 #include "host/libs/vm_manager/crosvm_builder.h"
47 #include "host/libs/vm_manager/qemu_manager.h"
48 #include "host/libs/vm_manager/vhost_user.h"
49
50 namespace cuttlefish {
51 namespace vm_manager {
52
53 constexpr auto kTouchpadDefaultPrefix = "Crosvm_Virtio_Multitouch_Touchpad_";
54
IsSupported()55 bool CrosvmManager::IsSupported() {
56 #ifdef __ANDROID__
57 return true;
58 #else
59 return HostSupportsQemuCli();
60 #endif
61 }
62
63 Result<std::unordered_map<std::string, std::string>>
ConfigureGraphics(const CuttlefishConfig::InstanceSpecific & instance)64 CrosvmManager::ConfigureGraphics(
65 const CuttlefishConfig::InstanceSpecific& instance) {
66 // Override the default HAL search paths in all cases. We do this because
67 // the HAL search path allows for fallbacks, and fallbacks in conjunction
68 // with properties lead to non-deterministic behavior while loading the
69 // HALs.
70
71 std::unordered_map<std::string, std::string> bootconfig_args;
72
73 if (instance.gpu_mode() == kGpuModeGuestSwiftshader) {
74 bootconfig_args = {
75 {"androidboot.cpuvulkan.version", std::to_string(VK_API_VERSION_1_3)},
76 {"androidboot.hardware.gralloc", "minigbm"},
77 {"androidboot.hardware.hwcomposer", instance.hwcomposer()},
78 {"androidboot.hardware.hwcomposer.display_finder_mode", "drm"},
79 {"androidboot.hardware.hwcomposer.display_framebuffer_format",
80 instance.guest_uses_bgra_framebuffers() ? "bgra" : "rgba"},
81 {"androidboot.hardware.egl", "angle"},
82 {"androidboot.hardware.vulkan", "pastel"},
83 {"androidboot.opengles.version", "196609"}, // OpenGL ES 3.1
84 };
85 } else if (instance.gpu_mode() == kGpuModeDrmVirgl) {
86 bootconfig_args = {
87 {"androidboot.cpuvulkan.version", "0"},
88 {"androidboot.hardware.gralloc", "minigbm"},
89 {"androidboot.hardware.hwcomposer", "ranchu"},
90 {"androidboot.hardware.hwcomposer.mode", "client"},
91 {"androidboot.hardware.hwcomposer.display_finder_mode", "drm"},
92 {"androidboot.hardware.hwcomposer.display_framebuffer_format",
93 instance.guest_uses_bgra_framebuffers() ? "bgra" : "rgba"},
94 {"androidboot.hardware.egl", "mesa"},
95 // No "hardware" Vulkan support, yet
96 {"androidboot.opengles.version", "196608"}, // OpenGL ES 3.0
97 };
98 } else if (instance.gpu_mode() == kGpuModeGfxstream ||
99 instance.gpu_mode() == kGpuModeGfxstreamGuestAngle ||
100 instance.gpu_mode() ==
101 kGpuModeGfxstreamGuestAngleHostSwiftShader) {
102 const bool uses_angle =
103 instance.gpu_mode() == kGpuModeGfxstreamGuestAngle ||
104 instance.gpu_mode() == kGpuModeGfxstreamGuestAngleHostSwiftShader;
105
106 const std::string gles_impl = uses_angle ? "angle" : "emulation";
107
108 const std::string gfxstream_transport = instance.gpu_gfxstream_transport();
109 CF_EXPECT(gfxstream_transport == "virtio-gpu-asg" ||
110 gfxstream_transport == "virtio-gpu-pipe",
111 "Invalid Gfxstream transport option: \"" << gfxstream_transport
112 << "\"");
113
114 bootconfig_args = {
115 {"androidboot.cpuvulkan.version", "0"},
116 {"androidboot.hardware.gralloc", "minigbm"},
117 {"androidboot.hardware.hwcomposer", instance.hwcomposer()},
118 {"androidboot.hardware.hwcomposer.display_finder_mode", "drm"},
119 {"androidboot.hardware.hwcomposer.display_framebuffer_format",
120 instance.guest_uses_bgra_framebuffers() ? "bgra" : "rgba"},
121 {"androidboot.hardware.egl", gles_impl},
122 {"androidboot.hardware.vulkan", "ranchu"},
123 {"androidboot.hardware.gltransport", gfxstream_transport},
124 {"androidboot.opengles.version", "196609"}, // OpenGL ES 3.1
125 };
126 } else if (instance.gpu_mode() == kGpuModeCustom) {
127 bootconfig_args = {
128 {"androidboot.cpuvulkan.version", "0"},
129 {"androidboot.hardware.gralloc", "minigbm"},
130 {"androidboot.hardware.hwcomposer", instance.hwcomposer()},
131 {"androidboot.hardware.hwcomposer.display_finder_mode", "drm"},
132 {"androidboot.hardware.hwcomposer.display_framebuffer_format",
133 instance.guest_uses_bgra_framebuffers() ? "bgra" : "rgba"},
134 {"androidboot.hardware.egl", "angle"},
135 {"androidboot.hardware.vulkan", instance.guest_vulkan_driver()},
136 {"androidboot.hardware.gltransport", "virtio-gpu-asg"},
137 {"androidboot.opengles.version", "196609"}, // OpenGL ES 3.1
138 };
139 } else if (instance.gpu_mode() == kGpuModeNone) {
140 return {};
141 } else {
142 return CF_ERR("Unknown GPU mode " << instance.gpu_mode());
143 }
144
145 if (!instance.gpu_angle_feature_overrides_enabled().empty()) {
146 bootconfig_args["androidboot.hardware.angle_feature_overrides_enabled"] =
147 instance.gpu_angle_feature_overrides_enabled();
148 }
149 if (!instance.gpu_angle_feature_overrides_disabled().empty()) {
150 bootconfig_args["androidboot.hardware.angle_feature_overrides_disabled"] =
151 instance.gpu_angle_feature_overrides_disabled();
152 }
153
154 return bootconfig_args;
155 }
156
157 Result<std::unordered_map<std::string, std::string>>
ConfigureBootDevices(const CuttlefishConfig::InstanceSpecific & instance)158 CrosvmManager::ConfigureBootDevices(
159 const CuttlefishConfig::InstanceSpecific& instance) {
160 const int num_disks = instance.virtual_disk_paths().size();
161 const bool has_gpu = instance.hwcomposer() != kHwComposerNone;
162 // TODO There is no way to control this assignment with crosvm (yet)
163 if (HostArch() == Arch::X86_64) {
164 int num_gpu_pcis = has_gpu ? 1 : 0;
165 if (instance.gpu_mode() != kGpuModeNone &&
166 !instance.enable_gpu_vhost_user()) {
167 // crosvm has an additional PCI device for an ISA bridge when running
168 // with a gpu and without vhost user gpu.
169 num_gpu_pcis += 1;
170 }
171 // virtio_gpu and virtio_wl precedes the first console or disk
172 return ConfigureMultipleBootDevices("pci0000:00/0000:00:", 1 + num_gpu_pcis,
173 num_disks);
174 } else {
175 // On ARM64 crosvm, block devices are on their own bridge, so we don't
176 // need to calculate it, and the path is always the same
177 return {{{"androidboot.boot_devices", "10000.pci"}}};
178 }
179 }
180
ToSingleLineString(const Json::Value & value)181 std::string ToSingleLineString(const Json::Value& value) {
182 Json::StreamWriterBuilder builder;
183 builder["indentation"] = "";
184 return Json::writeString(builder, value);
185 }
186
HostSwiftShaderIcdPathForArch()187 Result<std::string> HostSwiftShaderIcdPathForArch() {
188 switch (HostArch()) {
189 case Arch::Arm64:
190 return HostBinaryPath("aarch64-linux-gnu/vk_swiftshader_icd.json");
191 case Arch::X86:
192 case Arch::X86_64:
193 return HostUsrSharePath("vulkan/icd.d/vk_swiftshader_icd.json");
194 default:
195 break;
196 }
197 return CF_ERR("Unhandled host arch " << HostArchStr()
198 << " for finding SwiftShader ICD.");
199 }
200
MaybeConfigureVulkanIcd(const CuttlefishConfig & config,Command * command)201 Result<void> MaybeConfigureVulkanIcd(const CuttlefishConfig& config,
202 Command* command) {
203 const auto& gpu_mode = config.ForDefaultInstance().gpu_mode();
204 if (gpu_mode == kGpuModeGfxstreamGuestAngleHostSwiftShader) {
205 const std::string swiftshader_icd_json_path =
206 CF_EXPECT(HostSwiftShaderIcdPathForArch());
207
208 // See https://github.com/KhronosGroup/Vulkan-Loader.
209 command->AddEnvironmentVariable("VK_DRIVER_FILES",
210 swiftshader_icd_json_path);
211 command->AddEnvironmentVariable("VK_ICD_FILENAMES",
212 swiftshader_icd_json_path);
213 }
214
215 return {};
216 }
217
CrosvmPathForVhostUserGpu(const CuttlefishConfig & config)218 Result<std::string> CrosvmPathForVhostUserGpu(const CuttlefishConfig& config) {
219 const auto& instance = config.ForDefaultInstance();
220 switch (HostArch()) {
221 case Arch::Arm64:
222 return HostBinaryPath("aarch64-linux-gnu/crosvm");
223 case Arch::X86:
224 case Arch::X86_64:
225 return instance.crosvm_binary();
226 default:
227 break;
228 }
229 return CF_ERR("Unhandled host arch " << HostArchStr()
230 << " for vhost user gpu crosvm");
231 }
232
BuildVhostUserGpu(const CuttlefishConfig & config,Command * main_crosvm_cmd)233 Result<VhostUserDeviceCommands> BuildVhostUserGpu(
234 const CuttlefishConfig& config, Command* main_crosvm_cmd) {
235 const auto& instance = config.ForDefaultInstance();
236 if (!instance.enable_gpu_vhost_user()) {
237 return CF_ERR("Attempting to build vhost user gpu when not enabled?");
238 }
239
240 auto gpu_device_socket_path =
241 instance.PerInstanceInternalUdsPath("vhost-user-gpu-socket");
242 auto gpu_device_logs_path =
243 instance.PerInstanceInternalPath("crosvm_vhost_user_gpu.fifo");
244 auto gpu_device_logs = CF_EXPECT(SharedFD::Fifo(gpu_device_logs_path, 0666));
245
246 Command gpu_device_logs_cmd(HostBinaryPath("log_tee"));
247 gpu_device_logs_cmd.AddParameter("--process_name=crosvm_gpu");
248 gpu_device_logs_cmd.AddParameter("--log_fd_in=", gpu_device_logs);
249 gpu_device_logs_cmd.SetStopper(KillSubprocessFallback([](Subprocess* proc) {
250 // Ask nicely so that log_tee gets a chance to process all the logs.
251 // TODO: b/335934714 - Make sure the process actually exits
252 bool res = kill(proc->pid(), SIGINT) == 0;
253 return res ? StopperResult::kStopSuccess : StopperResult::kStopFailure;
254 }));
255
256 const std::string crosvm_path = CF_EXPECT(CrosvmPathForVhostUserGpu(config));
257
258 CrosvmBuilder gpu_device_cmd;
259
260 // NOTE: The "main" crosvm process returns a kCrosvmVmResetExitCode when the
261 // guest exits but the "gpu" crosvm just exits cleanly with 0 after the "main"
262 // crosvm disconnects.
263 gpu_device_cmd.ApplyProcessRestarter(crosvm_path,
264 /*first_time_argument=*/"",
265 /*exit_code=*/0);
266
267 gpu_device_cmd.Cmd().AddParameter("device");
268 gpu_device_cmd.Cmd().AddParameter("gpu");
269
270 const auto& gpu_mode = instance.gpu_mode();
271 CF_EXPECT(
272 gpu_mode == kGpuModeGfxstream ||
273 gpu_mode == kGpuModeGfxstreamGuestAngle ||
274 gpu_mode == kGpuModeGfxstreamGuestAngleHostSwiftShader,
275 "GPU mode " << gpu_mode << " not yet supported with vhost user gpu.");
276
277 const std::string gpu_pci_address =
278 fmt::format("00:{:0>2x}.0", VmManager::kGpuPciSlotNum);
279
280 // Why does this need JSON instead of just following the normal flags style...
281 Json::Value gpu_params_json;
282 gpu_params_json["pci-address"] = gpu_pci_address;
283 if (gpu_mode == kGpuModeGfxstream) {
284 gpu_params_json["context-types"] = "gfxstream-gles:gfxstream-vulkan";
285 gpu_params_json["egl"] = true;
286 gpu_params_json["gles"] = true;
287 } else if (gpu_mode == kGpuModeGfxstreamGuestAngle ||
288 gpu_mode == kGpuModeGfxstreamGuestAngleHostSwiftShader) {
289 gpu_params_json["context-types"] = "gfxstream-vulkan";
290 gpu_params_json["egl"] = false;
291 gpu_params_json["gles"] = false;
292 }
293 gpu_params_json["glx"] = false;
294 gpu_params_json["surfaceless"] = true;
295 gpu_params_json["external-blob"] = instance.enable_gpu_external_blob();
296 gpu_params_json["system-blob"] = instance.enable_gpu_system_blob();
297 if (!instance.gpu_renderer_features().empty()) {
298 gpu_params_json["renderer-features"] = instance.gpu_renderer_features();
299 }
300
301 if (instance.hwcomposer() != kHwComposerNone) {
302 // "displays": [
303 // {
304 // "mode": {
305 // "windowed": [
306 // 720,
307 // 1280
308 // ]
309 // },
310 // "dpi": [
311 // 320,
312 // 320
313 // ],
314 // "refresh-rate": 60
315 // }
316 // ]
317 Json::Value displays(Json::arrayValue);
318 for (const auto& display_config : instance.display_configs()) {
319 Json::Value display_mode_windowed(Json::arrayValue);
320 display_mode_windowed[0] = display_config.width;
321 display_mode_windowed[1] = display_config.height;
322
323 Json::Value display_mode;
324 display_mode["windowed"] = display_mode_windowed;
325
326 Json::Value display_dpi(Json::arrayValue);
327 display_dpi[0] = display_config.dpi;
328 display_dpi[1] = display_config.dpi;
329
330 Json::Value display;
331 display["mode"] = display_mode;
332 display["dpi"] = display_dpi;
333 display["refresh-rate"] = display_config.refresh_rate_hz;
334
335 displays.append(display);
336 }
337 gpu_params_json["displays"] = displays;
338
339 gpu_device_cmd.Cmd().AddParameter("--wayland-sock=",
340 instance.frames_socket_path());
341 }
342
343 // Connect device to main crosvm:
344 gpu_device_cmd.Cmd().AddParameter("--socket=", gpu_device_socket_path);
345
346 main_crosvm_cmd->AddParameter(
347 "--vhost-user=gpu,pci-address=", gpu_pci_address,
348 ",socket=", gpu_device_socket_path);
349
350 gpu_device_cmd.Cmd().AddParameter("--params");
351 gpu_device_cmd.Cmd().AddParameter(ToSingleLineString(gpu_params_json));
352
353 CF_EXPECT(MaybeConfigureVulkanIcd(config, &gpu_device_cmd.Cmd()));
354
355 gpu_device_cmd.Cmd().RedirectStdIO(Subprocess::StdIOChannel::kStdOut,
356 gpu_device_logs);
357 gpu_device_cmd.Cmd().RedirectStdIO(Subprocess::StdIOChannel::kStdErr,
358 gpu_device_logs);
359
360 return VhostUserDeviceCommands{
361 .device_cmd = std::move(gpu_device_cmd.Cmd()),
362 .device_logs_cmd = std::move(gpu_device_logs_cmd),
363 .socket_path = gpu_device_socket_path,
364 };
365 }
366
ConfigureGpu(const CuttlefishConfig & config,Command * crosvm_cmd)367 Result<void> ConfigureGpu(const CuttlefishConfig& config, Command* crosvm_cmd) {
368 const auto& instance = config.ForDefaultInstance();
369 const auto& gpu_mode = instance.gpu_mode();
370
371 const std::string gles_string =
372 gpu_mode == kGpuModeGfxstreamGuestAngle ||
373 gpu_mode == kGpuModeGfxstreamGuestAngleHostSwiftShader
374 ? ",gles=false"
375 : ",gles=true";
376
377 // 256MB so it is small enough for a 32-bit kernel.
378 const bool target_is_32bit = instance.target_arch() == Arch::Arm ||
379 instance.target_arch() == Arch::X86;
380 const std::string gpu_pci_bar_size =
381 target_is_32bit ? ",pci-bar-size=268435456" : "";
382
383 const std::string gpu_udmabuf_string =
384 instance.enable_gpu_udmabuf() ? ",udmabuf=true" : "";
385
386 const std::string gpu_renderer_features = instance.gpu_renderer_features();
387 const std::string gpu_renderer_features_param =
388 !gpu_renderer_features.empty()
389 ? ",renderer-features=\"" + gpu_renderer_features + "\""
390 : "";
391
392 const std::string gpu_common_string =
393 fmt::format(",pci-address=00:{:0>2x}.0", VmManager::kGpuPciSlotNum) +
394 gpu_udmabuf_string + gpu_pci_bar_size;
395 const std::string gpu_common_3d_string =
396 gpu_common_string + ",egl=true,surfaceless=true,glx=false" + gles_string +
397 gpu_renderer_features_param;
398
399 std::string gpu_displays_string = "";
400 if (instance.hwcomposer() != kHwComposerNone) {
401 std::vector<std::string> gpu_displays_strings;
402 for (const auto& display_config : instance.display_configs()) {
403 const auto display_w = std::to_string(display_config.width);
404 const auto display_h = std::to_string(display_config.height);
405 const auto display_dpi = std::to_string(display_config.dpi);
406 const auto display_rr = std::to_string(display_config.refresh_rate_hz);
407 gpu_displays_strings.push_back(android::base::Join(
408 std::vector<std::string>{
409 "mode=windowed[" + display_w + "," + display_h + "]",
410 "dpi=[" + display_dpi + "," + display_dpi + "]",
411 "refresh-rate=" + display_rr,
412 },
413 ","));
414 }
415 gpu_displays_string =
416 "displays=[[" + android::base::Join(gpu_displays_strings, "],[") + "]],";
417
418 crosvm_cmd->AddParameter("--wayland-sock=", instance.frames_socket_path());
419 }
420
421 if (gpu_mode == kGpuModeGuestSwiftshader) {
422 crosvm_cmd->AddParameter("--gpu=", gpu_displays_string, "backend=2D",
423 gpu_common_string);
424 } else if (gpu_mode == kGpuModeDrmVirgl) {
425 crosvm_cmd->AddParameter("--gpu=", gpu_displays_string,
426 "backend=virglrenderer,context-types=virgl2",
427 gpu_common_3d_string);
428 } else if (gpu_mode == kGpuModeGfxstream) {
429 crosvm_cmd->AddParameter(
430 "--gpu=", gpu_displays_string,
431 "context-types=gfxstream-gles:gfxstream-vulkan:gfxstream-composer",
432 gpu_common_3d_string);
433 } else if (gpu_mode == kGpuModeGfxstreamGuestAngle ||
434 gpu_mode == kGpuModeGfxstreamGuestAngleHostSwiftShader) {
435 crosvm_cmd->AddParameter(
436 "--gpu=", gpu_displays_string,
437 "context-types=gfxstream-vulkan:gfxstream-composer",
438 gpu_common_3d_string);
439 } else if (gpu_mode == kGpuModeCustom) {
440 crosvm_cmd->AddParameter("--gpu=", gpu_displays_string,
441 "context-types=" + instance.gpu_context_types(),
442 gpu_common_string);
443 }
444
445 CF_EXPECT(MaybeConfigureVulkanIcd(config, crosvm_cmd));
446
447 return {};
448 }
449
StartCommands(const CuttlefishConfig & config,std::vector<VmmDependencyCommand * > & dependencyCommands)450 Result<std::vector<MonitorCommand>> CrosvmManager::StartCommands(
451 const CuttlefishConfig& config,
452 std::vector<VmmDependencyCommand*>& dependencyCommands) {
453 auto instance = config.ForDefaultInstance();
454 auto environment = config.ForDefaultEnvironment();
455
456 std::vector<MonitorCommand> commands;
457
458 CrosvmBuilder crosvm_cmd;
459 crosvm_cmd.Cmd().AddPrerequisite([&dependencyCommands]() -> Result<void> {
460 for (auto dependencyCommand : dependencyCommands) {
461 CF_EXPECT(dependencyCommand->WaitForAvailability());
462 }
463
464 return {};
465 });
466
467 // Add "--restore_path=<guest snapshot directory>" if there is a snapshot
468 // path supplied.
469 //
470 // Use the process_restarter "-first_time_argument" flag to only do this for
471 // the first invocation. If the guest requests a restart, we don't want crosvm
472 // to restore again. It should reboot normally.
473 std::string first_time_argument;
474 if (IsRestoring(config)) {
475 const std::string snapshot_dir_path = config.snapshot_path();
476 auto meta_info_json = CF_EXPECT(LoadMetaJson(snapshot_dir_path));
477 const std::vector<std::string> selectors{kGuestSnapshotField,
478 instance.id()};
479 const auto guest_snapshot_dir_suffix =
480 CF_EXPECT(GetValue<std::string>(meta_info_json, selectors));
481 // guest_snapshot_dir_suffix is a relative to
482 // the snapshot_path
483 const auto restore_path = snapshot_dir_path + "/" +
484 guest_snapshot_dir_suffix + "/" +
485 kGuestSnapshotBase;
486 first_time_argument = "--restore=" + restore_path;
487 }
488
489 crosvm_cmd.ApplyProcessRestarter(instance.crosvm_binary(),
490 first_time_argument, kCrosvmVmResetExitCode);
491 crosvm_cmd.Cmd().AddParameter("run");
492 crosvm_cmd.AddControlSocket(instance.CrosvmSocketPath(),
493 instance.crosvm_binary());
494
495 if (!instance.smt()) {
496 crosvm_cmd.Cmd().AddParameter("--no-smt");
497 }
498
499 // Disable USB passthrough. It isn't needed for any key use cases and it is
500 // not compatible with crosvm suspend-resume support yet (b/266622743).
501 // TODO: Allow it to be turned back on using a flag.
502 if (!instance.enable_usb()) {
503 crosvm_cmd.Cmd().AddParameter("--no-usb");
504 }
505
506 crosvm_cmd.Cmd().AddParameter("--core-scheduling=false");
507
508 crosvm_cmd.Cmd().AddParameter("--vhost-user-connect-timeout-ms=", 30 * 1000);
509
510 if (instance.vhost_net()) {
511 crosvm_cmd.Cmd().AddParameter("--vhost-net");
512 }
513
514 if (config.virtio_mac80211_hwsim() &&
515 !environment.vhost_user_mac80211_hwsim().empty()) {
516 crosvm_cmd.Cmd().AddParameter("--vhost-user=mac80211-hwsim,socket=",
517 environment.vhost_user_mac80211_hwsim());
518 }
519
520 if (instance.protected_vm()) {
521 crosvm_cmd.Cmd().AddParameter("--protected-vm");
522 }
523
524 if (!instance.crosvm_use_balloon()) {
525 crosvm_cmd.Cmd().AddParameter("--no-balloon");
526 }
527
528 if (!instance.crosvm_use_rng()) {
529 crosvm_cmd.Cmd().AddParameter("--no-rng");
530 }
531
532 if (instance.gdb_port() > 0) {
533 CF_EXPECT(instance.cpus() == 1, "CPUs must be 1 for crosvm gdb mode");
534 crosvm_cmd.Cmd().AddParameter("--gdb=", instance.gdb_port());
535 }
536
537 std::optional<VhostUserDeviceCommands> vhost_user_gpu;
538 if (instance.enable_gpu_vhost_user()) {
539 vhost_user_gpu.emplace(
540 CF_EXPECT(BuildVhostUserGpu(config, &crosvm_cmd.Cmd())));
541 } else {
542 CF_EXPECT(ConfigureGpu(config, &crosvm_cmd.Cmd()));
543 }
544
545 if (instance.hwcomposer() != kHwComposerNone) {
546 const bool pmem_disabled = instance.mte() || !instance.use_pmem();
547 if (!pmem_disabled && FileExists(instance.hwcomposer_pmem_path())) {
548 crosvm_cmd.Cmd().AddParameter("--pmem=path=",
549 instance.hwcomposer_pmem_path());
550 }
551 }
552
553 const auto gpu_capture_enabled = !instance.gpu_capture_binary().empty();
554
555 // crosvm_cmd.Cmd().AddParameter("--null-audio");
556 crosvm_cmd.Cmd().AddParameter("--mem=", instance.memory_mb());
557 if (instance.mte()) {
558 crosvm_cmd.Cmd().AddParameter("--mte");
559 }
560
561 CF_EXPECT(crosvm_cmd.AddCpus(instance.cpus(), instance.vcpu_config_path()));
562
563 auto disk_num = instance.virtual_disk_paths().size();
564 CF_EXPECT(VmManager::kMaxDisks >= disk_num,
565 "Provided too many disks (" << disk_num << "), maximum "
566 << VmManager::kMaxDisks << "supported");
567 size_t disk_i = 0;
568 for (const auto& disk : instance.virtual_disk_paths()) {
569 if (instance.protected_vm()) {
570 crosvm_cmd.AddReadOnlyDisk(disk);
571 } else if (instance.vhost_user_block() && disk_i == 2) {
572 // TODO: b/346855591 - Run on all devices
573 auto block = CF_EXPECT(VhostUserBlockDevice(config, disk_i, disk));
574 commands.emplace_back(std::move(block.device_cmd));
575 commands.emplace_back(std::move(block.device_logs_cmd));
576 auto socket_path = std::move(block.socket_path);
577 crosvm_cmd.Cmd().AddPrerequisite([socket_path]() -> Result<void> {
578 #ifdef __linux__
579 return WaitForUnixSocketListeningWithoutConnect(socket_path,
580 /*timeoutSec=*/30);
581 #else
582 return CF_ERR("Unhandled check if vhost user block ready.");
583 #endif
584 });
585 auto pci_addr = fmt::format("00:{:0>2x}.0", 0x13 + disk_i);
586 crosvm_cmd.Cmd().AddParameter("--vhost-user=block,socket=", socket_path,
587 ",pci-address=", pci_addr);
588 } else {
589 crosvm_cmd.AddReadWriteDisk(disk);
590 }
591 disk_i++;
592 }
593
594 if (instance.enable_webrtc()) {
595 bool is_chromeos =
596 instance.boot_flow() ==
597 CuttlefishConfig::InstanceSpecific::BootFlow::ChromeOs ||
598 instance.boot_flow() ==
599 CuttlefishConfig::InstanceSpecific::BootFlow::ChromeOsDisk;
600 auto touch_type_parameter =
601 is_chromeos ? "single-touch" : "multi-touch";
602
603 auto display_configs = instance.display_configs();
604 CF_EXPECT(display_configs.size() >= 1);
605
606 int touch_idx = 0;
607 for (auto& display_config : display_configs) {
608 crosvm_cmd.Cmd().AddParameter(
609 "--input=", touch_type_parameter, "[path=",
610 instance.touch_socket_path(touch_idx++),
611 ",width=", display_config.width,
612 ",height=", display_config.height, "]");
613 }
614 auto touchpad_configs = instance.touchpad_configs();
615 for (int i = 0; i < touchpad_configs.size(); ++i) {
616 auto touchpad_config = touchpad_configs[i];
617 crosvm_cmd.Cmd().AddParameter(
618 "--input=", touch_type_parameter, "[path=",
619 instance.touch_socket_path(touch_idx++),
620 ",width=", touchpad_config.width,
621 ",height=", touchpad_config.height,
622 ",name=", kTouchpadDefaultPrefix, i, "]");
623 }
624 if (instance.enable_mouse()) {
625 crosvm_cmd.Cmd().AddParameter(
626 "--input=mouse[path=", instance.mouse_socket_path(), "]");
627 }
628 crosvm_cmd.Cmd().AddParameter("--input=rotary[path=",
629 instance.rotary_socket_path(), "]");
630 crosvm_cmd.Cmd().AddParameter("--input=keyboard[path=",
631 instance.keyboard_socket_path(), "]");
632 crosvm_cmd.Cmd().AddParameter("--input=switches[path=",
633 instance.switches_socket_path(), "]");
634 }
635
636 // GPU capture can only support named files and not file descriptors due to
637 // having to pass arguments to crosvm via a wrapper script.
638 #ifdef __linux__
639 if (!gpu_capture_enabled) {
640 // The PCI ordering of tap devices is important. Make sure any change here
641 // is reflected in ethprime u-boot variable.
642 // TODO(b/218364216, b/322862402): Crosvm occupies 32 PCI devices first and only then uses PCI
643 // functions which may break order. The final solution is going to be a PCI allocation strategy
644 // that will guarantee the ordering. For now, hardcode PCI network devices to unoccupied
645 // functions.
646 const pci::Address mobile_pci = pci::Address(0, VmManager::kNetPciDeviceNum, 1);
647 const pci::Address ethernet_pci = pci::Address(0, VmManager::kNetPciDeviceNum, 2);
648 crosvm_cmd.AddTap(instance.mobile_tap_name(), instance.mobile_mac(), mobile_pci);
649 crosvm_cmd.AddTap(instance.ethernet_tap_name(), instance.ethernet_mac(), ethernet_pci);
650
651 if (!config.virtio_mac80211_hwsim() && environment.enable_wifi()) {
652 crosvm_cmd.AddTap(instance.wifi_tap_name());
653 }
654 }
655 #endif
656
657 const bool pmem_disabled = instance.mte() || !instance.use_pmem();
658 if (!pmem_disabled && FileExists(instance.access_kregistry_path())) {
659 crosvm_cmd.Cmd().AddParameter("--pmem=path=",
660 instance.access_kregistry_path());
661 }
662
663 if (!pmem_disabled && FileExists(instance.pstore_path())) {
664 crosvm_cmd.Cmd().AddParameter("--pstore=path=", instance.pstore_path(),
665 ",size=", FileSize(instance.pstore_path()));
666 }
667
668 if (instance.enable_sandbox()) {
669 const bool seccomp_exists = DirectoryExists(instance.seccomp_policy_dir());
670 const std::string& var_empty_dir = kCrosvmVarEmptyDir;
671 const bool var_empty_available = DirectoryExists(var_empty_dir);
672 CF_EXPECT(var_empty_available && seccomp_exists,
673 var_empty_dir << " is not an existing, empty directory."
674 << "seccomp-policy-dir, "
675 << instance.seccomp_policy_dir()
676 << " does not exist");
677 crosvm_cmd.Cmd().AddParameter("--seccomp-policy-dir=",
678 instance.seccomp_policy_dir());
679 } else {
680 crosvm_cmd.Cmd().AddParameter("--disable-sandbox");
681 }
682
683 if (instance.vsock_guest_cid() >= 2) {
684 if (instance.vhost_user_vsock()) {
685 auto param =
686 fmt::format("/tmp/vsock_{}_{}/vhost.socket,max-queue-size=256",
687 instance.vsock_guest_cid(), std::to_string(getuid()));
688 crosvm_cmd.Cmd().AddParameter("--vhost-user=vsock,socket=", param);
689 } else {
690 crosvm_cmd.Cmd().AddParameter("--cid=", instance.vsock_guest_cid());
691 }
692 }
693
694 // /dev/hvc0 = kernel console
695 // If kernel log is enabled, the virtio-console port will be specified as
696 // a true console for Linux, and kernel messages will be printed there.
697 // Otherwise, the port will still be set up for bootloader and userspace
698 // messages, but the kernel will not print anything here. This keeps our
699 // kernel log event features working. If an alternative "earlycon" boot
700 // console is configured below on a legacy serial port, it will control
701 // the main log until the virtio-console takes over.
702 crosvm_cmd.AddHvcReadOnly(instance.kernel_log_pipe_name(),
703 instance.enable_kernel_log());
704
705 // /dev/hvc1 = serial console
706 if (instance.console()) {
707 // stdin is the only currently supported way to write data to a serial port
708 // in crosvm. A file (named pipe) is used here instead of stdout to ensure
709 // only the serial port output is received by the console forwarder as
710 // crosvm may print other messages to stdout.
711 if (instance.kgdb() || instance.use_bootloader()) {
712 crosvm_cmd.AddSerialConsoleReadWrite(instance.console_out_pipe_name(),
713 instance.console_in_pipe_name(),
714 instance.enable_kernel_log());
715 // In kgdb mode, we have the interactive console on ttyS0 (both Android's
716 // console and kdb), so we can disable the virtio-console port usually
717 // allocated to Android's serial console, and redirect it to a sink. This
718 // ensures that that the PCI device assignments (and thus sepolicy) don't
719 // have to change
720 crosvm_cmd.AddHvcSink();
721 } else {
722 crosvm_cmd.AddSerialSink();
723 crosvm_cmd.AddHvcReadWrite(instance.console_out_pipe_name(),
724 instance.console_in_pipe_name());
725 }
726 } else {
727 // Use an 8250 UART (ISA or platform device) for earlycon, as the
728 // virtio-console driver may not be available for early messages
729 // In kgdb mode, earlycon is an interactive console, and so early
730 // dmesg will go there instead of the kernel.log
731 if (instance.enable_kernel_log() &&
732 (instance.kgdb() || instance.use_bootloader())) {
733 crosvm_cmd.AddSerialConsoleReadOnly(instance.kernel_log_pipe_name());
734 }
735
736 // as above, create a fake virtio-console 'sink' port when the serial
737 // console is disabled, so the PCI device ID assignments don't move
738 // around
739 crosvm_cmd.AddHvcSink();
740 }
741
742 auto crosvm_logs_path = instance.PerInstanceInternalPath("crosvm.fifo");
743 auto crosvm_logs = CF_EXPECT(SharedFD::Fifo(crosvm_logs_path, 0666));
744
745 Command crosvm_log_tee_cmd(HostBinaryPath("log_tee"));
746 crosvm_log_tee_cmd.AddParameter("--process_name=crosvm");
747 crosvm_log_tee_cmd.AddParameter("--log_fd_in=", crosvm_logs);
748 crosvm_log_tee_cmd.SetStopper(KillSubprocessFallback([](Subprocess* proc) {
749 // Ask nicely so that log_tee gets a chance to process all the logs.
750 bool res = kill(proc->pid(), SIGINT) == 0;
751 // TODO: b/335934714 - Make sure the process actually exits
752 return res ? StopperResult::kStopSuccess : StopperResult::kStopFailure;
753 }));
754
755 // /dev/hvc2 = serial logging
756 // Serial port for logcat, redirected to a pipe
757 crosvm_cmd.AddHvcReadOnly(instance.logcat_pipe_name());
758
759 // /dev/hvc3 = keymaster (C++ implementation)
760 crosvm_cmd.AddHvcReadWrite(
761 instance.PerInstanceInternalPath("keymaster_fifo_vm.out"),
762 instance.PerInstanceInternalPath("keymaster_fifo_vm.in"));
763 // /dev/hvc4 = gatekeeper
764 crosvm_cmd.AddHvcReadWrite(
765 instance.PerInstanceInternalPath("gatekeeper_fifo_vm.out"),
766 instance.PerInstanceInternalPath("gatekeeper_fifo_vm.in"));
767
768 // /dev/hvc5 = bt
769 if (config.enable_host_bluetooth()) {
770 crosvm_cmd.AddHvcReadWrite(
771 instance.PerInstanceInternalPath("bt_fifo_vm.out"),
772 instance.PerInstanceInternalPath("bt_fifo_vm.in"));
773 } else {
774 crosvm_cmd.AddHvcSink();
775 }
776
777 // /dev/hvc6 = gnss
778 // /dev/hvc7 = location
779 if (instance.enable_gnss_grpc_proxy()) {
780 crosvm_cmd.AddHvcReadWrite(
781 instance.PerInstanceInternalPath("gnsshvc_fifo_vm.out"),
782 instance.PerInstanceInternalPath("gnsshvc_fifo_vm.in"));
783 crosvm_cmd.AddHvcReadWrite(
784 instance.PerInstanceInternalPath("locationhvc_fifo_vm.out"),
785 instance.PerInstanceInternalPath("locationhvc_fifo_vm.in"));
786 } else {
787 for (auto i = 0; i < 2; i++) {
788 crosvm_cmd.AddHvcSink();
789 }
790 }
791
792 // /dev/hvc8 = confirmationui
793 crosvm_cmd.AddHvcReadWrite(
794 instance.PerInstanceInternalPath("confui_fifo_vm.out"),
795 instance.PerInstanceInternalPath("confui_fifo_vm.in"));
796
797 // /dev/hvc9 = uwb
798 if (config.enable_host_uwb()) {
799 crosvm_cmd.AddHvcReadWrite(
800 instance.PerInstanceInternalPath("uwb_fifo_vm.out"),
801 instance.PerInstanceInternalPath("uwb_fifo_vm.in"));
802 } else {
803 crosvm_cmd.AddHvcSink();
804 }
805
806 // /dev/hvc10 = oemlock
807 crosvm_cmd.AddHvcReadWrite(
808 instance.PerInstanceInternalPath("oemlock_fifo_vm.out"),
809 instance.PerInstanceInternalPath("oemlock_fifo_vm.in"));
810
811 // /dev/hvc11 = keymint (Rust implementation)
812 crosvm_cmd.AddHvcReadWrite(
813 instance.PerInstanceInternalPath("keymint_fifo_vm.out"),
814 instance.PerInstanceInternalPath("keymint_fifo_vm.in"));
815
816 // /dev/hvc12 = NFC
817 if (config.enable_host_nfc()) {
818 crosvm_cmd.AddHvcReadWrite(
819 instance.PerInstanceInternalPath("nfc_fifo_vm.out"),
820 instance.PerInstanceInternalPath("nfc_fifo_vm.in"));
821 } else {
822 crosvm_cmd.AddHvcSink();
823 }
824
825
826 // /dev/hvc13 = sensors
827 crosvm_cmd.AddHvcReadWrite(
828 instance.PerInstanceInternalPath("sensors_fifo_vm.out"),
829 instance.PerInstanceInternalPath("sensors_fifo_vm.in"));
830
831 // /dev/hvc14 = MCU CONTROL
832 if (instance.mcu()["control"]["type"].asString() == "serial") {
833 auto path = instance.PerInstanceInternalPath("mcu");
834 path += "/" + instance.mcu()["control"]["path"].asString();
835 crosvm_cmd.AddHvcReadWrite(path, path);
836 } else {
837 crosvm_cmd.AddHvcSink();
838 }
839
840 // /dev/hvc15 = MCU UART
841 if (instance.mcu()["uart0"]["type"].asString() == "serial") {
842 auto path = instance.PerInstanceInternalPath("mcu");
843 path += "/" + instance.mcu()["uart0"]["path"].asString();
844 crosvm_cmd.AddHvcReadWrite(path, path);
845 } else {
846 crosvm_cmd.AddHvcSink();
847 }
848
849 for (auto i = 0; i < VmManager::kMaxDisks - disk_num; i++) {
850 crosvm_cmd.AddHvcSink();
851 }
852 CF_EXPECT(crosvm_cmd.HvcNum() + disk_num ==
853 VmManager::kMaxDisks + VmManager::kDefaultNumHvcs,
854 "HVC count (" << crosvm_cmd.HvcNum() << ") + disk count ("
855 << disk_num << ") is not the expected total of "
856 << VmManager::kMaxDisks + VmManager::kDefaultNumHvcs
857 << " devices");
858
859 if (instance.enable_audio()) {
860 crosvm_cmd.Cmd().AddParameter("--sound=", instance.audio_server_path());
861 }
862
863 // TODO(b/162071003): virtiofs crashes without sandboxing, this should be
864 // fixed
865 if (instance.enable_virtiofs()) {
866 CF_EXPECT(instance.enable_sandbox(),
867 "virtiofs is currently not supported without sandboxing");
868 // Set up directory shared with virtiofs, setting security_ctx option to
869 // false prevents host error when unable to write data in the
870 // /proc/thread-self/attr/fscreate file.
871 crosvm_cmd.Cmd().AddParameter(
872 "--shared-dir=", instance.PerInstancePath(kSharedDirName),
873 ":shared:type=fs:security_ctx=false");
874 }
875
876 if (instance.target_arch() == Arch::X86_64) {
877 crosvm_cmd.Cmd().AddParameter("--pflash=", instance.pflash_path());
878 }
879
880 // This needs to be the last parameter
881 crosvm_cmd.Cmd().AddParameter("--bios=", instance.bootloader());
882
883 if (vhost_user_gpu) {
884 // The vhost user gpu crosvm command should be added before the main
885 // crosvm command so that the main crosvm command can use a prerequisite
886 // to wait for the communication socket to be ready.
887 commands.emplace_back(std::move(vhost_user_gpu->device_cmd));
888 commands.emplace_back(std::move(vhost_user_gpu->device_logs_cmd));
889 }
890
891 // log_tee must be added before crosvm_cmd to ensure all of crosvm's logs are
892 // captured during shutdown. Processes are stopped in reverse order.
893 commands.emplace_back(std::move(crosvm_log_tee_cmd));
894
895 if (gpu_capture_enabled) {
896 const std::string gpu_capture_basename =
897 android::base::Basename(instance.gpu_capture_binary());
898
899 auto gpu_capture_logs_path =
900 instance.PerInstanceInternalPath("gpu_capture.fifo");
901 auto gpu_capture_logs =
902 CF_EXPECT(SharedFD::Fifo(gpu_capture_logs_path, 0666));
903
904 Command gpu_capture_log_tee_cmd(HostBinaryPath("log_tee"));
905 gpu_capture_log_tee_cmd.AddParameter("--process_name=",
906 gpu_capture_basename);
907 gpu_capture_log_tee_cmd.AddParameter("--log_fd_in=", gpu_capture_logs);
908
909 Command gpu_capture_command(instance.gpu_capture_binary());
910 if (gpu_capture_basename == "ngfx") {
911 // Crosvm depends on command line arguments being passed as multiple
912 // arguments but ngfx only allows a single `--args`. To work around this,
913 // create a wrapper script that launches crosvm with all of the arguments
914 // and pass this wrapper script to ngfx.
915 const std::string crosvm_wrapper_path =
916 instance.PerInstanceInternalPath("crosvm_wrapper.sh");
917 const std::string crosvm_wrapper_content =
918 crosvm_cmd.Cmd().AsBashScript(crosvm_logs_path);
919
920 CF_EXPECT(android::base::WriteStringToFile(crosvm_wrapper_content,
921 crosvm_wrapper_path));
922 CF_EXPECT(MakeFileExecutable(crosvm_wrapper_path));
923
924 gpu_capture_command.AddParameter("--exe=", crosvm_wrapper_path);
925 gpu_capture_command.AddParameter("--launch-detached");
926 gpu_capture_command.AddParameter("--verbose");
927 gpu_capture_command.AddParameter("--activity=Frame Debugger");
928 } else {
929 // TODO(natsu): renderdoc
930 return CF_ERR(
931 "Unhandled GPU capture binary: " << instance.gpu_capture_binary());
932 }
933
934 gpu_capture_command.RedirectStdIO(Subprocess::StdIOChannel::kStdOut,
935 gpu_capture_logs);
936 gpu_capture_command.RedirectStdIO(Subprocess::StdIOChannel::kStdErr,
937 gpu_capture_logs);
938
939 commands.emplace_back(std::move(gpu_capture_log_tee_cmd));
940 commands.emplace_back(std::move(gpu_capture_command));
941 } else {
942 crosvm_cmd.Cmd().RedirectStdIO(Subprocess::StdIOChannel::kStdOut,
943 crosvm_logs);
944 crosvm_cmd.Cmd().RedirectStdIO(Subprocess::StdIOChannel::kStdErr,
945 crosvm_logs);
946 commands.emplace_back(std::move(crosvm_cmd.Cmd()), true);
947 }
948
949 return commands;
950 }
951
WaitForRestoreComplete(SharedFD stop_fd) const952 Result<bool> CrosvmManager::WaitForRestoreComplete(SharedFD stop_fd) const {
953 auto instance = CF_EXPECT(CuttlefishConfig::Get())->ForDefaultInstance();
954
955 // Wait for the control socket to exist. It is created early in crosvm's
956 // startup sequence, but the process may not even have been exec'd by CF at
957 // this point.
958 while (!FileExists(instance.CrosvmSocketPath())) {
959 std::vector<PollSharedFd> poll = {{.fd = stop_fd, .events = POLLIN}};
960 const int result = SharedFD::Poll(poll, 50 /* ms */);
961 // Check for errors.
962 CF_EXPECT(result >= 0, "failed to wait on stop_fd: " << strerror(errno));
963 // Check if pipe became readable or closed.
964 if (result > 0) {
965 return false;
966 }
967 }
968
969 // Ask crosvm to resume the VM. crosvm promises to not complete this command
970 // until the vCPUs are started (even if it was never suspended to begin
971 // with).
972 auto infop = CF_EXPECT(Execute(
973 std::vector<std::string>{
974 instance.crosvm_binary(),
975 "resume",
976 instance.CrosvmSocketPath(),
977 "--full",
978 },
979 SubprocessOptions(), WEXITED));
980 CF_EXPECT_EQ(infop.si_code, CLD_EXITED);
981 CF_EXPECTF(infop.si_status == 0, "crosvm resume returns non zero code {}",
982 infop.si_status);
983 return true;
984 }
985
986 } // namespace vm_manager
987 } // namespace cuttlefish
988