From: Marc Zyngier <maz@kernel.org>
To: "Lorenzo Stoakes (ARM)" <ljs@kernel.org>
Cc: Catalin Marinas <catalin.marinas@arm.com>,
Will Deacon <will@kernel.org>, Oliver Upton <oupton@kernel.org>,
Fuad Tabba <tabba@google.com>, Joey Gouly <joey.gouly@arm.com>,
Steffen Eiden <seiden@linux.ibm.com>,
Suzuki K Poulose <suzuki.poulose@arm.com>,
Zenghui Yu <yuzenghui@huawei.com>,
Paolo Bonzini <pbonzini@redhat.com>,
Jonathan Corbet <corbet@lwn.net>,
linux-arm-kernel@lists.infradead.org,
linux-kernel@vger.kernel.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org, linux-doc@vger.kernel.org,
linux-kselftest@vger.kernel.org,
Jack Thomson <jackabt@amazon.com>,
Jack Thomson <jackabt.amazon@gmail.com>,
Alexandru Elisei <alexandru.elisei@arm.com>,
Vincent Donnefort <vdonnefort@google.com>,
"Aneesh Kumar K.V" <aneesh.kumar@kernel.org>,
Sean Christopherson <seanjc@google.com>,
Claudio Imbrenda <imbrenda@linux.ibm.com>,
Leo Soares Passos <Leo.Bras@arm.com>
Subject: Re: [PATCH 1/8] KVM: arm64: Propagate and use esr in s2fd when handling guest aborts
Date: Thu, 10 Sep 2026 09:39:58 +0100 [thread overview]
Message-ID: <86wlst7b4x.wl-maz@kernel.org> (raw)
In-Reply-To: <20260825-kvm-arm-prefault-v1-1-befe8947702e@kernel.org>
On Tue, 25 Aug 2026 17:00:35 +0100,
"Lorenzo Stoakes (ARM)" <ljs@kernel.org> 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) <ljs@kernel.org>
> ---
> 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.
next prev parent reply other threads:[~2026-09-10 8:40 UTC|newest]
Thread overview: 24+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-25 16:00 [PATCH 0/8] KVM: arm64: Add KVM_PRE_FAULT_MEMORY support Lorenzo Stoakes (ARM)
2026-08-25 16:00 ` [PATCH 1/8] KVM: arm64: Propagate and use esr in s2fd when handling guest aborts Lorenzo Stoakes (ARM)
2026-09-10 8:39 ` Marc Zyngier [this message]
2026-09-10 9:04 ` Lorenzo Stoakes (ARM)
2026-08-25 16:00 ` [PATCH 2/8] KVM: arm64: Propagate and use mmu " Lorenzo Stoakes (ARM)
2026-08-25 16:00 ` [PATCH 3/8] KVM: arm64: Propagate and use kvm_s2_fault_result on S2 fault Lorenzo Stoakes (ARM)
2026-09-10 8:49 ` Marc Zyngier
2026-09-10 9:00 ` Lorenzo Stoakes (ARM)
2026-08-25 16:00 ` [PATCH 4/8] KVM: arm64: Pass walk flags to kvm_pgtable_get_leaf() Lorenzo Stoakes (ARM)
2026-08-25 16:00 ` [PATCH 5/8] KVM: arm64: Implement KVM_PRE_FAULT_MEMORY Lorenzo Stoakes (ARM)
2026-09-10 10:02 ` Marc Zyngier
2026-09-10 15:32 ` Lorenzo Stoakes (ARM)
2026-08-25 16:00 ` [PATCH 6/8] KVM: selftests: Enable pre_fault_memory_test for arm64 Lorenzo Stoakes (ARM)
2026-09-10 18:52 ` Fuad Tabba
2026-08-25 16:00 ` [PATCH 7/8] KVM: selftests: Add option for different backing in pre-fault tests Lorenzo Stoakes (ARM)
2026-08-25 16:00 ` [PATCH 8/8] KVM: selftests: Add nested pre-fault test for arm64 Lorenzo Stoakes (ARM)
2026-09-10 18:57 ` Fuad Tabba
2026-09-11 9:21 ` Lorenzo Stoakes (ARM)
2026-09-11 6:30 ` Itaru Kitayama
2026-09-11 9:32 ` Lorenzo Stoakes (ARM)
2026-09-11 12:42 ` Wei-Lin Chang
2026-09-11 15:44 ` Lorenzo Stoakes (ARM)
2026-09-10 18:44 ` [PATCH 0/8] KVM: arm64: Add KVM_PRE_FAULT_MEMORY support Fuad Tabba
2026-09-11 9:16 ` Lorenzo Stoakes (ARM)
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=86wlst7b4x.wl-maz@kernel.org \
--to=maz@kernel.org \
--cc=Leo.Bras@arm.com \
--cc=alexandru.elisei@arm.com \
--cc=aneesh.kumar@kernel.org \
--cc=catalin.marinas@arm.com \
--cc=corbet@lwn.net \
--cc=imbrenda@linux.ibm.com \
--cc=jackabt.amazon@gmail.com \
--cc=jackabt@amazon.com \
--cc=joey.gouly@arm.com \
--cc=kvm@vger.kernel.org \
--cc=kvmarm@lists.linux.dev \
--cc=linux-arm-kernel@lists.infradead.org \
--cc=linux-doc@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-kselftest@vger.kernel.org \
--cc=ljs@kernel.org \
--cc=oupton@kernel.org \
--cc=pbonzini@redhat.com \
--cc=seanjc@google.com \
--cc=seiden@linux.ibm.com \
--cc=suzuki.poulose@arm.com \
--cc=tabba@google.com \
--cc=vdonnefort@google.com \
--cc=will@kernel.org \
--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
all inboxes | Powered by JetHome®