1 /*
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 <sys/stat.h>
20 #include <sys/types.h>
21
22 #include <cassert>
23 #include <string>
24 #include <vector>
25
26 #include <android-base/strings.h>
27 #include <android-base/logging.h>
28
29 #include "common/libs/utils/environment.h"
30 #include "common/libs/utils/network.h"
31 #include "common/libs/utils/subprocess.h"
32 #include "common/libs/utils/files.h"
33 #include "host/libs/config/cuttlefish_config.h"
34 #include "host/libs/vm_manager/qemu_manager.h"
35
36 namespace cuttlefish {
37 namespace vm_manager {
38
39 namespace {
40
GetControlSocketPath(const cuttlefish::CuttlefishConfig * config)41 std::string GetControlSocketPath(const cuttlefish::CuttlefishConfig* config) {
42 return config->ForDefaultInstance()
43 .PerInstanceInternalPath("crosvm_control.sock");
44 }
45
AddTapFdParameter(cuttlefish::Command * crosvm_cmd,const std::string & tap_name)46 cuttlefish::SharedFD AddTapFdParameter(cuttlefish::Command* crosvm_cmd,
47 const std::string& tap_name) {
48 auto tap_fd = cuttlefish::OpenTapInterface(tap_name);
49 if (tap_fd->IsOpen()) {
50 crosvm_cmd->AddParameter("--tap-fd=", tap_fd);
51 } else {
52 LOG(ERROR) << "Unable to connect to " << tap_name << ": "
53 << tap_fd->StrError();
54 }
55 return tap_fd;
56 }
57
ReleaseDhcpLeases(const std::string & lease_path,cuttlefish::SharedFD tap_fd)58 bool ReleaseDhcpLeases(const std::string& lease_path, cuttlefish::SharedFD tap_fd) {
59 auto lease_file_fd = cuttlefish::SharedFD::Open(lease_path, O_RDONLY);
60 if (!lease_file_fd->IsOpen()) {
61 LOG(ERROR) << "Could not open leases file \"" << lease_path << '"';
62 return false;
63 }
64 bool success = true;
65 auto dhcp_leases = cuttlefish::ParseDnsmasqLeases(lease_file_fd);
66 for (auto& lease : dhcp_leases) {
67 std::uint8_t dhcp_server_ip[] = {192, 168, 96, (std::uint8_t) (cuttlefish::ForCurrentInstance(1) * 4 - 3)};
68 if (!cuttlefish::ReleaseDhcp4(tap_fd, lease.mac_address, lease.ip_address, dhcp_server_ip)) {
69 LOG(ERROR) << "Failed to release " << lease;
70 success = false;
71 } else {
72 LOG(INFO) << "Successfully dropped " << lease;
73 }
74 }
75 return success;
76 }
77
Stop()78 bool Stop() {
79 auto config = cuttlefish::CuttlefishConfig::Get();
80 cuttlefish::Command command(config->crosvm_binary());
81 command.AddParameter("stop");
82 command.AddParameter(GetControlSocketPath(config));
83
84 auto process = command.Start();
85
86 return process.Wait() == 0;
87 }
88
89 } // namespace
90
name()91 const std::string CrosvmManager::name() { return "crosvm"; }
92
ConfigureGpu(const std::string & gpu_mode)93 std::vector<std::string> CrosvmManager::ConfigureGpu(const std::string& gpu_mode) {
94 // Override the default HAL search paths in all cases. We do this because
95 // the HAL search path allows for fallbacks, and fallbacks in conjunction
96 // with properities lead to non-deterministic behavior while loading the
97 // HALs.
98 if (gpu_mode == cuttlefish::kGpuModeGuestSwiftshader) {
99 return {
100 "androidboot.hardware.gralloc=minigbm",
101 "androidboot.hardware.hwcomposer=cutf_hwc2",
102 "androidboot.hardware.egl=swiftshader",
103 "androidboot.hardware.vulkan=pastel",
104 };
105 }
106
107 // Try to load the Nvidia modeset kernel module. Running Crosvm with Nvidia's EGL library on a
108 // fresh machine after a boot will fail because the Nvidia EGL library will fork to run the
109 // nvidia-modprobe command and the main Crosvm process will abort after receiving the exit signal
110 // of the forked child which is interpreted as a failure.
111 cuttlefish::Command modprobe_cmd("/usr/bin/nvidia-modprobe");
112 modprobe_cmd.AddParameter("--modeset");
113 modprobe_cmd.Start().Wait();
114
115 if (gpu_mode == cuttlefish::kGpuModeDrmVirgl) {
116 return {
117 "androidboot.hardware.gralloc=minigbm",
118 "androidboot.hardware.hwcomposer=drm_minigbm",
119 "androidboot.hardware.egl=mesa",
120 };
121 }
122 if (gpu_mode == cuttlefish::kGpuModeGfxStream) {
123 return {
124 "androidboot.hardware.gralloc=minigbm",
125 "androidboot.hardware.hwcomposer=drm_minigbm",
126 "androidboot.hardware.egl=emulation",
127 "androidboot.hardware.vulkan=ranchu",
128 "androidboot.hardware.gltransport=virtio-gpu-asg",
129 };
130 }
131 return {};
132 }
133
ConfigureBootDevices()134 std::vector<std::string> CrosvmManager::ConfigureBootDevices() {
135 // TODO There is no way to control this assignment with crosvm (yet)
136 if (cuttlefish::HostArch() == "x86_64") {
137 // PCI domain 0, bus 0, device 4, function 0
138 return { "androidboot.boot_devices=pci0000:00/0000:00:04.0" };
139 } else {
140 return { "androidboot.boot_devices=10000.pci" };
141 }
142 }
143
CrosvmManager(const cuttlefish::CuttlefishConfig * config)144 CrosvmManager::CrosvmManager(const cuttlefish::CuttlefishConfig* config)
145 : VmManager(config) {}
146
StartCommands()147 std::vector<cuttlefish::Command> CrosvmManager::StartCommands() {
148 auto instance = config_->ForDefaultInstance();
149 cuttlefish::Command crosvm_cmd(config_->crosvm_binary(), [](cuttlefish::Subprocess* proc) {
150 auto stopped = Stop();
151 if (stopped) {
152 return true;
153 }
154 LOG(WARNING) << "Failed to stop VMM nicely, attempting to KILL";
155 return KillSubprocess(proc);
156 });
157 crosvm_cmd.AddParameter("run");
158
159 auto gpu_mode = config_->gpu_mode();
160
161 if (gpu_mode == cuttlefish::kGpuModeGuestSwiftshader) {
162 crosvm_cmd.AddParameter("--gpu=2D,",
163 "width=", config_->x_res(), ",",
164 "height=", config_->y_res());
165 } else if (gpu_mode == cuttlefish::kGpuModeDrmVirgl ||
166 gpu_mode == cuttlefish::kGpuModeGfxStream) {
167 crosvm_cmd.AddParameter(gpu_mode == cuttlefish::kGpuModeGfxStream ?
168 "--gpu=gfxstream," : "--gpu=",
169 "width=", config_->x_res(), ",",
170 "height=", config_->y_res(), ",",
171 "egl=true,surfaceless=true,glx=false,gles=true");
172 crosvm_cmd.AddParameter("--wayland-sock=", instance.frames_socket_path());
173 }
174 if (!config_->final_ramdisk_path().empty()) {
175 crosvm_cmd.AddParameter("--initrd=", config_->final_ramdisk_path());
176 }
177 // crosvm_cmd.AddParameter("--null-audio");
178 crosvm_cmd.AddParameter("--mem=", config_->memory_mb());
179 crosvm_cmd.AddParameter("--cpus=", config_->cpus());
180 crosvm_cmd.AddParameter("--params=", kernel_cmdline_);
181 for (const auto& disk : instance.virtual_disk_paths()) {
182 crosvm_cmd.AddParameter("--rwdisk=", disk);
183 }
184 crosvm_cmd.AddParameter("--socket=", GetControlSocketPath(config_));
185
186 if (frontend_enabled_) {
187 crosvm_cmd.AddParameter("--single-touch=", instance.touch_socket_path(),
188 ":", config_->x_res(), ":", config_->y_res());
189 crosvm_cmd.AddParameter("--keyboard=", instance.keyboard_socket_path());
190 }
191
192 auto wifi_tap = AddTapFdParameter(&crosvm_cmd, instance.wifi_tap_name());
193 AddTapFdParameter(&crosvm_cmd, instance.mobile_tap_name());
194
195 crosvm_cmd.AddParameter("--rw-pmem-device=", instance.access_kregistry_path());
196 crosvm_cmd.AddParameter("--pstore=path=", instance.pstore_path(), ",size=",
197 cuttlefish::FileSize(instance.pstore_path()));
198
199 if (config_->enable_sandbox()) {
200 const bool seccomp_exists = cuttlefish::DirectoryExists(config_->seccomp_policy_dir());
201 const std::string& var_empty_dir = cuttlefish::kCrosvmVarEmptyDir;
202 const bool var_empty_available = cuttlefish::DirectoryExists(var_empty_dir);
203 if (!var_empty_available || !seccomp_exists) {
204 LOG(FATAL) << var_empty_dir << " is not an existing, empty directory."
205 << "seccomp-policy-dir, " << config_->seccomp_policy_dir()
206 << " does not exist " << std::endl;
207 return {};
208 }
209 crosvm_cmd.AddParameter("--seccomp-policy-dir=", config_->seccomp_policy_dir());
210 } else {
211 crosvm_cmd.AddParameter("--disable-sandbox");
212 }
213
214 if (instance.vsock_guest_cid() >= 2) {
215 crosvm_cmd.AddParameter("--cid=", instance.vsock_guest_cid());
216 }
217
218 // Use an 8250 UART (ISA or platform device) for earlycon, as the
219 // virtio-console driver may not be available for early messages
220 // In kgdb mode, earlycon is an interactive console, and so early
221 // dmesg will go there instead of the kernel.log
222 if (!(config_->use_bootloader() || config_->kgdb())) {
223 crosvm_cmd.AddParameter("--serial=hardware=serial,num=1,type=file,path=",
224 instance.kernel_log_pipe_name(), ",earlycon=true");
225 }
226
227 // Use a virtio-console instance for the main kernel console. All
228 // messages will switch from earlycon to virtio-console after the driver
229 // is loaded, and crosvm will append to the kernel log automatically
230 crosvm_cmd.AddParameter("--serial=hardware=virtio-console,num=1,type=file,path=",
231 instance.kernel_log_pipe_name(), ",console=true");
232
233 auto console_in_pipe_name = instance.console_in_pipe_name();
234 if (mkfifo(console_in_pipe_name.c_str(), 0600) != 0) {
235 auto error = errno;
236 LOG(ERROR) << "Failed to create console input fifo for crosvm: "
237 << strerror(error);
238 return {};
239 }
240 auto console_out_pipe_name = instance.console_out_pipe_name();
241 if (mkfifo(console_out_pipe_name.c_str(), 0660) != 0) {
242 auto error = errno;
243 LOG(ERROR) << "Failed to create console output fifo for crosvm: "
244 << strerror(error);
245 return {};
246 }
247
248 // These fds will only be read from or written to, but open them with
249 // read and write access to keep them open in case the subprocesses exit
250 cuttlefish::SharedFD console_in_wr =
251 cuttlefish::SharedFD::Open(console_in_pipe_name.c_str(), O_RDWR);
252 if (!console_in_wr->IsOpen()) {
253 LOG(ERROR) << "Failed to open console input fifo for writes: "
254 << console_in_wr->StrError();
255 return {};
256 }
257 cuttlefish::SharedFD console_out_rd =
258 cuttlefish::SharedFD::Open(console_out_pipe_name.c_str(), O_RDWR);
259 if (!console_out_rd->IsOpen()) {
260 LOG(ERROR) << "Failed to open console output fifo for reads: "
261 << console_out_rd->StrError();
262 return {};
263 }
264
265 // stdin is the only currently supported way to write data to a serial port in
266 // crosvm. A file (named pipe) is used here instead of stdout to ensure only
267 // the serial port output is received by the console forwarder as crosvm may
268 // print other messages to stdout.
269 if (config_->kgdb() || config_->use_bootloader()) {
270 crosvm_cmd.AddParameter("--serial=hardware=serial,num=1,type=file,path=",
271 console_out_pipe_name, ",input=", console_in_pipe_name,
272 ",earlycon=true");
273 // In kgdb mode, we have the interactive console on ttyS0 (both Android's
274 // console and kdb), so we can disable the virtio-console port usually
275 // allocated to Android's serial console, and redirect it to a sink. This
276 // ensures that that the PCI device assignments (and thus sepolicy) don't
277 // have to change
278 crosvm_cmd.AddParameter("--serial=hardware=virtio-console,num=2,type=sink");
279 } else {
280 crosvm_cmd.AddParameter("--serial=hardware=virtio-console,num=2,type=file,path=",
281 console_out_pipe_name, ",input=", console_in_pipe_name);
282 }
283
284 cuttlefish::Command console_cmd(config_->console_forwarder_binary());
285 console_cmd.AddParameter("--console_in_fd=", console_in_wr);
286 console_cmd.AddParameter("--console_out_fd=", console_out_rd);
287
288 cuttlefish::SharedFD log_out_rd, log_out_wr;
289 if (!cuttlefish::SharedFD::Pipe(&log_out_rd, &log_out_wr)) {
290 LOG(ERROR) << "Failed to create log pipe for crosvm's stdout/stderr: "
291 << log_out_rd->StrError();
292 return {};
293 }
294 crosvm_cmd.RedirectStdIO(cuttlefish::Subprocess::StdIOChannel::kStdOut,
295 log_out_wr);
296 crosvm_cmd.RedirectStdIO(cuttlefish::Subprocess::StdIOChannel::kStdErr,
297 log_out_wr);
298
299 cuttlefish::Command log_tee_cmd(cuttlefish::DefaultHostArtifactsPath("bin/log_tee"));
300 log_tee_cmd.AddParameter("--process_name=crosvm");
301 log_tee_cmd.AddParameter("--log_fd_in=", log_out_rd);
302
303 // Serial port for logcat, redirected to a pipe
304 crosvm_cmd.AddParameter("--serial=hardware=virtio-console,num=3,type=file,path=",
305 instance.logcat_pipe_name());
306
307 // TODO(b/162071003): virtiofs crashes without sandboxing, this should be fixed
308 if (config_->enable_sandbox()) {
309 // Set up directory shared with virtiofs
310 crosvm_cmd.AddParameter("--shared-dir=", instance.PerInstancePath(cuttlefish::kSharedDirName),
311 ":shared:type=fs");
312 }
313
314 // This needs to be the last parameter
315 if (config_->use_bootloader()) {
316 crosvm_cmd.AddParameter("--bios=", config_->bootloader());
317 } else {
318 crosvm_cmd.AddParameter(config_->GetKernelImageToUse());
319 }
320
321 // Only run the leases workaround if we are not using the new network
322 // bridge architecture - in that case, we have a wider DHCP address
323 // space and stale leases should be much less of an issue
324 if (!cuttlefish::FileExists("/var/run/cuttlefish-dnsmasq-cvd-wbr.leases")) {
325 // TODO(schuffelen): QEMU also needs this and this is not the best place for
326 // this code. Find a better place to put it.
327 auto lease_file =
328 cuttlefish::ForCurrentInstance("/var/run/cuttlefish-dnsmasq-cvd-wbr-")
329 + ".leases";
330 if (!ReleaseDhcpLeases(lease_file, wifi_tap)) {
331 LOG(ERROR) << "Failed to release wifi DHCP leases. Connecting to the wifi "
332 << "network may not work.";
333 }
334 }
335
336 std::vector<cuttlefish::Command> ret;
337 ret.push_back(std::move(crosvm_cmd));
338 ret.push_back(std::move(console_cmd));
339 ret.push_back(std::move(log_tee_cmd));
340 return ret;
341 }
342
343 } // namespace vm_manager
344 } // namespace cuttlefish
345
346