From: Dave Jiang <dave.jiang@intel.com>
To: shiju.jose@huawei.com, linux-cxl@vger.kernel.org,
dan.j.williams@intel.com, jonathan.cameron@huawei.com,
dave@stgolabs.net, alison.schofield@intel.com,
vishal.l.verma@intel.com, ira.weiny@intel.com
Cc: linux-edac@vger.kernel.org, linux-doc@vger.kernel.org,
bp@alien8.de, tony.luck@intel.com, lenb@kernel.org,
leo.duran@amd.com, Yazen.Ghannam@amd.com, mchehab@kernel.org,
nifan.cxl@gmail.com, linuxarm@huawei.com, tanxiaofei@huawei.com,
prime.zeng@hisilicon.com, roberto.sassu@huawei.com,
kangkang.shen@futurewei.com, wanghuiqiang@huawei.com
Subject: Re: [PATCH v3 6/8] cxl/edac: Support for finding memory operation attributes from the current boot
Date: Mon, 28 Apr 2025 13:53:40 -0700 [thread overview]
Message-ID: <72fd82fa-a604-467e-906f-04d050e5ce8e@intel.com> (raw)
In-Reply-To: <20250407174920.625-7-shiju.jose@huawei.com>
On 4/7/25 10:49 AM, shiju.jose@huawei.com wrote:
> From: Shiju Jose <shiju.jose@huawei.com>
>
> Certain operations on memory, such as memory repair, are permitted
> only when the address and other attributes for the operation are
> from the current boot. This is determined by checking whether the
> memory attributes for the operation match those in the CXL gen_media
> or CXL DRAM memory event records reported during the current boot.
>
> The CXL event records must be backed up because they are cleared
> in the hardware after being processed by the kernel.
>
> Support is added for storing CXL gen_media or CXL DRAM memory event
> records in xarrays. Old records are deleted when they expire or when
> there is an overflow and which depends on platform correctly report
> Event Record Timestamp field of CXL spec Table 8-55 Common Event
> Record Format.
>
> Additionally, helper functions are implemented to find a matching
> record in the xarray storage based on the memory attributes and
> repair type.
>
> Add validity check, when matching attributes for sparing, using
> the validity flag in the DRAM event record, to ensure that all
> required attributes for a requested repair operation are valid and
> set.
>
> Co-developed-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
> Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
> Signed-off-by: Shiju Jose <shiju.jose@huawei.com>
Reviewed-by: Dave Jiang <dave.jiang@intel.com>
> ---
> drivers/cxl/Kconfig | 4 +
> drivers/cxl/core/edac.c | 307 ++++++++++++++++++++++++++++++++++++++
> drivers/cxl/core/mbox.c | 11 +-
> drivers/cxl/core/memdev.c | 1 +
> drivers/cxl/cxlmem.h | 19 +++
> 5 files changed, 340 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/cxl/Kconfig b/drivers/cxl/Kconfig
> index 2333f7c0b6db..48b7314afdb8 100644
> --- a/drivers/cxl/Kconfig
> +++ b/drivers/cxl/Kconfig
> @@ -173,6 +173,10 @@ config CXL_EDAC_MEM_REPAIR
> to control the memory repair features (e.g. sparing, PPR)
> configurations of CXL memory expander devices.
>
> + When enabled, the memory repair feature requires an additional
> + memory of approximately 43KB to store CXL DRAM and CXL general
> + media event records.
> +
> When enabled 'cxl_mem' EDAC devices are published with memory
> repair control attributes as described by
> Documentation/ABI/testing/sysfs-edac-memory-repair.
> diff --git a/drivers/cxl/core/edac.c b/drivers/cxl/core/edac.c
> index 246a02785f1d..02bd4c675871 100644
> --- a/drivers/cxl/core/edac.c
> +++ b/drivers/cxl/core/edac.c
> @@ -14,10 +14,12 @@
> #include <linux/cleanup.h>
> #include <linux/edac.h>
> #include <linux/limits.h>
> +#include <linux/xarray.h>
> #include <cxl/features.h>
> #include <cxl.h>
> #include <cxlmem.h>
> #include "core.h"
> +#include "trace.h"
>
> #define CXL_NR_EDAC_DEV_FEATURES 2
>
> @@ -840,6 +842,280 @@ static int cxl_perform_maintenance(struct cxl_mailbox *cxl_mbox, u8 class,
>
> return cxl_internal_send_cmd(cxl_mbox, &mbox_cmd);
> }
> +
> +/*
> + * Support for finding a memory operation attributes
> + * are from the current boot or not.
> + */
> +
> +struct cxl_mem_err_rec {
> + struct xarray rec_gen_media;
> + struct xarray rec_dram;
> +};
> +
> +enum cxl_mem_repair_type {
> + CXL_PPR,
> + CXL_CACHELINE_SPARING,
> + CXL_ROW_SPARING,
> + CXL_BANK_SPARING,
> + CXL_RANK_SPARING,
> + CXL_REPAIR_MAX,
> +};
> +
> +/**
> + * struct cxl_mem_repair_attrbs - CXL memory repair attributes
> + * @dpa: DPA of memory to repair
> + * @nibble_mask: nibble mask, identifies one or more nibbles on the memory bus
> + * @row: row of memory to repair
> + * @column: column of memory to repair
> + * @channel: channel of memory to repair
> + * @sub_channel: sub channel of memory to repair
> + * @rank: rank of memory to repair
> + * @bank_group: bank group of memory to repair
> + * @bank: bank of memory to repair
> + * @repair_type: repair type. For eg. PPR, memory sparing etc.
> + */
> +struct cxl_mem_repair_attrbs {
> + u64 dpa;
> + u32 nibble_mask;
> + u32 row;
> + u16 column;
> + u8 channel;
> + u8 sub_channel;
> + u8 rank;
> + u8 bank_group;
> + u8 bank;
> + enum cxl_mem_repair_type repair_type;
> +};
> +
> +static struct cxl_event_gen_media *
> +cxl_find_rec_gen_media(struct cxl_memdev *cxlmd,
> + struct cxl_mem_repair_attrbs *attrbs)
> +{
> + struct cxl_mem_err_rec *array_rec = cxlmd->array_err_rec;
> + struct cxl_event_gen_media *rec;
> +
> + if (!array_rec)
> + return NULL;
> +
> + rec = xa_load(&array_rec->rec_gen_media, attrbs->dpa);
> + if (!rec)
> + return NULL;
> +
> + if (attrbs->repair_type == CXL_PPR)
> + return rec;
> +
> + return NULL;
> +}
> +
> +static struct cxl_event_dram *
> +cxl_find_rec_dram(struct cxl_memdev *cxlmd,
> + struct cxl_mem_repair_attrbs *attrbs)
> +{
> + struct cxl_mem_err_rec *array_rec = cxlmd->array_err_rec;
> + struct cxl_event_dram *rec;
> + u16 validity_flags;
> +
> + if (!array_rec)
> + return NULL;
> +
> + rec = xa_load(&array_rec->rec_dram, attrbs->dpa);
> + if (!rec)
> + return NULL;
> +
> + validity_flags = get_unaligned_le16(rec->media_hdr.validity_flags);
> + if (!(validity_flags & CXL_DER_VALID_CHANNEL) ||
> + !(validity_flags & CXL_DER_VALID_RANK))
> + return NULL;
> +
> + switch (attrbs->repair_type) {
> + case CXL_PPR:
> + if (!(validity_flags & CXL_DER_VALID_NIBBLE) ||
> + get_unaligned_le24(rec->nibble_mask) == attrbs->nibble_mask)
> + return rec;
> + break;
> + case CXL_CACHELINE_SPARING:
> + if (!(validity_flags & CXL_DER_VALID_BANK_GROUP) ||
> + !(validity_flags & CXL_DER_VALID_BANK) ||
> + !(validity_flags & CXL_DER_VALID_ROW) ||
> + !(validity_flags & CXL_DER_VALID_COLUMN))
> + return NULL;
> +
> + if (rec->media_hdr.channel == attrbs->channel &&
> + rec->media_hdr.rank == attrbs->rank &&
> + rec->bank_group == attrbs->bank_group &&
> + rec->bank == attrbs->bank &&
> + get_unaligned_le24(rec->row) == attrbs->row &&
> + get_unaligned_le16(rec->column) == attrbs->column &&
> + (!(validity_flags & CXL_DER_VALID_NIBBLE) ||
> + get_unaligned_le24(rec->nibble_mask) ==
> + attrbs->nibble_mask) &&
> + (!(validity_flags & CXL_DER_VALID_SUB_CHANNEL) ||
> + rec->sub_channel == attrbs->sub_channel))
> + return rec;
> + break;
> + case CXL_ROW_SPARING:
> + if (!(validity_flags & CXL_DER_VALID_BANK_GROUP) ||
> + !(validity_flags & CXL_DER_VALID_BANK) ||
> + !(validity_flags & CXL_DER_VALID_ROW))
> + return NULL;
> +
> + if (rec->media_hdr.channel == attrbs->channel &&
> + rec->media_hdr.rank == attrbs->rank &&
> + rec->bank_group == attrbs->bank_group &&
> + rec->bank == attrbs->bank &&
> + get_unaligned_le24(rec->row) == attrbs->row &&
> + (!(validity_flags & CXL_DER_VALID_NIBBLE) ||
> + get_unaligned_le24(rec->nibble_mask) ==
> + attrbs->nibble_mask))
> + return rec;
> + break;
> + case CXL_BANK_SPARING:
> + if (!(validity_flags & CXL_DER_VALID_BANK_GROUP) ||
> + !(validity_flags & CXL_DER_VALID_BANK))
> + return NULL;
> +
> + if (rec->media_hdr.channel == attrbs->channel &&
> + rec->media_hdr.rank == attrbs->rank &&
> + rec->bank_group == attrbs->bank_group &&
> + rec->bank == attrbs->bank &&
> + (!(validity_flags & CXL_DER_VALID_NIBBLE) ||
> + get_unaligned_le24(rec->nibble_mask) ==
> + attrbs->nibble_mask))
> + return rec;
> + break;
> + case CXL_RANK_SPARING:
> + if (rec->media_hdr.channel == attrbs->channel &&
> + rec->media_hdr.rank == attrbs->rank &&
> + (!(validity_flags & CXL_DER_VALID_NIBBLE) ||
> + get_unaligned_le24(rec->nibble_mask) ==
> + attrbs->nibble_mask))
> + return rec;
> + break;
> + default:
> + return NULL;
> + }
> +
> + return NULL;
> +}
> +
> +#define CXL_MAX_STORAGE_DAYS 10
> +#define CXL_MAX_STORAGE_TIME_SECS (CXL_MAX_STORAGE_DAYS * 24 * 60 * 60)
> +
> +static void cxl_del_expired_gmedia_recs(struct xarray *rec_xarray,
> + struct cxl_event_gen_media *cur_rec)
> +{
> + u64 cur_ts = le64_to_cpu(cur_rec->media_hdr.hdr.timestamp);
> + struct cxl_event_gen_media *rec;
> + unsigned long index;
> + u64 delta_ts_secs;
> +
> + xa_for_each(rec_xarray, index, rec) {
> + delta_ts_secs = (cur_ts -
> + le64_to_cpu(rec->media_hdr.hdr.timestamp)) / 1000000000ULL;
> + if (delta_ts_secs >= CXL_MAX_STORAGE_TIME_SECS) {
> + xa_erase(rec_xarray, index);
> + kfree(rec);
> + }
> + }
> +}
> +
> +static void cxl_del_expired_dram_recs(struct xarray *rec_xarray,
> + struct cxl_event_dram *cur_rec)
> +{
> + u64 cur_ts = le64_to_cpu(cur_rec->media_hdr.hdr.timestamp);
> + struct cxl_event_dram *rec;
> + unsigned long index;
> + u64 delta_secs;
> +
> + xa_for_each(rec_xarray, index, rec) {
> + delta_secs = (cur_ts -
> + le64_to_cpu(rec->media_hdr.hdr.timestamp)) / 1000000000ULL;
> + if (delta_secs >= CXL_MAX_STORAGE_TIME_SECS) {
> + xa_erase(rec_xarray, index);
> + kfree(rec);
> + }
> + }
> +}
> +
> +#define CXL_MAX_REC_STORAGE_COUNT 200
> +
> +static void cxl_del_overflow_old_recs(struct xarray *rec_xarray)
> +{
> + void *err_rec;
> + unsigned long index, count = 0;
> +
> + xa_for_each(rec_xarray, index, err_rec)
> + count++;
> +
> + if (count <= CXL_MAX_REC_STORAGE_COUNT)
> + return;
> +
> + count -= CXL_MAX_REC_STORAGE_COUNT;
> + xa_for_each(rec_xarray, index, err_rec) {
> + xa_erase(rec_xarray, index);
> + kfree(err_rec);
> + count--;
> + if (!count)
> + break;
> + }
> +}
> +
> +int cxl_store_rec_gen_media(struct cxl_memdev *cxlmd, union cxl_event *evt)
> +{
> + struct cxl_mem_err_rec *array_rec = cxlmd->array_err_rec;
> + struct cxl_event_gen_media *rec;
> + void *old_rec;
> +
> + if (!array_rec)
> + return 0;
> +
> + rec = kmemdup(&evt->gen_media, sizeof(*rec), GFP_KERNEL);
> + if (!rec)
> + return -ENOMEM;
> +
> + old_rec = xa_store(&array_rec->rec_gen_media,
> + le64_to_cpu(rec->media_hdr.phys_addr), rec,
> + GFP_KERNEL);
> + if (xa_is_err(old_rec))
> + return xa_err(old_rec);
> +
> + kfree(old_rec);
> +
> + cxl_del_expired_gmedia_recs(&array_rec->rec_gen_media, rec);
> + cxl_del_overflow_old_recs(&array_rec->rec_gen_media);
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_NS_GPL(cxl_store_rec_gen_media, "CXL");
> +
> +int cxl_store_rec_dram(struct cxl_memdev *cxlmd, union cxl_event *evt)
> +{
> + struct cxl_mem_err_rec *array_rec = cxlmd->array_err_rec;
> + struct cxl_event_dram *rec;
> + void *old_rec;
> +
> + if (!array_rec)
> + return 0;
> +
> + rec = kmemdup(&evt->dram, sizeof(*rec), GFP_KERNEL);
> + if (!rec)
> + return -ENOMEM;
> +
> + old_rec = xa_store(&array_rec->rec_dram,
> + le64_to_cpu(rec->media_hdr.phys_addr), rec,
> + GFP_KERNEL);
> + if (xa_is_err(old_rec))
> + return xa_err(old_rec);
> +
> + kfree(old_rec);
> +
> + cxl_del_expired_dram_recs(&array_rec->rec_dram, rec);
> + cxl_del_overflow_old_recs(&array_rec->rec_dram);
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_NS_GPL(cxl_store_rec_dram, "CXL");
> #endif /* CONFIG_CXL_EDAC_MEM_REPAIR */
>
> int devm_cxl_memdev_edac_register(struct cxl_memdev *cxlmd)
> @@ -868,6 +1144,16 @@ int devm_cxl_memdev_edac_register(struct cxl_memdev *cxlmd)
> num_ras_features++;
> #endif
>
> +#ifdef CONFIG_CXL_EDAC_MEM_REPAIR
> + struct cxl_mem_err_rec *array_rec =
> + devm_kzalloc(&cxlmd->dev, sizeof(*array_rec), GFP_KERNEL);
> + if (!array_rec)
> + return -ENOMEM;
> +
> + cxlmd->array_err_rec = array_rec;
> + xa_init(&array_rec->rec_gen_media);
> + xa_init(&array_rec->rec_dram);
> +#endif
> char *cxl_dev_name __free(kfree) =
> kasprintf(GFP_KERNEL, "cxl_%s", dev_name(&cxlmd->dev));
> if (!cxl_dev_name)
> @@ -903,3 +1189,24 @@ int devm_cxl_region_edac_register(struct cxl_region *cxlr)
> #endif
> }
> EXPORT_SYMBOL_NS_GPL(devm_cxl_region_edac_register, "CXL");
> +
> +void devm_cxl_memdev_edac_release(struct cxl_memdev *cxlmd)
> +{
> +#ifdef CONFIG_CXL_EDAC_MEM_REPAIR
> + struct cxl_mem_err_rec *array_rec = cxlmd->array_err_rec;
> + struct cxl_event_gen_media *rec_gen_media;
> + struct cxl_event_dram *rec_dram;
> + unsigned long index;
> +
> + if (!array_rec)
> + return;
> +
> + xa_for_each(&array_rec->rec_dram, index, rec_dram)
> + kfree(rec_dram);
> + xa_destroy(&array_rec->rec_dram);
> + xa_for_each(&array_rec->rec_gen_media, index, rec_gen_media)
> + kfree(rec_gen_media);
> + xa_destroy(&array_rec->rec_gen_media);
> +#endif
> +}
> +EXPORT_SYMBOL_NS_GPL(devm_cxl_memdev_edac_release, "CXL");
> diff --git a/drivers/cxl/core/mbox.c b/drivers/cxl/core/mbox.c
> index d72764056ce6..2689e6453c5a 100644
> --- a/drivers/cxl/core/mbox.c
> +++ b/drivers/cxl/core/mbox.c
> @@ -922,12 +922,19 @@ void cxl_event_trace_record(const struct cxl_memdev *cxlmd,
> hpa_alias = hpa - cache_size;
> }
>
> - if (event_type == CXL_CPER_EVENT_GEN_MEDIA)
> + if (event_type == CXL_CPER_EVENT_GEN_MEDIA) {
> + if (cxl_store_rec_gen_media((struct cxl_memdev *)cxlmd, evt))
> + dev_dbg(&cxlmd->dev, "CXL store rec_gen_media failed\n");
> +
> trace_cxl_general_media(cxlmd, type, cxlr, hpa,
> hpa_alias, &evt->gen_media);
> - else if (event_type == CXL_CPER_EVENT_DRAM)
> + } else if (event_type == CXL_CPER_EVENT_DRAM) {
> + if (cxl_store_rec_dram((struct cxl_memdev *)cxlmd, evt))
> + dev_dbg(&cxlmd->dev, "CXL store rec_dram failed\n");
> +
> trace_cxl_dram(cxlmd, type, cxlr, hpa, hpa_alias,
> &evt->dram);
> + }
> }
> }
> EXPORT_SYMBOL_NS_GPL(cxl_event_trace_record, "CXL");
> diff --git a/drivers/cxl/core/memdev.c b/drivers/cxl/core/memdev.c
> index a16a5886d40a..953d8407d0dd 100644
> --- a/drivers/cxl/core/memdev.c
> +++ b/drivers/cxl/core/memdev.c
> @@ -27,6 +27,7 @@ static void cxl_memdev_release(struct device *dev)
> struct cxl_memdev *cxlmd = to_cxl_memdev(dev);
>
> ida_free(&cxl_memdev_ida, cxlmd->id);
> + devm_cxl_memdev_edac_release(cxlmd);
> kfree(cxlmd);
> }
>
> diff --git a/drivers/cxl/cxlmem.h b/drivers/cxl/cxlmem.h
> index 1b9bf6b42521..6f808809f7c0 100644
> --- a/drivers/cxl/cxlmem.h
> +++ b/drivers/cxl/cxlmem.h
> @@ -47,6 +47,9 @@
> * @depth: endpoint port depth
> * @cur_scrub_cycle: current scrub cycle set for this device
> * @cur_region_id: id number of a backed region (if any) for which current scrub cycle set
> + * @array_err_rec: List of xarrarys to store the memdev error records to
> + * check attributes for a memory repair operation are from
> + * current boot.
> */
> struct cxl_memdev {
> struct device dev;
> @@ -62,6 +65,7 @@ struct cxl_memdev {
> u8 cur_scrub_cycle;
> int cur_region_id;
> #endif
> + void *array_err_rec;
> };
>
> static inline struct cxl_memdev *to_cxl_memdev(struct device *dev)
> @@ -863,11 +867,26 @@ int cxl_clear_poison(struct cxl_memdev *cxlmd, u64 dpa);
> #if IS_ENABLED(CONFIG_CXL_EDAC_MEM_FEATURES)
> int devm_cxl_memdev_edac_register(struct cxl_memdev *cxlmd);
> int devm_cxl_region_edac_register(struct cxl_region *cxlr);
> +void devm_cxl_memdev_edac_release(struct cxl_memdev *cxlmd);
> #else
> static inline int devm_cxl_memdev_edac_register(struct cxl_memdev *cxlmd)
> { return 0; }
> static inline int devm_cxl_region_edac_register(struct cxl_region *cxlr)
> { return 0; }
> +static inline void devm_cxl_memdev_edac_release(struct cxl_memdev *cxlmd)
> +{ return; }
> +#endif
> +
> +#ifdef CONFIG_CXL_EDAC_MEM_REPAIR
> +int cxl_store_rec_gen_media(struct cxl_memdev *cxlmd, union cxl_event *evt);
> +int cxl_store_rec_dram(struct cxl_memdev *cxlmd, union cxl_event *evt);
> +#else
> +static inline int cxl_store_rec_gen_media(struct cxl_memdev *cxlmd,
> + union cxl_event *evt)
> +{ return 0; }
> +static inline int cxl_store_rec_dram(struct cxl_memdev *cxlmd,
> + union cxl_event *evt)
> +{ return 0; }
> #endif
>
> #ifdef CONFIG_CXL_SUSPEND
next prev parent reply other threads:[~2025-04-28 20:53 UTC|newest]
Thread overview: 15+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-04-07 17:49 [PATCH v3 0/8] cxl: support CXL memory RAS features shiju.jose
2025-04-07 17:49 ` [PATCH v3 1/8] EDAC: Update documentation for the CXL memory patrol scrub control feature shiju.jose
2025-04-28 17:45 ` Dave Jiang
2025-05-01 9:22 ` Shiju Jose
2025-04-07 17:49 ` [PATCH v3 2/8] cxl: Update prototype of function get_support_feature_info() shiju.jose
2025-04-28 18:22 ` Dave Jiang
2025-04-07 17:49 ` [PATCH v3 3/8] cxl/edac: Add CXL memory device patrol scrub control feature shiju.jose
2025-04-07 17:49 ` [PATCH v3 4/8] cxl/edac: Add CXL memory device ECS " shiju.jose
2025-04-28 20:14 ` Dave Jiang
2025-04-07 17:49 ` [PATCH v3 5/8] cxl/edac: Add support for PERFORM_MAINTENANCE command shiju.jose
2025-04-07 17:49 ` [PATCH v3 6/8] cxl/edac: Support for finding memory operation attributes from the current boot shiju.jose
2025-04-28 20:53 ` Dave Jiang [this message]
2025-04-07 17:49 ` [PATCH v3 7/8] cxl/edac: Add CXL memory device memory sparing control feature shiju.jose
2025-04-28 21:46 ` Dave Jiang
2025-04-07 17:49 ` [PATCH v3 8/8] cxl/edac: Add CXL memory device soft PPR " shiju.jose
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=72fd82fa-a604-467e-906f-04d050e5ce8e@intel.com \
--to=dave.jiang@intel.com \
--cc=Yazen.Ghannam@amd.com \
--cc=alison.schofield@intel.com \
--cc=bp@alien8.de \
--cc=dan.j.williams@intel.com \
--cc=dave@stgolabs.net \
--cc=ira.weiny@intel.com \
--cc=jonathan.cameron@huawei.com \
--cc=kangkang.shen@futurewei.com \
--cc=lenb@kernel.org \
--cc=leo.duran@amd.com \
--cc=linux-cxl@vger.kernel.org \
--cc=linux-doc@vger.kernel.org \
--cc=linux-edac@vger.kernel.org \
--cc=linuxarm@huawei.com \
--cc=mchehab@kernel.org \
--cc=nifan.cxl@gmail.com \
--cc=prime.zeng@hisilicon.com \
--cc=roberto.sassu@huawei.com \
--cc=shiju.jose@huawei.com \
--cc=tanxiaofei@huawei.com \
--cc=tony.luck@intel.com \
--cc=vishal.l.verma@intel.com \
--cc=wanghuiqiang@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