[PATCH v2 14/20] KVM: s390: arm64: Integrate sysreg into the host

From: Steffen Eiden

Date: Mon Aug 31 2026 - 13:01:29 EST


Integrate the previously established system register managing
infrastructure into the host. Initialize sysregs during vcpu init.
Finalize sysregs on PID change, Initialize sysreg table on kvm init,
report availability to userspace and let userspace read&modify guest system
register.

Signed-off-by: Steffen Eiden <seiden@xxxxxxxxxxxxx>
---
arch/s390/include/asm/kvm_host_arm64.h | 3 ++
arch/s390/kvm/arm64/arm.c | 27 +++++++++++++++++
arch/s390/kvm/arm64/guest.c | 12 +++++---
arch/s390/kvm/arm64/handle_exit.c | 1 +
arch/s390/kvm/arm64/reset.c | 42 ++++++++++++++++++++++++++
5 files changed, 81 insertions(+), 4 deletions(-)

diff --git a/arch/s390/include/asm/kvm_host_arm64.h b/arch/s390/include/asm/kvm_host_arm64.h
index 65e5e0c48b07..0bf396ef54c2 100644
--- a/arch/s390/include/asm/kvm_host_arm64.h
+++ b/arch/s390/include/asm/kvm_host_arm64.h
@@ -156,6 +156,9 @@ struct kvm_arch {

/* Per-VM ID register storage */
struct kvm_vm_id_regs id_regs;
+
+ /* The EPD as it should be on all VCPUs. */
+ u64 epd;
};

static inline bool __vcpu_has_feature(const struct kvm_arch *ka, int feature)
diff --git a/arch/s390/kvm/arm64/arm.c b/arch/s390/kvm/arm64/arm.c
index c573a3da5766..3a0bba6e0a06 100644
--- a/arch/s390/kvm/arm64/arm.c
+++ b/arch/s390/kvm/arm64/arm.c
@@ -16,6 +16,7 @@

#include <arm64/kvm_emulate.h>
#include <arm64/sysreg.h>
+#include <arm64/sys_regs.h>

#include <gmap.h>
#include <kvm_mmu.h>
@@ -136,6 +137,8 @@ int kvm_arch_init_vm(struct kvm *kvm, unsigned long type)
goto out_err;
kvm->arch.mem_limit = kvm->arch.guest_phys_size;

+ kvm->arch.epd = ptff_qagto(0);
+
VM_EVENT(kvm, 3, "vm created with type %lu", type);
return 0;

@@ -191,6 +194,7 @@ int kvm_arch_vcpu_create(struct kvm_vcpu *vcpu)
sae_block->mso = 0L;
sae_block->msl = vcpu->kvm->arch.mem_limit;
sae_block->save_area = virt_to_phys(save_area);
+ sae_block->gpto = vcpu->kvm->arch.epd;

VM_EVENT(vcpu->kvm, 3,
"create cpu %d at 0x%p, sae block at 0x%p, satellite at 0x%p",
@@ -259,6 +263,7 @@ bool kvm_arch_vcpu_in_kernel(struct kvm_vcpu *vcpu)
int kvm_arch_vcpu_run_pid_change(struct kvm_vcpu *vcpu)
{
struct kvm *kvm = vcpu->kvm;
+ int ret;

if (!kvm_vcpu_initialized(vcpu))
return -ENOEXEC;
@@ -272,6 +277,10 @@ int kvm_arch_vcpu_run_pid_change(struct kvm_vcpu *vcpu)
scoped_guard(mutex, &kvm->arch.config_lock)
set_bit(KVM_ARCH_FLAG_HAS_RAN_ONCE, &kvm->arch.flags);

+ ret = kvm_finalize_sys_regs(vcpu);
+ if (ret)
+ return ret;
+
return 0;
}

@@ -390,10 +399,20 @@ static void arm_vcpu_run(struct kvm_vcpu *vcpu)
guest_state_enter_irqoff();
local_irq_enable();

+ vcpu_write_host_sys_reg(vcpu, vcpu->arch.hcr_elz, SYS_HCR_EL2);
+ vcpu_write_host_sys_reg(vcpu, vcpu->arch.hcrx_elz, SYS_HCRX_EL2);
+ vcpu_write_host_sys_reg(vcpu, vcpu->arch.mpidr, SYS_VMPIDR_EL2);
+
+ _vcpu_write_sys_reg(vcpu, vcpu->arch.ctxt.elr_el1, SYS_ELR_EL1);
+ _vcpu_write_sys_reg(vcpu, vcpu->arch.ctxt.spsr_el1, SYS_SPSR_EL1);
+
sae_block->icptr = 0;

sae64a(sae_block);

+ vcpu->arch.ctxt.elr_el1 = _vcpu_read_sys_reg(vcpu, SYS_ELR_EL1);
+ vcpu->arch.ctxt.spsr_el1 = _vcpu_read_sys_reg(vcpu, SYS_SPSR_EL1);
+
local_irq_disable();
guest_state_exit_irqoff();
guest_timing_exit_irqoff();
@@ -746,11 +765,19 @@ long kvm_arch_vcpu_unlocked_ioctl(struct file *filp, unsigned int ioctl,

static int __init kvm_s390_arm64_init(void)
{
+ int ret;
+
if (!aef_info()->arm_guest_supp)
return -ENXIO;

kvm_init_qaaf();

+ ret = kvm_sys_reg_table_init();
+ if (ret) {
+ kvm_info("Error initializing system register tables");
+ return ret;
+ }
+
return kvm_init_with_dev(sizeof(struct kvm_vcpu), 0, THIS_MODULE,
KVM_DEV_NAME, MISC_DYNAMIC_MINOR);
}
diff --git a/arch/s390/kvm/arm64/guest.c b/arch/s390/kvm/arm64/guest.c
index 8b5ee9e79a4f..047460975936 100644
--- a/arch/s390/kvm/arm64/guest.c
+++ b/arch/s390/kvm/arm64/guest.c
@@ -4,6 +4,7 @@

#include <arm64/kvm_emulate.h>
#include <arm64/kvm_nested.h>
+#include <arm64/sys_regs.h>

#define SVE_VQ_MIN __SVE_VQ_MIN
#define SVE_NUM_ZREGS KVM_ARM64_SVE_NUM_ZREGS
@@ -60,12 +61,15 @@ int kvm_arm_copy_reg_indices(struct kvm_vcpu *vcpu, u64 __user *uindices)
return ret;
uindices += ret;

- return 0;
+ return kvm_arm_copy_sys_reg_indices(vcpu, uindices);
}

unsigned long kvm_arm_num_regs(struct kvm_vcpu *vcpu)
{
- return num_core_regs(vcpu);
+ unsigned long num = num_core_regs(vcpu);
+
+ num += kvm_arm_num_sys_reg_descs(vcpu);
+ return num;
}

int kvm_arm_get_reg(struct kvm_vcpu *vcpu, const struct kvm_one_reg *reg)
@@ -78,7 +82,7 @@ int kvm_arm_get_reg(struct kvm_vcpu *vcpu, const struct kvm_one_reg *reg)
case KVM_REG_ARM_CORE:
return get_core_reg(vcpu, reg);
default:
- return -EINVAL;
+ return kvm_arm_sys_reg_get_reg(vcpu, reg);
}
}

@@ -92,7 +96,7 @@ int kvm_arm_set_reg(struct kvm_vcpu *vcpu, const struct kvm_one_reg *reg)
case KVM_REG_ARM_CORE:
return set_core_reg(vcpu, reg);
default:
- return -EINVAL;
+ return kvm_arm_sys_reg_set_reg(vcpu, reg);
}
}

diff --git a/arch/s390/kvm/arm64/handle_exit.c b/arch/s390/kvm/arm64/handle_exit.c
index 8b03e8ee5acf..b6cb4612675a 100644
--- a/arch/s390/kvm/arm64/handle_exit.c
+++ b/arch/s390/kvm/arm64/handle_exit.c
@@ -55,6 +55,7 @@ static int handle_hvc(struct kvm_vcpu *vcpu)
static exit_handle_fn arm_exit_handlers[] = {
[0 ... ESR_ELx_EC_MAX] = kvm_handle_unknown_ec,
[ESR_ELx_EC_HVC64] = handle_hvc,
+ [ESR_ELx_EC_SYS64] = kvm_handle_sys_reg,
[ESR_ELx_EC_IABT_LOW] = kvm_handle_guest_abort,
[ESR_ELx_EC_DABT_LOW] = kvm_handle_guest_abort,
};
diff --git a/arch/s390/kvm/arm64/reset.c b/arch/s390/kvm/arm64/reset.c
index 0e162be1bcd5..74622ba6f081 100644
--- a/arch/s390/kvm/arm64/reset.c
+++ b/arch/s390/kvm/arm64/reset.c
@@ -5,6 +5,10 @@

#include <arm64/kvm_emulate.h>
#include <arm64/kvm_nested.h>
+#include <arm64/sys_regs.h>
+#include <arm64/sysreg.h>
+
+#include <clocksource/arm_arch_timer.h>

bool kvm_arm_vcpu_is_finalized(struct kvm_vcpu *vcpu)
{
@@ -21,6 +25,39 @@ static inline void vcpu_reset_hcr(struct kvm_vcpu *vcpu)
HCR_EL2_TIDCP | HCR_EL2_TLOR;
}

+static inline void vcpu_reset_hcrx(struct kvm_vcpu *vcpu)
+{
+ vcpu->arch.hcrx_elz = 0;
+ if (kvm_has_feat(vcpu->kvm, ID_AA64ISAR2_EL1, MOPS, IMP))
+ vcpu->arch.hcrx_elz |= HCRX_EL2_MSCEn;
+}
+
+static inline void vcpu_reset_cptr(struct kvm_vcpu *vcpu)
+{
+ u64 cptr;
+
+ /* we unconditionally have E2H, so disable traps to EL2 for FP and SVE, if enabled */
+ cptr = CPACR_EL1_FPEN;
+ if (vcpu_has_sve(vcpu))
+ cptr |= CPACR_EL1_ZEN;
+ vcpu_write_host_sys_reg(vcpu, cptr, SYS_CPTR_EL2);
+}
+
+static inline void vcpu_reset_cnthctl(struct kvm_vcpu *vcpu)
+{
+ u64 cnthctl;
+
+ /* we unconditionally have E2H, so disable traps to EL2 for physical timer registers */
+ cnthctl = (CNTHCTL_EL1PCEN | CNTHCTL_EL1PCTEN) << 10;
+ vcpu_write_host_sys_reg(vcpu, cnthctl, SYS_CNTHCTL_EL2);
+}
+
+static inline void vcpu_reset_icc(struct kvm_vcpu *vcpu)
+{
+ /* ensure ICC_SRE_EL2.Enable = 1 so ICC_SRE_EL1 is handled in hardware */
+ vcpu_write_host_sys_reg(vcpu, 0xf, SYS_ICC_SRE_EL2);
+}
+
void kvm_reset_vcpu(struct kvm_vcpu *vcpu)
{
struct vcpu_reset_state reset_state;
@@ -43,9 +80,14 @@ void kvm_reset_vcpu(struct kvm_vcpu *vcpu)
vcpu_put(vcpu);

kvm_reset_vcpu_core(vcpu);
+ kvm_reset_sys_regs(vcpu);

/* Reset special registers */
vcpu_reset_hcr(vcpu);
+ vcpu_reset_hcrx(vcpu);
+ vcpu_reset_cptr(vcpu);
+ vcpu_reset_cnthctl(vcpu);
+ vcpu_reset_icc(vcpu);

if (reset_state.reset) {
*vcpu_pc(vcpu) = reset_state.pc;
--
2.53.0