• 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 <vector>
32 
33 #include <android-base/strings.h>
34 #include <android-base/logging.h>
35 #include <vulkan/vulkan.h>
36 
37 #include "common/libs/fs/shared_select.h"
38 #include "common/libs/utils/files.h"
39 #include "common/libs/utils/subprocess.h"
40 #include "common/libs/utils/users.h"
41 #include "host/libs/config/cuttlefish_config.h"
42 #include "host/libs/config/known_paths.h"
43 
44 namespace cuttlefish {
45 namespace vm_manager {
46 namespace {
47 
GetMonitorPath(const CuttlefishConfig & config)48 std::string GetMonitorPath(const CuttlefishConfig& config) {
49   return config.ForDefaultInstance()
50       .PerInstanceInternalPath("qemu_monitor.sock");
51 }
52 
LogAndSetEnv(const char * key,const std::string & value)53 void LogAndSetEnv(const char* key, const std::string& value) {
54   setenv(key, value.c_str(), 1);
55   LOG(INFO) << key << "=" << value;
56 }
57 
Stop()58 bool Stop() {
59   auto config = CuttlefishConfig::Get();
60   auto monitor_path = GetMonitorPath(*config);
61   auto monitor_sock = SharedFD::SocketLocalClient(
62       monitor_path.c_str(), false, SOCK_STREAM);
63 
64   if (!monitor_sock->IsOpen()) {
65     LOG(ERROR) << "The connection to qemu is closed, is it still running?";
66     return false;
67   }
68   char msg[] = "{\"execute\":\"qmp_capabilities\"}{\"execute\":\"quit\"}";
69   ssize_t len = sizeof(msg) - 1;
70   while (len > 0) {
71     int tmp = monitor_sock->Write(msg, len);
72     if (tmp < 0) {
73       LOG(ERROR) << "Error writing to socket: " << monitor_sock->StrError();
74       return false;
75     }
76     len -= tmp;
77   }
78   // Log the reply
79   char buff[1000];
80   while ((len = monitor_sock->Read(buff, sizeof(buff) - 1)) > 0) {
81     buff[len] = '\0';
82     LOG(INFO) << "From qemu monitor: " << buff;
83   }
84 
85   return true;
86 }
87 
GetQemuVersion(const std::string & qemu_binary)88 std::pair<int,int> GetQemuVersion(const std::string& qemu_binary)
89 {
90   Command qemu_version_cmd(qemu_binary);
91   qemu_version_cmd.AddParameter("-version");
92 
93   std::string qemu_version_input, qemu_version_output, qemu_version_error;
94   cuttlefish::SubprocessOptions options;
95   options.Verbose(false);
96   int qemu_version_ret =
97       cuttlefish::RunWithManagedStdio(std::move(qemu_version_cmd),
98                                       &qemu_version_input,
99                                       &qemu_version_output,
100                                       &qemu_version_error, options);
101   if (qemu_version_ret != 0) {
102     LOG(FATAL) << qemu_binary << " -version returned unexpected response "
103                << qemu_version_output << ". Stderr was " << qemu_version_error;
104     return { 0, 0 };
105   }
106 
107   // Snip around the extra text we don't care about
108   qemu_version_output.erase(0, std::string("QEMU emulator version ").length());
109   auto space_pos = qemu_version_output.find(" ", 0);
110   if (space_pos != std::string::npos) {
111     qemu_version_output.resize(space_pos);
112   }
113 
114   auto qemu_version_bits = android::base::Split(qemu_version_output, ".");
115   return { std::stoi(qemu_version_bits[0]), std::stoi(qemu_version_bits[1]) };
116 }
117 
118 }  // namespace
119 
QemuManager(Arch arch)120 QemuManager::QemuManager(Arch arch) : arch_(arch) {}
121 
IsSupported()122 bool QemuManager::IsSupported() {
123   return HostSupportsQemuCli();
124 }
125 
ConfigureGraphics(const CuttlefishConfig & config)126 std::vector<std::string> QemuManager::ConfigureGraphics(
127     const CuttlefishConfig& config) {
128   if (config.gpu_mode() == kGpuModeGuestSwiftshader) {
129     // Override the default HAL search paths in all cases. We do this because
130     // the HAL search path allows for fallbacks, and fallbacks in conjunction
131     // with properities lead to non-deterministic behavior while loading the
132     // HALs.
133     return {
134         "androidboot.cpuvulkan.version=" + std::to_string(VK_API_VERSION_1_2),
135         "androidboot.hardware.gralloc=minigbm",
136         "androidboot.hardware.hwcomposer=" + config.hwcomposer(),
137         "androidboot.hardware.egl=angle",
138         "androidboot.hardware.vulkan=pastel",
139         "androidboot.opengles.version=196609"};  // OpenGL ES 3.1
140   }
141 
142   if (config.gpu_mode() == kGpuModeDrmVirgl) {
143     return {
144       "androidboot.cpuvulkan.version=0",
145       "androidboot.hardware.gralloc=minigbm",
146       "androidboot.hardware.hwcomposer=ranchu",
147       "androidboot.hardware.hwcomposer.mode=client",
148       "androidboot.hardware.egl=mesa",
149       // No "hardware" Vulkan support, yet
150       "androidboot.opengles.version=196608"};  // OpenGL ES 3.0
151   }
152 
153   return {};
154 }
155 
ConfigureBootDevices(int num_disks)156 std::string QemuManager::ConfigureBootDevices(int num_disks) {
157   switch (arch_) {
158     case Arch::X86:
159     case Arch::X86_64: {
160       // QEMU has additional PCI devices for an ISA bridge and PIIX4
161       // virtio_gpu precedes the first console or disk
162       return ConfigureMultipleBootDevices("pci0000:00/0000:00:", 3, num_disks);
163     }
164     case Arch::Arm:
165       return "androidboot.boot_devices=3f000000.pcie";
166     case Arch::Arm64:
167       return "androidboot.boot_devices=4010000000.pcie";
168   }
169 }
170 
StartCommands(const CuttlefishConfig & config)171 std::vector<Command> QemuManager::StartCommands(
172     const CuttlefishConfig& config) {
173   auto instance = config.ForDefaultInstance();
174 
175   auto stop = [](Subprocess* proc) {
176     auto stopped = Stop();
177     if (stopped) {
178       return StopperResult::kStopSuccess;
179     }
180     LOG(WARNING) << "Failed to stop VMM nicely, "
181                   << "attempting to KILL";
182     return KillSubprocess(proc) == StopperResult::kStopSuccess
183                ? StopperResult::kStopCrash
184                : StopperResult::kStopFailure;
185   };
186   std::string qemu_binary = config.qemu_binary_dir();
187   switch (arch_) {
188     case Arch::Arm:
189       qemu_binary += "/qemu-system-arm";
190       break;
191     case Arch::Arm64:
192       qemu_binary += "/qemu-system-aarch64";
193       break;
194     case Arch::X86:
195       qemu_binary += "/qemu-system-i386";
196       break;
197     case Arch::X86_64:
198       qemu_binary += "/qemu-system-x86_64";
199       break;
200   }
201 
202   auto qemu_version = GetQemuVersion(qemu_binary);
203   Command qemu_cmd(qemu_binary, stop);
204 
205   int hvc_num = 0;
206   int serial_num = 0;
207   auto add_hvc_sink = [&qemu_cmd, &hvc_num]() {
208     qemu_cmd.AddParameter("-chardev");
209     qemu_cmd.AddParameter("null,id=hvc", hvc_num);
210     qemu_cmd.AddParameter("-device");
211     qemu_cmd.AddParameter(
212         "virtio-serial-pci-non-transitional,max_ports=1,id=virtio-serial",
213         hvc_num);
214     qemu_cmd.AddParameter("-device");
215     qemu_cmd.AddParameter("virtconsole,bus=virtio-serial", hvc_num,
216                           ".0,chardev=hvc", hvc_num);
217     hvc_num++;
218   };
219   auto add_serial_sink = [&qemu_cmd, &serial_num]() {
220     qemu_cmd.AddParameter("-chardev");
221     qemu_cmd.AddParameter("null,id=serial", serial_num);
222     qemu_cmd.AddParameter("-serial");
223     qemu_cmd.AddParameter("chardev:serial", serial_num);
224     serial_num++;
225   };
226   auto add_serial_console_ro = [&qemu_cmd,
227                                 &serial_num](const std::string& output) {
228     qemu_cmd.AddParameter("-chardev");
229     qemu_cmd.AddParameter("file,id=serial", serial_num, ",path=", output,
230                           ",append=on");
231     qemu_cmd.AddParameter("-serial");
232     qemu_cmd.AddParameter("chardev:serial", serial_num);
233     serial_num++;
234   };
235   auto add_serial_console = [&qemu_cmd,
236                              &serial_num](const std::string& prefix) {
237     qemu_cmd.AddParameter("-chardev");
238     qemu_cmd.AddParameter("pipe,id=serial", serial_num, ",path=", prefix);
239     qemu_cmd.AddParameter("-serial");
240     qemu_cmd.AddParameter("chardev:serial", serial_num);
241     serial_num++;
242   };
243   auto add_hvc_ro = [&qemu_cmd, &hvc_num](const std::string& output) {
244     qemu_cmd.AddParameter("-chardev");
245     qemu_cmd.AddParameter("file,id=hvc", hvc_num, ",path=", output,
246                           ",append=on");
247     qemu_cmd.AddParameter("-device");
248     qemu_cmd.AddParameter(
249         "virtio-serial-pci-non-transitional,max_ports=1,id=virtio-serial",
250         hvc_num);
251     qemu_cmd.AddParameter("-device");
252     qemu_cmd.AddParameter("virtconsole,bus=virtio-serial", hvc_num,
253                           ".0,chardev=hvc", hvc_num);
254     hvc_num++;
255   };
256   auto add_hvc = [&qemu_cmd, &hvc_num](const std::string& prefix) {
257     qemu_cmd.AddParameter("-chardev");
258     qemu_cmd.AddParameter("pipe,id=hvc", hvc_num, ",path=", prefix);
259     qemu_cmd.AddParameter("-device");
260     qemu_cmd.AddParameter(
261         "virtio-serial-pci-non-transitional,max_ports=1,id=virtio-serial",
262         hvc_num);
263     qemu_cmd.AddParameter("-device");
264     qemu_cmd.AddParameter("virtconsole,bus=virtio-serial", hvc_num,
265                           ".0,chardev=hvc", hvc_num);
266     hvc_num++;
267   };
268 
269   bool is_arm = arch_ == Arch::Arm || arch_ == Arch::Arm64;
270   bool is_arm64 = arch_ == Arch::Arm64;
271 
272   auto access_kregistry_size_bytes = 0;
273   if (FileExists(instance.access_kregistry_path())) {
274     access_kregistry_size_bytes = FileSize(instance.access_kregistry_path());
275     CHECK((access_kregistry_size_bytes & (1024 * 1024 - 1)) == 0)
276         << instance.access_kregistry_path() <<  " file size ("
277         << access_kregistry_size_bytes << ") not a multiple of 1MB";
278   }
279 
280   auto hwcomposer_pmem_size_bytes = 0;
281   if (FileExists(instance.hwcomposer_pmem_path())) {
282     hwcomposer_pmem_size_bytes = FileSize(instance.hwcomposer_pmem_path());
283     CHECK((hwcomposer_pmem_size_bytes & (1024 * 1024 - 1)) == 0)
284         << instance.hwcomposer_pmem_path() << " file size ("
285         << hwcomposer_pmem_size_bytes << ") not a multiple of 1MB";
286   }
287 
288   auto pstore_size_bytes = 0;
289   if (FileExists(instance.pstore_path())) {
290     pstore_size_bytes = FileSize(instance.pstore_path());
291     CHECK((pstore_size_bytes & (1024 * 1024 - 1)) == 0)
292         << instance.pstore_path() <<  " file size ("
293         << pstore_size_bytes << ") not a multiple of 1MB";
294   }
295 
296   qemu_cmd.AddParameter("-name");
297   qemu_cmd.AddParameter("guest=", instance.instance_name(), ",debug-threads=on");
298 
299   qemu_cmd.AddParameter("-machine");
300   std::string machine = is_arm ? "virt" : "pc-i440fx-2.8,nvdimm=on";
301   if (IsHostCompatible(arch_)) {
302     machine += ",accel=kvm";
303     if (is_arm) {
304       machine += ",gic-version=3";
305     }
306   } else if (is_arm) {
307     // QEMU doesn't support GICv3 with TCG yet
308     machine += ",gic-version=2";
309     if (is_arm64) {
310       // Only enable MTE in TCG mode. We haven't started to run on ARMv8/ARMv9
311       // devices with KVM and MTE, so MTE will always require TCG
312       machine += ",mte=on";
313     }
314     CHECK(config.cpus() <= 8) << "CPUs must be no more than 8 with GICv2";
315   }
316   qemu_cmd.AddParameter(machine, ",usb=off,dump-guest-core=off");
317 
318   qemu_cmd.AddParameter("-m");
319   auto maxmem = config.memory_mb() +
320                 (access_kregistry_size_bytes / 1024 / 1024) +
321                 (hwcomposer_pmem_size_bytes / 1024 / 1024) +
322                 (is_arm ? 0 : pstore_size_bytes / 1024 / 1024);
323   auto slots = is_arm ? "" : ",slots=2";
324   qemu_cmd.AddParameter("size=", config.memory_mb(), "M",
325                         ",maxmem=", maxmem, "M", slots);
326 
327   qemu_cmd.AddParameter("-overcommit");
328   qemu_cmd.AddParameter("mem-lock=off");
329 
330   // Assume SMT is always 2 threads per core, which is how most hardware
331   // today is configured, and the way crosvm does it
332   qemu_cmd.AddParameter("-smp");
333   if (config.smt()) {
334     CHECK(config.cpus() % 2 == 0)
335         << "CPUs must be a multiple of 2 in SMT mode";
336     qemu_cmd.AddParameter(config.cpus(), ",cores=",
337                           config.cpus() / 2, ",threads=2");
338   } else {
339     qemu_cmd.AddParameter(config.cpus(), ",cores=",
340                           config.cpus(), ",threads=1");
341   }
342 
343   qemu_cmd.AddParameter("-uuid");
344   qemu_cmd.AddParameter(instance.uuid());
345 
346   qemu_cmd.AddParameter("-no-user-config");
347   qemu_cmd.AddParameter("-nodefaults");
348   qemu_cmd.AddParameter("-no-shutdown");
349 
350   qemu_cmd.AddParameter("-rtc");
351   qemu_cmd.AddParameter("base=utc");
352 
353   qemu_cmd.AddParameter("-boot");
354   qemu_cmd.AddParameter("strict=on");
355 
356   qemu_cmd.AddParameter("-chardev");
357   qemu_cmd.AddParameter("socket,id=charmonitor,path=", GetMonitorPath(config),
358                         ",server=on,wait=off");
359 
360   qemu_cmd.AddParameter("-mon");
361   qemu_cmd.AddParameter("chardev=charmonitor,id=monitor,mode=control");
362 
363   if (config.gpu_mode() == kGpuModeDrmVirgl) {
364     qemu_cmd.AddParameter("-display");
365     qemu_cmd.AddParameter("egl-headless");
366 
367     qemu_cmd.AddParameter("-vnc");
368     qemu_cmd.AddParameter(":", instance.qemu_vnc_server_port());
369   } else {
370     qemu_cmd.AddParameter("-display");
371     qemu_cmd.AddParameter("none");
372   }
373 
374   auto display_configs = config.display_configs();
375   CHECK_GE(display_configs.size(), 1);
376   auto display_config = display_configs[0];
377 
378   qemu_cmd.AddParameter("-device");
379 
380   bool use_gpu_gl = qemu_version.first >= 6 &&
381                     config.gpu_mode() != kGpuModeGuestSwiftshader;
382   qemu_cmd.AddParameter(use_gpu_gl ?
383                             "virtio-gpu-gl-pci" : "virtio-gpu-pci", ",id=gpu0",
384                         ",xres=", display_config.width,
385                         ",yres=", display_config.height);
386 
387   // In kgdb mode, earlycon is an interactive console, and so early
388   // dmesg will go there instead of the kernel.log. On QEMU, we do this
389   // bit of logic up before the hvc console is set up, so the command line
390   // flags appear in the right order and "append=on" does the right thing
391   if (!config.console() && (config.kgdb() || config.use_bootloader())) {
392     add_serial_console_ro(instance.kernel_log_pipe_name());
393   }
394 
395   // Use a virtio-console instance for the main kernel console. All
396   // messages will switch from earlycon to virtio-console after the driver
397   // is loaded, and QEMU will append to the kernel log automatically
398   add_hvc_ro(instance.kernel_log_pipe_name());
399 
400   if (config.console()) {
401     if (config.kgdb() || config.use_bootloader()) {
402       add_serial_console(instance.console_pipe_prefix());
403 
404       // In kgdb mode, we have the interactive console on ttyS0 (both Android's
405       // console and kdb), so we can disable the virtio-console port usually
406       // allocated to Android's serial console, and redirect it to a sink. This
407       // ensures that that the PCI device assignments (and thus sepolicy) don't
408       // have to change
409       add_hvc_sink();
410     } else {
411       add_serial_sink();
412       add_hvc(instance.console_pipe_prefix());
413     }
414   } else {
415     if (config.kgdb() || config.use_bootloader()) {
416       // The add_serial_console_ro() call above was applied by the time we reach
417       // this code, so we don't need another add_serial_*() call
418     }
419 
420     // as above, create a fake virtio-console 'sink' port when the serial
421     // console is disabled, so the PCI device ID assignments don't move
422     // around
423     add_hvc_sink();
424   }
425 
426   // Serial port for logcat, redirected to a pipe
427   add_hvc_ro(instance.logcat_pipe_name());
428 
429   add_hvc(instance.PerInstanceInternalPath("keymaster_fifo_vm"));
430   add_hvc(instance.PerInstanceInternalPath("gatekeeper_fifo_vm"));
431   if (config.enable_host_bluetooth()) {
432     add_hvc(instance.PerInstanceInternalPath("bt_fifo_vm"));
433   } else {
434     add_hvc_sink();
435   }
436 
437   if (config.enable_gnss_grpc_proxy()) {
438     add_hvc(instance.PerInstanceInternalPath("gnsshvc_fifo_vm"));
439     add_hvc(instance.PerInstanceInternalPath("locationhvc_fifo_vm"));
440   } else {
441     for (auto i = 0; i < 2; i++) {
442       add_hvc_sink();
443     }
444   }
445 
446   auto disk_num = instance.virtual_disk_paths().size();
447 
448   for (auto i = 0; i < VmManager::kMaxDisks - disk_num; i++) {
449     add_hvc_sink();
450   }
451 
452   CHECK(hvc_num + disk_num == VmManager::kMaxDisks + VmManager::kDefaultNumHvcs)
453       << "HVC count (" << hvc_num << ") + disk count (" << disk_num << ") "
454       << "is not the expected total of "
455       << VmManager::kMaxDisks + VmManager::kDefaultNumHvcs << " devices";
456 
457   CHECK_GE(VmManager::kMaxDisks, disk_num)
458       << "Provided too many disks (" << disk_num << "), maximum "
459       << VmManager::kMaxDisks << "supported";
460   auto readonly = config.protected_vm() ? ",readonly" : "";
461   for (size_t i = 0; i < disk_num; i++) {
462     auto bootindex = i == 0 ? ",bootindex=1" : "";
463     auto format = i == 0 ? "" : ",format=raw";
464     auto disk = instance.virtual_disk_paths()[i];
465     qemu_cmd.AddParameter("-drive");
466     qemu_cmd.AddParameter("file=", disk, ",if=none,id=drive-virtio-disk", i,
467                           ",aio=threads", format, readonly);
468     qemu_cmd.AddParameter("-device");
469     qemu_cmd.AddParameter("virtio-blk-pci-non-transitional,scsi=off,drive=drive-virtio-disk", i,
470                           ",id=virtio-disk", i, bootindex);
471   }
472 
473   if (!is_arm && FileExists(instance.pstore_path())) {
474     // QEMU will assign the NVDIMM (ramoops pstore region) 100000000-1001fffff
475     // As we will pass this to ramoops, define this region first so it is always
476     // located at this address. This is currently x86 only.
477     qemu_cmd.AddParameter("-object");
478     qemu_cmd.AddParameter("memory-backend-file,id=objpmem0,share=on,mem-path=",
479                           instance.pstore_path(), ",size=", pstore_size_bytes);
480 
481     qemu_cmd.AddParameter("-device");
482     qemu_cmd.AddParameter("nvdimm,memdev=objpmem0,id=ramoops");
483   }
484 
485   // QEMU does not implement virtio-pmem-pci for ARM64 yet; restore this
486   // when the device has been added
487   if (!is_arm) {
488     if (access_kregistry_size_bytes > 0) {
489       qemu_cmd.AddParameter("-object");
490       qemu_cmd.AddParameter(
491           "memory-backend-file,id=objpmem1,share=on,mem-path=",
492           instance.access_kregistry_path(),
493           ",size=", access_kregistry_size_bytes);
494 
495       qemu_cmd.AddParameter("-device");
496       qemu_cmd.AddParameter(
497           "virtio-pmem-pci,disable-legacy=on,memdev=objpmem1,id=pmem0");
498     }
499     if (hwcomposer_pmem_size_bytes > 0) {
500       qemu_cmd.AddParameter("-object");
501       qemu_cmd.AddParameter(
502           "memory-backend-file,id=objpmem2,share=on,mem-path=",
503           instance.hwcomposer_pmem_path(),
504           ",size=", hwcomposer_pmem_size_bytes);
505 
506       qemu_cmd.AddParameter("-device");
507       qemu_cmd.AddParameter(
508           "virtio-pmem-pci,disable-legacy=on,memdev=objpmem2,id=pmem1");
509     }
510   }
511 
512   qemu_cmd.AddParameter("-object");
513   qemu_cmd.AddParameter("rng-random,id=objrng0,filename=/dev/urandom");
514 
515   qemu_cmd.AddParameter("-device");
516   qemu_cmd.AddParameter("virtio-rng-pci-non-transitional,rng=objrng0,id=rng0,",
517                         "max-bytes=1024,period=2000");
518 
519   qemu_cmd.AddParameter("-device");
520   qemu_cmd.AddParameter("virtio-mouse-pci,disable-legacy=on");
521 
522   qemu_cmd.AddParameter("-device");
523   qemu_cmd.AddParameter("virtio-keyboard-pci,disable-legacy=on");
524 
525   // device padding for unsupported "switches" input
526   qemu_cmd.AddParameter("-device");
527   qemu_cmd.AddParameter("virtio-keyboard-pci,disable-legacy=on");
528 
529   auto vhost_net = config.vhost_net() ? ",vhost=on" : "";
530 
531   qemu_cmd.AddParameter("-device");
532   qemu_cmd.AddParameter("virtio-balloon-pci-non-transitional,id=balloon0");
533 
534   qemu_cmd.AddParameter("-netdev");
535   qemu_cmd.AddParameter("tap,id=hostnet0,ifname=", instance.mobile_tap_name(),
536                         ",script=no,downscript=no", vhost_net);
537 
538   qemu_cmd.AddParameter("-device");
539   qemu_cmd.AddParameter("virtio-net-pci-non-transitional,netdev=hostnet0,id=net0");
540 
541   qemu_cmd.AddParameter("-netdev");
542   qemu_cmd.AddParameter("tap,id=hostnet1,ifname=", instance.ethernet_tap_name(),
543                         ",script=no,downscript=no", vhost_net);
544 
545   qemu_cmd.AddParameter("-device");
546   qemu_cmd.AddParameter("virtio-net-pci-non-transitional,netdev=hostnet1,id=net1");
547 #ifndef ENFORCE_MAC80211_HWSIM
548   qemu_cmd.AddParameter("-netdev");
549   qemu_cmd.AddParameter("tap,id=hostnet2,ifname=", instance.wifi_tap_name(),
550                         ",script=no,downscript=no", vhost_net);
551   qemu_cmd.AddParameter("-device");
552   qemu_cmd.AddParameter("virtio-net-pci-non-transitional,netdev=hostnet2,id=net2");
553 #endif
554 
555   qemu_cmd.AddParameter("-cpu");
556   qemu_cmd.AddParameter(IsHostCompatible(arch_) ? "host" : "max");
557 
558   qemu_cmd.AddParameter("-msg");
559   qemu_cmd.AddParameter("timestamp=on");
560 
561   qemu_cmd.AddParameter("-device");
562   qemu_cmd.AddParameter("vhost-vsock-pci-non-transitional,guest-cid=",
563                         instance.vsock_guest_cid());
564 
565   qemu_cmd.AddParameter("-device");
566   qemu_cmd.AddParameter("AC97");
567 
568   qemu_cmd.AddParameter("-device");
569   qemu_cmd.AddParameter("qemu-xhci,id=xhci");
570 
571   qemu_cmd.AddParameter("-bios");
572   qemu_cmd.AddParameter(config.bootloader());
573 
574   if (config.gdb_port() > 0) {
575     qemu_cmd.AddParameter("-S");
576     qemu_cmd.AddParameter("-gdb");
577     qemu_cmd.AddParameter("tcp::", config.gdb_port());
578   }
579 
580   LogAndSetEnv("QEMU_AUDIO_DRV", "none");
581 
582   std::vector<Command> ret;
583   ret.push_back(std::move(qemu_cmd));
584   return ret;
585 }
586 
587 } // namespace vm_manager
588 } // namespace cuttlefish
589 
590