1# Custom VM
2
3## Headless VMs
4
5If your VM is headless (i.e. console in/out is the primary way of interacting
6with it), you can spawn it by passing a JSON config file to the
7VirtualizationService via the `vm` tool on a rooted AVF-enabled device. If your
8device is attached over ADB, you can run:
9
10```shell
11cat > vm_config.json <<EOF
12{
13  "kernel": "/data/local/tmp/kernel",
14  "initrd": "/data/local/tmp/ramdisk",
15  "params": "rdinit=/bin/init"
16}
17EOF
18adb root
19adb push <kernel> /data/local/tmp/kernel
20adb push <ramdisk> /data/local/tmp/ramdisk
21adb push vm_config.json /data/local/tmp/vm_config.json
22adb shell "/apex/com.android.virt/bin/vm run /data/local/tmp/vm_config.json"
23```
24
25The `vm` command also has other subcommands for debugging; run
26`/apex/com.android.virt/bin/vm help` for details.
27
28### Running Debian with u-boot
291. Prepare u-boot binary from `u-boot_crosvm_aarch64` in https://ci.android.com/builds/branches/aosp_u-boot-mainline/grid
30or build it by https://source.android.com/docs/devices/cuttlefish/bootloader-dev#develop-bootloader
312. Prepare Debian image from https://cloud.debian.org/images/cloud/ (We tested nocloud image)
323. Copy `u-boot.bin`, Debian image file(like `debian-12-nocloud-arm64.raw`) and `vm_config.json` to `/data/local/tmp`
33```shell
34cat > vm_config.json <<EOF
35{
36    "name": "debian",
37    "bootloader": "/data/local/tmp/u-boot.bin",
38    "disks": [
39        {
40            "image": "/data/local/tmp/debian-12-nocloud-arm64.raw",
41            "partitions": [],
42            "writable": true
43        }
44    ],
45    "protected": false,
46    "cpu_topology": "match_host",
47    "platform_version": "~1.0",
48    "memory_mib" : 8096
49}
50EOF
51adb push `u-boot.bin` /data/local/tmp
52adb push `debian-12-nocloud-arm64.raw` /data/local/tmp
53adb push vm_config.json /data/local/tmp/vm_config.json
54```
554. Launch VmLauncherApp(the detail will be explain below)
56
57## Graphical VMs
58
59To run OSes with graphics support, follow the instruction below.
60
61### Prepare a guest image
62
63As of today (April 2024), ChromiumOS is the only officially supported guest
64payload. We will be adding more OSes in the future.
65
66#### Download from build server
67
68  - Step 1) Go to the link https://ci.chromium.org/ui/p/chromeos/builders/chromiumos/ferrochrome-public-main/
69    - Note: I 'searched' the ferrochrome target with builder search.
70  - Step 2) Click a build number
71  - Step 3) Expand steps and find `48. upload artifacts`.
72  - Step 4) Click `gs upload dir`. You'll see Cloud storage with comprehensive artifacts (e.g. [Here](https://pantheon.corp.google.com/storage/browser/chromiumos-image-archive/ferrochrome-public/R126-15883.0.0) is the initial build of ferrochrome)
73  - Step 5) Download `image.zip`, which contains working vmlinuz.
74    - Note: DO NOT DOWNLOAD `vmlinuz.tar.xz` from the CI.
75  - Step 6) Uncompress `image.zip`, and boot with `chromiumos_test_image.bin` and `boot_images/vmlinuz`.
76    - Note: DO NOT USE `vmlinuz.bin`.
77
78IMPORTANT: DO NOT USE `vmlinuz.bin` for passing to crosvm. It doesn't pick-up the correct `init` process (picks `/init` instead of `/sbin/init`, and `cfg80211` keeps crashing (i.e. no network)
79
80
81#### Build ChromiumOS for VM
82
83First, check out source code from the ChromiumOS and Chromium projects.
84
85* Checking out ChromiumOS: https://www.chromium.org/chromium-os/developer-library/guides/development/developer-guide/
86* Checking out Chromium: https://g3doc.corp.google.com/chrome/chromeos/system_services_team/dev_instructions/g3doc/setup_checkout.md?cl=head
87
88Important: When you are at the step “Set up gclient args” in the Chromium checkout instruction, configure .gclient as follows.
89
90```
91$ cat ~/chromium/.gclient
92solutions = [
93  {
94    "name": "src",
95    "url": "https://chromium.googlesource.com/chromium/src.git",
96    "managed": False,
97    "custom_deps": {},
98    "custom_vars": {
99      "checkout_src_internal": True,
100    },
101  },
102]
103target_os = ['chromeos']
104```
105
106In this doc, it is assumed that ChromiumOS is checked out at `~/chromiumos` and
107Chromium is at `~/chromium`. If you downloaded to different places, you can
108create symlinks.
109
110Then enter into the cros sdk.
111
112```
113$ cd ~/chromiumos
114$ cros_sdk --chrome-root=$(readlink -f ~/chromium)
115```
116
117Now you are in the cros sdk. `(cr)` below means that the commands should be
118executed inside the sdk.
119
120First, choose the target board. `ferrochrome` is the name of the virtual board
121for AVF-compatible VM.
122
123```
124(cr) setup_board --board=ferrochrome
125```
126
127Then, tell the cros sdk that you want to build chrome (the browser) from the
128local checkout and also with your local modifications instead of prebuilts.
129
130```
131(cr) CHROME_ORIGIN=LOCAL_SOURCE
132(cr) ACCEPT_LICENSES='*'
133(cr) cros workon -b ferrochrome start \
134chromeos-base/chromeos-chrome \
135chromeos-base/chrome-icu
136```
137
138Optionally, if you have touched the kernel source code (which is under
139~/chromiumos/src/third_party/kernel/v5.15), you have to tell the cros sdk that
140you want it also to be built from the modified source code, not from the
141official HEAD.
142
143```
144(cr) cros workon -b ferrochrome start chromeos-kernel-5_15
145```
146
147Finally, build individual packages, and build the disk image out of the packages.
148
149```
150(cr) cros build-packages --board=ferrochrome --chromium --accept-licenses='*'
151(cr) cros build-image --board=ferrochrome --no-enable-rootfs-verification test
152```
153
154This takes some time. When the build is done, exit from the sdk.
155
156Note: If build-packages doesn’t seem to include your local changes, try
157invoking emerge directly:
158
159```
160(cr) emerge-ferrochrome -av chromeos-base/chromeos-chrome
161```
162
163Don’t forget to call `build-image` afterwards.
164
165You need two outputs:
166
167* ChromiumOS disk image: ~/chromiumos/src/build/images/ferrochrome/latest/chromiumos_test_image.bin
168* The kernel: ~/chromiumos/src/build/images/ferrochrome/latest/boot_images/vmlinuz
169
170### Create a guest VM configuration
171
172Push the kernel and the main image to the Android device.
173
174```
175$ adb push  ~/chromiumos/src/build/images/ferrochrome/latest/chromiumos_test_image.bin /data/local/tmp/
176$ adb push ~/chromiumos/out/build/ferrochrome/boot/vmlinuz /data/local/tmp/kernel
177```
178
179Create a VM config file as below.
180
181```
182$ cat > vm_config.json; adb push vm_config.json /data/local/tmp
183{
184    "name": "cros",
185    "kernel": "/data/local/tmp/kernel",
186    "disks": [
187        {
188            "image": "/data/local/tmp/chromiumos_test_image.bin",
189            "partitions": [],
190            "writable": true
191        }
192    ],
193    "gpu": {
194        "backend": "virglrenderer",
195        "context_types": ["virgl2"]
196    },
197    "params": "root=/dev/vda3 rootwait noinitrd ro enforcing=0 cros_debug cros_secure",
198    "protected": false,
199    "cpu_topology": "match_host",
200    "platform_version": "~1.0",
201    "memory_mib" : 8096,
202    "console_input_device": "ttyS0"
203}
204```
205
206### Running the VM
207
208First, enable the `VmLauncherApp` app. This needs to be done only once. In the
209future, this step won't be necesssary.
210
211```
212$ adb root
213$ adb shell pm enable com.android.virtualization.vmlauncher/.MainActivity
214$ adb unroot
215```
216
217If virt apex is Google-signed, you need to enable the app and grant the
218permission to the app.
219```
220$ adb root
221$ adb shell pm enable com.google.android.virtualization.vmlauncher/com.android.virtualization.vmlauncher.MainActivity
222$ adb shell pm grant com.google.android.virtualization.vmlauncher android.permission.USE_CUSTOM_VIRTUAL_MACHINE
223$ adb unroot
224```
225Then execute the below to set up the network. In the future, this step won't be necessary.
226
227```
228$ cat > setup_network.sh; adb push setup_network.sh /data/local/tmp
229#!/system/bin/sh
230
231set -e
232
233TAP_IFACE=crosvm_tap
234TAP_ADDR=192.168.1.1
235TAP_NET=192.168.1.0
236
237function setup_network() {
238  local WAN_IFACE=$(ip route get 8.8.8.8 2> /dev/null | awk -- '{printf $5}')
239  if [ "${WAN_IFACE}" == "" ]; then
240    echo "No network. Connect to a WiFi network and start again"
241    return 1
242  fi
243
244  if ip link show ${TAP_IFACE} &> /dev/null ; then
245    echo "TAP interface ${TAP_IFACE} already exists"
246    return 1
247  fi
248
249  ip tuntap add mode tap group virtualmachine vnet_hdr ${TAP_IFACE}
250  ip addr add ${TAP_ADDR}/24 dev ${TAP_IFACE}
251  ip link set ${TAP_IFACE} up
252  ip rule flush
253  ip rule add from all lookup ${WAN_IFACE}
254  ip route add ${TAP_NET}/24 dev ${TAP_IFACE} table ${WAN_IFACE}
255  sysctl net.ipv4.ip_forward=1
256  iptables -t filter -F
257  iptables -t nat -A POSTROUTING -s ${TAP_NET}/24 -j MASQUERADE
258}
259
260function setup_if_necessary() {
261  if [ "$(getprop ro.crosvm.network.setup.done)" == 1 ]; then
262    return
263  fi
264  echo "Setting up..."
265  check_privilege
266  setup_network
267  setenforce 0
268  chmod 666 /dev/tun
269  setprop ro.crosvm.network.setup.done 1
270}
271
272function check_privilege() {
273  if [ "$(id -u)" -ne 0 ]; then
274    echo "Run 'adb root' first"
275    return 1
276  fi
277}
278
279setup_if_necessary
280^D
281
282adb root; adb shell /data/local/tmp/setup_network.sh
283```
284
285Then, finally tap the VmLauncherApp app from the launcher UI. You will see
286Ferrochrome booting!
287
288If it doesn’t work well, try
289
290```
291$ adb shell pm clear com.android.virtualization.vmlauncher
292```
293
294### Inside guest OS (for ChromiumOS only)
295
296Go to the network setting and configure as below.
297
298* IP: 192.168.1.2 (other addresses in the 192.168.1.0/24 subnet also works)
299* netmask: 255.255.255.0
300* gateway: 192.168.1.1
301* DNS: 8.8.8.8 (or any DNS server you know)
302
303These settings are persistent; stored in chromiumos_test_image.bin. So you
304don’t have to repeat this next time.
305
306### Debugging
307
308To see console log, check
309`/data/data/com.android.virtualization.vmlauncher/files/console.log`
310
311For ChromiumOS, you can ssh-in. Use following commands after network setup.
312
313```shell
314$ adb kill-server ; adb start-server; adb forward tcp:9222 tcp:9222
315$ ssh -oProxyCommand=none -o UserKnownHostsFile=/dev/null root@localhost -p 9222
316```
317