From: Marc Zyngier <maz@kernel.org>
To: Oliver Upton <oliver.upton@linux.dev>
Cc: kvmarm@lists.linux.dev, Joey Gouly <joey.gouly@arm.com>,
Suzuki K Poulose <suzuki.poulose@arm.com>,
Zenghui Yu <yuzenghui@huawei.com>
Subject: Re: [PATCH 1/4] KVM: arm64: nv: Respect exception routing rules for SEAs
Date: Sat, 31 May 2025 17:23:44 +0100 [thread overview]
Message-ID: <87r004eokv.wl-maz@kernel.org> (raw)
In-Reply-To: <20250530230623.650888-2-oliver.upton@linux.dev>
On Sat, 31 May 2025 00:06:20 +0100,
Oliver Upton <oliver.upton@linux.dev> wrote:
>
> Synchronous external aborts are taken to EL2 if ELIsInHost() or
> HCR_EL2.TEA=1. Rework the SEA injection plumbing to respect the imposed
> routing of the guest hypervisor and opportunistically rephrase things to
> make their function a bit more obvious.
nit: this is only true when FEAT_RAS is implemented, which isn't the
case so far when NV is enabled.
>
> Signed-off-by: Oliver Upton <oliver.upton@linux.dev>
> ---
> arch/arm64/include/asm/kvm_emulate.h | 15 +++++++++--
> arch/arm64/kvm/emulate-nested.c | 20 ++++++++++++++
> arch/arm64/kvm/guest.c | 8 ++++--
> arch/arm64/kvm/inject_fault.c | 40 +++++++++-------------------
> arch/arm64/kvm/mmio.c | 6 ++---
> arch/arm64/kvm/mmu.c | 15 +++--------
> 6 files changed, 57 insertions(+), 47 deletions(-)
>
> diff --git a/arch/arm64/include/asm/kvm_emulate.h b/arch/arm64/include/asm/kvm_emulate.h
> index bd020fc28aa9..e0fa8a5a2530 100644
> --- a/arch/arm64/include/asm/kvm_emulate.h
> +++ b/arch/arm64/include/asm/kvm_emulate.h
> @@ -46,15 +46,26 @@ void kvm_skip_instr32(struct kvm_vcpu *vcpu);
>
> void kvm_inject_undefined(struct kvm_vcpu *vcpu);
> void kvm_inject_vabt(struct kvm_vcpu *vcpu);
> -void kvm_inject_dabt(struct kvm_vcpu *vcpu, unsigned long addr);
> -void kvm_inject_pabt(struct kvm_vcpu *vcpu, unsigned long addr);
> +void __kvm_inject_sea(struct kvm_vcpu *vcpu, bool iabt, u64 addr);
> +int kvm_inject_sea(struct kvm_vcpu *vcpu, bool iabt, u64 addr);
> void kvm_inject_size_fault(struct kvm_vcpu *vcpu);
>
> +static inline int kvm_inject_sea_dabt(struct kvm_vcpu *vcpu, u64 addr)
> +{
> + return kvm_inject_sea(vcpu, false, addr);
> +}
> +
> +static inline int kvm_inject_sea_iabt(struct kvm_vcpu *vcpu, u64 addr)
> +{
> + return kvm_inject_sea(vcpu, true, addr);
> +}
> +
> void kvm_vcpu_wfi(struct kvm_vcpu *vcpu);
>
> void kvm_emulate_nested_eret(struct kvm_vcpu *vcpu);
> int kvm_inject_nested_sync(struct kvm_vcpu *vcpu, u64 esr_el2);
> int kvm_inject_nested_irq(struct kvm_vcpu *vcpu);
> +int kvm_inject_nested_sea(struct kvm_vcpu *vcpu, bool iabt, u64 addr);
>
> static inline void kvm_inject_nested_sve_trap(struct kvm_vcpu *vcpu)
> {
> diff --git a/arch/arm64/kvm/emulate-nested.c b/arch/arm64/kvm/emulate-nested.c
> index 3a384e9660b8..3d2f98fdca2f 100644
> --- a/arch/arm64/kvm/emulate-nested.c
> +++ b/arch/arm64/kvm/emulate-nested.c
> @@ -2816,3 +2816,23 @@ int kvm_inject_nested_irq(struct kvm_vcpu *vcpu)
> /* esr_el2 value doesn't matter for exits due to irqs. */
> return kvm_inject_nested(vcpu, 0, except_type_irq);
> }
> +
> +int kvm_inject_nested_sea(struct kvm_vcpu *vcpu, bool iabt, u64 addr)
> +{
> + u64 esr;
> +
> + /*
> + * The destination EL is in the same translation regime as the origin;
> + * directly inject the SEA.
> + */
> + if (is_hyp_ctxt(vcpu) || !(__vcpu_sys_reg(vcpu, HCR_EL2) & HCR_TEA)) {
> + __kvm_inject_sea(vcpu, iabt, addr);
> + return 1;
> + }
I find this a bit confusing.
Here, we end-up *not* injecting a nested exception, but instead
delivering it in context. I think it would be clearer to move this
condition in kvm_inject_sea(), and then make __kvm_inject_sea()
static.
I guess the confusion also stems from the fact that we tend to lump
two things together:
- exception taken from EL2&0 to EL2
- exception taken from EL1&0 to EL2
I would like to make sure that it is the second case we deal with in
emulated-nested.c, and the first one locally.
At which point, you can end up with something like:
static inline bool is_nested_ctxt(struct kvm_vcpu *vcpu)
{
return vcpu_has_nv(vcpu) && !is_hyp_ctxt(vcpu);
}
int kvm_inject_sea(struct kvm_vcpu *vcpu, bool iabt, u64 addr)
{
lockdep_assert_held(&vcpu->mutex);
if (is_nested_ctxt(vcpu) && (__vcpu_sys_reg(vcpu, HCR_EL2) & HCR_TEA))
return kvm_inject_nested_sea(vcpu, iabt, addr);
__kvm_inject_sea(vcpu, iabt, addr);
return 1;
}
I'll post a separate patch with the is_nested_ctxt() helper, as it
makes things more readable overall.
> +
> + esr = FIELD_PREP(ESR_ELx_EC_MASK,
> + iabt ? ESR_ELx_EC_IABT_LOW : ESR_ELx_EC_DABT_LOW);
> + esr |= ESR_ELx_FSC_EXTABT | ESR_ELx_IL;
> +
> + return kvm_inject_s2_fault(vcpu, esr);
> +}
> diff --git a/arch/arm64/kvm/guest.c b/arch/arm64/kvm/guest.c
> index 2196979a24a3..dd5cce0006f3 100644
> --- a/arch/arm64/kvm/guest.c
> +++ b/arch/arm64/kvm/guest.c
> @@ -839,6 +839,7 @@ int __kvm_arm_vcpu_set_events(struct kvm_vcpu *vcpu,
> bool serror_pending = events->exception.serror_pending;
> bool has_esr = events->exception.serror_has_esr;
> bool ext_dabt_pending = events->exception.ext_dabt_pending;
> + int ret;
>
> if (serror_pending && has_esr) {
> if (!cpus_have_final_cap(ARM64_HAS_RAS_EXTN))
> @@ -852,8 +853,11 @@ int __kvm_arm_vcpu_set_events(struct kvm_vcpu *vcpu,
> kvm_inject_vabt(vcpu);
> }
>
> - if (ext_dabt_pending)
> - kvm_inject_dabt(vcpu, kvm_vcpu_get_hfar(vcpu));
> + if (ext_dabt_pending) {
> + ret = kvm_inject_sea_dabt(vcpu, kvm_vcpu_get_hfar(vcpu));
> + if (ret < 0)
> + return ret;
> + }
>
> return 0;
> }
> diff --git a/arch/arm64/kvm/inject_fault.c b/arch/arm64/kvm/inject_fault.c
> index a640e839848e..3e61fa0a721b 100644
> --- a/arch/arm64/kvm/inject_fault.c
> +++ b/arch/arm64/kvm/inject_fault.c
> @@ -155,36 +155,23 @@ static void inject_abt32(struct kvm_vcpu *vcpu, bool is_pabt, u32 addr)
> vcpu_write_sys_reg(vcpu, far, FAR_EL1);
> }
>
> -/**
> - * kvm_inject_dabt - inject a data abort into the guest
> - * @vcpu: The VCPU to receive the data abort
> - * @addr: The address to report in the DFAR
> - *
> - * It is assumed that this code is called from the VCPU thread and that the
> - * VCPU therefore is not currently executing guest code.
> - */
> -void kvm_inject_dabt(struct kvm_vcpu *vcpu, unsigned long addr)
> +void __kvm_inject_sea(struct kvm_vcpu *vcpu, bool iabt, u64 addr)
> {
> if (vcpu_el1_is_32bit(vcpu))
> - inject_abt32(vcpu, false, addr);
> + inject_abt32(vcpu, iabt, addr);
> else
> - inject_abt64(vcpu, false, addr);
> + inject_abt64(vcpu, iabt, addr);
> }
>
> -/**
> - * kvm_inject_pabt - inject a prefetch abort into the guest
> - * @vcpu: The VCPU to receive the prefetch abort
> - * @addr: The address to report in the DFAR
> - *
> - * It is assumed that this code is called from the VCPU thread and that the
> - * VCPU therefore is not currently executing guest code.
> - */
> -void kvm_inject_pabt(struct kvm_vcpu *vcpu, unsigned long addr)
> +int kvm_inject_sea(struct kvm_vcpu *vcpu, bool iabt, u64 addr)
> {
> - if (vcpu_el1_is_32bit(vcpu))
> - inject_abt32(vcpu, true, addr);
> - else
> - inject_abt64(vcpu, true, addr);
> + lockdep_assert_held(&vcpu->mutex);
> +
> + if (vcpu_has_nv(vcpu))
> + return kvm_inject_nested_sea(vcpu, iabt, addr);
> +
> + __kvm_inject_sea(vcpu, iabt, addr);
> + return 1;
> }
>
> void kvm_inject_size_fault(struct kvm_vcpu *vcpu)
> @@ -194,10 +181,7 @@ void kvm_inject_size_fault(struct kvm_vcpu *vcpu)
> addr = kvm_vcpu_get_fault_ipa(vcpu);
> addr |= kvm_vcpu_get_hfar(vcpu) & GENMASK(11, 0);
>
> - if (kvm_vcpu_trap_is_iabt(vcpu))
> - kvm_inject_pabt(vcpu, addr);
> - else
> - kvm_inject_dabt(vcpu, addr);
> + __kvm_inject_sea(vcpu, kvm_vcpu_trap_is_iabt(vcpu), addr);
>
> /*
> * If AArch64 or LPAE, set FSC to 0 to indicate an Address
> diff --git a/arch/arm64/kvm/mmio.c b/arch/arm64/kvm/mmio.c
> index ab365e839874..573a6ade2f4e 100644
> --- a/arch/arm64/kvm/mmio.c
> +++ b/arch/arm64/kvm/mmio.c
> @@ -169,10 +169,8 @@ int io_mem_abort(struct kvm_vcpu *vcpu, phys_addr_t fault_ipa)
> trace_kvm_mmio_nisv(*vcpu_pc(vcpu), kvm_vcpu_get_esr(vcpu),
> kvm_vcpu_get_hfar(vcpu), fault_ipa);
>
> - if (vcpu_is_protected(vcpu)) {
> - kvm_inject_dabt(vcpu, kvm_vcpu_get_hfar(vcpu));
> - return 1;
> - }
> + if (vcpu_is_protected(vcpu))
> + return kvm_inject_sea_dabt(vcpu, kvm_vcpu_get_hfar(vcpu));
>
> if (test_bit(KVM_ARCH_FLAG_RETURN_NISV_IO_ABORT_TO_USER,
> &vcpu->kvm->arch.flags)) {
> diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c
> index e445db2cb4a4..0e0d51d7ab85 100644
> --- a/arch/arm64/kvm/mmu.c
> +++ b/arch/arm64/kvm/mmu.c
> @@ -1833,11 +1833,7 @@ int kvm_handle_guest_abort(struct kvm_vcpu *vcpu)
> if (fault_ipa >= BIT_ULL(VTCR_EL2_IPA(vcpu->arch.hw_mmu->vtcr))) {
> fault_ipa |= kvm_vcpu_get_hfar(vcpu) & GENMASK(11, 0);
>
> - if (is_iabt)
> - kvm_inject_pabt(vcpu, fault_ipa);
> - else
> - kvm_inject_dabt(vcpu, fault_ipa);
> - return 1;
> + return kvm_inject_sea(vcpu, is_iabt, fault_ipa);
> }
> }
>
> @@ -1909,8 +1905,7 @@ int kvm_handle_guest_abort(struct kvm_vcpu *vcpu)
> }
>
> if (kvm_vcpu_abt_iss1tw(vcpu)) {
> - kvm_inject_dabt(vcpu, kvm_vcpu_get_hfar(vcpu));
> - ret = 1;
> + ret = kvm_inject_sea_dabt(vcpu, kvm_vcpu_get_hfar(vcpu));
> goto out_unlock;
> }
>
> @@ -1955,10 +1950,8 @@ int kvm_handle_guest_abort(struct kvm_vcpu *vcpu)
> if (ret == 0)
> ret = 1;
> out:
> - if (ret == -ENOEXEC) {
> - kvm_inject_pabt(vcpu, kvm_vcpu_get_hfar(vcpu));
> - ret = 1;
> - }
> + if (ret == -ENOEXEC)
> + ret = kvm_inject_sea_iabt(vcpu, kvm_vcpu_get_hfar(vcpu));
> out_unlock:
> srcu_read_unlock(&vcpu->kvm->srcu, idx);
> return ret;
Other than my rambling above, this looks rather good. But there is a
bit more, "thanks" to FEAT_DoubleFault2:
- HCRX_EL2.TMEA also affects this patch, both on the SEA and SError
paths (both can be routed to EL2 when masked).
- SCTLR2_EL{1,2}2.EASE also influence the delivery of the SEA,
upgrading it to a SError (yes, this is the routing from hell and
ties directly into the following patches).
I was expecting to see FEAT_RAS being enabled at some point, but
that's not the case yet. Are you planning to do so?
Thanks,
M.
--
Jazz isn't dead. It just smells funny.
next prev parent reply other threads:[~2025-05-31 16:23 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-05-30 23:06 [PATCH 0/4] KVM: arm64: nv: Fixes for external abort exception routing Oliver Upton
2025-05-30 23:06 ` [PATCH 1/4] KVM: arm64: nv: Respect exception routing rules for SEAs Oliver Upton
2025-05-31 16:23 ` Marc Zyngier [this message]
2025-05-31 17:51 ` Oliver Upton
2025-05-30 23:06 ` [PATCH 2/4] KVM: arm64: nv: Ensure Address size faults affect correct ESR Oliver Upton
2025-05-30 23:06 ` [PATCH 3/4] KVM: arm64: nv: Honor SError exception routing / masking Oliver Upton
2025-05-30 23:06 ` [PATCH 4/4] KVM: arm64: Treat vCPU with pending SError as runnable Oliver Upton
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=87r004eokv.wl-maz@kernel.org \
--to=maz@kernel.org \
--cc=joey.gouly@arm.com \
--cc=kvmarm@lists.linux.dev \
--cc=oliver.upton@linux.dev \
--cc=suzuki.poulose@arm.com \
--cc=yuzenghui@huawei.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox