An Android VM for ARMv8
In my last article I described how to build a Linux image that can be executed by Genode's VMM on i.MX8 platforms. With this article we take things to the next level by creating and executing an unmodified Android 10 VM on the i.MX 8M EVK SoC.
Building Android from source
NXP provides good documentation on how to build Android from source, it can be obtained here. Additionally source.android.com offers good reference. So let's start building now:
-
For source code checkout the repo tool is required and must be put in your PATH
curl https://storage.googleapis.com/git-repo-downloads/repo > ~/bin/repo chmod 755 ~/bin/repo
-
Install Python >= 3.6
-
The Android source code is split in open source and vendor specific parts. Fortunately NXP provide the vendor specific code that also downloads the other parts. It can be obtained here An NXP account is required.
-
Unpack the source code
tar -xzf imx-android-10.0.0_1.0.0.tar.gz
-
Download the public sources, per default the sources are put into ~/android_build. The download size is about 90GB.
source imx-android-10.0.0_1.0.0/imx_android_setup.sh export MY_ANDROID=~/android_build
-
Every path from here is relative to MY_ANDROID
-
Configure the build environment
source build/envsetup.sh
-
Configure the build for our i.MX8 SoC
lunch evk_8mq-userdebug
-
Android uses the LLVM compiler. This is currently not supported by NXP. Therefore, the kernel and the bootloader (U - Boot) need to be build separately with the GCC tool chain. GCC can be obtained here. We chose gcc-arm-8.3-2019.03-x86_64-aarch64-elf.tar.xz. Extract the tool chain. And set
export AARCH64_GCC_CROSS_COMPILE=<tool-chain>/gcc-arm-8.3-2019.03-x86_64-aarch64-elf/bin/aarch64-elf-
accordingly.
-
For development we like to network boot Genode scenarios. Per default U-Boot only supports small boot image sizes. Therefore, we adjusted:
CONFIG_SYS_BOOTM_LEN (256 << 20) /* 256 MB */
in vendor/nxp-opensource/uboot-imx/common/bootm.c.for the bootm command.
-
Build U-Boot
imx-make.sh -j32 bootloader
-
Build the Linux kernel
imx-make.sh -j32 kernel
-
Build the Android user land
make -j32
-
Detailed build logs can be found in out/verbose.log.*
Test drive the Android build
In order to the test our Android build we flashed an SD card. For this install android-tools-fsutils Ubuntu to obtain simg2img.
-
Go to the build directory of our board:
cd out/target/product/evk_8mq
-
Copy the U-Boot binaries
cp obj/UBOOT_COLLECTION/* .
-
Flash the SD card
./fsl-sdcard-partition.sh -f imx8mq /dev/<sdcard>
In case the script exists stating the partitions cannot be re-read. Increase the value of the sleep command in the script. Insert the SD card into the i.MX8 board and check if Android boots up correctly.
Starting Android as a virtual machine
Since Genode's VMM already supports the execution of unmodified Linux kernels, Android can be started in the same way as a normal Linux guest is. All that is needed is a kernel image, an initial RAM disk, and a device tree. This way we were able to build a Genode image, where Genode is loaded by U-Boot and Genode's VMM starts the Android Linux kernel as a VM. As we will see adjustments had only been made to the device tree. In the first step we want to give Android access to most of the SOCs hardware directly, exceptions are the UART and the interrupt controller, which are emulated by the VMM. So first, we exchanged the UART and the interrupt controller descriptions in the device tree with Genode's versions.
Hardware device pass-through
Our VMM already supports this feature. For this to work, all the physical resources described in the device tree have to be mapped to the exact same location within the guest VM, and the correct interrupts have to be injected. On i.MX8 DMA memory becomes an issue, because there is no I/O MMU. This means there exists no guest physical to host physical memory translation support by the hardware. In order to support DMA guest physical and host physical memory have to be mapped one to one by the VMM. As the time of writing, Android can already access the most important devices, e.g., SD card, USB, HDMI, and the GPU. But other devices, like audio support and the camera are still a work in progress.
Power management considerations
On ARM core idle management, addition and removal of cores, and system shutdown are defined by the Power State Coordination Interface. This interface is vendor implemented in firmware at a higher privilege level. Per default Linux assumes to run in privilege mode EL2. This means that the kernel will try to issue smc calls to the secure word, in order to call power state functions. Fortunately, this behaviour can be changed in the device tree and exchanged with hyp calls to the VMM. The VMM in turn emulates the calls issued by the guest, and thus, remains in control of the system. For an in depth article please refer to Stefan's post about this topic.
Additionally, we had to instruct the Linux kernel to not disable any clocks of the SoC, were Linux assumes they are unused. The default behaviour is that the kernel disables all clocks that are not explicitly enabled by a Linux driver. This can be achieved by a simple kernel parameter (clk_ignore_unused).
Android Verified Boot (AVB)
Android implements AVB in order to ensure the integrity of software running on a device. Additionally Android offers integrity checking of block devices through the device-mapper-verity, that is initialized by AVB. Much of the AVB setup is performed by the boot loader (U-Boot). Because the Android VM is loaded by Genode's VMM, which does not offers AVB functionality, Android is unable to mount the file system on the SD card. Therefore, both features need to be disabled.
Steps to disable dm-verity:
-
Boot Android in About tablet click seven times on build number to enable developer mode
-
Enable Settings -> Developer Options -> OEM Unlocking
-
Reboot into U-Boot
-
Connect USB-C cable to PC Android build
-
In U-Boot start fastboot
fastboot usb 1
-
Unlock device from the Android build
out/host/linux-x86/bin/fastboot oem unlock
-
Boot into Android and disable dm-verity from PC
out/host/linux-x86/bin/adb root out/host/linux-x86/bin/adb disable-verity out/host/linux-x86/bin/adb reboot
For AVB we extracted the kernel parameters and values from U-Boot that disable the feature:
androidboot.vbmeta.device=PARTUUID=$(ANDROID_VBMETA_PARTUUID) androidboot.vbmeta.avb_version=1.1 androidboot.vbmeta.device_state=unlocked androidboot.vbmeta.hash_alg=sha256 androidboot.vbmeta.size=3904 androidboot.vbmeta.digest=2072986c8709ab0f6d60f89ccbdc49f1ea687283ae88c7022db0aa8553c89393 androidboot.vbmeta.invalidate_on_error=no androidboot.veritymode=disabled androidboot.verifiedbootstate=orange
The digest can have an arbitrary value, I long as its length matches the one of a SHA256 checksum.
Current state
In the current state we are able to boot Android, there is input, the graphics, SD-card are working, but there still some issues with audio and the camera that will be addressed shortly.
My Genode Android branch can be found here.