• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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/qemu_manager.h"
18 
19 #include <string.h>
20 #include <sys/socket.h>
21 #include <sys/stat.h>
22 #include <sys/types.h>
23 #include <sys/un.h>
24 #include <sys/wait.h>
25 #include <unistd.h>
26 
27 #include <cstdlib>
28 #include <sstream>
29 #include <string>
30 #include <thread>
31 #include <unordered_map>
32 #include <utility>
33 #include <vector>
34 
35 #include <android-base/strings.h>
36 #include <android-base/logging.h>
37 #include <vulkan/vulkan.h>
38 
39 #include "common/libs/fs/shared_select.h"
40 #include "common/libs/utils/files.h"
41 #include "common/libs/utils/result.h"
42 #include "common/libs/utils/subprocess.h"
43 #include "common/libs/utils/users.h"
44 #include "host/libs/config/command_source.h"
45 #include "host/libs/config/cuttlefish_config.h"
46 #include "host/libs/config/known_paths.h"
47 
48 namespace cuttlefish {
49 namespace vm_manager {
50 namespace {
51 
GetMonitorPath(const CuttlefishConfig & config)52 std::string GetMonitorPath(const CuttlefishConfig& config) {
53   return config.ForDefaultInstance().PerInstanceInternalUdsPath(
54       "qemu_monitor.sock");
55 }
56 
LogAndSetEnv(const char * key,const std::string & value)57 void LogAndSetEnv(const char* key, const std::string& value) {
58   setenv(key, value.c_str(), 1);
59   LOG(INFO) << key << "=" << value;
60 }
61 
Stop()62 bool Stop() {
63   auto config = CuttlefishConfig::Get();
64   auto monitor_path = GetMonitorPath(*config);
65   auto monitor_sock = SharedFD::SocketLocalClient(
66       monitor_path.c_str(), false, SOCK_STREAM);
67 
68   if (!monitor_sock->IsOpen()) {
69     LOG(ERROR) << "The connection to qemu is closed, is it still running?";
70     return false;
71   }
72   char msg[] = "{\"execute\":\"qmp_capabilities\"}{\"execute\":\"quit\"}";
73   ssize_t len = sizeof(msg) - 1;
74   while (len > 0) {
75     int tmp = monitor_sock->Write(msg, len);
76     if (tmp < 0) {
77       LOG(ERROR) << "Error writing to socket: " << monitor_sock->StrError();
78       return false;
79     }
80     len -= tmp;
81   }
82   // Log the reply
83   char buff[1000];
84   while ((len = monitor_sock->Read(buff, sizeof(buff) - 1)) > 0) {
85     buff[len] = '\0';
86     LOG(INFO) << "From qemu monitor: " << buff;
87   }
88 
89   return true;
90 }
91 
GetQemuVersion(const std::string & qemu_binary)92 Result<std::pair<int, int>> GetQemuVersion(const std::string& qemu_binary) {
93   Command qemu_version_cmd(qemu_binary);
94   qemu_version_cmd.AddParameter("-version");
95 
96   std::string qemu_version_input, qemu_version_output, qemu_version_error;
97   cuttlefish::SubprocessOptions options;
98   options.Verbose(false);
99   int qemu_version_ret =
100       cuttlefish::RunWithManagedStdio(std::move(qemu_version_cmd),
101                                       &qemu_version_input,
102                                       &qemu_version_output,
103                                       &qemu_version_error, options);
104   CF_EXPECT(qemu_version_ret == 0,
105             qemu_binary << " -version returned unexpected response "
106                         << qemu_version_output << ". Stderr was "
107                         << qemu_version_error);
108 
109   // Snip around the extra text we don't care about
110   qemu_version_output.erase(0, std::string("QEMU emulator version ").length());
111   auto space_pos = qemu_version_output.find(" ", 0);
112   if (space_pos != std::string::npos) {
113     qemu_version_output.resize(space_pos);
114   }
115 
116   auto qemu_version_bits = android::base::Split(qemu_version_output, ".");
117   return {{std::stoi(qemu_version_bits[0]), std::stoi(qemu_version_bits[1])}};
118 }
119 
120 }  // namespace
121 
QemuManager(Arch arch)122 QemuManager::QemuManager(Arch arch) : arch_(arch) {}
123 
IsSupported()124 bool QemuManager::IsSupported() {
125   return HostSupportsQemuCli();
126 }
127 
128 Result<std::unordered_map<std::string, std::string>>
ConfigureGraphics(const CuttlefishConfig::InstanceSpecific & instance)129 QemuManager::ConfigureGraphics(
130     const CuttlefishConfig::InstanceSpecific& instance) {
131   // Override the default HAL search paths in all cases. We do this because
132   // the HAL search path allows for fallbacks, and fallbacks in conjunction
133   // with properities lead to non-deterministic behavior while loading the
134   // HALs.
135 
136   std::unordered_map<std::string, std::string> bootconfig_args;
137 
138   if (instance.gpu_mode() == kGpuModeGuestSwiftshader) {
139     bootconfig_args = {
140         {"androidboot.cpuvulkan.version", std::to_string(VK_API_VERSION_1_2)},
141         {"androidboot.hardware.gralloc", "minigbm"},
142         {"androidboot.hardware.hwcomposer", instance.hwcomposer()},
143         {"androidboot.hardware.egl", "angle"},
144         {"androidboot.hardware.vulkan", "pastel"},
145         // OpenGL ES 3.1
146         {"androidboot.opengles.version", "196609"},
147     };
148   } else if (instance.gpu_mode() == kGpuModeDrmVirgl) {
149     bootconfig_args = {
150         {"androidboot.cpuvulkan.version", "0"},
151         {"androidboot.hardware.gralloc", "minigbm"},
152         {"androidboot.hardware.hwcomposer", "ranchu"},
153         {"androidboot.hardware.hwcomposer.mode", "client"},
154         {"androidboot.hardware.egl", "mesa"},
155         // No "hardware" Vulkan support, yet
156         // OpenGL ES 3.0
157         {"androidboot.opengles.version", "196608"},
158     };
159   } else if (instance.gpu_mode() == kGpuModeGfxstream ||
160              instance.gpu_mode() == kGpuModeGfxstreamGuestAngle) {
161     const bool uses_angle = instance.gpu_mode() == kGpuModeGfxstreamGuestAngle;
162     const std::string gles_impl = uses_angle ? "angle" : "emulation";
163     const std::string gltransport =
164         (instance.guest_android_version() == "11.0.0") ? "virtio-gpu-pipe"
165                                                        : "virtio-gpu-asg";
166     bootconfig_args = {
167         {"androidboot.cpuvulkan.version", "0"},
168         {"androidboot.hardware.gralloc", "minigbm"},
169         {"androidboot.hardware.hwcomposer", instance.hwcomposer()},
170         {"androidboot.hardware.hwcomposer.display_finder_mode", "drm"},
171         {"androidboot.hardware.egl", gles_impl},
172         {"androidboot.hardware.vulkan", "ranchu"},
173         {"androidboot.hardware.gltransport", gltransport},
174         {"androidboot.opengles.version", "196609"},  // OpenGL ES 3.1
175     };
176   } else if (instance.gpu_mode() == kGpuModeNone) {
177     return {};
178   } else {
179     return CF_ERR("Unhandled GPU mode: " << instance.gpu_mode());
180   }
181 
182   if (!instance.gpu_angle_feature_overrides_enabled().empty()) {
183     bootconfig_args["androidboot.hardware.angle_feature_overrides_enabled"] =
184         instance.gpu_angle_feature_overrides_enabled();
185   }
186   if (!instance.gpu_angle_feature_overrides_disabled().empty()) {
187     bootconfig_args["androidboot.hardware.angle_feature_overrides_disabled"] =
188         instance.gpu_angle_feature_overrides_disabled();
189   }
190 
191   return bootconfig_args;
192 }
193 
194 Result<std::unordered_map<std::string, std::string>>
ConfigureBootDevices(int num_disks,bool have_gpu)195 QemuManager::ConfigureBootDevices(int num_disks, bool have_gpu) {
196   switch (arch_) {
197     case Arch::Arm:
198       return {{{"androidboot.boot_devices", "3f000000.pcie"}}};
199     case Arch::Arm64:
200       return {{{"androidboot.boot_devices", "4010000000.pcie"}}};
201     case Arch::RiscV64:
202       return {{{"androidboot.boot_devices", "soc/30000000.pci"}}};
203     case Arch::X86:
204     case Arch::X86_64: {
205       // QEMU has additional PCI devices for an ISA bridge and PIIX4
206       // virtio_gpu precedes the first console or disk
207       return ConfigureMultipleBootDevices("pci0000:00/0000:00:",
208                                           2 + (have_gpu ? 1 : 0), num_disks);
209     }
210   }
211 }
212 
StartCommands(const CuttlefishConfig & config)213 Result<std::vector<MonitorCommand>> QemuManager::StartCommands(
214     const CuttlefishConfig& config) {
215   auto instance = config.ForDefaultInstance();
216 
217   auto stop = [](Subprocess* proc) {
218     auto stopped = Stop();
219     if (stopped) {
220       return StopperResult::kStopSuccess;
221     }
222     LOG(WARNING) << "Failed to stop VMM nicely, "
223                   << "attempting to KILL";
224     return KillSubprocess(proc) == StopperResult::kStopSuccess
225                ? StopperResult::kStopCrash
226                : StopperResult::kStopFailure;
227   };
228   std::string qemu_binary = instance.qemu_binary_dir();
229   switch (arch_) {
230     case Arch::Arm:
231       qemu_binary += "/qemu-system-arm";
232       break;
233     case Arch::Arm64:
234       qemu_binary += "/qemu-system-aarch64";
235       break;
236     case Arch::RiscV64:
237       qemu_binary += "/qemu-system-riscv64";
238       break;
239     case Arch::X86:
240       qemu_binary += "/qemu-system-i386";
241       break;
242     case Arch::X86_64:
243       qemu_binary += "/qemu-system-x86_64";
244       break;
245   }
246 
247   auto qemu_version = CF_EXPECT(GetQemuVersion(qemu_binary));
248   Command qemu_cmd(qemu_binary, stop);
249 
250   int hvc_num = 0;
251   int serial_num = 0;
252   auto add_hvc_sink = [&qemu_cmd, &hvc_num]() {
253     qemu_cmd.AddParameter("-chardev");
254     qemu_cmd.AddParameter("null,id=hvc", hvc_num);
255     qemu_cmd.AddParameter("-device");
256     qemu_cmd.AddParameter(
257         "virtio-serial-pci-non-transitional,max_ports=1,id=virtio-serial",
258         hvc_num);
259     qemu_cmd.AddParameter("-device");
260     qemu_cmd.AddParameter("virtconsole,bus=virtio-serial", hvc_num,
261                           ".0,chardev=hvc", hvc_num);
262     hvc_num++;
263   };
264   auto add_serial_sink = [&qemu_cmd, &serial_num]() {
265     qemu_cmd.AddParameter("-chardev");
266     qemu_cmd.AddParameter("null,id=serial", serial_num);
267     qemu_cmd.AddParameter("-serial");
268     qemu_cmd.AddParameter("chardev:serial", serial_num);
269     serial_num++;
270   };
271   auto add_serial_console_ro = [&qemu_cmd,
272                                 &serial_num](const std::string& output) {
273     qemu_cmd.AddParameter("-chardev");
274     qemu_cmd.AddParameter("file,id=serial", serial_num, ",path=", output,
275                           ",append=on");
276     qemu_cmd.AddParameter("-serial");
277     qemu_cmd.AddParameter("chardev:serial", serial_num);
278     serial_num++;
279   };
280   auto add_serial_console = [&qemu_cmd,
281                              &serial_num](const std::string& prefix) {
282     qemu_cmd.AddParameter("-chardev");
283     qemu_cmd.AddParameter("pipe,id=serial", serial_num, ",path=", prefix);
284     qemu_cmd.AddParameter("-serial");
285     qemu_cmd.AddParameter("chardev:serial", serial_num);
286     serial_num++;
287   };
288   auto add_hvc_ro = [&qemu_cmd, &hvc_num](const std::string& output) {
289     qemu_cmd.AddParameter("-chardev");
290     qemu_cmd.AddParameter("file,id=hvc", hvc_num, ",path=", output,
291                           ",append=on");
292     qemu_cmd.AddParameter("-device");
293     qemu_cmd.AddParameter(
294         "virtio-serial-pci-non-transitional,max_ports=1,id=virtio-serial",
295         hvc_num);
296     qemu_cmd.AddParameter("-device");
297     qemu_cmd.AddParameter("virtconsole,bus=virtio-serial", hvc_num,
298                           ".0,chardev=hvc", hvc_num);
299     hvc_num++;
300   };
301   auto add_hvc = [&qemu_cmd, &hvc_num](const std::string& prefix) {
302     qemu_cmd.AddParameter("-chardev");
303     qemu_cmd.AddParameter("pipe,id=hvc", hvc_num, ",path=", prefix);
304     qemu_cmd.AddParameter("-device");
305     qemu_cmd.AddParameter(
306         "virtio-serial-pci-non-transitional,max_ports=1,id=virtio-serial",
307         hvc_num);
308     qemu_cmd.AddParameter("-device");
309     qemu_cmd.AddParameter("virtconsole,bus=virtio-serial", hvc_num,
310                           ".0,chardev=hvc", hvc_num);
311     hvc_num++;
312   };
313 
314   bool is_arm = arch_ == Arch::Arm || arch_ == Arch::Arm64;
315   bool is_x86 = arch_ == Arch::X86 || arch_ == Arch::X86_64;
316   bool is_riscv64 = arch_ == Arch::RiscV64;
317 
318   auto access_kregistry_size_bytes = 0;
319   if (FileExists(instance.access_kregistry_path())) {
320     access_kregistry_size_bytes = FileSize(instance.access_kregistry_path());
321     CF_EXPECT((access_kregistry_size_bytes & (1024 * 1024 - 1)) == 0,
322               instance.access_kregistry_path()
323                   << " file size (" << access_kregistry_size_bytes
324                   << ") not a multiple of 1MB");
325   }
326 
327   auto hwcomposer_pmem_size_bytes = 0;
328   if (instance.hwcomposer() != kHwComposerNone) {
329     if (FileExists(instance.hwcomposer_pmem_path())) {
330       hwcomposer_pmem_size_bytes = FileSize(instance.hwcomposer_pmem_path());
331       CF_EXPECT((hwcomposer_pmem_size_bytes & (1024 * 1024 - 1)) == 0,
332                 instance.hwcomposer_pmem_path()
333                     << " file size (" << hwcomposer_pmem_size_bytes
334                     << ") not a multiple of 1MB");
335     }
336   }
337 
338   auto pstore_size_bytes = 0;
339   if (FileExists(instance.pstore_path())) {
340     pstore_size_bytes = FileSize(instance.pstore_path());
341     CF_EXPECT((pstore_size_bytes & (1024 * 1024 - 1)) == 0,
342               instance.pstore_path() << " file size (" << pstore_size_bytes
343                                      << ") not a multiple of 1MB");
344   }
345 
346   qemu_cmd.AddParameter("-name");
347   qemu_cmd.AddParameter("guest=", instance.instance_name(), ",debug-threads=on");
348 
349   qemu_cmd.AddParameter("-machine");
350   std::string machine = is_x86 ? "pc,nvdimm=on" : "virt";
351   if (IsHostCompatible(arch_)) {
352     machine += ",accel=kvm";
353     if (is_arm) {
354       machine += ",gic-version=3";
355     }
356   } else if (is_arm) {
357     // QEMU doesn't support GICv3 with TCG yet
358     machine += ",gic-version=2";
359     CF_EXPECT(instance.cpus() <= 8, "CPUs must be no more than 8 with GICv2");
360   }
361   if (instance.mte()) {
362     machine += ",mte=on";
363   }
364   qemu_cmd.AddParameter(machine, ",usb=off,dump-guest-core=off");
365 
366   qemu_cmd.AddParameter("-m");
367   auto maxmem = instance.memory_mb() +
368                 (access_kregistry_size_bytes / 1024 / 1024) +
369                 (hwcomposer_pmem_size_bytes / 1024 / 1024) +
370                 (is_x86 ? pstore_size_bytes / 1024 / 1024 : 0);
371   auto slots = is_x86 ? ",slots=2" : "";
372   qemu_cmd.AddParameter("size=", instance.memory_mb(), "M",
373                         ",maxmem=", maxmem, "M", slots);
374 
375   qemu_cmd.AddParameter("-overcommit");
376   qemu_cmd.AddParameter("mem-lock=off");
377 
378   // Assume SMT is always 2 threads per core, which is how most hardware
379   // today is configured, and the way crosvm does it
380   qemu_cmd.AddParameter("-smp");
381   if (instance.smt()) {
382     CF_EXPECT(instance.cpus() % 2 == 0,
383               "CPUs must be a multiple of 2 in SMT mode");
384     qemu_cmd.AddParameter(instance.cpus(), ",cores=",
385                           instance.cpus() / 2, ",threads=2");
386   } else {
387     qemu_cmd.AddParameter(instance.cpus(), ",cores=",
388                           instance.cpus(), ",threads=1");
389   }
390 
391   qemu_cmd.AddParameter("-uuid");
392   qemu_cmd.AddParameter(instance.uuid());
393 
394   qemu_cmd.AddParameter("-no-user-config");
395   qemu_cmd.AddParameter("-nodefaults");
396   qemu_cmd.AddParameter("-no-shutdown");
397 
398   qemu_cmd.AddParameter("-rtc");
399   qemu_cmd.AddParameter("base=utc");
400 
401   qemu_cmd.AddParameter("-boot");
402   qemu_cmd.AddParameter("strict=on");
403 
404   qemu_cmd.AddParameter("-chardev");
405   qemu_cmd.AddParameter("socket,id=charmonitor,path=", GetMonitorPath(config),
406                         ",server=on,wait=off");
407 
408   qemu_cmd.AddParameter("-mon");
409   qemu_cmd.AddParameter("chardev=charmonitor,id=monitor,mode=control");
410 
411   if (instance.gpu_mode() == kGpuModeDrmVirgl) {
412     qemu_cmd.AddParameter("-display");
413     qemu_cmd.AddParameter("egl-headless");
414 
415     qemu_cmd.AddParameter("-vnc");
416     qemu_cmd.AddParameter("127.0.0.1:", instance.qemu_vnc_server_port());
417   } else if (instance.gpu_mode() == kGpuModeGfxstream ||
418              instance.gpu_mode() == kGpuModeGfxstreamGuestAngle) {
419     qemu_cmd.AddParameter("-vnc");
420     qemu_cmd.AddParameter("127.0.0.1:", instance.qemu_vnc_server_port());
421   } else {
422     qemu_cmd.AddParameter("-display");
423     qemu_cmd.AddParameter("none");
424   }
425 
426   if (instance.hwcomposer() != kHwComposerNone) {
427     auto display_configs = instance.display_configs();
428     CF_EXPECT(display_configs.size() >= 1);
429     auto display_config = display_configs[0];
430 
431     qemu_cmd.AddParameter("-device");
432 
433     std::string gpu_device;
434     if (instance.gpu_mode() == kGpuModeGuestSwiftshader ||
435         qemu_version.first < 6) {
436         gpu_device = "virtio-gpu-pci";
437     } else if (instance.gpu_mode() == kGpuModeDrmVirgl) {
438         gpu_device = "virtio-gpu-gl-pci";
439     } else if (instance.gpu_mode() == kGpuModeGfxstream ||
440                instance.gpu_mode() == kGpuModeGfxstreamGuestAngle) {
441         gpu_device = "virtio-gpu-gl-pci,capset_names=gfxstream,hostmem=256M";
442     }
443 
444     qemu_cmd.AddParameter(gpu_device, ",id=gpu0",
445                           ",xres=", display_config.width,
446                           ",yres=", display_config.height);
447   }
448 
449   if (!instance.console()) {
450     // In kgdb mode, earlycon is an interactive console, and so early
451     // dmesg will go there instead of the kernel.log. On QEMU, we do this
452     // bit of logic up before the hvc console is set up, so the command line
453     // flags appear in the right order and "append=on" does the right thing
454     if (instance.enable_kernel_log() &&
455         (instance.kgdb() || instance.use_bootloader())) {
456       add_serial_console_ro(instance.kernel_log_pipe_name());
457     }
458   }
459 
460   // /dev/hvc0 = kernel console
461   // If kernel log is enabled, the virtio-console port will be specified as
462   // a true console for Linux, and kernel messages will be printed there.
463   // Otherwise, the port will still be set up for bootloader and userspace
464   // messages, but the kernel will not print anything here. This keeps our
465   // kernel log event features working. If an alternative "earlycon" boot
466   // console is configured above on a legacy serial port, it will control
467   // the main log until the virtio-console takes over.
468   // (Note that QEMU does not automatically generate console= parameters for
469   //  the bootloader/kernel cmdline, so the control of whether this pipe is
470   //  actually managed by the kernel as a console is handled elsewhere.)
471   add_hvc_ro(instance.kernel_log_pipe_name());
472 
473   // /dev/hvc1 = serial console
474   if (instance.console()) {
475     if (instance.kgdb() || instance.use_bootloader()) {
476       add_serial_console(instance.console_pipe_prefix());
477 
478       // In kgdb mode, we have the interactive console on ttyS0 (both Android's
479       // console and kdb), so we can disable the virtio-console port usually
480       // allocated to Android's serial console, and redirect it to a sink. This
481       // ensures that that the PCI device assignments (and thus sepolicy) don't
482       // have to change
483       add_hvc_sink();
484     } else {
485       add_serial_sink();
486       add_hvc(instance.console_pipe_prefix());
487     }
488   } else {
489     if (instance.kgdb() || instance.use_bootloader()) {
490       // The add_serial_console_ro() call above was applied by the time we reach
491       // this code, so we don't need another add_serial_*() call
492     }
493 
494     // as above, create a fake virtio-console 'sink' port when the serial
495     // console is disabled, so the PCI device ID assignments don't move
496     // around
497     add_hvc_sink();
498   }
499 
500   // /dev/hvc2 = serial logging
501   // Serial port for logcat, redirected to a pipe
502   add_hvc_ro(instance.logcat_pipe_name());
503 
504   // /dev/hvc3 = keymaster (C++ implementation)
505   add_hvc(instance.PerInstanceInternalPath("keymaster_fifo_vm"));
506   // /dev/hvc4 = gatekeeper
507   add_hvc(instance.PerInstanceInternalPath("gatekeeper_fifo_vm"));
508   // /dev/hvc5 = bt
509   if (config.enable_host_bluetooth()) {
510     add_hvc(instance.PerInstanceInternalPath("bt_fifo_vm"));
511   } else {
512     add_hvc_sink();
513   }
514 
515   // /dev/hvc6 = gnss
516   // /dev/hvc7 = location
517   if (instance.enable_gnss_grpc_proxy()) {
518     add_hvc(instance.PerInstanceInternalPath("gnsshvc_fifo_vm"));
519     add_hvc(instance.PerInstanceInternalPath("locationhvc_fifo_vm"));
520   } else {
521     for (auto i = 0; i < 2; i++) {
522       add_hvc_sink();
523     }
524   }
525 
526   /* Added one for confirmation UI.
527    *
528    * b/237452165
529    *
530    * Confirmation UI is not supported with QEMU for now. In order
531    * to not conflict with confirmation UI-related configurations used
532    * w/ Crosvm, we should add one generic avc.
533    *
534    * confui_fifo_vm.{in/out} are created along with the streamer process,
535    * which is not created w/ QEMU.
536    */
537   // /dev/hvc8 = confirmationui
538   add_hvc_sink();
539 
540   // /dev/hvc9 = uwb
541   if (config.enable_host_uwb()) {
542     add_hvc("uwb_fifo_vm");
543   } else {
544     add_hvc_sink();
545   }
546 
547   // /dev/hvc10 = oemlock
548   add_hvc(instance.PerInstanceInternalPath("oemlock_fifo_vm"));
549 
550   // /dev/hvc11 = keymint (Rust implementation)
551   add_hvc(instance.PerInstanceInternalPath("keymint_fifo_vm"));
552 
553   auto disk_num = instance.virtual_disk_paths().size();
554 
555   for (auto i = 0; i < VmManager::kMaxDisks - disk_num; i++) {
556     add_hvc_sink();
557   }
558 
559   CF_EXPECT(
560       hvc_num + disk_num == VmManager::kMaxDisks + VmManager::kDefaultNumHvcs,
561       "HVC count (" << hvc_num << ") + disk count (" << disk_num << ") "
562                     << "is not the expected total of "
563                     << VmManager::kMaxDisks + VmManager::kDefaultNumHvcs
564                     << " devices");
565 
566   CF_EXPECT(VmManager::kMaxDisks >= disk_num,
567             "Provided too many disks (" << disk_num << "), maximum "
568                                         << VmManager::kMaxDisks << "supported");
569   auto readonly = instance.protected_vm() ? ",readonly" : "";
570   for (size_t i = 0; i < disk_num; i++) {
571     auto bootindex = i == 0 ? ",bootindex=1" : "";
572     auto format = i == 0 ? "" : ",format=raw";
573     auto disk = instance.virtual_disk_paths()[i];
574     qemu_cmd.AddParameter("-drive");
575     qemu_cmd.AddParameter("file=", disk, ",if=none,id=drive-virtio-disk", i,
576                           ",aio=threads", format, readonly);
577     qemu_cmd.AddParameter("-device");
578     qemu_cmd.AddParameter("virtio-blk-pci-non-transitional,scsi=off,drive=drive-virtio-disk", i,
579                           ",id=virtio-disk", i, bootindex);
580   }
581 
582   if (is_x86 && FileExists(instance.pstore_path())) {
583     // QEMU will assign the NVDIMM (ramoops pstore region) 150000000-1501fffff
584     // As we will pass this to ramoops, define this region first so it is always
585     // located at this address. This is currently x86 only.
586     qemu_cmd.AddParameter("-object");
587     qemu_cmd.AddParameter("memory-backend-file,id=objpmem0,share=on,mem-path=",
588                           instance.pstore_path(), ",size=", pstore_size_bytes);
589 
590     qemu_cmd.AddParameter("-device");
591     qemu_cmd.AddParameter("nvdimm,memdev=objpmem0,id=ramoops");
592   }
593 
594   // QEMU does not implement virtio-pmem-pci for ARM64 or RISC-V yet; restore
595   // this when the device has been added
596   if (is_x86) {
597     if (access_kregistry_size_bytes > 0) {
598       qemu_cmd.AddParameter("-object");
599       qemu_cmd.AddParameter(
600           "memory-backend-file,id=objpmem1,share=on,mem-path=",
601           instance.access_kregistry_path(),
602           ",size=", access_kregistry_size_bytes);
603 
604       qemu_cmd.AddParameter("-device");
605       qemu_cmd.AddParameter(
606           "virtio-pmem-pci,disable-legacy=on,memdev=objpmem1,id=pmem0");
607     }
608     if (hwcomposer_pmem_size_bytes > 0) {
609       qemu_cmd.AddParameter("-object");
610       qemu_cmd.AddParameter(
611           "memory-backend-file,id=objpmem2,share=on,mem-path=",
612           instance.hwcomposer_pmem_path(),
613           ",size=", hwcomposer_pmem_size_bytes);
614 
615       qemu_cmd.AddParameter("-device");
616       qemu_cmd.AddParameter(
617           "virtio-pmem-pci,disable-legacy=on,memdev=objpmem2,id=pmem1");
618     }
619   }
620 
621   qemu_cmd.AddParameter("-object");
622   qemu_cmd.AddParameter("rng-random,id=objrng0,filename=/dev/urandom");
623 
624   qemu_cmd.AddParameter("-device");
625   qemu_cmd.AddParameter("virtio-rng-pci-non-transitional,rng=objrng0,id=rng0,",
626                         "max-bytes=1024,period=2000");
627 
628   qemu_cmd.AddParameter("-device");
629   qemu_cmd.AddParameter("virtio-mouse-pci,disable-legacy=on");
630 
631   qemu_cmd.AddParameter("-device");
632   qemu_cmd.AddParameter("virtio-keyboard-pci,disable-legacy=on");
633 
634   // device padding for unsupported "switches" input
635   qemu_cmd.AddParameter("-device");
636   qemu_cmd.AddParameter("virtio-keyboard-pci,disable-legacy=on");
637 
638   auto vhost_net = instance.vhost_net() ? ",vhost=on" : "";
639 
640   qemu_cmd.AddParameter("-device");
641   qemu_cmd.AddParameter("virtio-balloon-pci-non-transitional,id=balloon0");
642 
643   // The ordering of tap devices is important. Make sure any change here
644   // is reflected in ethprime u-boot variable
645   qemu_cmd.AddParameter("-netdev");
646   qemu_cmd.AddParameter("tap,id=hostnet0,ifname=", instance.mobile_tap_name(),
647                         ",script=no,downscript=no", vhost_net);
648 
649   qemu_cmd.AddParameter("-device");
650   qemu_cmd.AddParameter("virtio-net-pci-non-transitional,netdev=hostnet0,id=net0,mac=",
651                         instance.mobile_mac());
652 
653   qemu_cmd.AddParameter("-netdev");
654   qemu_cmd.AddParameter("tap,id=hostnet1,ifname=", instance.ethernet_tap_name(),
655                         ",script=no,downscript=no", vhost_net);
656 
657   qemu_cmd.AddParameter("-device");
658   qemu_cmd.AddParameter("virtio-net-pci-non-transitional,netdev=hostnet1,id=net1,mac=",
659                         instance.ethernet_mac());
660   if (!config.virtio_mac80211_hwsim()) {
661     qemu_cmd.AddParameter("-netdev");
662     qemu_cmd.AddParameter("tap,id=hostnet2,ifname=", instance.wifi_tap_name(),
663                           ",script=no,downscript=no", vhost_net);
664     qemu_cmd.AddParameter("-device");
665     qemu_cmd.AddParameter("virtio-net-pci-non-transitional,netdev=hostnet2,id=net2,mac=",
666                           instance.wifi_mac());
667   }
668 
669   if (is_x86 || is_arm) {
670     qemu_cmd.AddParameter("-cpu");
671     qemu_cmd.AddParameter(IsHostCompatible(arch_) ? "host" : "max");
672   }
673 
674   qemu_cmd.AddParameter("-msg");
675   qemu_cmd.AddParameter("timestamp=on");
676 
677   qemu_cmd.AddParameter("-device");
678   qemu_cmd.AddParameter("vhost-vsock-pci-non-transitional,guest-cid=",
679                         instance.vsock_guest_cid());
680 
681   qemu_cmd.AddParameter("-device");
682   qemu_cmd.AddParameter("AC97");
683 
684   qemu_cmd.AddParameter("-device");
685   qemu_cmd.AddParameter("qemu-xhci,id=xhci");
686 
687   if (is_riscv64) {
688     qemu_cmd.AddParameter("-kernel");
689   } else {
690     qemu_cmd.AddParameter("-bios");
691   }
692   qemu_cmd.AddParameter(instance.bootloader());
693 
694   if (instance.gdb_port() > 0) {
695     qemu_cmd.AddParameter("-S");
696     qemu_cmd.AddParameter("-gdb");
697     qemu_cmd.AddParameter("tcp::", instance.gdb_port());
698   }
699 
700   LogAndSetEnv("QEMU_AUDIO_DRV", "none");
701 
702   std::vector<MonitorCommand> commands;
703   commands.emplace_back(std::move(qemu_cmd), true);
704   return commands;
705 }
706 
707 } // namespace vm_manager
708 }  // namespace cuttlefish
709