On Tue, 25 Aug 2026 17:00:35 +0100, "Lorenzo Stoakes (ARM)" <[email protected]> wrote: > > kvm_handle_guest_abort() establishes a kvm_s2_fault_desc data structure, > s2fd, to store and propagate state to either pkvm_mem_abort(), gmem_abort() > or user_mem_abort() handlers. > > Each of these, however, examines the Exception Syndrome Register (ESR) via > s2fd->vcpu. > > Introduce an s2fd->esr field to abstract this and propagate it to callers. > > The value of this (beyond refactoring) is to be able to later generate > faults with a synthetic esr, specifically to implement stage 2 page table > pre-faulting. > > Abstract esr-specific predicates and helpers to the esr.h header and either > have vcpu wrappers call these, or eliminate them if they are not used > elsewhere. > > Provide kvm_s2_fault_is_[write,exec,perm]() helpers for convenience. > > Since kvm_s2_fault_map() either sets perm_fault_granule to the permission > fault granule or 0 if not a permission fault, implement > kvm_s2_perm_fault_granule() to do this directly. > > Abort handlers which use kvm_s2_fault_desc - gmem_abort() and > user_mem_abort() - now only reference s2fd->esr and do not look it up in > any other way, which makes it safe to pass a synthetic s2fd->esr value to > these functions.
Please split this. ESR helpers in one patch, hacking the MMU code to use it in another, s2fd->esr stuff last. > > No functional change intended. > > Signed-off-by: Lorenzo Stoakes (ARM) <[email protected]> > --- > arch/arm64/include/asm/esr.h | 129 > +++++++++++++++++++++++++---------- > arch/arm64/include/asm/kvm_emulate.h | 52 ++++---------- > arch/arm64/kvm/mmu.c | 80 +++++++++++++--------- > 3 files changed, 156 insertions(+), 105 deletions(-) > > diff --git a/arch/arm64/include/asm/esr.h b/arch/arm64/include/asm/esr.h > index f816f5d77f1a..162e90c832e9 100644 > --- a/arch/arm64/include/asm/esr.h > +++ b/arch/arm64/include/asm/esr.h > @@ -437,6 +437,32 @@ > #ifndef __ASSEMBLER__ > #include <asm/types.h> > > +static inline u8 esr_trap_get_class(unsigned long esr) nit: there is no notion of trap here. This is simply extracting the EC from the ESR. My personal (and wholly unreliable) taste would be to go for something like esr_get_ec(). I appreciate that you are simply propagating the names used in KVM, but they were pretty poor the first place, and have only been kept to avoid churn. Also, 'inline' is a bit of a problem given that the callers are __always_inline for good reasons (see 5c37f1ae1c3358). > +{ > + return ESR_ELx_EC(esr); > +} > + > +static inline bool esr_trap_is_iabt(unsigned long esr) > +{ > + return esr_trap_get_class(esr) == ESR_ELx_EC_IABT_LOW; > +} > + > +static inline bool esr_abt_is_s1ptw(unsigned long esr) > +{ > + return esr & ESR_ELx_S1PTW; > +} > + > +/* Always check for S1PTW *before* using this. */ > +static inline bool esr_dabt_is_write(unsigned long esr) > +{ > + return esr & ESR_ELx_WNR; > +} > + > +static inline bool esr_dabt_is_cm(unsigned long esr) > +{ > + return esr & ESR_ELx_CM; > +} > + > static inline unsigned long esr_brk_comment(unsigned long esr) > { > return esr & ESR_ELx_BRK64_ISS_COMMENT_MASK; > @@ -460,75 +486,104 @@ static inline bool esr_is_ubsan_brk(unsigned long esr) > return (esr_brk_comment(esr) & ~UBSAN_BRK_MASK) == UBSAN_BRK_IMM; > } > > +static inline u8 esr_fsc_get_fault(unsigned long esr) > +{ > + return esr & ESR_ELx_FSC; > +} > + > static inline bool esr_fsc_is_translation_fault(unsigned long esr) > { > - esr = esr & ESR_ELx_FSC; > + const u8 fault = esr_fsc_get_fault(esr); > > - return (esr == ESR_ELx_FSC_FAULT_L(3)) || > - (esr == ESR_ELx_FSC_FAULT_L(2)) || > - (esr == ESR_ELx_FSC_FAULT_L(1)) || > - (esr == ESR_ELx_FSC_FAULT_L(0)) || > - (esr == ESR_ELx_FSC_FAULT_L(-1)); > + return (fault == ESR_ELx_FSC_FAULT_L(3)) || > + (fault == ESR_ELx_FSC_FAULT_L(2)) || > + (fault == ESR_ELx_FSC_FAULT_L(1)) || > + (fault == ESR_ELx_FSC_FAULT_L(0)) || > + (fault == ESR_ELx_FSC_FAULT_L(-1)); I really think we could do without this sort of churn. > } > > static inline bool esr_fsc_is_permission_fault(unsigned long esr) > { > - esr = esr & ESR_ELx_FSC; > + const u8 fault = esr_fsc_get_fault(esr); > > - return (esr == ESR_ELx_FSC_PERM_L(3)) || > - (esr == ESR_ELx_FSC_PERM_L(2)) || > - (esr == ESR_ELx_FSC_PERM_L(1)) || > - (esr == ESR_ELx_FSC_PERM_L(0)); > + return (fault == ESR_ELx_FSC_PERM_L(3)) || > + (fault == ESR_ELx_FSC_PERM_L(2)) || > + (fault == ESR_ELx_FSC_PERM_L(1)) || > + (fault == ESR_ELx_FSC_PERM_L(0)); > } > > static inline bool esr_fsc_is_access_flag_fault(unsigned long esr) > { > - esr = esr & ESR_ELx_FSC; > + const u8 fault = esr_fsc_get_fault(esr); > > - return (esr == ESR_ELx_FSC_ACCESS_L(3)) || > - (esr == ESR_ELx_FSC_ACCESS_L(2)) || > - (esr == ESR_ELx_FSC_ACCESS_L(1)) || > - (esr == ESR_ELx_FSC_ACCESS_L(0)); > + return (fault == ESR_ELx_FSC_ACCESS_L(3)) || > + (fault == ESR_ELx_FSC_ACCESS_L(2)) || > + (fault == ESR_ELx_FSC_ACCESS_L(1)) || > + (fault == ESR_ELx_FSC_ACCESS_L(0)); > } > > static inline bool esr_fsc_is_excl_atomic_fault(unsigned long esr) > { > - esr = esr & ESR_ELx_FSC; > - > - return esr == ESR_ELx_FSC_EXCL_ATOMIC; > + return esr_fsc_get_fault(esr) == ESR_ELx_FSC_EXCL_ATOMIC; > } > > static inline bool esr_fsc_is_addr_sz_fault(unsigned long esr) > { > - esr &= ESR_ELx_FSC; > + const u8 fault = esr_fsc_get_fault(esr); > + > + return (fault == ESR_ELx_FSC_ADDRSZ_L(3)) || > + (fault == ESR_ELx_FSC_ADDRSZ_L(2)) || > + (fault == ESR_ELx_FSC_ADDRSZ_L(1)) || > + (fault == ESR_ELx_FSC_ADDRSZ_L(0)) || > + (fault == ESR_ELx_FSC_ADDRSZ_L(-1)); > +} > + > +static inline bool esr_abt_is_exec_fault(unsigned long esr) > +{ > + return esr_trap_is_iabt(esr) && !esr_abt_is_s1ptw(esr); > +} > > - return (esr == ESR_ELx_FSC_ADDRSZ_L(3)) || > - (esr == ESR_ELx_FSC_ADDRSZ_L(2)) || > - (esr == ESR_ELx_FSC_ADDRSZ_L(1)) || > - (esr == ESR_ELx_FSC_ADDRSZ_L(0)) || > - (esr == ESR_ELx_FSC_ADDRSZ_L(-1)); > +static inline bool esr_abt_is_sea(unsigned long esr) > +{ > + const u8 fault = esr_fsc_get_fault(esr); > + > + switch (fault) { > + case ESR_ELx_FSC_EXTABT: > + case ESR_ELx_FSC_SEA_TTW(-1) ... ESR_ELx_FSC_SEA_TTW(3): > + case ESR_ELx_FSC_SECC: > + case ESR_ELx_FSC_SECC_TTW(-1) ... ESR_ELx_FSC_SECC_TTW(3): > + return true; > + default: > + return false; > + } > +} > + > +/* Not valid for negative levels. */ > +static inline u64 esr_fsc_get_level(unsigned long esr) > +{ > + return esr & ESR_ELx_FSC_LEVEL; > } If that's such an unreliable helper, why is it exposed to everyone instead of being kept local to the single caller? > > static inline bool esr_fsc_is_sea_ttw(unsigned long esr) > { > - esr = esr & ESR_ELx_FSC; > + const u8 fault = esr_fsc_get_fault(esr); > > - return (esr == ESR_ELx_FSC_SEA_TTW(3)) || > - (esr == ESR_ELx_FSC_SEA_TTW(2)) || > - (esr == ESR_ELx_FSC_SEA_TTW(1)) || > - (esr == ESR_ELx_FSC_SEA_TTW(0)) || > - (esr == ESR_ELx_FSC_SEA_TTW(-1)); > + return (fault == ESR_ELx_FSC_SEA_TTW(3)) || > + (fault == ESR_ELx_FSC_SEA_TTW(2)) || > + (fault == ESR_ELx_FSC_SEA_TTW(1)) || > + (fault == ESR_ELx_FSC_SEA_TTW(0)) || > + (fault == ESR_ELx_FSC_SEA_TTW(-1)); > } > > static inline bool esr_fsc_is_secc_ttw(unsigned long esr) > { > - esr = esr & ESR_ELx_FSC; > + const u8 fault = esr_fsc_get_fault(esr); > > - return (esr == ESR_ELx_FSC_SECC_TTW(3)) || > - (esr == ESR_ELx_FSC_SECC_TTW(2)) || > - (esr == ESR_ELx_FSC_SECC_TTW(1)) || > - (esr == ESR_ELx_FSC_SECC_TTW(0)) || > - (esr == ESR_ELx_FSC_SECC_TTW(-1)); > + return (fault == ESR_ELx_FSC_SECC_TTW(3)) || > + (fault == ESR_ELx_FSC_SECC_TTW(2)) || > + (fault == ESR_ELx_FSC_SECC_TTW(1)) || > + (fault == ESR_ELx_FSC_SECC_TTW(0)) || > + (fault == ESR_ELx_FSC_SECC_TTW(-1)); > } > > /* Indicate whether ESR.EC==0x1A is for an ERETAx instruction */ > diff --git a/arch/arm64/include/asm/kvm_emulate.h > b/arch/arm64/include/asm/kvm_emulate.h > index a3c1928bdf74..811d7a68a9f9 100644 > --- a/arch/arm64/include/asm/kvm_emulate.h > +++ b/arch/arm64/include/asm/kvm_emulate.h > @@ -411,18 +411,13 @@ static __always_inline int kvm_vcpu_dabt_get_rd(const > struct kvm_vcpu *vcpu) > > static __always_inline bool kvm_vcpu_abt_iss1tw(const struct kvm_vcpu *vcpu) > { > - return !!(kvm_vcpu_get_esr(vcpu) & ESR_ELx_S1PTW); > + return esr_abt_is_s1ptw(kvm_vcpu_get_esr(vcpu)); > } > > /* Always check for S1PTW *before* using this. */ > static __always_inline bool kvm_vcpu_dabt_iswrite(const struct kvm_vcpu > *vcpu) > { > - return kvm_vcpu_get_esr(vcpu) & ESR_ELx_WNR; > -} > - > -static inline bool kvm_vcpu_dabt_is_cm(const struct kvm_vcpu *vcpu) > -{ > - return !!(kvm_vcpu_get_esr(vcpu) & ESR_ELx_CM); > + return esr_dabt_is_write(kvm_vcpu_get_esr(vcpu)); > } > > static __always_inline unsigned int kvm_vcpu_dabt_get_as(const struct > kvm_vcpu *vcpu) > @@ -438,17 +433,12 @@ static __always_inline bool > kvm_vcpu_trap_il_is32bit(const struct kvm_vcpu *vcpu > > static __always_inline u8 kvm_vcpu_trap_get_class(const struct kvm_vcpu > *vcpu) > { > - return ESR_ELx_EC(kvm_vcpu_get_esr(vcpu)); > + return esr_trap_get_class(kvm_vcpu_get_esr(vcpu)); > } > > static inline bool kvm_vcpu_trap_is_iabt(const struct kvm_vcpu *vcpu) > { > - return kvm_vcpu_trap_get_class(vcpu) == ESR_ELx_EC_IABT_LOW; > -} > - > -static inline bool kvm_vcpu_trap_is_exec_fault(const struct kvm_vcpu *vcpu) > -{ > - return kvm_vcpu_trap_is_iabt(vcpu) && !kvm_vcpu_abt_iss1tw(vcpu); > + return esr_trap_is_iabt(kvm_vcpu_get_esr(vcpu)); > } > > static __always_inline u8 kvm_vcpu_trap_get_fault(const struct kvm_vcpu > *vcpu) > @@ -468,26 +458,9 @@ bool kvm_vcpu_trap_is_translation_fault(const struct > kvm_vcpu *vcpu) > return esr_fsc_is_translation_fault(kvm_vcpu_get_esr(vcpu)); > } > > -static inline > -u64 kvm_vcpu_trap_get_perm_fault_granule(const struct kvm_vcpu *vcpu) > -{ > - unsigned long esr = kvm_vcpu_get_esr(vcpu); > - > - BUG_ON(!esr_fsc_is_permission_fault(esr)); > - return BIT(ARM64_HW_PGTABLE_LEVEL_SHIFT(esr & ESR_ELx_FSC_LEVEL)); > -} > - > static __always_inline bool kvm_vcpu_abt_issea(const struct kvm_vcpu *vcpu) > { > - switch (kvm_vcpu_trap_get_fault(vcpu)) { > - case ESR_ELx_FSC_EXTABT: > - case ESR_ELx_FSC_SEA_TTW(-1) ... ESR_ELx_FSC_SEA_TTW(3): > - case ESR_ELx_FSC_SECC: > - case ESR_ELx_FSC_SECC_TTW(-1) ... ESR_ELx_FSC_SECC_TTW(3): > - return true; > - default: > - return false; > - } > + return esr_abt_is_sea(kvm_vcpu_get_esr(vcpu)); > } > > static __always_inline int kvm_vcpu_sys_get_rt(struct kvm_vcpu *vcpu) > @@ -496,9 +469,9 @@ static __always_inline int kvm_vcpu_sys_get_rt(struct > kvm_vcpu *vcpu) > return ESR_ELx_SYS64_ISS_RT(esr); > } > > -static inline bool kvm_is_write_fault(struct kvm_vcpu *vcpu) > +static inline bool esr_abt_is_write_fault(unsigned long esr) > { > - if (kvm_vcpu_abt_iss1tw(vcpu)) { > + if (esr_abt_is_s1ptw(esr)) { > /* > * Only a permission fault on a S1PTW should be > * considered as a write. Otherwise, page tables baked > @@ -511,13 +484,18 @@ static inline bool kvm_is_write_fault(struct kvm_vcpu > *vcpu) > * first), then a permission fault to allow the flags > * to be set. > */ > - return kvm_vcpu_trap_is_permission_fault(vcpu); > + return esr_fsc_is_permission_fault(esr); > } > > - if (kvm_vcpu_trap_is_iabt(vcpu)) > + if (esr_trap_is_iabt(esr)) > return false; > > - return kvm_vcpu_dabt_iswrite(vcpu); > + return esr_dabt_is_write(esr); > +} > + > +static inline bool kvm_is_write_fault(struct kvm_vcpu *vcpu) > +{ > + return esr_abt_is_write_fault(kvm_vcpu_get_esr(vcpu)); > } > > static inline unsigned long kvm_vcpu_get_mpidr_aff(struct kvm_vcpu *vcpu) > diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c > index 74e7e7f7564c..30d605e87b01 100644 > --- a/arch/arm64/kvm/mmu.c > +++ b/arch/arm64/kvm/mmu.c > @@ -1603,12 +1603,38 @@ struct kvm_s2_fault_desc { > struct kvm_s2_trans *nested; > struct kvm_memory_slot *memslot; > unsigned long hva; > + unsigned long esr; > }; > > +static bool kvm_s2_fault_is_perm(const struct kvm_s2_fault_desc *s2fd) > +{ > + return esr_fsc_is_permission_fault(s2fd->esr); > +} > + > +static bool kvm_s2_fault_is_exec(const struct kvm_s2_fault_desc *s2fd) > +{ > + return esr_abt_is_exec_fault(s2fd->esr); > +} > + > +static bool kvm_s2_fault_is_write(const struct kvm_s2_fault_desc *s2fd) > +{ > + return esr_abt_is_write_fault(s2fd->esr); > +} > + > +static u64 kvm_s2_perm_fault_granule(const struct kvm_s2_fault_desc *s2fd) > +{ > + u64 level; > + > + if (!kvm_s2_fault_is_perm(s2fd)) > + return 0; > + level = esr_fsc_get_level(s2fd->esr); > + return BIT(ARM64_HW_PGTABLE_LEVEL_SHIFT(level)); > +} > + > static int gmem_abort(const struct kvm_s2_fault_desc *s2fd) > { > bool write_fault, exec_fault; > - bool perm_fault = kvm_vcpu_trap_is_permission_fault(s2fd->vcpu); > + const bool perm_fault = kvm_s2_fault_is_perm(s2fd); Please don't randomly introduce const local variables. I understand the benefit, but *if* we want to go down that road, then we do it for all the predicates, as a separate series, because this obviously applies to {exec,write}_fault as well. Thanks, M. -- Without deviation from the norm, progress is not possible.

