1 /*
2  * Copyright (C) 2021 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/commands/assemble_cvd/bootconfig_args.h"
18 
19 #include <array>
20 #include <sstream>
21 #include <string>
22 #include <vector>
23 
24 #include <android-base/parseint.h>
25 
26 #include "common/libs/utils/environment.h"
27 #include "common/libs/utils/files.h"
28 #include "common/libs/utils/json.h"
29 #include "host/libs/config/cuttlefish_config.h"
30 #include "host/libs/config/known_paths.h"
31 #include "host/libs/vm_manager/crosvm_manager.h"
32 #include "host/libs/vm_manager/qemu_manager.h"
33 #include "host/libs/vm_manager/vm_manager.h"
34 
35 namespace cuttlefish {
36 
37 using vm_manager::CrosvmManager;
38 using vm_manager::QemuManager;
39 
40 namespace {
41 
42 template <typename T>
AppendMapWithReplacement(T * destination,const T & source)43 void AppendMapWithReplacement(T* destination, const T& source) {
44   for (const auto& [k, v] : source) {
45     (*destination)[k] = v;
46   }
47 }
48 
ConsoleBootconfig(const CuttlefishConfig::InstanceSpecific & instance)49 Result<std::unordered_map<std::string, std::string>> ConsoleBootconfig(
50     const CuttlefishConfig::InstanceSpecific& instance) {
51   std::unordered_map<std::string, std::string> bootconfig_args;
52   if (instance.console()) {
53     bootconfig_args["androidboot.console"] = instance.console_dev();
54     bootconfig_args["androidboot.serialconsole"] = "1";
55   } else {
56     // Specify an invalid path under /dev, so the init process will disable the
57     // console service due to the console not being found. On physical devices,
58     // *and on older kernels* it is enough to not specify androidboot.console=
59     // *and* not specify the console= kernel command line parameter, because
60     // the console and kernel dmesg are muxed. However, on cuttlefish, we don't
61     // need to mux, and would prefer to retain the kernel dmesg logging, so we
62     // must work around init falling back to the check for /dev/console (which
63     // we'll always have).
64     // bootconfig_args["androidboot.console"] = "invalid";
65     // The bug above has been fixed in Android 14 and later so we can just
66     // specify androidboot.serialconsole=0 instead.
67     bootconfig_args["androidboot.serialconsole"] = "0";
68   }
69   return bootconfig_args;
70 }
71 
72 }  // namespace
73 
BootconfigArgsFromConfig(const CuttlefishConfig & config,const CuttlefishConfig::InstanceSpecific & instance)74 Result<std::unordered_map<std::string, std::string>> BootconfigArgsFromConfig(
75     const CuttlefishConfig& config,
76     const CuttlefishConfig::InstanceSpecific& instance) {
77   std::unordered_map<std::string, std::string> bootconfig_args;
78 
79   AppendMapWithReplacement(&bootconfig_args,
80                            CF_EXPECT(ConsoleBootconfig(instance)));
81 
82   auto vmm =
83       vm_manager::GetVmManager(config.vm_manager(), instance.target_arch());
84   AppendMapWithReplacement(&bootconfig_args,
85                            CF_EXPECT(vmm->ConfigureBootDevices(instance)));
86 
87   AppendMapWithReplacement(&bootconfig_args,
88                            CF_EXPECT(vmm->ConfigureGraphics(instance)));
89 
90   bootconfig_args["androidboot.serialno"] = instance.serial_number();
91   bootconfig_args["androidboot.ddr_size"] =
92       std::to_string(instance.ddr_mem_mb()) + "MB";
93 
94   // TODO(b/131884992): update to specify multiple once supported.
95   const auto display_configs = instance.display_configs();
96   if (!display_configs.empty()) {
97     bootconfig_args["androidboot.lcd_density"] =
98         std::to_string(display_configs[0].dpi);
99   }
100 
101   bootconfig_args["androidboot.setupwizard_mode"] = instance.setupwizard_mode();
102 
103   bootconfig_args["androidboot.enable_bootanimation"] =
104       std::to_string(instance.enable_bootanimation());
105 
106   if (!instance.guest_enforce_security()) {
107     bootconfig_args["androidboot.selinux"] = "permissive";
108   }
109 
110   if (instance.tombstone_receiver_port()) {
111     bootconfig_args["androidboot.vsock_tombstone_port"] =
112         std::to_string(instance.tombstone_receiver_port());
113   }
114 
115   if (instance.openthread_node_id()) {
116     bootconfig_args["androidboot.openthread_node_id"] =
117         std::to_string(instance.openthread_node_id());
118   }
119 
120   const auto enable_confui = (config.vm_manager() == VmmMode::kQemu ? 0 : 1);
121   bootconfig_args["androidboot.enable_confirmationui"] =
122       std::to_string(enable_confui);
123 
124   if (instance.audiocontrol_server_port()) {
125     bootconfig_args["androidboot.vendor.audiocontrol.server.cid"] =
126         std::to_string(instance.vsock_guest_cid());
127     bootconfig_args["androidboot.vendor.audiocontrol.server.port"] =
128         std::to_string(instance.audiocontrol_server_port());
129   }
130 
131   if (!instance.enable_audio()) {
132     bootconfig_args["androidboot.audio.tinyalsa.ignore_output"] = "true";
133     bootconfig_args["androidboot.audio.tinyalsa.simulate_input"] = "true";
134   }
135 
136   if (instance.camera_server_port()) {
137     bootconfig_args["androidboot.vsock_camera_port"] =
138         std::to_string(instance.camera_server_port());
139     bootconfig_args["androidboot.vsock_camera_cid"] =
140         std::to_string(instance.vsock_guest_cid());
141   }
142 
143   if (instance.lights_server_port()) {
144     bootconfig_args["androidboot.vsock_lights_port"] =
145         std::to_string(instance.lights_server_port());
146     bootconfig_args["androidboot.vsock_lights_cid"] =
147         std::to_string(instance.vsock_guest_cid());
148   }
149 
150   if (instance.enable_modem_simulator() &&
151       instance.modem_simulator_ports() != "") {
152     bootconfig_args["androidboot.modem_simulator_ports"] =
153         instance.modem_simulator_ports();
154   }
155 
156   // Once all Cuttlefish kernel versions are at least 5.15, filename encryption
157   // will not need to be set conditionally. HCTR2 will always be available.
158   // At that point fstab.cf.f2fs.cts and fstab.cf.ext4.cts can be removed.
159   std::string fstab_suffix = fmt::format("cf.{}.{}", instance.userdata_format(),
160                                          instance.filename_encryption_mode());
161 
162   bootconfig_args["androidboot.fstab_suffix"] = fstab_suffix;
163 
164   bootconfig_args["androidboot.wifi_mac_prefix"] =
165       std::to_string(instance.wifi_mac_prefix());
166 
167   // Non-native architecture implies a significantly slower execution speed, so
168   // set a large timeout multiplier.
169   if (!IsHostCompatible(instance.target_arch())) {
170     bootconfig_args["androidboot.hw_timeout_multiplier"] = "50";
171   } else {
172     // Even on native architecture, Cuttlefish is still slower than physical
173     // devices in CI environments, so add a small timeout multiplier.
174     bootconfig_args["androidboot.hw_timeout_multiplier"] = "3";
175   }
176 
177   // TODO(b/217564326): improve this checks for a hypervisor in the VM.
178   if (instance.target_arch() == Arch::X86 ||
179       instance.target_arch() == Arch::X86_64) {
180     bootconfig_args["androidboot.hypervisor.version"] =
181         "cf-" + ToString(config.vm_manager());
182     bootconfig_args["androidboot.hypervisor.vm.supported"] = "1";
183   } else {
184     bootconfig_args["androidboot.hypervisor.vm.supported"] = "0";
185   }
186   bootconfig_args["androidboot.hypervisor.protected_vm.supported"] = "0";
187   if (!instance.kernel_path().empty()) {
188     bootconfig_args["androidboot.kernel_hotswapped"] = "1";
189   }
190   if (!instance.initramfs_path().empty()) {
191     bootconfig_args["androidboot.ramdisk_hotswapped"] = "1";
192   }
193 
194   const auto& secure_hals = CF_EXPECT(config.secure_hals());
195   if (secure_hals.count(SecureHal::kGuestKeymintInsecure)) {
196     bootconfig_args["androidboot.vendor.apex.com.android.hardware.keymint"] =
197         "com.android.hardware.keymint.rust_nonsecure";
198   } else if (secure_hals.count(SecureHal::kGuestKeymintTrustyInsecure)) {
199     bootconfig_args["androidboot.vendor.apex.com.android.hardware.keymint"] =
200         "com.android.hardware.keymint.rust_cf_guest_trusty_nonsecure";
201   } else {
202     bootconfig_args["androidboot.vendor.apex.com.android.hardware.keymint"] =
203         "com.android.hardware.keymint.rust_cf_remote";
204   }
205 
206   // Preemptive for when we set up the HAL to be runtime selectable
207   bootconfig_args["androidboot.vendor.apex.com.android.hardware.gatekeeper"] =
208       secure_hals.count(SecureHal::kGuestGatekeeperInsecure)
209           ? "com.android.hardware.gatekeeper.nonsecure"
210           : "com.android.hardware.gatekeeper.cf_remote";
211 
212   bootconfig_args
213       ["androidboot.vendor.apex.com.android.hardware.graphics.composer"] =
214           instance.hwcomposer() == kHwComposerDrm
215               ? "com.android.hardware.graphics.composer.drm_hwcomposer"
216               : "com.android.hardware.graphics.composer.ranchu";
217 
218   if (config.vhal_proxy_server_port()) {
219     bootconfig_args["androidboot.vhal_proxy_server_port"] =
220         std::to_string(config.vhal_proxy_server_port());
221     int32_t instance_id;
222     CF_EXPECT(android::base::ParseInt(instance.id(), &instance_id),
223               "instance id: " << instance.id() << " is not a valid int");
224     // The static ethernet IP address assigned for the guest.
225     bootconfig_args["androidboot.auto_eth_guest_addr"] =
226         fmt::format("192.168.98.{}", instance_id + 2);
227   }
228 
229   if (!instance.vcpu_config_path().empty()) {
230     auto vcpu_config_json =
231         CF_EXPECT(LoadFromFile(instance.vcpu_config_path()));
232 
233     const auto guest_soc =
234         CF_EXPECT(GetValue<std::string>(vcpu_config_json, {"guest_soc"}));
235 
236     bootconfig_args["androidboot.guest_soc.model"] = guest_soc;
237   }
238 
239   std::vector<std::string> args = instance.extra_bootconfig_args();
240 
241   LOG(DEBUG) << "Parsing extra_bootconfig_args of size:" << args.size()
242              << "; Contents: " << android::base::Join(args, "\n");
243 
244   for (const std::string& kv : args) {
245     if (kv.empty()) {
246       continue;
247     }
248     const auto& parts = android::base::Split(kv, "=");
249     CF_EXPECT_EQ(parts.size(), 2,
250                  "Failed to parse --extra_bootconfig_args: \"" << kv << "\"");
251     bootconfig_args[parts[0]] = parts[1];
252   }
253 
254   return bootconfig_args;
255 }
256 
BootconfigArgsString(const std::unordered_map<std::string,std::string> & args,const std::string & separator)257 Result<std::string> BootconfigArgsString(
258     const std::unordered_map<std::string, std::string>& args,
259     const std::string& separator) {
260   std::vector<std::string> combined_args;
261   for (const auto& [k, v] : args) {
262     CF_EXPECT(!v.empty(), "Found empty bootconfig value for " << k);
263     combined_args.push_back(k + "=" + v);
264   }
265   return android::base::Join(combined_args, separator);
266 }
267 
268 }  // namespace cuttlefish
269