* [PATCH 01/16] KVM: arm64: Add support for self-hosted debug in protected guests
2026-11-09 8:31 [PATCH 00/16] KVM: arm64: Support debug, SVE, MOPS, PIE/POE, FPMR and more in protected VMs Fuad Tabba
@ 2026-11-09 8:31 ` Fuad Tabba
2026-11-09 8:31 ` [PATCH 02/16] KVM: arm64: Remove redundant kern_hyp_va() calls from the pKVM FP sync Fuad Tabba
` (15 subsequent siblings)
16 siblings, 0 replies; 18+ messages in thread
From: Fuad Tabba @ 2026-11-09 8:31 UTC (permalink / raw)
To: Marc Zyngier, Oliver Upton, kvmarm, linux-arm-kernel
Cc: Joey Gouly, Suzuki K Poulose, Zenghui Yu, Steffen Eiden,
Catalin Marinas, Will Deacon, Mark Rutland, Vincent Donnefort,
Fuad Tabba, kvm, linux-kernel
pKVM hides DebugVer from protected guests and makes their debug
registers RAZ/WI or UNDEFINED, so they don't have breakpoints,
watchpoints or single-step. For a non-protected guest, the host sets the
owner of the debug registers at vcpu load, from the guest's MDSCR_EL1
and OS Lock, and EL2 copies the owner and the registers in at each
entry. However, the host can't see a protected guest's registers.
Set the owner at EL2 for a protected vCPU instead, by the rules the host
uses for a non-protected guest, but keep MDCR_EL2.TDA set when the guest
is the owner. A trap handler returns to the guest without going back
through the world switch, so a handler that changes the owner switches
the debug registers itself. The PSCI CPU_ON reset runs after the vcpu
load, so it sets the owner again.
Advertise DebugVer, capped at V8P8 as for a non-protected guest, and the
breakpoint, watchpoint and context-aware breakpoint counts. The other
debug registers that took an UNDEF behave as they do for a non-protected
guest that doesn't own its debug registers.
Signed-off-by: Fuad Tabba <fuad.tabba@linux.dev>
---
Documentation/virt/kvm/arm/pkvm.rst | 7 +-
arch/arm64/include/asm/kvm_hyp.h | 1 +
arch/arm64/kvm/arm.c | 1 +
arch/arm64/kvm/hyp/include/nvhe/pkvm.h | 1 +
arch/arm64/kvm/hyp/nvhe/hyp-main.c | 2 +
arch/arm64/kvm/hyp/nvhe/pkvm.c | 8 +-
arch/arm64/kvm/hyp/nvhe/sys_regs.c | 253 +++++++++++++++++++++++--
7 files changed, 248 insertions(+), 25 deletions(-)
diff --git a/Documentation/virt/kvm/arm/pkvm.rst b/Documentation/virt/kvm/arm/pkvm.rst
index b75bb8ad6f26d..b8c8c94cba534 100644
--- a/Documentation/virt/kvm/arm/pkvm.rst
+++ b/Documentation/virt/kvm/arm/pkvm.rst
@@ -241,10 +241,9 @@ Other interface differences
Debug
-----
-Hardware-assisted debugging is not available to protected guests.
-``MDSCR_EL1``, ``OSLAR_EL1``, ``OSLSR_EL1``, ``OSDLR_EL1``, and breakpoint
-and watchpoint 0 are RAZ/WI; any other debug register access takes an
-undefined instruction exception.
+A protected guest can use self-hosted debug (breakpoints, watchpoints and
+single-step) as a non-protected guest can. ``KVM_SET_GUEST_DEBUG`` returns
+``-EPERM`` for a protected VM.
Resources
=========
diff --git a/arch/arm64/include/asm/kvm_hyp.h b/arch/arm64/include/asm/kvm_hyp.h
index b0e42be3d9eef..9a785a341090e 100644
--- a/arch/arm64/include/asm/kvm_hyp.h
+++ b/arch/arm64/include/asm/kvm_hyp.h
@@ -151,6 +151,7 @@ extern u64 kvm_nvhe_sym(id_aa64mmfr0_el1_sys_val);
extern u64 kvm_nvhe_sym(id_aa64mmfr1_el1_sys_val);
extern u64 kvm_nvhe_sym(id_aa64mmfr2_el1_sys_val);
extern u64 kvm_nvhe_sym(id_aa64smfr0_el1_sys_val);
+extern u64 kvm_nvhe_sym(id_aa64dfr0_el1_sys_val);
extern unsigned long kvm_nvhe_sym(__icache_flags);
extern unsigned int kvm_nvhe_sym(kvm_arm_vmid_bits);
diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
index 157bf4b812020..ec90a580e82a4 100644
--- a/arch/arm64/kvm/arm.c
+++ b/arch/arm64/kvm/arm.c
@@ -2834,6 +2834,7 @@ static void kvm_hyp_init_symbols(void)
kvm_nvhe_sym(id_aa64mmfr1_el1_sys_val) = read_sanitised_ftr_reg(SYS_ID_AA64MMFR1_EL1);
kvm_nvhe_sym(id_aa64mmfr2_el1_sys_val) = read_sanitised_ftr_reg(SYS_ID_AA64MMFR2_EL1);
kvm_nvhe_sym(id_aa64smfr0_el1_sys_val) = read_sanitised_ftr_reg(SYS_ID_AA64SMFR0_EL1);
+ kvm_nvhe_sym(id_aa64dfr0_el1_sys_val) = read_sanitised_ftr_reg(SYS_ID_AA64DFR0_EL1);
kvm_nvhe_sym(__icache_flags) = __icache_flags;
kvm_nvhe_sym(kvm_arm_vmid_bits) = kvm_arm_vmid_bits;
diff --git a/arch/arm64/kvm/hyp/include/nvhe/pkvm.h b/arch/arm64/kvm/hyp/include/nvhe/pkvm.h
index 0261b2bc681f7..993d54ce44408 100644
--- a/arch/arm64/kvm/hyp/include/nvhe/pkvm.h
+++ b/arch/arm64/kvm/hyp/include/nvhe/pkvm.h
@@ -130,6 +130,7 @@ void put_pkvm_hyp_vm(struct pkvm_hyp_vm *hyp_vm);
bool kvm_handle_pvm_hvc64(struct kvm_vcpu *vcpu, u64 *exit_code);
bool kvm_handle_pvm_sysreg(struct kvm_vcpu *vcpu, u64 *exit_code);
bool kvm_handle_pvm_restricted(struct kvm_vcpu *vcpu, u64 *exit_code);
+void kvm_load_pvm_debug(struct kvm_vcpu *vcpu);
void kvm_init_pvm_id_regs(struct kvm_vcpu *vcpu);
void kvm_reset_pvm_sys_regs(struct kvm_vcpu *vcpu);
int kvm_check_pvm_sysreg_table(void);
diff --git a/arch/arm64/kvm/hyp/nvhe/hyp-main.c b/arch/arm64/kvm/hyp/nvhe/hyp-main.c
index 179d7c8277b88..9958ca6d7b0a2 100644
--- a/arch/arm64/kvm/hyp/nvhe/hyp-main.c
+++ b/arch/arm64/kvm/hyp/nvhe/hyp-main.c
@@ -837,6 +837,8 @@ DEFINE_KVM_HOST_HCALL(void, __pkvm_vcpu_load,
hyp_vcpu->vcpu.arch.hcr_el2 &= ~(HCR_EL2_TWE | HCR_EL2_TWI);
hyp_vcpu->vcpu.arch.hcr_el2 |= hcr_el2 & (HCR_EL2_TWE | HCR_EL2_TWI);
+ kvm_load_pvm_debug(&hyp_vcpu->vcpu);
+
/* HPMN == 0 is reserved without FEAT_HPMN0. */
if (pmuv3_implemented(SYS_FIELD_GET(ID_AA64DFR0_EL1, PMUVer, dfr0)))
u64p_replace_bits(&hyp_vcpu->vcpu.arch.mdcr_el2,
diff --git a/arch/arm64/kvm/hyp/nvhe/pkvm.c b/arch/arm64/kvm/hyp/nvhe/pkvm.c
index fc0475f0877d1..1d484da274c0e 100644
--- a/arch/arm64/kvm/hyp/nvhe/pkvm.c
+++ b/arch/arm64/kvm/hyp/nvhe/pkvm.c
@@ -123,8 +123,11 @@ static void pvm_init_traps_mdcr(struct kvm_vcpu *vcpu)
val &= ~(MDCR_EL2_HPME | MDCR_EL2_MTPME);
}
- if (!kvm_has_feat(kvm, ID_AA64DFR0_EL1, DebugVer, IMP))
- val |= MDCR_EL2_TDRA | MDCR_EL2_TDA;
+ /*
+ * Keep TDA set even when the guest owns debug: it also traps the DCC
+ * and the claim tags, which the world switch doesn't switch.
+ */
+ val |= MDCR_EL2_TDRA | MDCR_EL2_TDA;
if (!kvm_has_feat(kvm, ID_AA64DFR0_EL1, DoubleLock, IMP))
val |= MDCR_EL2_TDOSA;
@@ -1245,6 +1248,7 @@ int pkvm_reset_vcpu(struct pkvm_hyp_vcpu *hyp_vcpu)
kvm_reset_vcpu_core(&hyp_vcpu->vcpu);
kvm_reset_pvm_sys_regs(&hyp_vcpu->vcpu);
+ kvm_load_pvm_debug(&hyp_vcpu->vcpu);
/* Must be done after resetting sys registers. */
kvm_reset_vcpu_psci(&hyp_vcpu->vcpu, reset_state);
diff --git a/arch/arm64/kvm/hyp/nvhe/sys_regs.c b/arch/arm64/kvm/hyp/nvhe/sys_regs.c
index 32e82653ebf7e..e7148254ec5b8 100644
--- a/arch/arm64/kvm/hyp/nvhe/sys_regs.c
+++ b/arch/arm64/kvm/hyp/nvhe/sys_regs.c
@@ -10,6 +10,8 @@
#include <asm/kvm_mmu.h>
#include <hyp/adjust_pc.h>
+#include <hyp/debug-sr.h>
+#include <hyp/sysreg-sr.h>
#include <nvhe/pkvm.h>
@@ -28,6 +30,7 @@ u64 id_aa64mmfr0_el1_sys_val;
u64 id_aa64mmfr1_el1_sys_val;
u64 id_aa64mmfr2_el1_sys_val;
u64 id_aa64smfr0_el1_sys_val;
+u64 id_aa64dfr0_el1_sys_val;
struct pvm_ftr_bits {
bool sign;
@@ -55,6 +58,13 @@ struct pvm_ftr_bits {
#define MAX_FEAT_ENUM(id, fld, max) \
__MAX_FEAT_FUNC(id, fld, max, NULL, false)
+#define MAX_FEAT_PASSTHRU(id, fld) \
+ { \
+ .shift = id##_##fld##_SHIFT, \
+ .width = id##_##fld##_WIDTH, \
+ .max_val = GENMASK(id##_##fld##_WIDTH - 1, 0), \
+ }
+
#define FEAT_END { .width = 0, }
static bool vm_has_ptrauth(const struct kvm *kvm)
@@ -175,17 +185,18 @@ static const struct pvm_ftr_bits pvmid_aa64isar2[] = {
FEAT_END
};
-/*
- * None of the features in ID_AA64DFR0_EL1 nor ID_AA64MMFR4_EL1 are supported.
- * However, both have Not-Implemented values that are non-zero. Define them
- * so they can be used when getting the value of these registers.
- */
-#define ID_AA64DFR0_EL1_NONZERO_NI \
-( \
- SYS_FIELD_PREP_ENUM(ID_AA64DFR0_EL1, DoubleLock, NI) | \
- SYS_FIELD_PREP_ENUM(ID_AA64DFR0_EL1, MTPMU, NI) \
-)
+/* DoubleLock and MTPMU are hidden, but their Not-Implemented values are non-zero. */
+static const struct pvm_ftr_bits pvmid_aa64dfr0[] = {
+ MAX_FEAT(ID_AA64DFR0_EL1, DebugVer, V8P8),
+ MAX_FEAT_PASSTHRU(ID_AA64DFR0_EL1, BRPs),
+ MAX_FEAT_PASSTHRU(ID_AA64DFR0_EL1, WRPs),
+ MAX_FEAT_PASSTHRU(ID_AA64DFR0_EL1, CTX_CMPs),
+ MAX_FEAT(ID_AA64DFR0_EL1, DoubleLock, NI),
+ MAX_FEAT(ID_AA64DFR0_EL1, MTPMU, NI),
+ FEAT_END
+};
+/* ID_AA64MMFR4_EL1 is hidden, but its Not-Implemented value is non-zero. */
#define ID_AA64MMFR4_EL1_NONZERO_NI \
SYS_FIELD_PREP_ENUM(ID_AA64MMFR4_EL1, E2H0, NI)
@@ -245,7 +256,7 @@ static u64 pvm_calc_id_reg(const struct kvm_vcpu *vcpu, u32 id)
case SYS_ID_AA64MMFR2_EL1:
return get_restricted_features(vcpu, id_aa64mmfr2_el1_sys_val, pvmid_aa64mmfr2);
case SYS_ID_AA64DFR0_EL1:
- return ID_AA64DFR0_EL1_NONZERO_NI;
+ return get_restricted_features(vcpu, id_aa64dfr0_el1_sys_val, pvmid_aa64dfr0);
case SYS_ID_AA64MMFR4_EL1:
return ID_AA64MMFR4_EL1_NONZERO_NI;
default:
@@ -372,6 +383,179 @@ static bool pvm_idst_access(struct kvm_vcpu *vcpu,
return false;
}
+static void pvm_set_external_mdscr(struct kvm_vcpu *vcpu, u64 mdscr)
+{
+ vcpu->arch.external_mdscr_el1 = mdscr & ~(MDSCR_EL1_SS | MDSCR_EL1_MDE | MDSCR_EL1_KDE);
+}
+
+/* KVM_SET_GUEST_DEBUG fails for a protected vCPU, so only the OS Lock makes it HOST_OWNED. */
+static int pvm_debug_owner(struct kvm_vcpu *vcpu, u64 mdscr)
+{
+ if (kvm_vcpu_os_lock_enabled(vcpu))
+ return VCPU_DEBUG_HOST_OWNED;
+
+ if (mdscr & (MDSCR_EL1_KDE | MDSCR_EL1_MDE))
+ return VCPU_DEBUG_GUEST_OWNED;
+
+ return VCPU_DEBUG_FREE;
+}
+
+/* Trap handlers run after entry has loaded the debug state. */
+static void pvm_debug_switch_owner(struct kvm_vcpu *vcpu, int new)
+{
+ *ctxt_mdscr_el1(&vcpu->arch.ctxt) = read_sysreg(mdscr_el1);
+
+ __debug_switch_to_host_common(vcpu);
+ vcpu->arch.debug_owner = new;
+ __debug_switch_to_guest_common(vcpu);
+
+ write_sysreg(*ctxt_mdscr_el1(&vcpu->arch.ctxt), mdscr_el1);
+}
+
+static void pvm_debug_set_guest_ownership(struct kvm_vcpu *vcpu)
+{
+ if (kvm_debug_regs_in_use(vcpu))
+ return;
+
+ pvm_debug_switch_owner(vcpu, VCPU_DEBUG_GUEST_OWNED);
+}
+
+/* Runs before the world switch, which loads the debug state that debug_owner selects. */
+void kvm_load_pvm_debug(struct kvm_vcpu *vcpu)
+{
+ u64 mdscr = __vcpu_sys_reg(vcpu, MDSCR_EL1);
+
+ vcpu->arch.debug_owner = pvm_debug_owner(vcpu, mdscr);
+
+ if (kvm_host_owns_debug_regs(vcpu))
+ pvm_set_external_mdscr(vcpu, mdscr);
+}
+
+static bool pvm_trap_dbg_wb_reg(struct kvm_vcpu *vcpu, struct sys_reg_params *p,
+ const struct sys_reg_desc *r)
+{
+ struct kvm_guest_debug_arch *dbg = &vcpu->arch.vcpu_debug_state;
+ u64 *reg;
+
+ switch (r->Op2) {
+ case 0b100:
+ reg = &dbg->dbg_bvr[r->CRm];
+ break;
+ case 0b101:
+ reg = &dbg->dbg_bcr[r->CRm];
+ break;
+ case 0b110:
+ reg = &dbg->dbg_wvr[r->CRm];
+ break;
+ default:
+ reg = &dbg->dbg_wcr[r->CRm];
+ }
+
+ if (p->is_write)
+ *reg = p->regval;
+ else
+ p->regval = *reg;
+
+ /* Guest-owned registers are live: __debug_switch_to_host() overwrites memory with them. */
+ if (p->is_write && kvm_guest_owns_debug_regs(vcpu))
+ __debug_restore_state(dbg, &vcpu->arch.ctxt);
+
+ pvm_debug_set_guest_ownership(vcpu);
+ return true;
+}
+
+/* Outside HOST_OWNED, use the live register: __sysreg_save_state_nvhe() overwrites memory. */
+static u64 pvm_read_mdscr(struct kvm_vcpu *vcpu)
+{
+ if (kvm_host_owns_debug_regs(vcpu))
+ return __vcpu_sys_reg(vcpu, MDSCR_EL1);
+
+ return read_sysreg(mdscr_el1);
+}
+
+static bool pvm_trap_mdscr(struct kvm_vcpu *vcpu, struct sys_reg_params *p,
+ const struct sys_reg_desc *r)
+{
+ if (!p->is_write) {
+ p->regval = pvm_read_mdscr(vcpu);
+ } else if (kvm_host_owns_debug_regs(vcpu)) {
+ __vcpu_assign_sys_reg(vcpu, MDSCR_EL1, p->regval);
+ pvm_set_external_mdscr(vcpu, p->regval);
+ write_sysreg(vcpu->arch.external_mdscr_el1, mdscr_el1);
+ } else {
+ write_sysreg(p->regval, mdscr_el1);
+ }
+
+ pvm_debug_set_guest_ownership(vcpu);
+ return true;
+}
+
+/* __debug_restore_state() loads MDCCINT_EL1 from memory, so keep memory current. */
+static bool pvm_trap_mdccint(struct kvm_vcpu *vcpu, struct sys_reg_params *p,
+ const struct sys_reg_desc *r)
+{
+ if (p->is_write) {
+ __vcpu_assign_sys_reg(vcpu, MDCCINT_EL1, p->regval);
+ if (kvm_debug_regs_in_use(vcpu))
+ write_sysreg(p->regval, mdccint_el1);
+ } else {
+ p->regval = __vcpu_sys_reg(vcpu, MDCCINT_EL1);
+ }
+
+ pvm_debug_set_guest_ownership(vcpu);
+ return true;
+}
+
+static bool pvm_trap_oslar(struct kvm_vcpu *vcpu, struct sys_reg_params *p,
+ const struct sys_reg_desc *r)
+{
+ int new_owner;
+ u64 mdscr;
+
+ if (!p->is_write) {
+ inject_undef64(vcpu);
+ return false;
+ }
+
+ mdscr = pvm_read_mdscr(vcpu);
+
+ if (p->regval & OSLAR_EL1_OSLK)
+ __vcpu_rmw_sys_reg(vcpu, OSLSR_EL1, |=, OSLSR_EL1_OSLK);
+ else
+ __vcpu_rmw_sys_reg(vcpu, OSLSR_EL1, &=, ~OSLSR_EL1_OSLK);
+
+ new_owner = pvm_debug_owner(vcpu, mdscr);
+ if (new_owner == VCPU_DEBUG_HOST_OWNED)
+ pvm_set_external_mdscr(vcpu, mdscr);
+
+ if (new_owner != vcpu->arch.debug_owner)
+ pvm_debug_switch_owner(vcpu, new_owner);
+
+ return true;
+}
+
+static bool pvm_trap_oslsr(struct kvm_vcpu *vcpu, struct sys_reg_params *p,
+ const struct sys_reg_desc *r)
+{
+ if (p->is_write) {
+ inject_undef64(vcpu);
+ return false;
+ }
+
+ p->regval = __vcpu_sys_reg(vcpu, OSLSR_EL1);
+ return true;
+}
+
+/* As the host's trap_dbgauthstatus_el1(). */
+static bool pvm_trap_dbgauthstatus(struct kvm_vcpu *vcpu, struct sys_reg_params *p,
+ const struct sys_reg_desc *r)
+{
+ if (!p->is_write)
+ p->regval = read_sysreg(dbgauthstatus_el1);
+
+ return true;
+}
+
/* Mark the specified system register as an AArch32 feature id register. */
#define AARCH32(REG) { SYS_DESC(REG), .access = pvm_access_id_aarch32 }
@@ -391,6 +575,12 @@ static bool pvm_idst_access(struct kvm_vcpu *vcpu,
/* Mark the specified system register as Read-As-Zero/Write-Ignored */
#define RAZ_WI(REG) { SYS_DESC(REG), .access = pvm_access_raz_wi }
+#define DBG_WB(n) \
+ { SYS_DESC(SYS_DBGBVRn_EL1(n)), .access = pvm_trap_dbg_wb_reg }, \
+ { SYS_DESC(SYS_DBGBCRn_EL1(n)), .access = pvm_trap_dbg_wb_reg }, \
+ { SYS_DESC(SYS_DBGWVRn_EL1(n)), .access = pvm_trap_dbg_wb_reg }, \
+ { SYS_DESC(SYS_DBGWCRn_EL1(n)), .access = pvm_trap_dbg_wb_reg }
+
/* Mark the specified system register as not being handled in hyp. */
#define HOST_HANDLED(REG) { SYS_DESC(REG), .access = NULL }
@@ -404,15 +594,36 @@ static bool pvm_idst_access(struct kvm_vcpu *vcpu,
static const struct sys_reg_desc pvm_sys_reg_descs[] = {
/* Cache maintenance by set/way operations are restricted. */
- /* Debug and Trace Registers are restricted. */
- RAZ_WI(SYS_DBGBVRn_EL1(0)),
- RAZ_WI(SYS_DBGBCRn_EL1(0)),
- RAZ_WI(SYS_DBGWVRn_EL1(0)),
- RAZ_WI(SYS_DBGWCRn_EL1(0)),
- RAZ_WI(SYS_MDSCR_EL1),
- RAZ_WI(SYS_OSLAR_EL1),
- RAZ_WI(SYS_OSLSR_EL1),
+ /* Debug registers */
+ DBG_WB(0),
+ DBG_WB(1),
+ { SYS_DESC(SYS_MDCCINT_EL1), .access = pvm_trap_mdccint },
+ { SYS_DESC(SYS_MDSCR_EL1), .access = pvm_trap_mdscr },
+ DBG_WB(2),
+ DBG_WB(3),
+ DBG_WB(4),
+ DBG_WB(5),
+ DBG_WB(6),
+ DBG_WB(7),
+ DBG_WB(8),
+ DBG_WB(9),
+ DBG_WB(10),
+ DBG_WB(11),
+ DBG_WB(12),
+ DBG_WB(13),
+ DBG_WB(14),
+ DBG_WB(15),
+ RAZ_WI(SYS_MDRAR_EL1),
+ { SYS_DESC(SYS_OSLAR_EL1), .access = pvm_trap_oslar },
+ { SYS_DESC(SYS_OSLSR_EL1), .access = pvm_trap_oslsr },
RAZ_WI(SYS_OSDLR_EL1),
+ RAZ_WI(SYS_DBGPRCR_EL1),
+ RAZ_WI(SYS_DBGCLAIMSET_EL1),
+ RAZ_WI(SYS_DBGCLAIMCLR_EL1),
+ { SYS_DESC(SYS_DBGAUTHSTATUS_EL1), .access = pvm_trap_dbgauthstatus },
+ RAZ_WI(SYS_MDCCSR_EL0),
+ RAZ_WI(SYS_DBGDTR_EL0),
+ RAZ_WI(SYS_DBGDTRTX_EL0),
/* Group 1 ID registers */
HOST_HANDLED(SYS_REVIDR_EL1),
@@ -559,6 +770,8 @@ static const struct sys_reg_desc_reset pvm_sys_reg_reset_vals[] = {
RESET_UNKNOWN(TPIDR_EL1),
RESET_ZERO(CNTKCTL_EL1),
RESET_UNKNOWN(PAR_EL1),
+ RESET_ZERO(MDCCINT_EL1),
+ RESET_VAL(OSLSR_EL1, OSLSR_EL1_OSLM_IMPLEMENTED),
RESET_ZERO(DISR_EL1),
RESET_ZERO(CPACR_EL1),
RESET_VAL(CONTEXTIDR_EL1, 0x00000000dbadc0deULL),
@@ -586,6 +799,8 @@ void kvm_reset_pvm_sys_regs(struct kvm_vcpu *vcpu)
r->reset(vcpu, r);
}
+
+ memset(&vcpu->arch.vcpu_debug_state, 0, sizeof(vcpu->arch.vcpu_debug_state));
}
/*
base-commit: 6628e7d4c6b25c8d18f1345e3a20d21829aae7e4
--
2.47.3
^ permalink raw reply [flat|nested] 18+ messages in thread* [PATCH 02/16] KVM: arm64: Remove redundant kern_hyp_va() calls from the pKVM FP sync
2026-11-09 8:31 [PATCH 00/16] KVM: arm64: Support debug, SVE, MOPS, PIE/POE, FPMR and more in protected VMs Fuad Tabba
2026-11-09 8:31 ` [PATCH 01/16] KVM: arm64: Add support for self-hosted debug in protected guests Fuad Tabba
@ 2026-11-09 8:31 ` Fuad Tabba
2026-11-09 8:31 ` [PATCH 03/16] KVM: arm64: Allocate a protected vCPU's SVE state at EL2 Fuad Tabba
` (14 subsequent siblings)
16 siblings, 0 replies; 18+ messages in thread
From: Fuad Tabba @ 2026-11-09 8:31 UTC (permalink / raw)
To: Marc Zyngier, Oliver Upton, kvmarm, linux-arm-kernel
Cc: Joey Gouly, Suzuki K Poulose, Zenghui Yu, Steffen Eiden,
Catalin Marinas, Will Deacon, Mark Rutland, Vincent Donnefort,
Fuad Tabba, kvm, linux-kernel
With pKVM, EL2 saves a guest's FP and SVE state on exit into the hyp
vCPU, whose kvm is the hyp VM's and whose sve_state is already a hyp
address. kern_hyp_va() returns both unchanged.
Remove the two calls, in fpsimd_sve_sync() and __hyp_sve_save_guest().
The one in __hyp_sve_restore_guest() stays, as nVHE without pKVM reaches
it with the host's pointer.
No functional change intended.
Tested-by: Vincent Donnefort <vdonnefort@google.com>
Reviewed-by: Vincent Donnefort <vdonnefort@google.com>
Signed-off-by: Fuad Tabba <fuad.tabba@linux.dev>
---
arch/arm64/kvm/hyp/nvhe/hyp-main.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/arch/arm64/kvm/hyp/nvhe/hyp-main.c b/arch/arm64/kvm/hyp/nvhe/hyp-main.c
index 9958ca6d7b0a2..57c3f7a2d4b14 100644
--- a/arch/arm64/kvm/hyp/nvhe/hyp-main.c
+++ b/arch/arm64/kvm/hyp/nvhe/hyp-main.c
@@ -482,7 +482,7 @@ static void __hyp_sve_save_guest(struct kvm_vcpu *vcpu)
* on the VL, so use a consistent (i.e., the maximum) guest VL.
*/
sve_cond_update_zcr_vq(vcpu_sve_max_vq(vcpu) - 1, SYS_ZCR_EL2);
- sve_save_state(kern_hyp_va(vcpu->arch.sve_state), true);
+ sve_save_state(vcpu->arch.sve_state, true);
fpsimd_save_common(&vcpu->arch.ctxt.fp_regs);
write_sysreg_s(sve_vq_from_vl(kvm_host_sve_max_vl) - 1, SYS_ZCR_EL2);
}
@@ -531,7 +531,7 @@ static void fpsimd_sve_sync(struct kvm_vcpu *vcpu)
else
fpsimd_save_state(&vcpu->arch.ctxt.fp_regs);
- has_fpmr = kvm_has_fpmr(kern_hyp_va(vcpu->kvm));
+ has_fpmr = kvm_has_fpmr(vcpu->kvm);
if (has_fpmr)
__vcpu_assign_sys_reg(vcpu, FPMR, read_sysreg_s(SYS_FPMR));
--
2.47.3
^ permalink raw reply [flat|nested] 18+ messages in thread* [PATCH 03/16] KVM: arm64: Allocate a protected vCPU's SVE state at EL2
2026-11-09 8:31 [PATCH 00/16] KVM: arm64: Support debug, SVE, MOPS, PIE/POE, FPMR and more in protected VMs Fuad Tabba
2026-11-09 8:31 ` [PATCH 01/16] KVM: arm64: Add support for self-hosted debug in protected guests Fuad Tabba
2026-11-09 8:31 ` [PATCH 02/16] KVM: arm64: Remove redundant kern_hyp_va() calls from the pKVM FP sync Fuad Tabba
@ 2026-11-09 8:31 ` Fuad Tabba
2026-11-09 8:31 ` [PATCH 04/16] KVM: arm64: Advertise ID_AA64ZFR0_EL1 to protected VMs Fuad Tabba
` (13 subsequent siblings)
16 siblings, 0 replies; 18+ messages in thread
From: Fuad Tabba @ 2026-11-09 8:31 UTC (permalink / raw)
To: Marc Zyngier, Oliver Upton, kvmarm, linux-arm-kernel
Cc: Joey Gouly, Suzuki K Poulose, Zenghui Yu, Steffen Eiden,
Catalin Marinas, Will Deacon, Mark Rutland, Vincent Donnefort,
Fuad Tabba, kvm, linux-kernel
For a vCPU with SVE, EL2 pins the buffer the host allocated at
KVM_ARM_VCPU_FINALIZE and saves the guest's SVE registers there. The
host can still read and write that buffer, so it can't hold a protected
vCPU's state.
Allocate a protected vCPU's SVE state from the hyp heap instead, and
free it with the vCPU. When EL2 resets a protected vCPU, zero its SVE
state and ZCR_EL1, as KVM does for a non-protected one. The host still
allocates its own buffer, which KVM_{GET,SET}_ONE_REG access before the
first run, but EL2 doesn't use it.
For a protected vCPU, the host's sve_max_vl now sizes an EL2 allocation
and is the guest's maximum vector length on every CPU. Give EL2 a copy
of kvm_sve_max_vl, the largest length all CPUs can give a guest, and
check every vCPU's sve_max_vl against it rather than clamping it to the
host's own maximum. set_sve_vls() already keeps the host's value within
that bound.
Tested-by: Vincent Donnefort <vdonnefort@google.com>
Reviewed-by: Vincent Donnefort <vdonnefort@google.com>
Signed-off-by: Fuad Tabba <fuad.tabba@linux.dev>
---
arch/arm64/include/asm/kvm_hyp.h | 1 +
arch/arm64/kvm/hyp/nvhe/pkvm.c | 41 ++++++++++++++++++++----------
arch/arm64/kvm/hyp/nvhe/sys_regs.c | 1 +
arch/arm64/kvm/reset.c | 2 ++
4 files changed, 31 insertions(+), 14 deletions(-)
diff --git a/arch/arm64/include/asm/kvm_hyp.h b/arch/arm64/include/asm/kvm_hyp.h
index 9a785a341090e..22601a4e36f4e 100644
--- a/arch/arm64/include/asm/kvm_hyp.h
+++ b/arch/arm64/include/asm/kvm_hyp.h
@@ -158,6 +158,7 @@ extern unsigned int kvm_nvhe_sym(kvm_arm_vmid_bits);
extern enum mitigation_state kvm_nvhe_sym(spectre_v2_state);
extern enum mitigation_state kvm_nvhe_sym(spectre_v4_state);
extern enum mitigation_state kvm_nvhe_sym(spectre_bhb_state);
+extern unsigned int kvm_nvhe_sym(kvm_sve_max_vl);
extern unsigned int kvm_nvhe_sym(kvm_host_sve_max_vl);
extern unsigned long kvm_nvhe_sym(hyp_nr_cpus);
extern unsigned int kvm_nvhe_sym(hyp_gicv3_nr_lr);
diff --git a/arch/arm64/kvm/hyp/nvhe/pkvm.c b/arch/arm64/kvm/hyp/nvhe/pkvm.c
index 1d484da274c0e..fba8dfe6389d6 100644
--- a/arch/arm64/kvm/hyp/nvhe/pkvm.c
+++ b/arch/arm64/kvm/hyp/nvhe/pkvm.c
@@ -30,6 +30,7 @@ unsigned long __icache_flags;
/* Used by kvm_get_vttbr(). */
unsigned int kvm_arm_vmid_bits;
+unsigned int kvm_sve_max_vl;
unsigned int kvm_host_sve_max_vl;
/*
@@ -479,7 +480,7 @@ static void unpin_host_vcpu(struct kvm_vcpu *host_vcpu)
hyp_unpin_shared_mem(host_vcpu, host_vcpu + 1);
}
-static void unpin_host_sve_state(struct pkvm_hyp_vcpu *hyp_vcpu)
+static void teardown_sve_state(struct pkvm_hyp_vcpu *hyp_vcpu)
{
void *sve_state;
@@ -487,8 +488,10 @@ static void unpin_host_sve_state(struct pkvm_hyp_vcpu *hyp_vcpu)
if (!sve_state)
return;
- hyp_unpin_shared_mem(sve_state,
- sve_state + vcpu_sve_state_size(&hyp_vcpu->vcpu));
+ if (pkvm_hyp_vcpu_is_protected(hyp_vcpu))
+ hyp_free(sve_state);
+ else
+ hyp_unpin_shared_mem(sve_state, sve_state + vcpu_sve_state_size(&hyp_vcpu->vcpu));
}
static void unpin_host_vcpus(struct pkvm_hyp_vcpu *hyp_vcpus[],
@@ -503,7 +506,7 @@ static void unpin_host_vcpus(struct pkvm_hyp_vcpu *hyp_vcpus[],
continue;
unpin_host_vcpu(hyp_vcpu->host_vcpu);
- unpin_host_sve_state(hyp_vcpu);
+ teardown_sve_state(hyp_vcpu);
}
}
@@ -546,18 +549,25 @@ static int pkvm_vcpu_init_sve(struct pkvm_hyp_vcpu *hyp_vcpu, struct kvm_vcpu *h
return 0;
}
- /* Limit guest vector length to the maximum supported by the host. */
- sve_max_vl = min(READ_ONCE(host_vcpu->arch.sve_max_vl), kvm_host_sve_max_vl);
- sve_state = kern_hyp_va(READ_ONCE(host_vcpu->arch.sve_state));
-
- if (!sve_vl_valid(sve_max_vl) || !sve_state)
+ sve_max_vl = READ_ONCE(host_vcpu->arch.sve_max_vl);
+ if (!sve_vl_valid(sve_max_vl) || sve_max_vl > kvm_sve_max_vl)
return -EINVAL;
sve_state_size = sve_state_size_from_vl(sve_max_vl);
- ret = hyp_pin_shared_mem(sve_state, sve_state + sve_state_size);
- if (ret)
- return ret;
+ if (pkvm_hyp_vcpu_is_protected(hyp_vcpu)) {
+ sve_state = hyp_alloc(sve_state_size);
+ if (!sve_state)
+ return hyp_alloc_errno();
+ } else {
+ sve_state = kern_hyp_va(READ_ONCE(host_vcpu->arch.sve_state));
+ if (!sve_state)
+ return -EINVAL;
+
+ ret = hyp_pin_shared_mem(sve_state, sve_state + sve_state_size);
+ if (ret)
+ return ret;
+ }
vcpu->arch.sve_state = sve_state;
vcpu->arch.sve_max_vl = sve_max_vl;
@@ -646,7 +656,7 @@ static int init_pkvm_hyp_vcpu(struct pkvm_hyp_vcpu *hyp_vcpu,
done:
if (ret) {
unpin_host_vcpu(host_vcpu);
- unpin_host_sve_state(hyp_vcpu);
+ teardown_sve_state(hyp_vcpu);
}
return ret;
}
@@ -1018,7 +1028,7 @@ int __pkvm_init_vcpu(pkvm_handle_t handle, struct kvm_vcpu *host_vcpu)
ret = register_hyp_vcpu(hyp_vm, hyp_vcpu);
if (ret) {
unpin_host_vcpu(host_vcpu);
- unpin_host_sve_state(hyp_vcpu);
+ teardown_sve_state(hyp_vcpu);
goto unclaim;
}
goto unlock;
@@ -1246,6 +1256,9 @@ int pkvm_reset_vcpu(struct pkvm_hyp_vcpu *hyp_vcpu)
PSCI_0_2_AFFINITY_LEVEL_ON) != PSCI_0_2_AFFINITY_LEVEL_ON_PENDING)
return -ECANCELED;
+ if (vcpu_has_sve(&hyp_vcpu->vcpu))
+ memset(hyp_vcpu->vcpu.arch.sve_state, 0, vcpu_sve_state_size(&hyp_vcpu->vcpu));
+
kvm_reset_vcpu_core(&hyp_vcpu->vcpu);
kvm_reset_pvm_sys_regs(&hyp_vcpu->vcpu);
kvm_load_pvm_debug(&hyp_vcpu->vcpu);
diff --git a/arch/arm64/kvm/hyp/nvhe/sys_regs.c b/arch/arm64/kvm/hyp/nvhe/sys_regs.c
index e7148254ec5b8..7a0aed1d8b0b3 100644
--- a/arch/arm64/kvm/hyp/nvhe/sys_regs.c
+++ b/arch/arm64/kvm/hyp/nvhe/sys_regs.c
@@ -783,6 +783,7 @@ static const struct sys_reg_desc_reset pvm_sys_reg_reset_vals[] = {
RESET_UNKNOWN(MAIR_EL1),
RESET_ZERO(AMAIR_EL1),
RESET_ZERO(MDSCR_EL1),
+ RESET_ZERO(ZCR_EL1),
RESET_ZERO(CNTV_CTL_EL0),
RESET_UNKNOWN(TTBR0_EL1),
RESET_UNKNOWN(TTBR1_EL1),
diff --git a/arch/arm64/kvm/reset.c b/arch/arm64/kvm/reset.c
index 10eb7249aa9e8..aaaa1b8634ba4 100644
--- a/arch/arm64/kvm/reset.c
+++ b/arch/arm64/kvm/reset.c
@@ -52,6 +52,8 @@ int __init kvm_arm_init_sve(void)
if (WARN_ON(kvm_sve_max_vl > VL_ARCH_MAX))
kvm_sve_max_vl = VL_ARCH_MAX;
+ kvm_nvhe_sym(kvm_sve_max_vl) = kvm_sve_max_vl;
+
/*
* Don't even try to make use of vector lengths that
* aren't available on all CPUs, for now:
--
2.47.3
^ permalink raw reply [flat|nested] 18+ messages in thread* [PATCH 04/16] KVM: arm64: Advertise ID_AA64ZFR0_EL1 to protected VMs
2026-11-09 8:31 [PATCH 00/16] KVM: arm64: Support debug, SVE, MOPS, PIE/POE, FPMR and more in protected VMs Fuad Tabba
` (2 preceding siblings ...)
2026-11-09 8:31 ` [PATCH 03/16] KVM: arm64: Allocate a protected vCPU's SVE state at EL2 Fuad Tabba
@ 2026-11-09 8:31 ` Fuad Tabba
2026-11-09 8:31 ` [PATCH 05/16] KVM: arm64: Allow protected VMs to use SVE Fuad Tabba
` (12 subsequent siblings)
16 siblings, 0 replies; 18+ messages in thread
From: Fuad Tabba @ 2026-11-09 8:31 UTC (permalink / raw)
To: Marc Zyngier, Oliver Upton, kvmarm, linux-arm-kernel
Cc: Joey Gouly, Suzuki K Poulose, Zenghui Yu, Steffen Eiden,
Catalin Marinas, Will Deacon, Mark Rutland, Vincent Donnefort,
Fuad Tabba, kvm, linux-kernel
EL2 builds a protected guest's ID registers from the host's sanitised
values, most of them capped per field. It has no case for
ID_AA64ZFR0_EL1, so the register reads as 0, and a protected guest with
SVE wouldn't see any of the SVE extensions the CPUs implement.
Keep a copy of the host's sanitised ID_AA64ZFR0_EL1 at EL2, and
advertise it when the guest has SVE, as KVM does for a non-protected
guest. Cap each field at the highest value the sysreg file defines.
Tested-by: Vincent Donnefort <vdonnefort@google.com>
Reviewed-by: Vincent Donnefort <vdonnefort@google.com>
Signed-off-by: Fuad Tabba <fuad.tabba@linux.dev>
---
arch/arm64/include/asm/kvm_hyp.h | 1 +
arch/arm64/kvm/arm.c | 1 +
arch/arm64/kvm/hyp/nvhe/sys_regs.c | 19 +++++++++++++++++++
3 files changed, 21 insertions(+)
diff --git a/arch/arm64/include/asm/kvm_hyp.h b/arch/arm64/include/asm/kvm_hyp.h
index 22601a4e36f4e..920c1d8d45b1a 100644
--- a/arch/arm64/include/asm/kvm_hyp.h
+++ b/arch/arm64/include/asm/kvm_hyp.h
@@ -144,6 +144,7 @@ void __noreturn __host_enter(struct kvm_cpu_context *host_ctxt);
extern u64 kvm_nvhe_sym(id_aa64pfr0_el1_sys_val);
extern u64 kvm_nvhe_sym(id_aa64pfr1_el1_sys_val);
extern u64 kvm_nvhe_sym(id_aa64pfr2_el1_sys_val);
+extern u64 kvm_nvhe_sym(id_aa64zfr0_el1_sys_val);
extern u64 kvm_nvhe_sym(id_aa64isar0_el1_sys_val);
extern u64 kvm_nvhe_sym(id_aa64isar1_el1_sys_val);
extern u64 kvm_nvhe_sym(id_aa64isar2_el1_sys_val);
diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
index ec90a580e82a4..2753686982f0b 100644
--- a/arch/arm64/kvm/arm.c
+++ b/arch/arm64/kvm/arm.c
@@ -2827,6 +2827,7 @@ static void kvm_hyp_init_symbols(void)
kvm_nvhe_sym(id_aa64pfr0_el1_sys_val) = get_hyp_id_aa64pfr0_el1();
kvm_nvhe_sym(id_aa64pfr1_el1_sys_val) = read_sanitised_ftr_reg(SYS_ID_AA64PFR1_EL1);
kvm_nvhe_sym(id_aa64pfr2_el1_sys_val) = read_sanitised_ftr_reg(SYS_ID_AA64PFR2_EL1);
+ kvm_nvhe_sym(id_aa64zfr0_el1_sys_val) = read_sanitised_ftr_reg(SYS_ID_AA64ZFR0_EL1);
kvm_nvhe_sym(id_aa64isar0_el1_sys_val) = read_sanitised_ftr_reg(SYS_ID_AA64ISAR0_EL1);
kvm_nvhe_sym(id_aa64isar1_el1_sys_val) = read_sanitised_ftr_reg(SYS_ID_AA64ISAR1_EL1);
kvm_nvhe_sym(id_aa64isar2_el1_sys_val) = read_sanitised_ftr_reg(SYS_ID_AA64ISAR2_EL1);
diff --git a/arch/arm64/kvm/hyp/nvhe/sys_regs.c b/arch/arm64/kvm/hyp/nvhe/sys_regs.c
index 7a0aed1d8b0b3..8456bb44edd52 100644
--- a/arch/arm64/kvm/hyp/nvhe/sys_regs.c
+++ b/arch/arm64/kvm/hyp/nvhe/sys_regs.c
@@ -23,6 +23,7 @@
u64 id_aa64pfr0_el1_sys_val;
u64 id_aa64pfr1_el1_sys_val;
u64 id_aa64pfr2_el1_sys_val;
+u64 id_aa64zfr0_el1_sys_val;
u64 id_aa64isar0_el1_sys_val;
u64 id_aa64isar1_el1_sys_val;
u64 id_aa64isar2_el1_sys_val;
@@ -124,6 +125,22 @@ static const struct pvm_ftr_bits pvmid_aa64pfr2[] = {
FEAT_END
};
+static const struct pvm_ftr_bits pvmid_aa64zfr0[] = {
+ MAX_FEAT_FUNC(ID_AA64ZFR0_EL1, SVEver, SVE2p3, vm_has_sve),
+ MAX_FEAT_FUNC(ID_AA64ZFR0_EL1, AES, AES2, vm_has_sve),
+ MAX_FEAT_FUNC(ID_AA64ZFR0_EL1, EltPerm, IMP, vm_has_sve),
+ MAX_FEAT_FUNC(ID_AA64ZFR0_EL1, BitPerm, IMP, vm_has_sve),
+ MAX_FEAT_FUNC(ID_AA64ZFR0_EL1, BF16, EBF16, vm_has_sve),
+ MAX_FEAT_FUNC(ID_AA64ZFR0_EL1, B16B16, B16MM, vm_has_sve),
+ MAX_FEAT_FUNC(ID_AA64ZFR0_EL1, SHA3, IMP, vm_has_sve),
+ MAX_FEAT_FUNC(ID_AA64ZFR0_EL1, SM4, IMP, vm_has_sve),
+ MAX_FEAT_FUNC(ID_AA64ZFR0_EL1, I8MM, IMP, vm_has_sve),
+ MAX_FEAT_FUNC(ID_AA64ZFR0_EL1, F16MM, IMP, vm_has_sve),
+ MAX_FEAT_FUNC(ID_AA64ZFR0_EL1, F32MM, IMP, vm_has_sve),
+ MAX_FEAT_FUNC(ID_AA64ZFR0_EL1, F64MM, IMP, vm_has_sve),
+ FEAT_END
+};
+
static const struct pvm_ftr_bits pvmid_aa64mmfr0[] = {
MAX_FEAT_ENUM(ID_AA64MMFR0_EL1, PARANGE, 40),
MAX_FEAT_ENUM(ID_AA64MMFR0_EL1, ASIDBITS, 16),
@@ -243,6 +260,8 @@ static u64 pvm_calc_id_reg(const struct kvm_vcpu *vcpu, u32 id)
return get_restricted_features(vcpu, id_aa64pfr1_el1_sys_val, pvmid_aa64pfr1);
case SYS_ID_AA64PFR2_EL1:
return get_restricted_features(vcpu, id_aa64pfr2_el1_sys_val, pvmid_aa64pfr2);
+ case SYS_ID_AA64ZFR0_EL1:
+ return get_restricted_features(vcpu, id_aa64zfr0_el1_sys_val, pvmid_aa64zfr0);
case SYS_ID_AA64ISAR0_EL1:
return id_aa64isar0_el1_sys_val;
case SYS_ID_AA64ISAR1_EL1:
--
2.47.3
^ permalink raw reply [flat|nested] 18+ messages in thread* [PATCH 05/16] KVM: arm64: Allow protected VMs to use SVE
2026-11-09 8:31 [PATCH 00/16] KVM: arm64: Support debug, SVE, MOPS, PIE/POE, FPMR and more in protected VMs Fuad Tabba
` (3 preceding siblings ...)
2026-11-09 8:31 ` [PATCH 04/16] KVM: arm64: Advertise ID_AA64ZFR0_EL1 to protected VMs Fuad Tabba
@ 2026-11-09 8:31 ` Fuad Tabba
2026-11-09 8:31 ` [PATCH 06/16] KVM: arm64: Move the VM's FGU initialisation into config.c Fuad Tabba
` (11 subsequent siblings)
16 siblings, 0 replies; 18+ messages in thread
From: Fuad Tabba @ 2026-11-09 8:31 UTC (permalink / raw)
To: Marc Zyngier, Oliver Upton, kvmarm, linux-arm-kernel
Cc: Joey Gouly, Suzuki K Poulose, Zenghui Yu, Steffen Eiden,
Catalin Marinas, Will Deacon, Mark Rutland, Vincent Donnefort,
Fuad Tabba, kvm, linux-kernel
Now that EL2 owns a protected vCPU's SVE state and advertises
ID_AA64ZFR0_EL1, allow KVM_CAP_ARM_SVE for a protected VM, which lets
KVM_ARM_VCPU_INIT take KVM_ARM_VCPU_SVE.
A protected guest's SVE access trap injects an UNDEF. An SVE
instruction takes the SVE trap rather than the FP one, so a guest whose
first vector instruction is an SVE one would get an UNDEF instead of
the lazy FP switch. Handle the SVE trap as the FP trap is:
kvm_handle_pvm_fpsimd() switches the state for a vCPU with SVE and
still injects the UNDEF for one without.
Update the documentation to match.
Tested-by: Vincent Donnefort <vdonnefort@google.com>
Reviewed-by: Vincent Donnefort <vdonnefort@google.com>
Signed-off-by: Fuad Tabba <fuad.tabba@linux.dev>
---
Documentation/virt/kvm/arm/pkvm.rst | 6 +++---
arch/arm64/include/asm/kvm_pkvm.h | 1 +
arch/arm64/kvm/hyp/nvhe/switch.c | 1 +
arch/arm64/kvm/hyp/nvhe/sys_regs.c | 2 --
4 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/Documentation/virt/kvm/arm/pkvm.rst b/Documentation/virt/kvm/arm/pkvm.rst
index b8c8c94cba534..c59e598645b1b 100644
--- a/Documentation/virt/kvm/arm/pkvm.rst
+++ b/Documentation/virt/kvm/arm/pkvm.rst
@@ -134,9 +134,9 @@ A protected VM boots from a single primary vCPU. Before the first
* Set the primary vCPU's boot state with ``KVM_SET_ONE_REG``: the kernel
entry address in ``PC`` and the DTB pointer in ``x0``.
-``PC`` and ``x0`` are the only registers EL2 takes from the host. Other
-pre-run writes are accepted, but the guest starts from the state the
-hypervisor resets it to.
+``PC``, ``x0`` and the SVE vector lengths (``KVM_REG_ARM64_SVE_VLS``) are
+the only registers EL2 takes from the host. Other pre-run writes are
+accepted, but the guest starts from the state the hypervisor resets it to.
Secondary vCPUs are started by the guest itself through PSCI ``CPU_ON``
(see `Power state`_), which supplies their entry point and context ID.
diff --git a/arch/arm64/include/asm/kvm_pkvm.h b/arch/arm64/include/asm/kvm_pkvm.h
index fd042d2b54624..5f1b3308b42e1 100644
--- a/arch/arm64/include/asm/kvm_pkvm.h
+++ b/arch/arm64/include/asm/kvm_pkvm.h
@@ -40,6 +40,7 @@ static inline bool kvm_pkvm_ext_allowed(struct kvm *kvm, long ext)
case KVM_CAP_MAX_VCPU_ID:
case KVM_CAP_MSI_DEVID:
case KVM_CAP_ARM_VM_IPA_SIZE:
+ case KVM_CAP_ARM_SVE:
case KVM_CAP_ARM_PTRAUTH_ADDRESS:
case KVM_CAP_ARM_PTRAUTH_GENERIC:
case KVM_CAP_ONE_REG:
diff --git a/arch/arm64/kvm/hyp/nvhe/switch.c b/arch/arm64/kvm/hyp/nvhe/switch.c
index 89fb9dd351ad5..0626f4203ba6d 100644
--- a/arch/arm64/kvm/hyp/nvhe/switch.c
+++ b/arch/arm64/kvm/hyp/nvhe/switch.c
@@ -230,6 +230,7 @@ static const exit_handler_fn pvm_exit_handlers[] = {
[ESR_ELx_EC_WFx] = kvm_handle_pvm_wfx,
[ESR_ELx_EC_HVC64] = kvm_handle_pvm_hvc64,
[ESR_ELx_EC_SYS64] = kvm_handle_pvm_sys64,
+ [ESR_ELx_EC_SVE] = kvm_handle_pvm_fpsimd,
[ESR_ELx_EC_FP_ASIMD] = kvm_handle_pvm_fpsimd,
[ESR_ELx_EC_IABT_LOW] = kvm_hyp_handle_iabt_low,
[ESR_ELx_EC_DABT_LOW] = kvm_hyp_handle_dabt_low,
diff --git a/arch/arm64/kvm/hyp/nvhe/sys_regs.c b/arch/arm64/kvm/hyp/nvhe/sys_regs.c
index 8456bb44edd52..78c25da737591 100644
--- a/arch/arm64/kvm/hyp/nvhe/sys_regs.c
+++ b/arch/arm64/kvm/hyp/nvhe/sys_regs.c
@@ -713,8 +713,6 @@ static const struct sys_reg_desc pvm_sys_reg_descs[] = {
ID_UNALLOCATED(7,6),
ID_UNALLOCATED(7,7),
- /* Scalable Vector Registers are restricted. */
-
HOST_HANDLED(SYS_ICC_PMR_EL1),
RAZ_WI(SYS_ERRIDR_EL1),
--
2.47.3
^ permalink raw reply [flat|nested] 18+ messages in thread* [PATCH 06/16] KVM: arm64: Move the VM's FGU initialisation into config.c
2026-11-09 8:31 [PATCH 00/16] KVM: arm64: Support debug, SVE, MOPS, PIE/POE, FPMR and more in protected VMs Fuad Tabba
` (4 preceding siblings ...)
2026-11-09 8:31 ` [PATCH 05/16] KVM: arm64: Allow protected VMs to use SVE Fuad Tabba
@ 2026-11-09 8:31 ` Fuad Tabba
2026-11-09 8:31 ` [PATCH 07/16] KVM: arm64: Compute a protected vCPU's fine-grained traps at EL2 Fuad Tabba
` (10 subsequent siblings)
16 siblings, 0 replies; 18+ messages in thread
From: Fuad Tabba @ 2026-11-09 8:31 UTC (permalink / raw)
To: Marc Zyngier, Oliver Upton, kvmarm, linux-arm-kernel
Cc: Joey Gouly, Suzuki K Poulose, Zenghui Yu, Steffen Eiden,
Catalin Marinas, Will Deacon, Mark Rutland, Vincent Donnefort,
Fuad Tabba, kvm, linux-kernel
The first time one of a VM's vCPUs runs, kvm_calculate_traps() computes
the VM's fine-grained UNDEF masks from its ID registers, one
compute_fgu() call per FGT group, and sets KVM_ARCH_FLAG_FGU_INITIALIZED
so that it computes them once. The next patch computes the same masks
for protected VMs at EL2.
Move the calls and the flag into kvm_init_fgu() in config.c, so that a
group added later is computed at EL2 too. compute_fgu() then has no
caller outside config.c, so make it static.
No functional change intended.
Signed-off-by: Fuad Tabba <fuad.tabba@linux.dev>
---
arch/arm64/include/asm/kvm_host.h | 2 +-
arch/arm64/kvm/config.c | 20 +++++++++++++++++++-
arch/arm64/kvm/sys_regs.c | 17 +----------------
3 files changed, 21 insertions(+), 18 deletions(-)
diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h
index 97332ffcb1300..82ba4bb9573f1 100644
--- a/arch/arm64/include/asm/kvm_host.h
+++ b/arch/arm64/include/asm/kvm_host.h
@@ -1748,7 +1748,7 @@ static inline bool kvm_arch_has_irq_bypass(void)
}
void kvm_idregs_enforce_dependencies(struct kvm *kvm);
-void compute_fgu(struct kvm *kvm, enum fgt_group_id fgt);
+void kvm_init_fgu(struct kvm *kvm);
struct resx get_reg_fixed_bits(struct kvm *kvm, enum vcpu_sysreg reg);
void check_feature_map(void);
void kvm_vcpu_load_fgt(struct kvm_vcpu *vcpu);
diff --git a/arch/arm64/kvm/config.c b/arch/arm64/kvm/config.c
index adaf26ab93b85..c65cf1dd27330 100644
--- a/arch/arm64/kvm/config.c
+++ b/arch/arm64/kvm/config.c
@@ -1586,7 +1586,7 @@ static u64 compute_fgu_bits(struct kvm *kvm, const struct reg_feat_map_desc *r)
return resx.res0 | resx.res1;
}
-void compute_fgu(struct kvm *kvm, enum fgt_group_id fgt)
+static void compute_fgu(struct kvm *kvm, enum fgt_group_id fgt)
{
u64 val = 0;
@@ -1630,6 +1630,24 @@ void compute_fgu(struct kvm *kvm, enum fgt_group_id fgt)
kvm->arch.fgu[fgt] = val;
}
+void kvm_init_fgu(struct kvm *kvm)
+{
+ if (test_bit(KVM_ARCH_FLAG_FGU_INITIALIZED, &kvm->arch.flags))
+ return;
+
+ compute_fgu(kvm, HFGRTR_GROUP);
+ compute_fgu(kvm, HFGITR_GROUP);
+ compute_fgu(kvm, HDFGRTR_GROUP);
+ compute_fgu(kvm, HAFGRTR_GROUP);
+ compute_fgu(kvm, HFGRTR2_GROUP);
+ compute_fgu(kvm, HFGITR2_GROUP);
+ compute_fgu(kvm, HDFGRTR2_GROUP);
+ compute_fgu(kvm, ICH_HFGRTR_GROUP);
+ compute_fgu(kvm, ICH_HFGITR_GROUP);
+
+ set_bit(KVM_ARCH_FLAG_FGU_INITIALIZED, &kvm->arch.flags);
+}
+
struct resx get_reg_fixed_bits(struct kvm *kvm, enum vcpu_sysreg reg)
{
struct resx resx;
diff --git a/arch/arm64/kvm/sys_regs.c b/arch/arm64/kvm/sys_regs.c
index 850eba167aad6..bcec0a7d2b2f5 100644
--- a/arch/arm64/kvm/sys_regs.c
+++ b/arch/arm64/kvm/sys_regs.c
@@ -5841,22 +5841,7 @@ void kvm_calculate_traps(struct kvm_vcpu *vcpu)
vcpu_set_hcr(vcpu);
vcpu_set_ich_hcr(vcpu);
vcpu_set_hcrx(vcpu);
-
- if (test_bit(KVM_ARCH_FLAG_FGU_INITIALIZED, &kvm->arch.flags))
- goto out;
-
- compute_fgu(kvm, HFGRTR_GROUP);
- compute_fgu(kvm, HFGITR_GROUP);
- compute_fgu(kvm, HDFGRTR_GROUP);
- compute_fgu(kvm, HAFGRTR_GROUP);
- compute_fgu(kvm, HFGRTR2_GROUP);
- compute_fgu(kvm, HFGITR2_GROUP);
- compute_fgu(kvm, HDFGRTR2_GROUP);
- compute_fgu(kvm, ICH_HFGRTR_GROUP);
- compute_fgu(kvm, ICH_HFGITR_GROUP);
-
- set_bit(KVM_ARCH_FLAG_FGU_INITIALIZED, &kvm->arch.flags);
-out:
+ kvm_init_fgu(kvm);
mutex_unlock(&kvm->arch.config_lock);
}
--
2.47.3
^ permalink raw reply [flat|nested] 18+ messages in thread* [PATCH 07/16] KVM: arm64: Compute a protected vCPU's fine-grained traps at EL2
2026-11-09 8:31 [PATCH 00/16] KVM: arm64: Support debug, SVE, MOPS, PIE/POE, FPMR and more in protected VMs Fuad Tabba
` (5 preceding siblings ...)
2026-11-09 8:31 ` [PATCH 06/16] KVM: arm64: Move the VM's FGU initialisation into config.c Fuad Tabba
@ 2026-11-09 8:31 ` Fuad Tabba
2026-11-09 8:31 ` [PATCH 08/16] KVM: arm64: Advertise TCR2, SCTLR2, S1PIE and S1POE to protected VMs Fuad Tabba
` (9 subsequent siblings)
16 siblings, 0 replies; 18+ messages in thread
From: Fuad Tabba @ 2026-11-09 8:31 UTC (permalink / raw)
To: Marc Zyngier, Oliver Upton, kvmarm, linux-arm-kernel
Cc: Joey Gouly, Suzuki K Poulose, Zenghui Yu, Steffen Eiden,
Catalin Marinas, Will Deacon, Mark Rutland, Vincent Donnefort,
Fuad Tabba, kvm, linux-kernel
pKVM sets a protected vCPU's HCR_EL2, MDCR_EL2 and HCRX_EL2 at EL2 from
the VM's ID registers, but never computes its fine-grained traps, which
stay 0. That traps every register with a negative-polarity trap bit, so
a feature such as S1PIE can't be advertised to a protected VM: EL2
would inject an UNDEF for the guest's accesses to PIR_EL1.
Link config.c into the nVHE hyp and compute the VM's FGU masks and the
vCPU's fine-grained traps there, under vm_table_lock, once the ID
registers are set. The init-time check of the feature maps stays out of
the hyp, since it prints and the host runs it anyway.
A protected vCPU then traps the registers of the features it doesn't
support, as a non-protected vCPU does. Its TCR_EL1 writes also get the
AmpereOne AC03_CPU_38 masking again, which protected vCPUs lost when the
erratum's trap moved into the per-vCPU fine-grained traps.
Fixes: fb10ddf35c1cc ("KVM: arm64: Compute per-vCPU FGTs at vcpu_load()")
Signed-off-by: Fuad Tabba <fuad.tabba@linux.dev>
---
arch/arm64/kvm/config.c | 12 +++++++-----
arch/arm64/kvm/hyp/nvhe/Makefile | 1 +
arch/arm64/kvm/hyp/nvhe/pkvm.c | 2 ++
3 files changed, 10 insertions(+), 5 deletions(-)
diff --git a/arch/arm64/kvm/config.c b/arch/arm64/kvm/config.c
index c65cf1dd27330..27ee8188fd76d 100644
--- a/arch/arm64/kvm/config.c
+++ b/arch/arm64/kvm/config.c
@@ -1395,6 +1395,12 @@ static const struct reg_bits_to_feat_map ich_hfgitr_feat_map[] = {
static const DECLARE_FEAT_MAP_FGT(ich_hfgitr_desc, ich_hfgitr_masks,
ich_hfgitr_feat_map, FEAT_GCIE);
+static u64 reg_feat_map_bits(const struct reg_bits_to_feat_map *map)
+{
+ return map->flags & MASKS_POINTER ? (map->masks->mask | map->masks->nmask) : map->bits;
+}
+
+#ifndef __KVM_NVHE_HYPERVISOR__
static void __init check_feat_map(const struct reg_bits_to_feat_map *map,
int map_size, u64 resx, const char *str)
{
@@ -1414,11 +1420,6 @@ static void __init check_feat_map(const struct reg_bits_to_feat_map *map,
str, mask ^ ~resx);
}
-static u64 reg_feat_map_bits(const struct reg_bits_to_feat_map *map)
-{
- return map->flags & MASKS_POINTER ? (map->masks->mask | map->masks->nmask) : map->bits;
-}
-
static void __init check_reg_desc(const struct reg_feat_map_desc *r)
{
check_feat_map(r->bit_feat_map, r->bit_feat_map_sz,
@@ -1451,6 +1452,7 @@ void __init check_feature_map(void)
check_reg_desc(&ich_hfgwtr_desc);
check_reg_desc(&ich_hfgitr_desc);
}
+#endif
static const struct reg_feat_map_desc *idreg_feat_maps[] = {
&id_aa64mmfr3_el1_desc,
diff --git a/arch/arm64/kvm/hyp/nvhe/Makefile b/arch/arm64/kvm/hyp/nvhe/Makefile
index 56a7e2c05f73a..06d7d8618d89f 100644
--- a/arch/arm64/kvm/hyp/nvhe/Makefile
+++ b/arch/arm64/kvm/hyp/nvhe/Makefile
@@ -27,6 +27,7 @@ hyp-obj-y := timer-sr.o sysreg-sr.o debug-sr.o switch.o tlb.o hyp-init.o host.o
cache.o setup.o mm.o mem_protect.o sys_regs.o pkvm.o stacktrace.o ffa.o
hyp-obj-y += ../vgic-v3-sr.o ../aarch32.o ../vgic-v2-cpuif-proxy.o ../entry.o \
../hyp-entry.o ../exception.o ../pgtable.o ../vgic-v5-sr.o
+hyp-obj-y += ../../config.o
hyp-obj-y += ../../../kernel/smccc-call.o
hyp-obj-$(CONFIG_LIST_HARDENED) += list_debug.o
hyp-obj-$(CONFIG_NVHE_EL2_TRACING) += clock.o trace.o events.o
diff --git a/arch/arm64/kvm/hyp/nvhe/pkvm.c b/arch/arm64/kvm/hyp/nvhe/pkvm.c
index fba8dfe6389d6..465d963d6ff57 100644
--- a/arch/arm64/kvm/hyp/nvhe/pkvm.c
+++ b/arch/arm64/kvm/hyp/nvhe/pkvm.c
@@ -206,6 +206,8 @@ static int pkvm_vcpu_init_traps(struct pkvm_hyp_vcpu *hyp_vcpu)
pvm_init_traps_hcr(vcpu);
pvm_init_traps_mdcr(vcpu);
vcpu_set_hcrx(vcpu);
+ kvm_init_fgu(vcpu->kvm);
+ kvm_vcpu_load_fgt(vcpu);
return 0;
}
--
2.47.3
^ permalink raw reply [flat|nested] 18+ messages in thread* [PATCH 08/16] KVM: arm64: Advertise TCR2, SCTLR2, S1PIE and S1POE to protected VMs
2026-11-09 8:31 [PATCH 00/16] KVM: arm64: Support debug, SVE, MOPS, PIE/POE, FPMR and more in protected VMs Fuad Tabba
` (6 preceding siblings ...)
2026-11-09 8:31 ` [PATCH 07/16] KVM: arm64: Compute a protected vCPU's fine-grained traps at EL2 Fuad Tabba
@ 2026-11-09 8:31 ` Fuad Tabba
2026-11-09 8:31 ` [PATCH 09/16] KVM: arm64: Advertise MOPS " Fuad Tabba
` (8 subsequent siblings)
16 siblings, 0 replies; 18+ messages in thread
From: Fuad Tabba @ 2026-11-09 8:31 UTC (permalink / raw)
To: Marc Zyngier, Oliver Upton, kvmarm, linux-arm-kernel
Cc: Joey Gouly, Suzuki K Poulose, Zenghui Yu, Steffen Eiden,
Catalin Marinas, Will Deacon, Mark Rutland, Vincent Donnefort,
Fuad Tabba, kvm, linux-kernel
Advertise FEAT_TCR2, FEAT_SCTLR2, FEAT_S1PIE and FEAT_S1POE to
protected VMs, with S1POE only when the kernel supports POE, as EL2
doesn't switch the POE registers otherwise. With the fields set,
vcpu_set_hcrx() sets HCRX_EL2.{TCR2En,SCTLR2En} for them, and since
the previous patch their PIE and POE registers don't trap.
Reset the new registers as KVM does for a non-protected vCPU.
Signed-off-by: Fuad Tabba <fuad.tabba@linux.dev>
---
arch/arm64/include/asm/kvm_hyp.h | 1 +
arch/arm64/kvm/arm.c | 1 +
arch/arm64/kvm/hyp/nvhe/sys_regs.c | 24 +++++++++++++++++++++++-
3 files changed, 25 insertions(+), 1 deletion(-)
diff --git a/arch/arm64/include/asm/kvm_hyp.h b/arch/arm64/include/asm/kvm_hyp.h
index 920c1d8d45b1a..ab22b395d0908 100644
--- a/arch/arm64/include/asm/kvm_hyp.h
+++ b/arch/arm64/include/asm/kvm_hyp.h
@@ -151,6 +151,7 @@ extern u64 kvm_nvhe_sym(id_aa64isar2_el1_sys_val);
extern u64 kvm_nvhe_sym(id_aa64mmfr0_el1_sys_val);
extern u64 kvm_nvhe_sym(id_aa64mmfr1_el1_sys_val);
extern u64 kvm_nvhe_sym(id_aa64mmfr2_el1_sys_val);
+extern u64 kvm_nvhe_sym(id_aa64mmfr3_el1_sys_val);
extern u64 kvm_nvhe_sym(id_aa64smfr0_el1_sys_val);
extern u64 kvm_nvhe_sym(id_aa64dfr0_el1_sys_val);
diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
index 2753686982f0b..fe60535e4f358 100644
--- a/arch/arm64/kvm/arm.c
+++ b/arch/arm64/kvm/arm.c
@@ -2834,6 +2834,7 @@ static void kvm_hyp_init_symbols(void)
kvm_nvhe_sym(id_aa64mmfr0_el1_sys_val) = read_sanitised_ftr_reg(SYS_ID_AA64MMFR0_EL1);
kvm_nvhe_sym(id_aa64mmfr1_el1_sys_val) = read_sanitised_ftr_reg(SYS_ID_AA64MMFR1_EL1);
kvm_nvhe_sym(id_aa64mmfr2_el1_sys_val) = read_sanitised_ftr_reg(SYS_ID_AA64MMFR2_EL1);
+ kvm_nvhe_sym(id_aa64mmfr3_el1_sys_val) = read_sanitised_ftr_reg(SYS_ID_AA64MMFR3_EL1);
kvm_nvhe_sym(id_aa64smfr0_el1_sys_val) = read_sanitised_ftr_reg(SYS_ID_AA64SMFR0_EL1);
kvm_nvhe_sym(id_aa64dfr0_el1_sys_val) = read_sanitised_ftr_reg(SYS_ID_AA64DFR0_EL1);
kvm_nvhe_sym(__icache_flags) = __icache_flags;
diff --git a/arch/arm64/kvm/hyp/nvhe/sys_regs.c b/arch/arm64/kvm/hyp/nvhe/sys_regs.c
index 78c25da737591..d186aba6aa09c 100644
--- a/arch/arm64/kvm/hyp/nvhe/sys_regs.c
+++ b/arch/arm64/kvm/hyp/nvhe/sys_regs.c
@@ -30,6 +30,7 @@ u64 id_aa64isar2_el1_sys_val;
u64 id_aa64mmfr0_el1_sys_val;
u64 id_aa64mmfr1_el1_sys_val;
u64 id_aa64mmfr2_el1_sys_val;
+u64 id_aa64mmfr3_el1_sys_val;
u64 id_aa64smfr0_el1_sys_val;
u64 id_aa64dfr0_el1_sys_val;
@@ -83,6 +84,11 @@ static bool vm_has_sve(const struct kvm *kvm)
return system_supports_sve() && kvm_vcpu_has_feature(kvm, KVM_ARM_VCPU_SVE);
}
+static bool vm_has_s1poe(const struct kvm *kvm)
+{
+ return system_supports_poe();
+}
+
/*
* Definitions for features to be allowed or restricted for protected guests.
*
@@ -177,6 +183,14 @@ static const struct pvm_ftr_bits pvmid_aa64mmfr2[] = {
FEAT_END
};
+static const struct pvm_ftr_bits pvmid_aa64mmfr3[] = {
+ MAX_FEAT(ID_AA64MMFR3_EL1, TCRX, IMP),
+ MAX_FEAT(ID_AA64MMFR3_EL1, SCTLRX, IMP),
+ MAX_FEAT(ID_AA64MMFR3_EL1, S1PIE, IMP),
+ MAX_FEAT_FUNC(ID_AA64MMFR3_EL1, S1POE, IMP, vm_has_s1poe),
+ FEAT_END
+};
+
static const struct pvm_ftr_bits pvmid_aa64isar1[] = {
MAX_FEAT(ID_AA64ISAR1_EL1, DPB, DPB2),
MAX_FEAT_FUNC(ID_AA64ISAR1_EL1, APA, PAuth, vm_has_ptrauth),
@@ -274,6 +288,8 @@ static u64 pvm_calc_id_reg(const struct kvm_vcpu *vcpu, u32 id)
return get_restricted_features(vcpu, id_aa64mmfr1_el1_sys_val, pvmid_aa64mmfr1);
case SYS_ID_AA64MMFR2_EL1:
return get_restricted_features(vcpu, id_aa64mmfr2_el1_sys_val, pvmid_aa64mmfr2);
+ case SYS_ID_AA64MMFR3_EL1:
+ return get_restricted_features(vcpu, id_aa64mmfr3_el1_sys_val, pvmid_aa64mmfr3);
case SYS_ID_AA64DFR0_EL1:
return get_restricted_features(vcpu, id_aa64dfr0_el1_sys_val, pvmid_aa64dfr0);
case SYS_ID_AA64MMFR4_EL1:
@@ -707,7 +723,7 @@ static const struct sys_reg_desc pvm_sys_reg_descs[] = {
AARCH64(SYS_ID_AA64MMFR0_EL1),
AARCH64(SYS_ID_AA64MMFR1_EL1),
AARCH64(SYS_ID_AA64MMFR2_EL1),
- ID_UNALLOCATED(7,3),
+ AARCH64(SYS_ID_AA64MMFR3_EL1),
ID_UNALLOCATED(7,4),
ID_UNALLOCATED(7,5),
ID_UNALLOCATED(7,6),
@@ -793,7 +809,13 @@ static const struct sys_reg_desc_reset pvm_sys_reg_reset_vals[] = {
RESET_ZERO(CPACR_EL1),
RESET_VAL(CONTEXTIDR_EL1, 0x00000000dbadc0deULL),
RESET_VAL(SCTLR_EL1, 0x00C50078ULL),
+ RESET_ZERO(SCTLR2_EL1),
RESET_ZERO(TCR_EL1),
+ RESET_ZERO(TCR2_EL1),
+ RESET_UNKNOWN(PIR_EL1),
+ RESET_UNKNOWN(PIRE0_EL1),
+ RESET_UNKNOWN(POR_EL1),
+ RESET_UNKNOWN(POR_EL0),
RESET_UNKNOWN(AFSR0_EL1),
RESET_UNKNOWN(AFSR1_EL1),
RESET_UNKNOWN(ESR_EL1),
--
2.47.3
^ permalink raw reply [flat|nested] 18+ messages in thread* [PATCH 09/16] KVM: arm64: Advertise MOPS to protected VMs
2026-11-09 8:31 [PATCH 00/16] KVM: arm64: Support debug, SVE, MOPS, PIE/POE, FPMR and more in protected VMs Fuad Tabba
` (7 preceding siblings ...)
2026-11-09 8:31 ` [PATCH 08/16] KVM: arm64: Advertise TCR2, SCTLR2, S1PIE and S1POE to protected VMs Fuad Tabba
@ 2026-11-09 8:31 ` Fuad Tabba
2026-11-09 8:31 ` [PATCH 10/16] KVM: arm64: Advertise FPMR " Fuad Tabba
` (7 subsequent siblings)
16 siblings, 0 replies; 18+ messages in thread
From: Fuad Tabba @ 2026-11-09 8:31 UTC (permalink / raw)
To: Marc Zyngier, Oliver Upton, kvmarm, linux-arm-kernel
Cc: Joey Gouly, Suzuki K Poulose, Zenghui Yu, Steffen Eiden,
Catalin Marinas, Will Deacon, Mark Rutland, Vincent Donnefort,
Fuad Tabba, kvm, linux-kernel
Advertise FEAT_MOPS to protected VMs. With the field set,
vcpu_set_hcrx() sets HCRX_EL2.{MSCEn,MCE2} for them, and
kvm_hyp_handle_mops() already handles their MOPS exceptions.
Signed-off-by: Fuad Tabba <fuad.tabba@linux.dev>
---
arch/arm64/kvm/hyp/nvhe/sys_regs.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/arch/arm64/kvm/hyp/nvhe/sys_regs.c b/arch/arm64/kvm/hyp/nvhe/sys_regs.c
index d186aba6aa09c..bcc35d6990e67 100644
--- a/arch/arm64/kvm/hyp/nvhe/sys_regs.c
+++ b/arch/arm64/kvm/hyp/nvhe/sys_regs.c
@@ -212,6 +212,7 @@ static const struct pvm_ftr_bits pvmid_aa64isar1[] = {
static const struct pvm_ftr_bits pvmid_aa64isar2[] = {
MAX_FEAT_FUNC(ID_AA64ISAR2_EL1, GPA3, IMP, vm_has_ptrauth),
MAX_FEAT_FUNC(ID_AA64ISAR2_EL1, APA3, PAuth, vm_has_ptrauth),
+ MAX_FEAT(ID_AA64ISAR2_EL1, MOPS, IMP),
MAX_FEAT(ID_AA64ISAR2_EL1, ATS1A, IMP),
FEAT_END
};
--
2.47.3
^ permalink raw reply [flat|nested] 18+ messages in thread* [PATCH 10/16] KVM: arm64: Advertise FPMR to protected VMs
2026-11-09 8:31 [PATCH 00/16] KVM: arm64: Support debug, SVE, MOPS, PIE/POE, FPMR and more in protected VMs Fuad Tabba
` (8 preceding siblings ...)
2026-11-09 8:31 ` [PATCH 09/16] KVM: arm64: Advertise MOPS " Fuad Tabba
@ 2026-11-09 8:31 ` Fuad Tabba
2026-11-09 8:32 ` [PATCH 11/16] KVM: arm64: Advertise HBC, CLRBHB, RPRFM, CSSC and LUT " Fuad Tabba
` (6 subsequent siblings)
16 siblings, 0 replies; 18+ messages in thread
From: Fuad Tabba @ 2026-11-09 8:31 UTC (permalink / raw)
To: Marc Zyngier, Oliver Upton, kvmarm, linux-arm-kernel
Cc: Joey Gouly, Suzuki K Poulose, Zenghui Yu, Steffen Eiden,
Catalin Marinas, Will Deacon, Mark Rutland, Vincent Donnefort,
Fuad Tabba, kvm, linux-kernel
Advertise FEAT_FPMR to protected VMs, and reset FPMR to 0 as KVM does
for a non-protected vCPU. With the field set, vcpu_set_hcrx() sets
HCRX_EL2.EnFPM for them, and FPMR is switched with the rest of the FP
state.
Signed-off-by: Fuad Tabba <fuad.tabba@linux.dev>
---
arch/arm64/kvm/hyp/nvhe/sys_regs.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/arch/arm64/kvm/hyp/nvhe/sys_regs.c b/arch/arm64/kvm/hyp/nvhe/sys_regs.c
index bcc35d6990e67..a937aad009348 100644
--- a/arch/arm64/kvm/hyp/nvhe/sys_regs.c
+++ b/arch/arm64/kvm/hyp/nvhe/sys_regs.c
@@ -128,6 +128,7 @@ static const struct pvm_ftr_bits pvmid_aa64pfr1[] = {
static const struct pvm_ftr_bits pvmid_aa64pfr2[] = {
MAX_FEAT(ID_AA64PFR2_EL1, GCIE, NI),
+ MAX_FEAT(ID_AA64PFR2_EL1, FPMR, IMP),
FEAT_END
};
@@ -829,6 +830,7 @@ static const struct sys_reg_desc_reset pvm_sys_reg_reset_vals[] = {
RESET_UNKNOWN(TTBR1_EL1),
RESET_UNKNOWN(FAR_EL1),
RESET_VAL(VBAR_EL1, 0x1de7ec7edbadc000ULL),
+ RESET_ZERO(FPMR),
};
void kvm_reset_pvm_sys_regs(struct kvm_vcpu *vcpu)
--
2.47.3
^ permalink raw reply [flat|nested] 18+ messages in thread* [PATCH 11/16] KVM: arm64: Advertise HBC, CLRBHB, RPRFM, CSSC and LUT to protected VMs
2026-11-09 8:31 [PATCH 00/16] KVM: arm64: Support debug, SVE, MOPS, PIE/POE, FPMR and more in protected VMs Fuad Tabba
` (9 preceding siblings ...)
2026-11-09 8:31 ` [PATCH 10/16] KVM: arm64: Advertise FPMR " Fuad Tabba
@ 2026-11-09 8:32 ` Fuad Tabba
2026-11-09 8:32 ` [PATCH 12/16] KVM: arm64: Advertise FAMINMAX, LSFE, LSUI and FPRCVT " Fuad Tabba
` (5 subsequent siblings)
16 siblings, 0 replies; 18+ messages in thread
From: Fuad Tabba @ 2026-11-09 8:32 UTC (permalink / raw)
To: Marc Zyngier, Oliver Upton, kvmarm, linux-arm-kernel
Cc: Joey Gouly, Suzuki K Poulose, Zenghui Yu, Steffen Eiden,
Catalin Marinas, Will Deacon, Mark Rutland, Vincent Donnefort,
Fuad Tabba, kvm, linux-kernel
Advertise FEAT_HBC, FEAT_CLRBHB, FEAT_RPRFM, FEAT_CSSC and FEAT_LUT to
protected VMs, with CSSC up to CMPBR and LUT up to LUT6, as for a
non-protected VM.
Signed-off-by: Fuad Tabba <fuad.tabba@linux.dev>
---
arch/arm64/kvm/hyp/nvhe/sys_regs.c | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/arch/arm64/kvm/hyp/nvhe/sys_regs.c b/arch/arm64/kvm/hyp/nvhe/sys_regs.c
index a937aad009348..b325bab5431a8 100644
--- a/arch/arm64/kvm/hyp/nvhe/sys_regs.c
+++ b/arch/arm64/kvm/hyp/nvhe/sys_regs.c
@@ -214,6 +214,11 @@ static const struct pvm_ftr_bits pvmid_aa64isar2[] = {
MAX_FEAT_FUNC(ID_AA64ISAR2_EL1, GPA3, IMP, vm_has_ptrauth),
MAX_FEAT_FUNC(ID_AA64ISAR2_EL1, APA3, PAuth, vm_has_ptrauth),
MAX_FEAT(ID_AA64ISAR2_EL1, MOPS, IMP),
+ MAX_FEAT(ID_AA64ISAR2_EL1, BC, IMP),
+ MAX_FEAT(ID_AA64ISAR2_EL1, CLRBHB, IMP),
+ MAX_FEAT(ID_AA64ISAR2_EL1, RPRFM, IMP),
+ MAX_FEAT(ID_AA64ISAR2_EL1, CSSC, CMPBR),
+ MAX_FEAT(ID_AA64ISAR2_EL1, LUT, LUT6),
MAX_FEAT(ID_AA64ISAR2_EL1, ATS1A, IMP),
FEAT_END
};
--
2.47.3
^ permalink raw reply [flat|nested] 18+ messages in thread* [PATCH 12/16] KVM: arm64: Advertise FAMINMAX, LSFE, LSUI and FPRCVT to protected VMs
2026-11-09 8:31 [PATCH 00/16] KVM: arm64: Support debug, SVE, MOPS, PIE/POE, FPMR and more in protected VMs Fuad Tabba
` (10 preceding siblings ...)
2026-11-09 8:32 ` [PATCH 11/16] KVM: arm64: Advertise HBC, CLRBHB, RPRFM, CSSC and LUT " Fuad Tabba
@ 2026-11-09 8:32 ` Fuad Tabba
2026-11-09 8:32 ` [PATCH 13/16] KVM: arm64: Advertise AFP and RPRES " Fuad Tabba
` (4 subsequent siblings)
16 siblings, 0 replies; 18+ messages in thread
From: Fuad Tabba @ 2026-11-09 8:32 UTC (permalink / raw)
To: Marc Zyngier, Oliver Upton, kvmarm, linux-arm-kernel
Cc: Joey Gouly, Suzuki K Poulose, Zenghui Yu, Steffen Eiden,
Catalin Marinas, Will Deacon, Mark Rutland, Vincent Donnefort,
Fuad Tabba, kvm, linux-kernel
Advertise FEAT_FAMINMAX, FEAT_LSFE, FEAT_LSUI and FEAT_FPRCVT to
protected VMs, the ID_AA64ISAR3_EL1 fields a non-protected VM sees.
Signed-off-by: Fuad Tabba <fuad.tabba@linux.dev>
---
arch/arm64/include/asm/kvm_hyp.h | 1 +
arch/arm64/kvm/arm.c | 1 +
arch/arm64/kvm/hyp/nvhe/sys_regs.c | 13 ++++++++++++-
3 files changed, 14 insertions(+), 1 deletion(-)
diff --git a/arch/arm64/include/asm/kvm_hyp.h b/arch/arm64/include/asm/kvm_hyp.h
index ab22b395d0908..fa9a99cae32a7 100644
--- a/arch/arm64/include/asm/kvm_hyp.h
+++ b/arch/arm64/include/asm/kvm_hyp.h
@@ -148,6 +148,7 @@ extern u64 kvm_nvhe_sym(id_aa64zfr0_el1_sys_val);
extern u64 kvm_nvhe_sym(id_aa64isar0_el1_sys_val);
extern u64 kvm_nvhe_sym(id_aa64isar1_el1_sys_val);
extern u64 kvm_nvhe_sym(id_aa64isar2_el1_sys_val);
+extern u64 kvm_nvhe_sym(id_aa64isar3_el1_sys_val);
extern u64 kvm_nvhe_sym(id_aa64mmfr0_el1_sys_val);
extern u64 kvm_nvhe_sym(id_aa64mmfr1_el1_sys_val);
extern u64 kvm_nvhe_sym(id_aa64mmfr2_el1_sys_val);
diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
index fe60535e4f358..1981646df42ce 100644
--- a/arch/arm64/kvm/arm.c
+++ b/arch/arm64/kvm/arm.c
@@ -2831,6 +2831,7 @@ static void kvm_hyp_init_symbols(void)
kvm_nvhe_sym(id_aa64isar0_el1_sys_val) = read_sanitised_ftr_reg(SYS_ID_AA64ISAR0_EL1);
kvm_nvhe_sym(id_aa64isar1_el1_sys_val) = read_sanitised_ftr_reg(SYS_ID_AA64ISAR1_EL1);
kvm_nvhe_sym(id_aa64isar2_el1_sys_val) = read_sanitised_ftr_reg(SYS_ID_AA64ISAR2_EL1);
+ kvm_nvhe_sym(id_aa64isar3_el1_sys_val) = read_sanitised_ftr_reg(SYS_ID_AA64ISAR3_EL1);
kvm_nvhe_sym(id_aa64mmfr0_el1_sys_val) = read_sanitised_ftr_reg(SYS_ID_AA64MMFR0_EL1);
kvm_nvhe_sym(id_aa64mmfr1_el1_sys_val) = read_sanitised_ftr_reg(SYS_ID_AA64MMFR1_EL1);
kvm_nvhe_sym(id_aa64mmfr2_el1_sys_val) = read_sanitised_ftr_reg(SYS_ID_AA64MMFR2_EL1);
diff --git a/arch/arm64/kvm/hyp/nvhe/sys_regs.c b/arch/arm64/kvm/hyp/nvhe/sys_regs.c
index b325bab5431a8..73ef47a8e687b 100644
--- a/arch/arm64/kvm/hyp/nvhe/sys_regs.c
+++ b/arch/arm64/kvm/hyp/nvhe/sys_regs.c
@@ -27,6 +27,7 @@ u64 id_aa64zfr0_el1_sys_val;
u64 id_aa64isar0_el1_sys_val;
u64 id_aa64isar1_el1_sys_val;
u64 id_aa64isar2_el1_sys_val;
+u64 id_aa64isar3_el1_sys_val;
u64 id_aa64mmfr0_el1_sys_val;
u64 id_aa64mmfr1_el1_sys_val;
u64 id_aa64mmfr2_el1_sys_val;
@@ -223,6 +224,14 @@ static const struct pvm_ftr_bits pvmid_aa64isar2[] = {
FEAT_END
};
+static const struct pvm_ftr_bits pvmid_aa64isar3[] = {
+ MAX_FEAT(ID_AA64ISAR3_EL1, FAMINMAX, IMP),
+ MAX_FEAT(ID_AA64ISAR3_EL1, LSFE, IMP),
+ MAX_FEAT(ID_AA64ISAR3_EL1, LSUI, IMP),
+ MAX_FEAT(ID_AA64ISAR3_EL1, FPRCVT, IMP),
+ FEAT_END
+};
+
/* DoubleLock and MTPMU are hidden, but their Not-Implemented values are non-zero. */
static const struct pvm_ftr_bits pvmid_aa64dfr0[] = {
MAX_FEAT(ID_AA64DFR0_EL1, DebugVer, V8P8),
@@ -289,6 +298,8 @@ static u64 pvm_calc_id_reg(const struct kvm_vcpu *vcpu, u32 id)
return get_restricted_features(vcpu, id_aa64isar1_el1_sys_val, pvmid_aa64isar1);
case SYS_ID_AA64ISAR2_EL1:
return get_restricted_features(vcpu, id_aa64isar2_el1_sys_val, pvmid_aa64isar2);
+ case SYS_ID_AA64ISAR3_EL1:
+ return get_restricted_features(vcpu, id_aa64isar3_el1_sys_val, pvmid_aa64isar3);
case SYS_ID_AA64MMFR0_EL1:
return get_restricted_features(vcpu, id_aa64mmfr0_el1_sys_val, pvmid_aa64mmfr0);
case SYS_ID_AA64MMFR1_EL1:
@@ -722,7 +733,7 @@ static const struct sys_reg_desc pvm_sys_reg_descs[] = {
AARCH64(SYS_ID_AA64ISAR0_EL1),
AARCH64(SYS_ID_AA64ISAR1_EL1),
AARCH64(SYS_ID_AA64ISAR2_EL1),
- ID_UNALLOCATED(6,3),
+ AARCH64(SYS_ID_AA64ISAR3_EL1),
ID_UNALLOCATED(6,4),
ID_UNALLOCATED(6,5),
ID_UNALLOCATED(6,6),
--
2.47.3
^ permalink raw reply [flat|nested] 18+ messages in thread* [PATCH 13/16] KVM: arm64: Advertise AFP and RPRES to protected VMs
2026-11-09 8:31 [PATCH 00/16] KVM: arm64: Support debug, SVE, MOPS, PIE/POE, FPMR and more in protected VMs Fuad Tabba
` (11 preceding siblings ...)
2026-11-09 8:32 ` [PATCH 12/16] KVM: arm64: Advertise FAMINMAX, LSFE, LSUI and FPRCVT " Fuad Tabba
@ 2026-11-09 8:32 ` Fuad Tabba
2026-11-09 8:32 ` [PATCH 14/16] KVM: arm64: Advertise ECBHB, TIDCP1, TTST and LVA " Fuad Tabba
` (3 subsequent siblings)
16 siblings, 0 replies; 18+ messages in thread
From: Fuad Tabba @ 2026-11-09 8:32 UTC (permalink / raw)
To: Marc Zyngier, Oliver Upton, kvmarm, linux-arm-kernel
Cc: Joey Gouly, Suzuki K Poulose, Zenghui Yu, Steffen Eiden,
Catalin Marinas, Will Deacon, Mark Rutland, Vincent Donnefort,
Fuad Tabba, kvm, linux-kernel
Advertise FEAT_AFP and FEAT_RPRES to protected VMs. Both are controlled
through FPCR, which EL2 switches with the rest of the FP state.
Signed-off-by: Fuad Tabba <fuad.tabba@linux.dev>
---
arch/arm64/kvm/hyp/nvhe/sys_regs.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/arch/arm64/kvm/hyp/nvhe/sys_regs.c b/arch/arm64/kvm/hyp/nvhe/sys_regs.c
index 73ef47a8e687b..08805c7e4e5a7 100644
--- a/arch/arm64/kvm/hyp/nvhe/sys_regs.c
+++ b/arch/arm64/kvm/hyp/nvhe/sys_regs.c
@@ -169,6 +169,7 @@ static const struct pvm_ftr_bits pvmid_aa64mmfr1[] = {
MAX_FEAT(ID_AA64MMFR1_EL1, PAN, PAN3),
MAX_FEAT(ID_AA64MMFR1_EL1, SpecSEI, IMP),
MAX_FEAT(ID_AA64MMFR1_EL1, ETS, IMP),
+ MAX_FEAT(ID_AA64MMFR1_EL1, AFP, IMP),
MAX_FEAT(ID_AA64MMFR1_EL1, CMOW, IMP),
FEAT_END
};
@@ -212,6 +213,7 @@ static const struct pvm_ftr_bits pvmid_aa64isar1[] = {
};
static const struct pvm_ftr_bits pvmid_aa64isar2[] = {
+ MAX_FEAT(ID_AA64ISAR2_EL1, RPRES, IMP),
MAX_FEAT_FUNC(ID_AA64ISAR2_EL1, GPA3, IMP, vm_has_ptrauth),
MAX_FEAT_FUNC(ID_AA64ISAR2_EL1, APA3, PAuth, vm_has_ptrauth),
MAX_FEAT(ID_AA64ISAR2_EL1, MOPS, IMP),
--
2.47.3
^ permalink raw reply [flat|nested] 18+ messages in thread* [PATCH 14/16] KVM: arm64: Advertise ECBHB, TIDCP1, TTST and LVA to protected VMs
2026-11-09 8:31 [PATCH 00/16] KVM: arm64: Support debug, SVE, MOPS, PIE/POE, FPMR and more in protected VMs Fuad Tabba
` (12 preceding siblings ...)
2026-11-09 8:32 ` [PATCH 13/16] KVM: arm64: Advertise AFP and RPRES " Fuad Tabba
@ 2026-11-09 8:32 ` Fuad Tabba
2026-11-09 8:32 ` [PATCH 15/16] KVM: arm64: Advertise FP8 and F16MM " Fuad Tabba
` (2 subsequent siblings)
16 siblings, 0 replies; 18+ messages in thread
From: Fuad Tabba @ 2026-11-09 8:32 UTC (permalink / raw)
To: Marc Zyngier, Oliver Upton, kvmarm, linux-arm-kernel
Cc: Joey Gouly, Suzuki K Poulose, Zenghui Yu, Steffen Eiden,
Catalin Marinas, Will Deacon, Mark Rutland, Vincent Donnefort,
Fuad Tabba, kvm, linux-kernel
Advertise FEAT_ECBHB, FEAT_TIDCP1, FEAT_TTST and FEAT_LVA to protected
VMs, capping VARange at LVA, since LVA3 requires FEAT_D128. TIDCP1's
control, SCTLR_EL1.TIDCP, is switched with the rest of SCTLR_EL1.
Signed-off-by: Fuad Tabba <fuad.tabba@linux.dev>
---
arch/arm64/kvm/hyp/nvhe/sys_regs.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/arch/arm64/kvm/hyp/nvhe/sys_regs.c b/arch/arm64/kvm/hyp/nvhe/sys_regs.c
index 08805c7e4e5a7..8e9283dae3484 100644
--- a/arch/arm64/kvm/hyp/nvhe/sys_regs.c
+++ b/arch/arm64/kvm/hyp/nvhe/sys_regs.c
@@ -170,7 +170,9 @@ static const struct pvm_ftr_bits pvmid_aa64mmfr1[] = {
MAX_FEAT(ID_AA64MMFR1_EL1, SpecSEI, IMP),
MAX_FEAT(ID_AA64MMFR1_EL1, ETS, IMP),
MAX_FEAT(ID_AA64MMFR1_EL1, AFP, IMP),
+ MAX_FEAT(ID_AA64MMFR1_EL1, TIDCP1, IMP),
MAX_FEAT(ID_AA64MMFR1_EL1, CMOW, IMP),
+ MAX_FEAT(ID_AA64MMFR1_EL1, ECBHB, IMP),
FEAT_END
};
@@ -178,6 +180,8 @@ static const struct pvm_ftr_bits pvmid_aa64mmfr2[] = {
MAX_FEAT(ID_AA64MMFR2_EL1, CnP, IMP),
MAX_FEAT(ID_AA64MMFR2_EL1, UAO, IMP),
MAX_FEAT(ID_AA64MMFR2_EL1, IESB, IMP),
+ MAX_FEAT(ID_AA64MMFR2_EL1, VARange, 52),
+ MAX_FEAT_ENUM(ID_AA64MMFR2_EL1, ST, 48_47),
MAX_FEAT(ID_AA64MMFR2_EL1, AT, IMP),
MAX_FEAT(ID_AA64MMFR2_EL1, IDS, IMP),
MAX_FEAT(ID_AA64MMFR2_EL1, TTL, IMP),
--
2.47.3
^ permalink raw reply [flat|nested] 18+ messages in thread* [PATCH 15/16] KVM: arm64: Advertise FP8 and F16MM to protected VMs
2026-11-09 8:31 [PATCH 00/16] KVM: arm64: Support debug, SVE, MOPS, PIE/POE, FPMR and more in protected VMs Fuad Tabba
` (13 preceding siblings ...)
2026-11-09 8:32 ` [PATCH 14/16] KVM: arm64: Advertise ECBHB, TIDCP1, TTST and LVA " Fuad Tabba
@ 2026-11-09 8:32 ` Fuad Tabba
2026-11-09 8:32 ` [PATCH 16/16] KVM: arm64: Advertise ECV, HAFT and ETS3 " Fuad Tabba
2026-11-09 20:20 ` [PATCH 00/16] KVM: arm64: Support debug, SVE, MOPS, PIE/POE, FPMR and more in " Oliver Upton
16 siblings, 0 replies; 18+ messages in thread
From: Fuad Tabba @ 2026-11-09 8:32 UTC (permalink / raw)
To: Marc Zyngier, Oliver Upton, kvmarm, linux-arm-kernel
Cc: Joey Gouly, Suzuki K Poulose, Zenghui Yu, Steffen Eiden,
Catalin Marinas, Will Deacon, Mark Rutland, Vincent Donnefort,
Fuad Tabba, kvm, linux-kernel
Advertise the FP8 features and FEAT_F16MM to protected VMs, the
ID_AA64FPFR0_EL1 fields a non-protected VM sees. Their instructions
only use the SIMD&FP registers and FPMR, which EL2 already switches.
Signed-off-by: Fuad Tabba <fuad.tabba@linux.dev>
---
arch/arm64/include/asm/kvm_hyp.h | 1 +
arch/arm64/kvm/arm.c | 1 +
arch/arm64/kvm/hyp/nvhe/sys_regs.c | 18 +++++++++++++++++-
3 files changed, 19 insertions(+), 1 deletion(-)
diff --git a/arch/arm64/include/asm/kvm_hyp.h b/arch/arm64/include/asm/kvm_hyp.h
index fa9a99cae32a7..09b19f2c3c090 100644
--- a/arch/arm64/include/asm/kvm_hyp.h
+++ b/arch/arm64/include/asm/kvm_hyp.h
@@ -145,6 +145,7 @@ extern u64 kvm_nvhe_sym(id_aa64pfr0_el1_sys_val);
extern u64 kvm_nvhe_sym(id_aa64pfr1_el1_sys_val);
extern u64 kvm_nvhe_sym(id_aa64pfr2_el1_sys_val);
extern u64 kvm_nvhe_sym(id_aa64zfr0_el1_sys_val);
+extern u64 kvm_nvhe_sym(id_aa64fpfr0_el1_sys_val);
extern u64 kvm_nvhe_sym(id_aa64isar0_el1_sys_val);
extern u64 kvm_nvhe_sym(id_aa64isar1_el1_sys_val);
extern u64 kvm_nvhe_sym(id_aa64isar2_el1_sys_val);
diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
index 1981646df42ce..6cc65ee4c6f2a 100644
--- a/arch/arm64/kvm/arm.c
+++ b/arch/arm64/kvm/arm.c
@@ -2828,6 +2828,7 @@ static void kvm_hyp_init_symbols(void)
kvm_nvhe_sym(id_aa64pfr1_el1_sys_val) = read_sanitised_ftr_reg(SYS_ID_AA64PFR1_EL1);
kvm_nvhe_sym(id_aa64pfr2_el1_sys_val) = read_sanitised_ftr_reg(SYS_ID_AA64PFR2_EL1);
kvm_nvhe_sym(id_aa64zfr0_el1_sys_val) = read_sanitised_ftr_reg(SYS_ID_AA64ZFR0_EL1);
+ kvm_nvhe_sym(id_aa64fpfr0_el1_sys_val) = read_sanitised_ftr_reg(SYS_ID_AA64FPFR0_EL1);
kvm_nvhe_sym(id_aa64isar0_el1_sys_val) = read_sanitised_ftr_reg(SYS_ID_AA64ISAR0_EL1);
kvm_nvhe_sym(id_aa64isar1_el1_sys_val) = read_sanitised_ftr_reg(SYS_ID_AA64ISAR1_EL1);
kvm_nvhe_sym(id_aa64isar2_el1_sys_val) = read_sanitised_ftr_reg(SYS_ID_AA64ISAR2_EL1);
diff --git a/arch/arm64/kvm/hyp/nvhe/sys_regs.c b/arch/arm64/kvm/hyp/nvhe/sys_regs.c
index 8e9283dae3484..a0f6737ff8f16 100644
--- a/arch/arm64/kvm/hyp/nvhe/sys_regs.c
+++ b/arch/arm64/kvm/hyp/nvhe/sys_regs.c
@@ -24,6 +24,7 @@ u64 id_aa64pfr0_el1_sys_val;
u64 id_aa64pfr1_el1_sys_val;
u64 id_aa64pfr2_el1_sys_val;
u64 id_aa64zfr0_el1_sys_val;
+u64 id_aa64fpfr0_el1_sys_val;
u64 id_aa64isar0_el1_sys_val;
u64 id_aa64isar1_el1_sys_val;
u64 id_aa64isar2_el1_sys_val;
@@ -149,6 +150,19 @@ static const struct pvm_ftr_bits pvmid_aa64zfr0[] = {
FEAT_END
};
+static const struct pvm_ftr_bits pvmid_aa64fpfr0[] = {
+ MAX_FEAT(ID_AA64FPFR0_EL1, F8E5M2, IMP),
+ MAX_FEAT(ID_AA64FPFR0_EL1, F8E4M3, IMP),
+ MAX_FEAT(ID_AA64FPFR0_EL1, F16MM2, IMP),
+ MAX_FEAT(ID_AA64FPFR0_EL1, F8MM4, IMP),
+ MAX_FEAT(ID_AA64FPFR0_EL1, F8MM8, IMP),
+ MAX_FEAT(ID_AA64FPFR0_EL1, F8DP2, IMP),
+ MAX_FEAT(ID_AA64FPFR0_EL1, F8DP4, IMP),
+ MAX_FEAT(ID_AA64FPFR0_EL1, F8FMA, IMP),
+ MAX_FEAT(ID_AA64FPFR0_EL1, F8CVT, IMP),
+ FEAT_END
+};
+
static const struct pvm_ftr_bits pvmid_aa64mmfr0[] = {
MAX_FEAT_ENUM(ID_AA64MMFR0_EL1, PARANGE, 40),
MAX_FEAT_ENUM(ID_AA64MMFR0_EL1, ASIDBITS, 16),
@@ -298,6 +312,8 @@ static u64 pvm_calc_id_reg(const struct kvm_vcpu *vcpu, u32 id)
return get_restricted_features(vcpu, id_aa64pfr2_el1_sys_val, pvmid_aa64pfr2);
case SYS_ID_AA64ZFR0_EL1:
return get_restricted_features(vcpu, id_aa64zfr0_el1_sys_val, pvmid_aa64zfr0);
+ case SYS_ID_AA64FPFR0_EL1:
+ return get_restricted_features(vcpu, id_aa64fpfr0_el1_sys_val, pvmid_aa64fpfr0);
case SYS_ID_AA64ISAR0_EL1:
return id_aa64isar0_el1_sys_val;
case SYS_ID_AA64ISAR1_EL1:
@@ -727,7 +743,7 @@ static const struct sys_reg_desc pvm_sys_reg_descs[] = {
AARCH64(SYS_ID_AA64ZFR0_EL1),
ID_UNALLOCATED(4,5),
ID_UNALLOCATED(4,6),
- ID_UNALLOCATED(4,7),
+ AARCH64(SYS_ID_AA64FPFR0_EL1),
AARCH64(SYS_ID_AA64DFR0_EL1),
AARCH64(SYS_ID_AA64DFR1_EL1),
ID_UNALLOCATED(5,2),
--
2.47.3
^ permalink raw reply [flat|nested] 18+ messages in thread* [PATCH 16/16] KVM: arm64: Advertise ECV, HAFT and ETS3 to protected VMs
2026-11-09 8:31 [PATCH 00/16] KVM: arm64: Support debug, SVE, MOPS, PIE/POE, FPMR and more in protected VMs Fuad Tabba
` (14 preceding siblings ...)
2026-11-09 8:32 ` [PATCH 15/16] KVM: arm64: Advertise FP8 and F16MM " Fuad Tabba
@ 2026-11-09 8:32 ` Fuad Tabba
2026-11-09 20:20 ` [PATCH 00/16] KVM: arm64: Support debug, SVE, MOPS, PIE/POE, FPMR and more in " Oliver Upton
16 siblings, 0 replies; 18+ messages in thread
From: Fuad Tabba @ 2026-11-09 8:32 UTC (permalink / raw)
To: Marc Zyngier, Oliver Upton, kvmarm, linux-arm-kernel
Cc: Joey Gouly, Suzuki K Poulose, Zenghui Yu, Steffen Eiden,
Catalin Marinas, Will Deacon, Mark Rutland, Vincent Donnefort,
Fuad Tabba, kvm, linux-kernel
Advertise FEAT_ECV, FEAT_HAFT and FEAT_ETS3 to protected VMs, with ECV
capped at IMP and HAFDBS at HAFT, since CNTPOFF and HDBSS only add EL2
and EL3 controls. ECV's self-synchronised counter views trap under the
same controls as CNTPCT_EL0 and CNTVCT_EL0, which kvm_handle_cntxct()
emulates.
Signed-off-by: Fuad Tabba <fuad.tabba@linux.dev>
---
arch/arm64/kvm/hyp/nvhe/sys_regs.c | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/arch/arm64/kvm/hyp/nvhe/sys_regs.c b/arch/arm64/kvm/hyp/nvhe/sys_regs.c
index a0f6737ff8f16..4ab0ee50eea20 100644
--- a/arch/arm64/kvm/hyp/nvhe/sys_regs.c
+++ b/arch/arm64/kvm/hyp/nvhe/sys_regs.c
@@ -173,16 +173,17 @@ static const struct pvm_ftr_bits pvmid_aa64mmfr0[] = {
MAX_FEAT(ID_AA64MMFR0_EL1, TGRAN4, IMP),
MAX_FEAT(ID_AA64MMFR0_EL1, TGRAN16, IMP),
MAX_FEAT(ID_AA64MMFR0_EL1, TGRAN64, IMP),
+ MAX_FEAT(ID_AA64MMFR0_EL1, ECV, IMP),
FEAT_END
};
static const struct pvm_ftr_bits pvmid_aa64mmfr1[] = {
- MAX_FEAT(ID_AA64MMFR1_EL1, HAFDBS, DBM),
+ MAX_FEAT(ID_AA64MMFR1_EL1, HAFDBS, HAFT),
MAX_FEAT_ENUM(ID_AA64MMFR1_EL1, VMIDBits, 16),
MAX_FEAT(ID_AA64MMFR1_EL1, HPDS, HPDS2),
MAX_FEAT(ID_AA64MMFR1_EL1, PAN, PAN3),
MAX_FEAT(ID_AA64MMFR1_EL1, SpecSEI, IMP),
- MAX_FEAT(ID_AA64MMFR1_EL1, ETS, IMP),
+ MAX_FEAT(ID_AA64MMFR1_EL1, ETS, ETS3),
MAX_FEAT(ID_AA64MMFR1_EL1, AFP, IMP),
MAX_FEAT(ID_AA64MMFR1_EL1, TIDCP1, IMP),
MAX_FEAT(ID_AA64MMFR1_EL1, CMOW, IMP),
--
2.47.3
^ permalink raw reply [flat|nested] 18+ messages in thread* Re: [PATCH 00/16] KVM: arm64: Support debug, SVE, MOPS, PIE/POE, FPMR and more in protected VMs
2026-11-09 8:31 [PATCH 00/16] KVM: arm64: Support debug, SVE, MOPS, PIE/POE, FPMR and more in protected VMs Fuad Tabba
` (15 preceding siblings ...)
2026-11-09 8:32 ` [PATCH 16/16] KVM: arm64: Advertise ECV, HAFT and ETS3 " Fuad Tabba
@ 2026-11-09 20:20 ` Oliver Upton
16 siblings, 0 replies; 18+ messages in thread
From: Oliver Upton @ 2026-11-09 20:20 UTC (permalink / raw)
To: Fuad Tabba
Cc: Marc Zyngier, kvmarm, linux-arm-kernel, Joey Gouly,
Suzuki K Poulose, Zenghui Yu, Steffen Eiden, Catalin Marinas,
Will Deacon, Mark Rutland, Vincent Donnefort, Fuad Tabba, kvm,
linux-kernel
Hi Fuad,
On Mon, Nov 09, 2026 at 08:31:49AM +0000, Fuad Tabba wrote:
> Hi folks,
>
> With most of the groundwork now merged, bringing protected VMs up to
> the features we support for them in Android turns out not to be a big
> series. So this folds self-hosted debug [1] and SVE [2], which I posted
> separately last week, into one series with the rest. Both are small,
> and keeping them apart would only leave a few conflicts where they add
> neighbouring lines to the pVM ID-register code. The debug and SVE
> patches are unchanged from those postings.
>
> 01: Self-hosted debug.
> 02-05: SVE, with EL2 owning a protected vCPU's SVE state.
> 06-07: Compute a protected vCPU's fine-grained traps at EL2.
> 08-16: Advertise TCR2, SCTLR2, S1PIE, S1POE, MOPS, FPMR and the
> features EL2 already handles for non-protected guests.
>
> Tested on QEMU, in hVHE and nVHE, and on FVP for S1POE and the fields
> QEMU doesn't model (CLRBHB, RPRFM, ECBHB, LSFE, LSUI, HAFT, ETS3 and
> F16MM). v2 of the pKVM selftests series [3] will cover debug, SVE,
> MOPS, PIE, POE and FPMR.
>
> Based on Linux 7.4-rc1 (6628e7d4c6b25); it also applies to 7.4-rc2
> (7475ea8b58616).
>
> Cheers,
> /fuad
>
> [1] https://lore.kernel.org/all/20261102093950.2922035-1-fuad.tabba@linux.dev/
> [2] https://lore.kernel.org/all/20261105130456.1617185-1-fuad.tabba@linux.dev/
> [3] https://lore.kernel.org/all/20261104084842.132944-1-fuad.tabba@linux.dev/
Ok, we really need to slow down the rate of patches here. I had already
applied the SVE series locally and now we've got a grab-bag of CPU
features in addition to what I was ready to apply.
The whole point of respinning a series is so we can converge on the
shape of the feature and get it merged. Adding a whole pile of things
that wasn't asked for just muddies the context for us dumb humans and
slows down the rate at which you're going to land things upstream.
Going forward, can you please:
- Keep an individual series focused on the original intent. Don't
unnecessarily expand scope.
- Only iterate on a couple of series on the list at a time. Sashiko
findings are interesting, but you're gonna have to draw a cutline in
terms of how many additional patches are going to ride the coattails
of any particular series.
You're obviously a very active contributor and reviewer, so I don't
anticipate such a rate limit to actually impede your patch velocity.
Sorry, I really do appreciate the amount of work that you're doing to
get pKVM in good shape upstream. I just get the sense that Marc and I
are going to quickly encounter bandwidth limitations.
Thanks,
Oliver
^ permalink raw reply [flat|nested] 18+ messages in thread