U-Boot/Linux and HYP mode on ARMv7

The newer ARMv7 Cortex-A class cores such Cortex-A7, A15 and A17 come with a virtualization extensions which allow to use KVM (kernel virtual machine). The NXP i.MX 7Dual SoC which I worked with lately includes the ARM Cortex-A7 CPU. I went ahead and tried to bring up KVM on i.MX 7. I was not really familiar with the ARMv7 virtualization architecture, so I had to read up on some concepts. This post summarizes what I learned and gives a big picture of software support.

The Hypervisor mode

To provide hardware support for full CPU virtualization an additional privilege level is required. User-space (PL0) uses the SVC (Supervisor) instruction to switch to kernel-space (PL1, SVC mode). A similar separation between Kernel and hypervisor is required. The ARMv7 architecture with virtualization extension calls this privilege level PL2 or HYP mode.

Linux with KVM for ARM uses this mode to provide CPU virtualization. The CPU needs to be in HYP mode when Linux is booting so KVM can make use of the extension. How KVM uses the HYP mode in detail is explained in this excellent LWN article. After building a kernel with KVM support, I encountered this problem first: By default, the system did boot in SVC mode.

...
Brought up 2 CPUs
CPU: All CPU(s) started in SVC mode.
...
kvm [1]: HYP mode not available
...

Secure and Non-Secure world

ARMv7 privilege levels

To understand how to switch into Hypervisor mode, one needs to understand the whole privilege level architecture first. Notable here is that on ARMv7 CPU’s the HYP mode is only available in non-secure mode, by design. Any hypervisor needs to operate in non-secure mode, there is no virtualization extension in secure mode.

An ARMv7 CPU boots in the highest privilege mode, in the secure SVC mode. But even when booting Linux in secure SVC mode, the kernel is not able to switch to the hypervisor mode by itself. This confused me initially, as the secure SVC mode is a superior mode to any non-secure mode. The reason is that the Linux kernel just does not provide the necessary infrastructure/code to switch the CPU into non-secure mode and ultimately enable the hypervisor mode. Technically, I see no reason why the kernel couldn’t employ such an implementation. However, it would be an abuse of the architecture, it would violate separation of concerns. The architecture envisions a firmware running on the secure side to do that job, initialized early in boot flow e.g. by the ROM or boot loader of the system. Thanks to the work of Andre Przywara, Marc Zyngier and others, the boot loader U-Boot has all the infrastructure to setup a secure monitor and switch the CPU into HYP mode, essentially providing such a firmware…. ARM also provides a reference implementation called ARM trusted firmware that includes PSCI and also a number of other features-

ARM privilege levels & Linux/KVM and U-Boot

PSCI

In that context the ARM PSCI (Power State Coordination Interface) should be mentioned too. The interface has to be implemented by a firmware running in secure mode. The upstream configuration of U-Boot for i.MX 7 uses the PSCI interface to allow Linux to start secondary CPUs. Therefore U-Boot already installs a secure monitor and switches the CPU into non-secure SVC mode (the configurations CONFIG_ARMV7_PSCI and CONFIG_ARMV7_NONSEC were already in use). The boot log shows that PSCI is available:

The PSCI configuration option causes U-Boot to add a node device tree specifying the location of the PSCI firmware implementation. Currently, U-Boot supports the PSCI interface v0.1, but there are already patches to support interface v1.0.

Boot modes

U-Boot allows to use the environment variable bootm_boot_mode to choose which mode the operating system should get started. By setting the variable to sec, U-Boot makes sure to boot it in secure mode (hence not installing the provided secure monitor). I did not found direct evidence whether the CPU is in secure or non-secure mode in the Linux kernel boot log. However, the PSCI messages were missing (since PSCI needs a secure monitor), and subsequently the kernel was not able to start the secondary CPU cores anymore. As mentioned before, even though the CPU is in the highest privileged state, Linux is not able to switch to the hypervisor mode by itself.

When using sec, this returns 0 for all bits including the NS (Non Secure) bit, which means the CPU is in secure mode. But when using nonsec to boot in non-secure mode, this read crashes the kernel since accessing the register requires the CPU to be in secure mode.

Enable the Hypervisor mode

Since U-Boot already provides all necessary configuration and the i.MX 7 configuration already makes use of the secure monitor/PSCI, it turned out to be rather simple to enable the hypervisor mode: Just by adding the additional configuration option CONFIG_ARMV7_VIRT U-Boot made sure that it not only switches to non-secure mode but also enables the Hypervisor mode (this patch makes the configuration option available through Kconfig).

It turned out that when using VMSPLIT_2G, the hypervisors virtual address space overlaps the location of the hypervisors page table (IDMAP), which is not safe. The overlap was already there in my earlier v4.7 experiments, but only since a KVM patch merged during the v4.8 merge window KVM aborts initialization in that case. It turned out that using a different VMSPLIT can solve the issue, and using VMSPLIT_3G is sufficient for my platform (a Toradex Colibri iMX7D). With that, KVM initializes successfully again: