On Tue, Sep 01, 2026 at 10:47:06PM +0100, Mark Brown wrote:
> As per I_CFFNS and the pseudocode for the SPSR_ELx and ELR_ELx registers
> a GCS exception with ExType 1 is generated for attempts to write to
> those registers when both GCSCR_ELx.EXLOCKEN and PSTATE.EXLOCK are set.
> Ensure that this is enforced for guests if access to these registers
> from the guest is handled by the hypervisor.
>
> Signed-off-by: Mark Brown <[email protected]>
> ---
> arch/arm64/include/asm/kvm_emulate.h | 8 +++++++
> arch/arm64/kvm/sys_regs.c | 42
> ++++++++++++++++++++++++++++++++----
> 2 files changed, 46 insertions(+), 4 deletions(-)
>
> diff --git a/arch/arm64/include/asm/kvm_emulate.h
> b/arch/arm64/include/asm/kvm_emulate.h
> index 0cd91b3d6c82..e47ab38327de 100644
> --- a/arch/arm64/include/asm/kvm_emulate.h
> +++ b/arch/arm64/include/asm/kvm_emulate.h
> @@ -81,6 +81,14 @@ int kvm_inject_nested_irq(struct kvm_vcpu *vcpu);
> int kvm_inject_nested_sea(struct kvm_vcpu *vcpu, bool iabt, u64 addr);
> int kvm_inject_nested_serror(struct kvm_vcpu *vcpu, u64 esr);
>
> +static inline void kvm_inject_exlock(struct kvm_vcpu *vcpu)
> +{
> + u64 esr = FIELD_PREP(ESR_ELx_EC_MASK, ESR_ELx_EC_GCS) | ESR_ELx_IL |
> + FIELD_PREP(ESR_ELx_ExType_MASK, ESR_ELx_ExType_EXLOCK);
Seems the likely encoding, based on reading the ESR_EL2 doc, not sure if
there is a better place to read that from.
> +
> + kvm_inject_sync(vcpu, esr);
> +}
> +
> static inline void kvm_inject_nested_sve_trap(struct kvm_vcpu *vcpu)
> {
> u64 esr = FIELD_PREP(ESR_ELx_EC_MASK, ESR_ELx_EC_SVE) |
> diff --git a/arch/arm64/kvm/sys_regs.c b/arch/arm64/kvm/sys_regs.c
> index c5ce18b3f7d8..1f80c26a9839 100644
> --- a/arch/arm64/kvm/sys_regs.c
> +++ b/arch/arm64/kvm/sys_regs.c
> @@ -2866,14 +2866,42 @@ static bool access_sp_el1(struct kvm_vcpu *vcpu,
> return true;
> }
>
> +static inline bool sysregs_exlocked(struct kvm_vcpu *vcpu)
> +{
> + u64 gcscr;
> +
> + if (!kvm_has_gcs(vcpu->kvm))
> + return false;
> +
> + if (!(vcpu->arch.ctxt.regs.pstate & PSR_EXLOCK_BIT))
> + return false;
> +
> + /*
> + * Note that the EXLOCKEN for the running EL is checked
> + * regardless of the register written to.
> + */
> + if (is_hyp_ctxt(vcpu))
> + gcscr = vcpu_read_sys_reg(vcpu, GCSCR_EL2);
> + else
> + gcscr = vcpu_read_sys_reg(vcpu, GCSCR_EL1);
> +
> + return gcscr & GCSCR_ELx_EXLOCKEN;
> +}
> +
Right, because on both ELR_ELx and SPSR_EL{1,2} we have to check if
GCS is enabled && EXLOCKEN=1 && EXLOCK=1,
so any of those conditions being false mean no exlock injection is needed.
> static bool access_elr(struct kvm_vcpu *vcpu,
> struct sys_reg_params *p,
> const struct sys_reg_desc *r)
> {
> - if (p->is_write)
> + if (p->is_write) {
> + if (sysregs_exlocked(vcpu)) {
> + kvm_inject_exlock(vcpu);
> + return false;
> + }
> +
> vcpu_write_sys_reg(vcpu, p->regval, ELR_EL1);
> - else
> + } else {
> p->regval = vcpu_read_sys_reg(vcpu, ELR_EL1);
> + }
>
> return true;
> }
> @@ -2882,10 +2910,16 @@ static bool access_spsr(struct kvm_vcpu *vcpu,
> struct sys_reg_params *p,
> const struct sys_reg_desc *r)
> {
> - if (p->is_write)
> + if (p->is_write) {
> + if (sysregs_exlocked(vcpu)) {
> + kvm_inject_exlock(vcpu);
> + return false;
> + }
> +
> __vcpu_assign_sys_reg(vcpu, SPSR_EL1, p->regval);
> - else
> + } else {
> p->regval = __vcpu_sys_reg(vcpu, SPSR_EL1);
> + }
>
> return true;
> }
FWIW:
Reviewed-by: Leonardo Bras <[email protected]>
Thanks!
Leo