[PATCH v1] mach-snapdragon: Add KVM hypervisor support
Aswin Murugan
aswin.murugan at oss.qualcomm.com
Sun Apr 19 19:38:29 CEST 2026
Enable Linux KVM virtualization on Snapdragon SoCs.
Introduce CONFIG_QCOM_KVM_SUPPORT to select KVM or Gunyah
hypervisor modes at build time.
qcom-priv.h:
- Add TrustZone SMC interface definitions and parameter IDs
- Define hypervisor boot types (GUNYAH=0, KVM=1)
- Add TCR_EL2 bit field definitions for memory config
board.c:
- Add qcom_configure_kvm_hypervisor() with EL-aware logic
- EL2: Perform direct SMC call for hypervisor setup
- EL1: Save context, disable caches, run SMC, restore state,
reconfigure TCR_EL2, re-enable caches
- Add qcom_configure_gunyah_hypervisor() for standard flow
- Add SCM service availability checks
Default mode remains Gunyah. Enable CONFIG_QCOM_KVM_SUPPORT to
select KVM for Linux.
Signed-off-by: Aswin Murugan <aswin.murugan at oss.qualcomm.com>
---
arch/arm/mach-snapdragon/Kconfig | 7 ++
arch/arm/mach-snapdragon/board.c | 154 +++++++++++++++++++++++++++
arch/arm/mach-snapdragon/qcom-priv.h | 31 ++++++
3 files changed, 192 insertions(+)
diff --git a/arch/arm/mach-snapdragon/Kconfig b/arch/arm/mach-snapdragon/Kconfig
index d3de8693b5a..2e5676945ca 100644
--- a/arch/arm/mach-snapdragon/Kconfig
+++ b/arch/arm/mach-snapdragon/Kconfig
@@ -42,4 +42,11 @@ config SYS_CONFIG_NAME
Based on this option include/configs/<CONFIG_SYS_CONFIG_NAME>.h header
will be used for board configuration.
+config QCOM_KVM_SUPPORT
+ bool "Enable KVM support for Qualcomm platforms"
+ depends on ARM64
+ help
+ This configures the hypervisor interface during boot to support
+ KVM virtualization instead of the default Gunyah hypervisor.
+
endif
diff --git a/arch/arm/mach-snapdragon/board.c b/arch/arm/mach-snapdragon/board.c
index 5fb3240acc5..2926dd8ccc0 100644
--- a/arch/arm/mach-snapdragon/board.c
+++ b/arch/arm/mach-snapdragon/board.c
@@ -510,6 +510,154 @@ void __weak qcom_late_init(void)
{
}
+/**
+ * qcom_configure_kvm_hypervisor() - Configure hypervisor for KVM guest mode
+ *
+ * Configures the hypervisor for KVM operation:
+ * - EL2 path: Direct SMC call
+ * - EL1 path: Save context, disable caches, SMC call, restore context
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+static int qcom_configure_kvm_hypervisor(void)
+{
+ struct arm_smccc_res res;
+ u64 current_el;
+
+ asm volatile("mrs %0, CurrentEL" : "=r" (current_el));
+ current_el = (current_el >> 2) & 0x3;
+
+ log_info("Configuring hypervisor for KVM (EL%llu)\n", current_el);
+
+ arm_smccc_smc(TZ_INFO_IS_SVC_AVAILABLE_ID,
+ TZ_INFO_IS_SVC_AVAILABLE_ID_PARAM_ID,
+ TZ_CONFIGURE_MILESTONE_SERVICE_ID,
+ 0, 0, 0, 0, 0, &res);
+
+ if (res.a0 != 0)
+ log_debug("KVM milestone service not available (0x%lx)\n", res.a0);
+
+ if (current_el == 2) {
+ log_debug("At EL2\n");
+
+ arm_smccc_smc(TZ_CONFIGURE_MILESTONE_SERVICE_ID,
+ TZ_CONFIGURE_MILESTONE_SERVICE_PARAM_ID,
+ 0, 0, QCOM_HYP_BOOT_TYPE_KVM,
+ 0, 0, 0, &res);
+
+ if (res.a0 != 0) {
+ log_err("Hypervisor configuration failed: 0x%lx\n", res.a0);
+ return -EIO;
+ }
+
+ log_info("KVM hypervisor configured\n");
+ return 0;
+ }
+
+ log_debug("At EL1, saving register context\n");
+
+ u64 ttbr0_el1, tcr_el1, tcr_el2, mair_el1;
+ u64 t0sz, phys_addr_size;
+
+ /* Save EL1 system register context */
+ asm volatile("mrs %0, ttbr0_el1" : "=r" (ttbr0_el1));
+ asm volatile("mrs %0, tcr_el1" : "=r" (tcr_el1));
+ asm volatile("mrs %0, mair_el1" : "=r" (mair_el1));
+
+ t0sz = tcr_el1 & TCR_T0SZ_MASK;
+ phys_addr_size = tcr_el1 & TCR_PS_MASK;
+
+ log_debug("Saved context: TTBR0=0x%llx TCR=0x%llx MAIR=0x%llx\n",
+ ttbr0_el1, tcr_el1, mair_el1);
+
+ icache_disable();
+ dcache_disable();
+
+ arm_smccc_smc(TZ_CONFIGURE_MILESTONE_SERVICE_ID,
+ TZ_CONFIGURE_MILESTONE_SERVICE_PARAM_ID,
+ 0, 0, QCOM_HYP_BOOT_TYPE_KVM,
+ 0, 0, 0, &res);
+
+ if (res.a0 != 0) {
+ log_err("Hypervisor configuration failed: 0x%lx\n", res.a0);
+ icache_enable();
+ dcache_enable();
+ return -EIO;
+ }
+
+ asm volatile("mrs %0, CurrentEL" : "=r" (current_el));
+ current_el = (current_el >> 2) & 0x3;
+
+ asm volatile("msr ttbr0_el1, %0" : : "r" (ttbr0_el1));
+ asm volatile("isb");
+
+ if (current_el != 2) {
+ log_debug("No EL2 transition, skipping TCR_EL2 config\n");
+ icache_enable();
+ dcache_enable();
+ log_warning("KVM hypervisor configuration failed\n");
+ return 0;
+ }
+
+ /* Read current TCR_EL2 and reconfigure it */
+ asm volatile("mrs %0, tcr_el2" : "=r" (tcr_el2));
+
+ tcr_el2 &= ~(TCR_T0SZ_MASK | (0x7UL << 16));
+ tcr_el2 |= t0sz | (phys_addr_size >> TCR_PS_SHIFT);
+
+ tcr_el2 &= ~TCR_SH_ORGN_IRGN_MASK;
+ tcr_el2 |= TCR_SH_INNER_SHAREABLE |
+ TCR_ORGN_WRITE_BACK_ALLOC |
+ TCR_IRGN_WRITE_BACK_ALLOC;
+
+ asm volatile("msr tcr_el2, %0" : : "r" (tcr_el2));
+ asm volatile("msr mair_el1, %0" : : "r" (mair_el1));
+ asm volatile("isb");
+
+ icache_enable();
+ dcache_enable();
+
+ log_info("KVM hypervisor configured\n");
+ return 0;
+}
+
+/**
+ * qcom_configure_gunyah_hypervisor() - Configure hypervisor for Gunyah mode
+ *
+ * Configures the hypervisor for standard Gunyah operation.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+static int qcom_configure_gunyah_hypervisor(void)
+{
+ struct arm_smccc_res res;
+
+ log_info("Configuring hypervisor for Gunyah mode\n");
+
+ arm_smccc_smc(TZ_INFO_IS_SVC_AVAILABLE_ID,
+ TZ_INFO_IS_SVC_AVAILABLE_ID_PARAM_ID,
+ TZ_CONFIGURE_MILESTONE_SERVICE_ID,
+ 0, 0, 0, 0, 0, &res);
+
+ if (res.a0 != 0) {
+ log_debug("Hypervisor milestone service not available (0x%lx)\n", res.a0);
+ return 0;
+ }
+
+ arm_smccc_smc(TZ_CONFIGURE_MILESTONE_SERVICE_ID,
+ TZ_CONFIGURE_MILESTONE_SERVICE_PARAM_ID,
+ 0, 0, QCOM_HYP_BOOT_TYPE_GUNYAH,
+ 0, 0, 0, &res);
+
+ if (res.a0 != 0) {
+ log_err("Hypervisor configuration failed: 0x%lx\n", res.a0);
+ return -EIO;
+ }
+
+ log_info("Gunyah hypervisor configured\n");
+ return 0;
+}
+
#define KERNEL_COMP_SIZE SZ_64M
arch/arm/mach-snapdragon/Kconfig #ifdef CONFIG_FASTBOOT_BUF_SIZE
#define FASTBOOT_BUF_SIZE CONFIG_FASTBOOT_BUF_SIZE
@@ -570,6 +718,12 @@ int board_late_init(void)
qcom_late_init();
qcom_show_boot_source();
+
+ if (IS_ENABLED(CONFIG_QCOM_KVM_SUPPORT))
+ qcom_configure_kvm_hypervisor();
+ else
+ qcom_configure_gunyah_hypervisor();
+
/* Configure the dfu_string for capsule updates */
qcom_configure_capsule_updates();
diff --git a/arch/arm/mach-snapdragon/qcom-priv.h b/arch/arm/mach-snapdragon/qcom-priv.h
index b8bf574e8bb..a5d9dec6aa7 100644
--- a/arch/arm/mach-snapdragon/qcom-priv.h
+++ b/arch/arm/mach-snapdragon/qcom-priv.h
@@ -17,6 +17,37 @@ enum qcom_boot_source {
extern enum qcom_boot_source qcom_boot_source;
+/* TrustZone SMC definitions */
+#define TZ_SYSCALL_CREATE_SMC_ID(o, s, f) \
+ ((u32)((((o) & 0x3f) << 24) | (((s) & 0xff) << 8) | ((f) & 0xff)))
+
+#define TZ_OWNER_SIP 2
+#define TZ_SVC_BOOT 1
+#define TZ_SVC_INFO 6
+#define TZ_BOOT_CMD_KVM_MILESTONE 0x21
+#define TZ_INFO_IS_SVC_AVAILABLE_CMD 0x01
+
+#define TZ_CONFIGURE_MILESTONE_SERVICE_ID \
+ TZ_SYSCALL_CREATE_SMC_ID(TZ_OWNER_SIP, TZ_SVC_BOOT, TZ_BOOT_CMD_KVM_MILESTONE)
+#define TZ_CONFIGURE_MILESTONE_SERVICE_PARAM_ID 0x23
+
+#define TZ_INFO_IS_SVC_AVAILABLE_ID \
+ TZ_SYSCALL_CREATE_SMC_ID(TZ_OWNER_SIP, TZ_SVC_INFO, TZ_INFO_IS_SVC_AVAILABLE_CMD)
+#define TZ_INFO_IS_SVC_AVAILABLE_ID_PARAM_ID 0x1
+
+/* Hypervisor boot types */
+#define QCOM_HYP_BOOT_TYPE_GUNYAH 0
+#define QCOM_HYP_BOOT_TYPE_KVM 1
+
+/* TCR_EL2 bit field definitions */
+#define TCR_T0SZ_MASK 0x1FUL
+#define TCR_PS_MASK (0x7UL << 32)
+#define TCR_PS_SHIFT 16
+#define TCR_SH_ORGN_IRGN_MASK 0x3F00UL
+#define TCR_SH_INNER_SHAREABLE (3UL << 12)
+#define TCR_ORGN_WRITE_BACK_ALLOC BIT(10)
+#define TCR_IRGN_WRITE_BACK_ALLOC BIT(8)
+
#if IS_ENABLED(CONFIG_EFI_HAVE_CAPSULE_SUPPORT)
void qcom_configure_capsule_updates(void);
#else
--
2.34.1
More information about the U-Boot
mailing list