From: Claire DuSoleil <claire.m.dusoleil@gmail.com>
To: Zack Rusin <zack.rusin@broadcom.com>
Cc: "Broadcom internal kernel review list"
<bcm-kernel-feedback-list@broadcom.com>,
dri-devel@lists.freedesktop.org, linux-kernel@vger.kernel.org,
"Maarten Lankhorst" <maarten.lankhorst@linux.intel.com>,
"Maxime Ripard" <mripard@kernel.org>,
"Thomas Zimmermann" <tzimmermann@suse.de>,
"David Airlie" <airlied@gmail.com>,
"Simona Vetter" <simona@ffwll.ch>,
"Sumit Semwal" <sumit.semwal@linaro.org>,
"Christian König" <christian.koenig@amd.com>,
linux-media@vger.kernel.org, linaro-mm-sig@lists.linaro.org,
"Claire DuSoleil" <claire.m.dusoleil@gmail.com>
Subject: [PATCH] drm/vmwgfx: Fix GEM_CLOSE returning -EINVAL for PRIME-imported surfaces
Date: Thu, 17 Sep 2026 11:13:12 -0700 [thread overview]
Message-ID: <20260917181312.12473-1-claire.m.dusoleil@gmail.com> (raw)
Importing a client's GPU buffer via the generic
DRM_IOCTL_PRIME_FD_TO_HANDLE ioctl succeeds for surface-backed dma-bufs,
but releasing the resulting handle via the equally-generic
DRM_IOCTL_GEM_CLOSE fails with -EINVAL.
vmw_prime_fd_to_handle() imports through ttm_prime_fd_to_handle(), which
files the handle in the driver's private ttm_object table (tdev->idr).
DRM_IOCTL_GEM_CLOSE only ever looks in the standard per-file GEM handle
table (file_priv->object_idr) -- there is no per-driver override for it
anywhere in struct drm_driver -- so it can never find a handle from the
private table and always returns -EINVAL.
This breaks any generic userspace that assumes PRIME_FD_TO_HANDLE and
GEM_CLOSE operate as a matched pair on the same table -- true for every
other driver, but not this one. Wayland compositors that validate a
client's dmabuf with a driver-agnostic import-then-close probe (e.g.
Hyprland's linux-dmabuf-v1 create_immed validation) treat the close
failure as "the buffer is invalid" and kill the client, even though the
buffer is fine.
ttm_prime_fd_to_handle() cannot simply be replaced with the generic
drm_gem_prime_fd_to_handle() path: vmw_prime_dmabuf_ops leaves .attach
and .map_dma_buf stubbed with -ENOSYS (surface storage lives host-side),
so the generic import path can never succeed for these buffers at all --
ttm_prime_fd_to_handle() is the only mechanism capable of importing a
surface-backed dma-buf.
Fix this by wrapping the private handle in a minimal, non-TTM-backed GEM
object registered in the standard per-file handle table, so ordinary
GEM_CLOSE finds it and releases the real reference through it.
Existing userspace (Mesa's own SVGA winsys, vmw_drm_surface_from_handle()
in vmw_screen_dri.c) also calls this ioctl for real rendering imports,
then feeds the returned value back into DRM_VMW_REF_SURFACE and
DRM_VMW_UNREF_SURFACE as a raw ttm handle -- so vmw_prime_fd_to_handle()
must keep returning something that still works as a raw ttm handle for
those two ioctls, and for every execbuf command that references a
surface by handle. vmw_prime_resolve_handle() is added to transparently
redirect a bridge handle back to the real ttm handle, and called from
every function that consumes a raw surface handle from userspace:
vmw_surface_handle_reference(), vmw_surface_destroy_ioctl(), and
vmw_user_resource_lookup_handle() -- the shared choke point behind
every execbuf command-validation call site, plus vmw_present_ioctl(),
vmw_user_object_lookup(), and vmw_dumb_create(). This keeps all of that
existing usage unaffected.
The bridge takes its own reference directly on the underlying
ttm_base_object (ttm_base_object_lookup_for_ref()/ttm_base_object_unref()),
independent of the tfile-scoped ttm_ref_object entry that
REF_SURFACE/UNREF_SURFACE manipulate, so its lifetime does not depend on
how many times userspace opens and closes its own references to the
same handle. The transient ttm_ref_object entry that
ttm_prime_fd_to_handle() itself creates is deliberately left in place
on success, matching stock behavior: vmw_surface_handle_reference()
forces require_exist=true for render clients, and ttm_ref_object_add()
with require_existed=true only reuses a pre-existing entry rather than
creating one, so removing it here would make every subsequent
DRM_VMW_REF_SURFACE call from a render client (both Mesa and this
validation pattern connect via the render node) fail with -EPERM.
Before this change, every object filed in file_priv->object_idr was
guaranteed to be a real struct vmw_bo, so vmw_user_bo_lookup() casts
the GEM object behind a looked-up handle straight to struct vmw_bo via
an unchecked container_of() (to_vmw_bo()). This is no longer true once
a bridge object can occupy a slot in the same table: passing a bridge
handle to any of vmw_user_bo_lookup()'s ten call sites (buffer/context/
shader binding, DRM_IOCTL_PRIME_HANDLE_TO_FD's small-handle branch,
etc.) would reinterpret a small vmw_prime_import_bridge allocation as
the much larger struct vmw_bo, causing an out-of-bounds read on every
subsequent field access. Fixed by rejecting a non-vmw_bo handle in
vmw_user_bo_lookup() itself: reject any handle whose gobj->funcs isn't
&vmw_gem_object_funcs, the same driver-established GEM object type tag
already used at both of struct vmw_bo's own creation sites, so this
introduces no new tagging mechanism and cannot reject a legitimate
vmw_bo.
Tested on a VMware Workstation guest, kernel 7.2.3-arch1-3 and, after a
later kernel update, 7.2.5-3-omarchy: a standalone
PRIME_FD_TO_HANDLE/GEM_CLOSE reproducer confirms the fix, real Hyprland
now compositing GPU-rendered clients that previously died on their
first frame, and the kernel log stays clean across driver init and
normal use, with no rejections logged from the new vmw_user_bo_lookup()
check. All seven changed files were also confirmed byte-for-byte
identical to drm-misc-next, and this patch applies to it directly with
no fuzz.
Signed-off-by: Claire DuSoleil <claire.m.dusoleil@gmail.com>
---
drivers/gpu/drm/vmwgfx/vmwgfx_bo.c | 13 ++
drivers/gpu/drm/vmwgfx/vmwgfx_drv.h | 3 +
drivers/gpu/drm/vmwgfx/vmwgfx_execbuf.c | 5 +-
drivers/gpu/drm/vmwgfx/vmwgfx_ioctl.c | 2 +-
drivers/gpu/drm/vmwgfx/vmwgfx_prime.c | 147 ++++++++++++++++++++++-
drivers/gpu/drm/vmwgfx/vmwgfx_resource.c | 4 +-
drivers/gpu/drm/vmwgfx/vmwgfx_surface.c | 13 +-
7 files changed, 178 insertions(+), 9 deletions(-)
diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_bo.c b/drivers/gpu/drm/vmwgfx/vmwgfx_bo.c
index 9c7a73c..9d87f19 100644
--- a/drivers/gpu/drm/vmwgfx/vmwgfx_bo.c
+++ b/drivers/gpu/drm/vmwgfx/vmwgfx_bo.c
@@ -651,6 +651,19 @@ int vmw_user_bo_lookup(struct drm_file *filp,
return -ESRCH;
}
+ /*
+ * A handle in the standard GEM table is not necessarily a real
+ * vmw_bo -- it may be a prime-import bridge object (see
+ * vmwgfx_prime.c). to_vmw_bo() below is an unchecked container_of;
+ * calling it on anything else corrupts an out-of-bounds pointer.
+ */
+ if (gobj->funcs != &vmw_gem_object_funcs) {
+ drm_gem_object_put(gobj);
+ DRM_ERROR("Handle 0x%08lx is not a vmwgfx buffer object.\n",
+ (unsigned long)handle);
+ return -ESRCH;
+ }
+
*out = to_vmw_bo(gobj);
return 0;
diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_drv.h b/drivers/gpu/drm/vmwgfx/vmwgfx_drv.h
index 38bea8a..bf8a453 100644
--- a/drivers/gpu/drm/vmwgfx/vmwgfx_drv.h
+++ b/drivers/gpu/drm/vmwgfx/vmwgfx_drv.h
@@ -788,6 +788,7 @@ extern int vmw_resource_reserve(struct vmw_resource *res, bool interruptible,
extern bool vmw_resource_needs_backup(const struct vmw_resource *res);
extern int vmw_user_resource_lookup_handle(
struct vmw_private *dev_priv,
+ struct drm_file *file_priv,
struct ttm_object_file *tfile,
uint32_t handle,
const struct vmw_user_resource_conv *converter,
@@ -1105,6 +1106,8 @@ extern int vmw_prime_handle_to_fd(struct drm_device *dev,
struct drm_file *file_priv,
uint32_t handle, uint32_t flags,
int *prime_fd);
+extern uint32_t vmw_prime_resolve_handle(struct drm_file *file_priv,
+ uint32_t handle);
struct drm_gem_object *vmw_prime_import_sg_table(struct drm_device *dev,
struct dma_buf_attachment *attach,
struct sg_table *table);
diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_execbuf.c b/drivers/gpu/drm/vmwgfx/vmwgfx_execbuf.c
index a9136a6..7383319 100644
--- a/drivers/gpu/drm/vmwgfx/vmwgfx_execbuf.c
+++ b/drivers/gpu/drm/vmwgfx/vmwgfx_execbuf.c
@@ -648,7 +648,8 @@ vmw_cmd_res_check(struct vmw_private *dev_priv,
return ret;
ret = vmw_user_resource_lookup_handle
- (dev_priv, sw_context->fp->tfile, *id_loc, converter, &res);
+ (dev_priv, sw_context->filp, sw_context->fp->tfile, *id_loc,
+ converter, &res);
if (ret != 0) {
VMW_DEBUG_USER("Could not find/use resource 0x%08x.\n",
(unsigned int) *id_loc);
@@ -4073,7 +4074,7 @@ static int vmw_execbuf_tie_context(struct vmw_private *dev_priv,
return ret;
ret = vmw_user_resource_lookup_handle
- (dev_priv, sw_context->fp->tfile, handle,
+ (dev_priv, sw_context->filp, sw_context->fp->tfile, handle,
user_context_converter, &res);
if (ret != 0) {
VMW_DEBUG_USER("Could not find or user DX context 0x%08x.\n",
diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_ioctl.c b/drivers/gpu/drm/vmwgfx/vmwgfx_ioctl.c
index d962ef2..1494a8d 100644
--- a/drivers/gpu/drm/vmwgfx/vmwgfx_ioctl.c
+++ b/drivers/gpu/drm/vmwgfx/vmwgfx_ioctl.c
@@ -220,7 +220,7 @@ int vmw_present_ioctl(struct drm_device *dev, void *data,
}
vfb = vmw_framebuffer_to_vfb(fb);
- ret = vmw_user_resource_lookup_handle(dev_priv, tfile, arg->sid,
+ ret = vmw_user_resource_lookup_handle(dev_priv, file_priv, tfile, arg->sid,
user_surface_converter,
&res);
if (ret)
diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_prime.c b/drivers/gpu/drm/vmwgfx/vmwgfx_prime.c
index 598b90a..5315251 100644
--- a/drivers/gpu/drm/vmwgfx/vmwgfx_prime.c
+++ b/drivers/gpu/drm/vmwgfx/vmwgfx_prime.c
@@ -35,6 +35,7 @@
#include "vmwgfx_bo.h"
#include "ttm_object.h"
#include <linux/dma-buf.h>
+#include <linux/slab.h>
/*
* DMA-BUF attach- and mapping methods. No need to implement
@@ -72,15 +73,157 @@ const struct dma_buf_ops vmw_prime_dmabuf_ops = {
.release = NULL,
};
+/*
+ * A surface-backed dma-buf can only be imported through
+ * ttm_prime_fd_to_handle() -- vmw_prime_dmabuf_ops leaves .attach and
+ * .map_dma_buf unimplemented above, so the generic PRIME import path
+ * (drm_gem_prime_fd_to_handle() -> dma_buf_attach()) can never reach it and
+ * fails with -ENOSYS before it starts. ttm_prime_fd_to_handle() works
+ * because it bypasses dma-buf attach/map entirely and reads dma_buf->priv
+ * directly, but the handle it returns lives in the private ttm_object
+ * table (tdev->idr), not file_priv->object_idr, so the generic
+ * DRM_IOCTL_GEM_CLOSE (drm_gem_handle_delete(), which only ever looks in
+ * file_priv->object_idr) can never find it and fails with -EINVAL.
+ *
+ * Bridge the two tables: wrap the real ttm_base_object in a minimal,
+ * non-TTM-backed GEM object and hand back a handle from the standard
+ * table instead, so ordinary GEM_CLOSE succeeds.
+ *
+ * Existing userspace (Mesa's own SVGA winsys, in vmw_drm_surface_from_handle())
+ * calls this exact ioctl for real rendering imports too, then feeds the
+ * returned value straight back into DRM_VMW_REF_SURFACE / DRM_VMW_UNREF_SURFACE
+ * as a raw ttm handle -- it is not just a probe-only code path. So the value
+ * returned here must keep working as a raw ttm handle for those two ioctls
+ * and for every execbuf command that references a surface by handle
+ * (vmw_user_resource_lookup_handle(), the single choke point all of those
+ * funnel through). vmw_prime_resolve_handle() below is called from all three
+ * of those places to transparently redirect a bridge handle back to the real
+ * ttm handle, so existing userspace keeps working unmodified.
+ *
+ * The bridge's own hold on the object is a *separate*, independent
+ * ttm_base_object reference (taken via ttm_base_object_lookup_for_ref(),
+ * released via ttm_base_object_unref()), not a claim on the tfile-scoped
+ * ttm_ref_object entry that REF_SURFACE/UNREF_SURFACE manipulate. That
+ * entry is created transiently by ttm_prime_fd_to_handle() below and is
+ * deliberately left alone on success -- see the comment at the end of
+ * vmw_prime_fd_to_handle() for why it cannot simply be dropped here
+ * (vmw_surface_handle_reference()'s require_exist path depends on it
+ * still existing). This bridge's own reference exists alongside it,
+ * purely so this bridge's lifetime never depends on how many times
+ * userspace itself opens and closes references to the same ttm handle:
+ * without it, if userspace fully released the ttm handle through its own
+ * REF/UNREF_SURFACE calls while this GEM handle was still open, this
+ * bridge's eventual .free() would unref a handle number that may by then
+ * have been recycled for a completely unrelated object.
+ */
+struct vmw_prime_import_bridge {
+ struct drm_gem_object base;
+ struct ttm_base_object *base_obj;
+ uint32_t ttm_handle;
+};
+
+static void vmw_prime_import_bridge_free(struct drm_gem_object *obj)
+{
+ struct vmw_prime_import_bridge *bridge =
+ container_of(obj, struct vmw_prime_import_bridge, base);
+
+ ttm_base_object_unref(&bridge->base_obj);
+ drm_gem_object_release(obj);
+ kfree(bridge);
+}
+
+static const struct drm_gem_object_funcs vmw_prime_import_bridge_funcs = {
+ .free = vmw_prime_import_bridge_free,
+};
+
+/**
+ * vmw_prime_resolve_handle - Translate a possible prime-import bridge GEM
+ * handle back to the real ttm handle it wraps.
+ *
+ * @file_priv: The caller's drm file, whose own GEM handle table is checked.
+ * @handle: A handle as supplied by userspace -- either an ordinary raw ttm
+ * handle (the common case, unchanged from historical behavior), or a
+ * bridge handle returned by vmw_prime_fd_to_handle() above.
+ *
+ * Returns the real ttm handle to use. If @handle does not name one of this
+ * file's own prime-import bridge objects, @handle is returned unchanged.
+ */
+uint32_t vmw_prime_resolve_handle(struct drm_file *file_priv, uint32_t handle)
+{
+ struct drm_gem_object *gobj = drm_gem_object_lookup(file_priv, handle);
+ uint32_t real_handle = handle;
+
+ if (gobj) {
+ if (gobj->funcs == &vmw_prime_import_bridge_funcs) {
+ struct vmw_prime_import_bridge *bridge =
+ container_of(gobj, struct vmw_prime_import_bridge, base);
+ real_handle = bridge->ttm_handle;
+ }
+ drm_gem_object_put(gobj);
+ }
+
+ return real_handle;
+}
+
int vmw_prime_fd_to_handle(struct drm_device *dev,
struct drm_file *file_priv,
int fd, u32 *handle)
{
+ struct vmw_private *dev_priv = vmw_priv(dev);
struct ttm_object_file *tfile = vmw_fpriv(file_priv)->tfile;
- int ret = ttm_prime_fd_to_handle(tfile, fd, handle);
+ struct vmw_prime_import_bridge *bridge;
+ struct ttm_base_object *base_obj;
+ uint32_t ttm_handle;
+ int ret = ttm_prime_fd_to_handle(tfile, fd, &ttm_handle);
if (ret)
- ret = drm_gem_prime_fd_to_handle(dev, file_priv, fd, handle);
+ return drm_gem_prime_fd_to_handle(dev, file_priv, fd, handle);
+
+ /*
+ * Take our own independent reference before dropping the transient
+ * one ttm_prime_fd_to_handle() just created, so the object can never
+ * be dropped to zero in between.
+ */
+ base_obj = ttm_base_object_lookup_for_ref(dev_priv->tdev, ttm_handle);
+ if (!base_obj) {
+ ttm_ref_object_base_unref(tfile, ttm_handle);
+ return -EINVAL;
+ }
+
+ bridge = kzalloc(sizeof(*bridge), GFP_KERNEL);
+ if (!bridge) {
+ ttm_base_object_unref(&base_obj);
+ ttm_ref_object_base_unref(tfile, ttm_handle);
+ return -ENOMEM;
+ }
+
+ drm_gem_private_object_init(dev, &bridge->base, PAGE_SIZE);
+ bridge->base.funcs = &vmw_prime_import_bridge_funcs;
+ bridge->base_obj = base_obj;
+ bridge->ttm_handle = ttm_handle;
+
+ ret = drm_gem_handle_create(file_priv, &bridge->base, handle);
+ drm_gem_object_put(&bridge->base);
+
+ /*
+ * On success, deliberately leave the transient ttm_ref_object entry
+ * ttm_prime_fd_to_handle() created in place -- do not touch it.
+ * vmw_surface_handle_reference()'s DRM_VMW_REF_SURFACE path forces
+ * require_exist=true for render clients (drm_is_render_client()),
+ * which is exactly what Mesa and this bridge's own callers are, and
+ * ttm_ref_object_add() with require_existed=true returns -EPERM
+ * unless a ref-object entry for this (tfile, ttm_handle) pair
+ * already exists -- it will not create a new one. This entry is
+ * that pre-existing one. Stock vmw_prime_fd_to_handle() never
+ * touched it either, for the same reason. Our own independent
+ * base_obj reference above is what makes GEM_CLOSE work; it does
+ * not replace this entry, it exists alongside it.
+ *
+ * On failure, nothing else will ever be able to reach this handle to
+ * release it, so clean it up here to avoid leaking it.
+ */
+ if (ret)
+ ttm_ref_object_base_unref(tfile, ttm_handle);
return ret;
}
diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_resource.c b/drivers/gpu/drm/vmwgfx/vmwgfx_resource.c
index e3a187a..7ba4dbf 100644
--- a/drivers/gpu/drm/vmwgfx/vmwgfx_resource.c
+++ b/drivers/gpu/drm/vmwgfx/vmwgfx_resource.c
@@ -255,6 +255,7 @@ int vmw_resource_init(struct vmw_private *dev_priv, struct vmw_resource *res,
* type, -EINVAL will be returned.
*/
int vmw_user_resource_lookup_handle(struct vmw_private *dev_priv,
+ struct drm_file *file_priv,
struct ttm_object_file *tfile,
uint32_t handle,
const struct vmw_user_resource_conv
@@ -265,6 +266,7 @@ int vmw_user_resource_lookup_handle(struct vmw_private *dev_priv,
struct vmw_resource *res;
int ret = -EINVAL;
+ handle = vmw_prime_resolve_handle(file_priv, handle);
base = ttm_base_object_lookup(tfile, handle);
if (unlikely(!base))
return -EINVAL;
@@ -300,7 +302,7 @@ int vmw_user_object_lookup(struct vmw_private *dev_priv,
WARN_ON(uo->surface || uo->buffer);
- ret = vmw_user_resource_lookup_handle(dev_priv, tfile, handle,
+ ret = vmw_user_resource_lookup_handle(dev_priv, filp, tfile, handle,
user_surface_converter,
&res);
if (!ret) {
diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_surface.c b/drivers/gpu/drm/vmwgfx/vmwgfx_surface.c
index bd05637..5aabb36 100644
--- a/drivers/gpu/drm/vmwgfx/vmwgfx_surface.c
+++ b/drivers/gpu/drm/vmwgfx/vmwgfx_surface.c
@@ -689,8 +689,9 @@ int vmw_surface_destroy_ioctl(struct drm_device *dev, void *data,
{
struct drm_vmw_surface_arg *arg = (struct drm_vmw_surface_arg *)data;
struct ttm_object_file *tfile = vmw_fpriv(file_priv)->tfile;
+ uint32_t sid = vmw_prime_resolve_handle(file_priv, arg->sid);
- return ttm_ref_object_base_unref(tfile, arg->sid);
+ return ttm_ref_object_base_unref(tfile, sid);
}
/**
@@ -999,7 +1000,13 @@ vmw_surface_handle_reference(struct vmw_private *dev_priv,
&handle,
base_p);
} else {
- handle = u_handle;
+ /*
+ * u_handle may be a raw ttm handle (the historical case), or
+ * a prime-import bridge GEM handle returned by an earlier
+ * generic PRIME_FD_TO_HANDLE call (see vmwgfx_prime.c) --
+ * resolve it back to the real ttm handle either way.
+ */
+ handle = vmw_prime_resolve_handle(file_priv, u_handle);
}
ret = -EINVAL;
@@ -2321,7 +2328,7 @@ int vmw_dumb_create(struct drm_file *file_priv,
args->size = arg.rep.buffer_size;
args->pitch = vmw_surface_calculate_pitch(desc, &drm_size);
- ret = vmw_user_resource_lookup_handle(dev_priv, tfile, arg.rep.handle,
+ ret = vmw_user_resource_lookup_handle(dev_priv, file_priv, tfile, arg.rep.handle,
user_surface_converter,
&res);
if (ret) {
next reply other threads:[~2026-09-17 18:13 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-17 18:13 Claire DuSoleil [this message]
2026-09-17 20:19 ` Zack Rusin
2026-09-18 11:26 ` kernel test robot
2026-09-18 20:56 ` Maaz Mombasawala
[not found] ` <CAEcBvW0+SqM2zHbaDS+JycXxc-ztzvixKtHVSVQby9viVH=+eA@mail.gmail.com>
2026-09-18 21:23 ` Claire DuSoleil
2026-09-18 23:36 ` Maaz Mombasawala
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=20260917181312.12473-1-claire.m.dusoleil@gmail.com \
--to=claire.m.dusoleil@gmail.com \
--cc=airlied@gmail.com \
--cc=bcm-kernel-feedback-list@broadcom.com \
--cc=christian.koenig@amd.com \
--cc=dri-devel@lists.freedesktop.org \
--cc=linaro-mm-sig@lists.linaro.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-media@vger.kernel.org \
--cc=maarten.lankhorst@linux.intel.com \
--cc=mripard@kernel.org \
--cc=simona@ffwll.ch \
--cc=sumit.semwal@linaro.org \
--cc=tzimmermann@suse.de \
--cc=zack.rusin@broadcom.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®