From: "Ghimiray, Himal Prasad" <himal.prasad.ghimiray@intel.com>
To: Matthew Brost <matthew.brost@intel.com>
Cc: intel-xe@lists.freedesktop.org,
"Thomas Hellström" <thomas.hellstrom@linux.intel.com>,
"Shuicheng Lin" <shuicheng.lin@intel.com>
Subject: Re: [PATCH v5 10/23] drm/xe: Implement madvise ioctl for xe
Date: Tue, 29 Jul 2025 15:13:29 +0530 [thread overview]
Message-ID: <fbd8a6b6-0802-4698-9e8a-751cdaa87b15@intel.com> (raw)
In-Reply-To: <aIhMtuEl8oBBapGS@lstrano-desk.jf.intel.com>
On 29-07-2025 09:53, Matthew Brost wrote:
> On Mon, Jul 28, 2025 at 08:52:26PM -0700, Matthew Brost wrote:
>> On Tue, Jul 22, 2025 at 07:05:13PM +0530, Himal Prasad Ghimiray wrote:
>>> This driver-specific ioctl enables UMDs to control the memory attributes
>>> for GPU VMAs within a specified input range. If the start or end
>>> addresses fall within an existing VMA, the VMA is split accordingly. The
>>> attributes of the VMA are modified as provided by the users. The old
>>> mappings of the VMAs are invalidated, and TLB invalidation is performed
>>> if necessary.
>>>
>>> v2(Matthew brost)
>>> - xe_vm_in_fault_mode can't be enabled by Mesa, hence allow ioctl in non
>>> fault mode too
>>> - fix tlb invalidation skip for same ranges in multiple op
>>> - use helper for tlb invalidation
>>> - use xe_svm_notifier_lock/unlock helper
>>> - s/lockdep_assert_held/lockdep_assert_held_write
>>> - Add kernel-doc
>>>
>>> v3(Matthew Brost)
>>> - make vfunc fail safe
>>> - Add sanitizing input args before vfunc
>>>
>>> v4(Matthew Brost/Shuicheng)
>>> - Make locks interruptable
>>> - Error handling fixes
>>> - vm_put fixes
>>>
>>> Cc: Matthew Brost <matthew.brost@intel.com>
>>> Cc: Shuicheng Lin <shuicheng.lin@intel.com>
>>> Signed-off-by: Himal Prasad Ghimiray <himal.prasad.ghimiray@intel.com>
>>> ---
>>> drivers/gpu/drm/xe/Makefile | 1 +
>>> drivers/gpu/drm/xe/xe_vm_madvise.c | 306 +++++++++++++++++++++++++++++
>>> drivers/gpu/drm/xe/xe_vm_madvise.h | 15 ++
>>> 3 files changed, 322 insertions(+)
>>> create mode 100644 drivers/gpu/drm/xe/xe_vm_madvise.c
>>> create mode 100644 drivers/gpu/drm/xe/xe_vm_madvise.h
>>>
>>> diff --git a/drivers/gpu/drm/xe/Makefile b/drivers/gpu/drm/xe/Makefile
>>> index 83a36c47a2f9..fa52866bb72c 100644
>>> --- a/drivers/gpu/drm/xe/Makefile
>>> +++ b/drivers/gpu/drm/xe/Makefile
>>> @@ -125,6 +125,7 @@ xe-y += xe_bb.o \
>>> xe_uc.o \
>>> xe_uc_fw.o \
>>> xe_vm.o \
>>> + xe_vm_madvise.o \
>>> xe_vram.o \
>>> xe_vram_freq.o \
>>> xe_vsec.o \
>>> diff --git a/drivers/gpu/drm/xe/xe_vm_madvise.c b/drivers/gpu/drm/xe/xe_vm_madvise.c
>>> new file mode 100644
>>> index 000000000000..f64728120d7c
>>> --- /dev/null
>>> +++ b/drivers/gpu/drm/xe/xe_vm_madvise.c
>>> @@ -0,0 +1,306 @@
>>> +// SPDX-License-Identifier: MIT
>>> +/*
>>> + * Copyright © 2025 Intel Corporation
>>> + */
>>> +
>>> +#include "xe_vm_madvise.h"
>>> +
>>> +#include <linux/nospec.h>
>>> +#include <drm/xe_drm.h>
>>> +
>>> +#include "xe_bo.h"
>>> +#include "xe_pt.h"
>>> +#include "xe_svm.h"
>>> +
>>> +struct xe_vmas_in_madvise_range {
>>> + u64 addr;
>>> + u64 range;
>>> + struct xe_vma **vmas;
>>> + int num_vmas;
>>> + bool has_svm_vmas;
>>> + bool has_bo_vmas;
>>> + bool has_userptr_vmas;
>>> +};
>>> +
>>> +static int get_vmas(struct xe_vm *vm, struct xe_vmas_in_madvise_range *madvise_range)
>>> +{
>>> + u64 addr = madvise_range->addr;
>>> + u64 range = madvise_range->range;
>>> +
>>> + struct xe_vma **__vmas;
>>> + struct drm_gpuva *gpuva;
>>> + int max_vmas = 8;
>>> +
>>> + lockdep_assert_held(&vm->lock);
>>> +
>>> + madvise_range->num_vmas = 0;
>>> + madvise_range->vmas = kmalloc_array(max_vmas, sizeof(*madvise_range->vmas), GFP_KERNEL);
>>> + if (!madvise_range->vmas)
>>> + return -ENOMEM;
>>> +
>>> + vm_dbg(&vm->xe->drm, "VMA's in range: start=0x%016llx, end=0x%016llx", addr, addr + range);
>>> +
>>> + drm_gpuvm_for_each_va_range(gpuva, &vm->gpuvm, addr, addr + range) {
>>> + struct xe_vma *vma = gpuva_to_vma(gpuva);
>>> +
>>> + if (xe_vma_bo(vma))
>>> + madvise_range->has_bo_vmas = true;
>>> + else if (xe_vma_is_cpu_addr_mirror(vma))
>>> + madvise_range->has_svm_vmas = true;
>>> + else if (xe_vma_is_userptr(vma))
>>> + madvise_range->has_userptr_vmas = true;
>>> +
>>> + if (madvise_range->num_vmas == max_vmas) {
>>> + max_vmas <<= 1;
>>> + __vmas = krealloc(madvise_range->vmas,
>>> + max_vmas * sizeof(*madvise_range->vmas),
>>> + GFP_KERNEL);
>>> + if (!__vmas) {
>>> + kfree(madvise_range->vmas);
>>> + return -ENOMEM;
>>> + }
>>> + madvise_range->vmas = __vmas;
>>> + }
>>> +
>>> + madvise_range->vmas[madvise_range->num_vmas] = vma;
>>> + (madvise_range->num_vmas)++;
>>> + }
>>> +
>>> + if (!madvise_range->num_vmas)
>>> + kfree(madvise_range->vmas);
>>> +
>>> + vm_dbg(&vm->xe->drm, "madvise_range-num_vmas = %d\n", madvise_range->num_vmas);
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static void madvise_preferred_mem_loc(struct xe_device *xe, struct xe_vm *vm,
>>> + struct xe_vma **vmas, int num_vmas,
>>> + struct drm_xe_madvise *op)
>>> +{
>>> + /* Implementation pending */
>>> +}
>>> +
>>> +static void madvise_atomic(struct xe_device *xe, struct xe_vm *vm,
>>> + struct xe_vma **vmas, int num_vmas,
>>> + struct drm_xe_madvise *op)
>>> +{
>>> + /* Implementation pending */
>>> +}
>>> +
>>> +static void madvise_pat_index(struct xe_device *xe, struct xe_vm *vm,
>>> + struct xe_vma **vmas, int num_vmas,
>>> + struct drm_xe_madvise *op)
>>> +{
>>> + /* Implementation pending */
>>> +}
>>> +
>>> +typedef void (*madvise_func)(struct xe_device *xe, struct xe_vm *vm,
>>> + struct xe_vma **vmas, int num_vmas,
>>> + struct drm_xe_madvise *op);
>>> +
>>> +static const madvise_func madvise_funcs[] = {
>>> + [DRM_XE_MEM_RANGE_ATTR_PREFERRED_LOC] = madvise_preferred_mem_loc,
>>> + [DRM_XE_MEM_RANGE_ATTR_ATOMIC] = madvise_atomic,
>>> + [DRM_XE_MEM_RANGE_ATTR_PAT] = madvise_pat_index,
>>> +};
>>> +
>>> +static u8 xe_zap_ptes_in_madvise_range(struct xe_vm *vm, u64 start, u64 end)
>>> +{
>>> + struct drm_gpuva *gpuva;
>>> + struct xe_tile *tile;
>>> + u8 id, tile_mask;
>>> +
>>> + lockdep_assert_held_write(&vm->lock);
>>> +
>>> + /* Wait for pending binds */
>>> + if (dma_resv_wait_timeout(xe_vm_resv(vm), DMA_RESV_USAGE_BOOKKEEP,
>>> + false, MAX_SCHEDULE_TIMEOUT) <= 0)
>>> + XE_WARN_ON(1);
>>> +
>>> + tile_mask = xe_svm_ranges_zap_ptes_in_range(vm, start, end);
>>> +
>>> + drm_gpuvm_for_each_va_range(gpuva, &vm->gpuvm, start, end) {
>>> + struct xe_vma *vma = gpuva_to_vma(gpuva);
>>> +
>>> + if (xe_vma_is_cpu_addr_mirror(vma))
>>
>> I think:
>>
>> xe_vma_is_cpu_addr_mirror(vma) || xe_vma_is_null(vma)
>>
>> No need to invalidate NULL VMA's as those mappings are not modified by
>> madvise (i.e., you can call madvise on NULL mappings but it doesn't
>> actually do anything).
>>
>>> + continue;
>>> +
>>> + for_each_tile(tile, vm->xe, id) {
>>> + if (xe_pt_zap_ptes(tile, vma)) {
>>> + tile_mask |= BIT(id);
>>> +
>>> + /*
>>> + * WRITE_ONCE pairs with READ_ONCE
>>> + * in xe_vm_has_valid_gpu_mapping()
>>> + */
>>> + WRITE_ONCE(vma->tile_invalidated,
>>> + vma->tile_invalidated | BIT(id));
>>> + }
>>> + }
>>> + }
>>> +
>>> + return tile_mask;
>>> +}
>>> +
>>> +static int xe_vm_invalidate_madvise_range(struct xe_vm *vm, u64 start, u64 end)
>>> +{
>>> + u8 tile_mask = xe_zap_ptes_in_madvise_range(vm, start, end);
>>> +
>>> + if (!tile_mask)
>>> + return 0;
>>> +
>>> + xe_device_wmb(vm->xe);
>>> +
>>> + return xe_vm_range_tilemask_tlb_invalidation(vm, start, end, tile_mask);
>>> +}
>>> +
>>> +static bool madvise_args_are_sane(struct xe_device *xe, const struct drm_xe_madvise *args)
>>> +{
>>> + if (XE_IOCTL_DBG(xe, !args))
>>> + return false;
>>> +
>>> + if (XE_IOCTL_DBG(xe, !IS_ALIGNED(args->start, SZ_4K)))
>>> + return false;
>>> +
>>> + if (XE_IOCTL_DBG(xe, !IS_ALIGNED(args->range, SZ_4K)))
>>> + return false;
>>> +
>>> + if (XE_IOCTL_DBG(xe, args->range < SZ_4K))
>>> + return false;
>>> +
>>> + switch (args->type) {
>>> + case DRM_XE_MEM_RANGE_ATTR_PREFERRED_LOC:
>>> + if (XE_IOCTL_DBG(xe, args->preferred_mem_loc.migration_policy >
>>> + DRM_XE_MIGRATE_ONLY_SYSTEM_PAGES))
>>> + return false;
>>> +
>>> + if (XE_IOCTL_DBG(xe, args->preferred_mem_loc.pad))
>>> + return false;
>>> +
>>> + if (XE_IOCTL_DBG(xe, args->atomic.reserved))
>>> + return false;
>>> + break;
>>> + case DRM_XE_MEM_RANGE_ATTR_ATOMIC:
>>> + if (XE_IOCTL_DBG(xe, args->atomic.val > DRM_XE_ATOMIC_CPU))
>>> + return false;
>>> +
>>> + if (XE_IOCTL_DBG(xe, args->atomic.pad))
>>> + return false;
>>> +
>>> + if (XE_IOCTL_DBG(xe, args->atomic.reserved))
>>> + return false;
>>> +
>>> + break;
>>> + case DRM_XE_MEM_RANGE_ATTR_PAT:
>>> + /*TODO: Add valid pat check */
>>> + break;
>>> + default:
>>> + if (XE_IOCTL_DBG(xe, 1))
>>> + return false;
>>> + }
>>> +
>>> + if (XE_IOCTL_DBG(xe, args->reserved[0] || args->reserved[1]))
>>> + return false;
>>> +
>>> + return true;
>>> +}
>>> +
>>> +/**
>>> + * xe_vm_madvise_ioctl - Handle MADVise ioctl for a VM
>>> + * @dev: DRM device pointer
>>> + * @data: Pointer to ioctl data (drm_xe_madvise*)
>>> + * @file: DRM file pointer
>>> + *
>>> + * Handles the MADVISE ioctl to provide memory advice for vma's within
>>> + * input range.
>>> + *
>>> + * Return: 0 on success or a negative error code on failure.
>>> + */
>>> +int xe_vm_madvise_ioctl(struct drm_device *dev, void *data, struct drm_file *file)
>>> +{
>>> + struct xe_device *xe = to_xe_device(dev);
>>> + struct xe_file *xef = to_xe_file(file);
>>> + struct drm_xe_madvise *args = data;
>>> + struct xe_vmas_in_madvise_range madvise_range = {.addr = args->start,
>>> + .range = args->range, };
>>> + struct xe_vm *vm;
>>> + struct drm_exec exec;
>>> + int err, attr_type;
>>> +
>>> + vm = xe_vm_lookup(xef, args->vm_id);
>>> + if (XE_IOCTL_DBG(xe, !vm))
>>> + return -EINVAL;
>>> +
>>> + if (!madvise_args_are_sane(vm->xe, args)) {
>>> + err = -EINVAL;
>>> + goto put_vm;
>>> + }
>>> +
>>
>> I think as this code can modify the ranges during a VMA split, you will
>> need to ensure all queued unmaps prior to this are complete.
>>
>
> The explaination is wrong, but it is still needed. You need a flush of
> garbage collector because it can modify VMAs and we need that view to be
> current. Feel free add this in this patch or patch 20.
Will add to this patch.
Thanks
>
> Matt
>
>> So call xe_svm_flush(vm) prior to taking any locks.
>>
>> Looks good otherwise.
>>
>> Matt
>>
>>> + err = down_write_killable(&vm->lock);
>>> + if (err)
>>> + goto put_vm;
>>> +
>>> + if (XE_IOCTL_DBG(xe, xe_vm_is_closed_or_banned(vm))) {
>>> + err = -ENOENT;
>>> + goto unlock_vm;
>>> + }
>>> +
>>> + err = xe_vm_alloc_madvise_vma(vm, args->start, args->range);
>>> + if (err)
>>> + goto unlock_vm;
>>> +
>>> + err = get_vmas(vm, &madvise_range);
>>> + if (err || !madvise_range.num_vmas)
>>> + goto unlock_vm;
>>> +
>>> + if (madvise_range.has_bo_vmas) {
>>> + drm_exec_init(&exec, DRM_EXEC_IGNORE_DUPLICATES | DRM_EXEC_INTERRUPTIBLE_WAIT, 0);
>>> + drm_exec_until_all_locked(&exec) {
>>> + for (int i = 0; i < madvise_range.num_vmas; i++) {
>>> + struct xe_bo *bo = xe_vma_bo(madvise_range.vmas[i]);
>>> +
>>> + if (!bo)
>>> + continue;
>>> + err = drm_exec_lock_obj(&exec, &bo->ttm.base);
>>> + drm_exec_retry_on_contention(&exec);
>>> + if (err)
>>> + goto err_fini;
>>> + }
>>> + }
>>> + }
>>> +
>>> + if (madvise_range.has_userptr_vmas) {
>>> + err = down_read_interruptible(&vm->userptr.notifier_lock);
>>> + if (err)
>>> + goto err_fini;
>>> + }
>>> +
>>> + if (madvise_range.has_svm_vmas) {
>>> + err = down_read_interruptible(&vm->svm.gpusvm.notifier_lock);
>>> + if (err)
>>> + goto unlock_userptr;
>>> + }
>>> +
>>> + attr_type = array_index_nospec(args->type, ARRAY_SIZE(madvise_funcs));
>>> + madvise_funcs[attr_type](xe, vm, madvise_range.vmas, madvise_range.num_vmas, args);
>>> +
>>> + err = xe_vm_invalidate_madvise_range(vm, args->start, args->start + args->range);
>>> +
>>> + if (madvise_range.has_svm_vmas)
>>> + xe_svm_notifier_unlock(vm);
>>> +
>>> +unlock_userptr:
>>> + if (madvise_range.has_userptr_vmas)
>>> + up_read(&vm->userptr.notifier_lock);
>>> +err_fini:
>>> + if (madvise_range.has_bo_vmas)
>>> + drm_exec_fini(&exec);
>>> + kfree(madvise_range.vmas);
>>> + madvise_range.vmas = NULL;
>>> +unlock_vm:
>>> + up_write(&vm->lock);
>>> +put_vm:
>>> + xe_vm_put(vm);
>>> + return err;
>>> +}
>>> diff --git a/drivers/gpu/drm/xe/xe_vm_madvise.h b/drivers/gpu/drm/xe/xe_vm_madvise.h
>>> new file mode 100644
>>> index 000000000000..b0e1fc445f23
>>> --- /dev/null
>>> +++ b/drivers/gpu/drm/xe/xe_vm_madvise.h
>>> @@ -0,0 +1,15 @@
>>> +/* SPDX-License-Identifier: MIT */
>>> +/*
>>> + * Copyright © 2025 Intel Corporation
>>> + */
>>> +
>>> +#ifndef _XE_VM_MADVISE_H_
>>> +#define _XE_VM_MADVISE_H_
>>> +
>>> +struct drm_device;
>>> +struct drm_file;
>>> +
>>> +int xe_vm_madvise_ioctl(struct drm_device *dev, void *data,
>>> + struct drm_file *file);
>>> +
>>> +#endif
>>> --
>>> 2.34.1
>>>
next prev parent reply other threads:[~2025-07-29 9:43 UTC|newest]
Thread overview: 55+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-07-22 13:35 [PATCH v5 00/23] MADVISE FOR XE Himal Prasad Ghimiray
2025-07-22 13:35 ` [PATCH v5 01/23] Introduce drm_gpuvm_sm_map_ops_flags enums for sm_map_ops Himal Prasad Ghimiray
2025-07-22 13:38 ` Danilo Krummrich
2025-07-24 0:43 ` Matthew Brost
2025-07-24 10:05 ` Ghimiray, Himal Prasad
2025-07-24 10:32 ` Caterina Shablia
2025-07-28 10:20 ` Ghimiray, Himal Prasad
2025-07-24 10:02 ` Ghimiray, Himal Prasad
2025-07-27 21:18 ` Matthew Brost
2025-07-28 6:16 ` Ghimiray, Himal Prasad
2025-07-22 13:35 ` [PATCH v5 02/23] drm/xe/uapi: Add madvise interface Himal Prasad Ghimiray
2025-07-29 3:29 ` Matthew Brost
2025-07-22 13:35 ` [PATCH v5 03/23] drm/xe/vm: Add attributes struct as member of vma Himal Prasad Ghimiray
2025-07-22 13:35 ` [PATCH v5 04/23] drm/xe/vma: Move pat_index to vma attributes Himal Prasad Ghimiray
2025-07-22 13:35 ` [PATCH v5 05/23] drm/xe/vma: Modify new_vma to accept struct xe_vma_mem_attr as parameter Himal Prasad Ghimiray
2025-07-22 13:35 ` [PATCH v5 06/23] drm/gpusvm: Make drm_gpusvm_for_each_* macros public Himal Prasad Ghimiray
2025-07-22 13:35 ` [PATCH v5 07/23] drm/xe/svm: Split system allocator vma incase of madvise call Himal Prasad Ghimiray
2025-07-22 13:35 ` [PATCH v5 08/23] drm/xe: Allow CPU address mirror VMA unbind with gpu bindings for madvise Himal Prasad Ghimiray
2025-07-29 3:40 ` Matthew Brost
2025-07-29 7:42 ` Ghimiray, Himal Prasad
2025-07-22 13:35 ` [PATCH v5 09/23] drm/xe/svm: Add xe_svm_ranges_zap_ptes_in_range() for PTE zapping Himal Prasad Ghimiray
2025-07-29 3:42 ` Matthew Brost
2025-07-22 13:35 ` [PATCH v5 10/23] drm/xe: Implement madvise ioctl for xe Himal Prasad Ghimiray
2025-07-29 3:52 ` Matthew Brost
2025-07-29 4:23 ` Matthew Brost
2025-07-29 9:43 ` Ghimiray, Himal Prasad [this message]
2025-07-22 13:35 ` [PATCH v5 11/23] drm/xe/svm : Add svm ranges migration policy on atomic access Himal Prasad Ghimiray
2025-07-29 4:04 ` Matthew Brost
2025-07-30 4:59 ` Ghimiray, Himal Prasad
2025-07-22 13:35 ` [PATCH v5 12/23] drm/xe/madvise: Update migration policy based on preferred location Himal Prasad Ghimiray
2025-07-29 4:07 ` Matthew Brost
2025-07-22 13:35 ` [PATCH v5 13/23] drm/xe/svm: Support DRM_XE_SVM_ATTR_PAT memory attribute Himal Prasad Ghimiray
2025-07-23 16:55 ` Ghimiray, Himal Prasad
2025-07-22 13:35 ` [PATCH v5 14/23] drm/xe/uapi: Add flag for consulting madvise hints on svm prefetch Himal Prasad Ghimiray
2025-07-22 13:35 ` [PATCH v5 15/23] drm/xe/svm: Consult madvise preferred location in prefetch Himal Prasad Ghimiray
2025-07-22 13:35 ` [PATCH v5 16/23] drm/xe/bo: Add attributes field to xe_bo Himal Prasad Ghimiray
2025-07-22 13:35 ` [PATCH v5 17/23] drm/xe/bo: Update atomic_access attribute on madvise Himal Prasad Ghimiray
2025-07-29 4:18 ` Matthew Brost
2025-07-22 13:35 ` [PATCH v5 18/23] drm/xe/madvise: Skip vma invalidation if mem attr are unchanged Himal Prasad Ghimiray
2025-07-29 4:19 ` Matthew Brost
2025-07-22 13:35 ` [PATCH v5 19/23] drm/xe/vm: Add helper to check for default VMA memory attributes Himal Prasad Ghimiray
2025-07-29 4:33 ` Matthew Brost
2025-07-22 13:35 ` [PATCH v5 20/23] drm/xe: Reset VMA attributes to default in SVM garbage collector Himal Prasad Ghimiray
2025-07-24 21:50 ` Matthew Brost
2025-07-29 5:27 ` Matthew Brost
2025-07-30 6:09 ` Ghimiray, Himal Prasad
2025-07-29 5:41 ` Matthew Brost
2025-07-30 6:06 ` Ghimiray, Himal Prasad
2025-07-22 13:35 ` [PATCH v5 21/23] drm/xe/vm: Add a delayed worker to merge fragmented vmas Himal Prasad Ghimiray
2025-07-29 4:39 ` Matthew Brost
2025-07-30 11:08 ` Ghimiray, Himal Prasad
2025-07-22 13:35 ` [PATCH v5 22/23] drm/xe: Enable madvise ioctl for xe Himal Prasad Ghimiray
2025-07-29 4:34 ` Matthew Brost
2025-07-22 13:35 ` [PATCH v5 23/23] drm/xe/uapi: Add UAPI for querying VMA count and memory attributes Himal Prasad Ghimiray
2025-07-29 5:37 ` Matthew Brost
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=fbd8a6b6-0802-4698-9e8a-751cdaa87b15@intel.com \
--to=himal.prasad.ghimiray@intel.com \
--cc=intel-xe@lists.freedesktop.org \
--cc=matthew.brost@intel.com \
--cc=shuicheng.lin@intel.com \
--cc=thomas.hellstrom@linux.intel.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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.