mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Youngjun Park <youngjun.park@lge.com>
To: akpm@linux-foundation.org
Cc: chrisl@kernel.org, youngjun.park@lge.com, linux-mm@kvack.org,
	cgroups@vger.kernel.org, linux-kernel@vger.kernel.org,
	kasong@tencent.com, hannes@cmpxchg.org, mhocko@kernel.org,
	roman.gushchin@linux.dev, shakeel.butt@linux.dev,
	muchun.song@linux.dev, shikemeng@huaweicloud.com,
	baoquan.he@linux.dev, baohua@kernel.org, yosry@kernel.org,
	joshua.hahnjy@gmail.com, taejoon.song@lge.com,
	her0gyugyu@gmail.com, lianux.mm@gmail.com
Subject: [RFC PATCH v11 3/4] mm: swap: add a debugfs interface for memcg tier selection
Date: Thu, 17 Sep 2026 03:34:36 +0900	[thread overview]
Message-ID: <20260916183437.2946306-4-youngjun.park@lge.com> (raw)
In-Reply-To: <20260916183437.2946306-1-youngjun.park@lge.com>

Swap tiers do nothing until something chooses between them. Let a memory
cgroup do that.

Each cgroup carries a mask of the tiers it may swap to. Write a cgroup
path and a hex mask to /sys/kernel/debug/swap/memcg_tiers to set it. Bit
i is the tier at index i. /sys/kernel/debug/swap/tiers lists each tier's
index and priority.

  # echo "/batch 0x2" > /sys/kernel/debug/swap/memcg_tiers

This lives in debugfs on purpose. A cgroup file is a permanent ABI, and
what a swap tier should look like is not settled yet. It is built only
with CONFIG_MEMCG and CONFIG_DEBUG_FS.

The masks are kept in a list keyed by cgroup ID rather than in struct
mem_cgroup, so memcg itself is not changed. A mask applies to the memory
charged to its cgroup and is not inherited by child cgroups. The entry of
a removed cgroup is dropped on the next write.

Each swap device is stamped with its tier's bit at swapon. A device's
tier never changes, so the stamp is written once and read locklessly by
the allocator.

When the last device of a tier is swapped off, the tier frees its index
for reuse. It waits until swapoff can no longer fail, so a device whose
swapoff fails goes back to the same index. The freed bit is set back in
every cgroup mask, so a cgroup that had disabled that tier must disable
it again once a new tier reuses the index.

Assisted-by: Claude:claude-opus-5
Signed-off-by: Youngjun Park <youngjun.park@lge.com>
---
 Documentation/mm/index.rst     |   1 +
 Documentation/mm/swap-tier.rst |  55 ++++++
 MAINTAINERS                    |   1 +
 include/linux/swap.h           |   1 +
 mm/swap_tier.c                 | 294 ++++++++++++++++++++++++++++++++-
 mm/swap_tier.h                 |  31 ++++
 mm/swapfile.c                  |   5 +
 7 files changed, 387 insertions(+), 1 deletion(-)
 create mode 100644 Documentation/mm/swap-tier.rst

diff --git a/Documentation/mm/index.rst b/Documentation/mm/index.rst
index 13a79f5d092c..6afc45cd4b3d 100644
--- a/Documentation/mm/index.rst
+++ b/Documentation/mm/index.rst
@@ -34,6 +34,7 @@ see the :doc:`admin guide <../admin-guide/mm/index>`.
    page_reclaim
    swap
    swap-table
+   swap-tier
    page_cache
    shmfs
    oom
diff --git a/Documentation/mm/swap-tier.rst b/Documentation/mm/swap-tier.rst
new file mode 100644
index 000000000000..4007c23f83a6
--- /dev/null
+++ b/Documentation/mm/swap-tier.rst
@@ -0,0 +1,55 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+:Author: Chris Li <chrisl@kernel.org>,
+         Youngjun Park <youngjun.park@lge.com>
+
+==========
+Swap Tier
+==========
+
+Swap tier is a group of swap devices that share a priority. It acts as a
+facilitation layer, allowing users to manage swap devices based on their
+speeds.
+
+Users are encouraged to assign swap device priorities according to device
+speed to fully utilize this feature.
+
+Tier Index
+----------
+
+A tier is created when the first swap device with its priority is swapped on,
+and removed when the last one is swapped off. Each tier is given an index when
+it is created and keeps it until it is removed, so a tier's index does not
+change when another priority is swapped on or off.
+
+Per-cgroup Tier Selection
+-------------------------
+
+A memory cgroup can be limited to some tiers through debugfs. This is for
+evaluation, not a stable ABI.
+
+``/sys/kernel/debug/swap/tiers`` lists the index and priority of each tier.
+``/sys/kernel/debug/swap/memcg_tiers`` takes a cgroup path and a mask in hex,
+where bit ``i`` allows the tier at index ``i``::
+
+    # cat /sys/kernel/debug/swap/tiers
+    Idx   Prio
+    0     100
+    1     50
+    # echo "/batch 0x2" > /sys/kernel/debug/swap/memcg_tiers
+
+There is no separate delete operation. Writing a mask that allows every tier
+clears the restriction, so the cgroup drops out of the file::
+
+    # echo "/batch 0xffffffff" > /sys/kernel/debug/swap/memcg_tiers
+
+A cgroup's mask is also dropped when the cgroup is removed.
+
+A mask applies to the memory charged to its own cgroup and is not inherited by
+child cgroups. A tier keeps its index for its lifetime, so the same mask keeps
+selecting the same tier across a swapon or swapoff.
+
+When a tier's last device is swapped off, its index is freed and can be reused
+by a later tier. The freed index is re-allowed in every cgroup mask, so a
+cgroup that had disabled it must disable it again once a new tier reuses the
+index.
diff --git a/MAINTAINERS b/MAINTAINERS
index 37f353015cae..af17b804f31b 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -17402,6 +17402,7 @@ L:	linux-mm@kvack.org
 S:	Maintained
 F:	Documentation/ABI/testing/sysfs-kernel-mm-swap
 F:	Documentation/mm/swap-table.rst
+F:	Documentation/mm/swap-tier.rst
 F:	include/linux/swap.h
 F:	include/linux/swap_ops.h
 F:	include/linux/swapfile.h
diff --git a/include/linux/swap.h b/include/linux/swap.h
index df69c2dd434a..30d3c37ca530 100644
--- a/include/linux/swap.h
+++ b/include/linux/swap.h
@@ -237,6 +237,7 @@ struct swap_info_struct {
 	struct percpu_ref users;	/* indicate and keep swap device valid. */
 	unsigned long	flags;		/* SWP_USED etc: see above */
 	signed short	prio;		/* swap priority of this type */
+	unsigned int	tier_mask;	/* swap tier mask */
 	struct plist_node list;		/* entry in its swap tier */
 	signed char	type;		/* strange name for an index */
 	unsigned int	max;		/* size of this swap device */
diff --git a/mm/swap_tier.c b/mm/swap_tier.c
index 286f319fb125..de183c2678e0 100644
--- a/mm/swap_tier.c
+++ b/mm/swap_tier.c
@@ -1,5 +1,10 @@
 // SPDX-License-Identifier: GPL-2.0
 #include <linux/swap.h>
+#if defined(CONFIG_MEMCG) && defined(CONFIG_DEBUG_FS)
+#include <linux/debugfs.h>
+#include <linux/memcontrol.h>
+#include <linux/seq_file.h>
+#endif
 
 #include "swap.h"
 #include "swap_tier.h"
@@ -17,6 +22,10 @@ static LIST_HEAD(swap_tier_inactive_list);
 	for (idx = 0, tier = &swap_tiers[0]; idx < MAX_SWAPTIER; \
 		idx++, tier = &swap_tiers[idx])
 
+/* A tier's index is its slot in the array, stable for its lifetime. */
+#define TIER_IDX(tier)	((tier) - swap_tiers)
+#define TIER_MASK(tier)	(1U << TIER_IDX(tier))
+
 /*
  * Naming Convention:
  *   swap_tiers_*() - Public/exported functions
@@ -56,6 +65,12 @@ static void swap_tier_inactivate(struct swap_tier *tier)
 	list_move_tail(&tier->list, &swap_tier_inactive_list);
 }
 
+#if defined(CONFIG_MEMCG) && defined(CONFIG_DEBUG_FS)
+static void swap_tiers_debugfs_init(void);
+#else
+static inline void swap_tiers_debugfs_init(void) {}
+#endif
+
 void swap_tiers_init(void)
 {
 	struct swap_tier *tier;
@@ -69,6 +84,8 @@ void swap_tiers_init(void)
 		INIT_LIST_HEAD(&tier->list);
 		swap_tier_inactivate(tier);
 	}
+
+	swap_tiers_debugfs_init();
 }
 
 static struct swap_tier *swap_tier_prepare(short prio)
@@ -103,6 +120,17 @@ void swap_tiers_assign_dev(struct swap_info_struct *swp)
 	spin_unlock(&swap_avail_lock);
 
 	plist_add(&swp->list, &tier->active_head);
+
+	/* Put back by a failed swapoff, so already stamped and counted. */
+	if (swp->tier_mask)
+		return;
+
+	/*
+	 * A device's tier never changes, so stamp it once here. Paired with
+	 * the READ_ONCE() in the allocator, which reads this without swap_lock.
+	 */
+	tier->nr_devs++;
+	WRITE_ONCE(swp->tier_mask, TIER_MASK(tier));
 }
 
 void swap_tiers_remove_dev(struct swap_info_struct *swp)
@@ -113,11 +141,31 @@ void swap_tiers_remove_dev(struct swap_info_struct *swp)
 
 	tier = swap_tier_lookup(swp->prio);
 	plist_del(&swp->list, &tier->active_head);
-	if (plist_head_empty(&tier->active_head)) {
+}
+
+/*
+ * A failed swapoff puts the device back into its tier, so the tier is given
+ * up only here, once swapoff can no longer fail. Returns the tier's mask if
+ * @swp was its last device, 0 otherwise.
+ */
+unsigned int swap_tiers_release_dev(struct swap_info_struct *swp)
+{
+	struct swap_tier *tier;
+	unsigned int freed = 0;
+
+	lockdep_assert_held(&swap_lock);
+
+	tier = swap_tier_lookup(swp->prio);
+	if (!--tier->nr_devs) {
 		spin_lock(&swap_avail_lock);
 		swap_tier_inactivate(tier);
 		spin_unlock(&swap_avail_lock);
+		freed = TIER_MASK(tier);
 	}
+
+	WRITE_ONCE(swp->tier_mask, 0);
+
+	return freed;
 }
 
 /* The avail list of the tier @swp belongs to. */
@@ -127,3 +175,247 @@ struct plist_head *swap_tiers_avail_head(struct swap_info_struct *swp)
 
 	return &swap_tier_lookup(swp->prio)->avail_head;
 }
+
+#if defined(CONFIG_MEMCG) && defined(CONFIG_DEBUG_FS)
+static DEFINE_MUTEX(swap_tier_lock);
+
+/*
+ * struct swap_tier_cgroup - tier mask of a cgroup.
+ *
+ * @id: cgroup ID of the cgroup.
+ * @mask: tiers the cgroup may swap to.
+ * @list: linkage into swap_tier_cgroup_list.
+ * @rcu: frees the entry after a grace period.
+ */
+struct swap_tier_cgroup {
+	u64 id;
+	unsigned int mask;
+	struct list_head list;
+	struct rcu_head rcu;
+};
+
+/*
+ * Cgroups written to memcg_tiers. Changed under swap_tier_lock, walked by
+ * the allocator under RCU. A cgroup not on the list may use every tier.
+ */
+static LIST_HEAD(swap_tier_cgroup_list);
+
+static struct swap_tier_cgroup *swap_tier_cgroup_lookup(u64 id)
+{
+	struct swap_tier_cgroup *stc;
+
+	list_for_each_entry_rcu(stc, &swap_tier_cgroup_list, list,
+				lockdep_is_held(&swap_tier_lock)) {
+		if (stc->id == id)
+			return stc;
+	}
+
+	return NULL;
+}
+
+/* Drop the entries that allow every tier or whose cgroup is removed. */
+static void swap_tier_cgroup_prune(void)
+{
+	struct swap_tier_cgroup *stc, *tmp;
+	struct cgroup *cgrp;
+
+	lockdep_assert_held(&swap_tier_lock);
+
+	list_for_each_entry_safe(stc, tmp, &swap_tier_cgroup_list, list) {
+		if (stc->mask != TIER_ALL_MASK) {
+			cgrp = __cgroup_get_from_id(stc->id);
+			if (!IS_ERR(cgrp)) {
+				cgroup_put(cgrp);
+				continue;
+			}
+		}
+
+		list_del_rcu(&stc->list);
+		kfree_rcu(stc, rcu);
+	}
+}
+
+/* One line per cgroup that dropped a tier, in the syntax a write takes. */
+static int swap_tiers_memcg_show(struct seq_file *m, void *v)
+{
+	struct swap_tier_cgroup *stc;
+	struct cgroup *cgrp;
+	char *path;
+
+	path = kmalloc(PATH_MAX, GFP_KERNEL);
+	if (!path)
+		return -ENOMEM;
+
+	mutex_lock(&swap_tier_lock);
+	list_for_each_entry(stc, &swap_tier_cgroup_list, list) {
+		if (stc->mask == TIER_ALL_MASK)
+			continue;
+
+		/* A removed cgroup's entry stays until the next write. */
+		cgrp = __cgroup_get_from_id(stc->id);
+		if (IS_ERR(cgrp))
+			continue;
+
+		cgroup_path(cgrp, path, PATH_MAX);
+		cgroup_put(cgrp);
+		seq_printf(m, "%s 0x%x\n", path, stc->mask);
+	}
+	mutex_unlock(&swap_tier_lock);
+
+	kfree(path);
+	return 0;
+}
+
+/*
+ * Keep only the tiers set in @mask.  @cgpath must name a cgroup that has
+ * the memory controller enabled.
+ */
+static int swap_tiers_memcg_set(const char *cgpath, unsigned int mask)
+{
+	struct swap_tier_cgroup *stc;
+	struct cgroup *cgrp;
+	bool enabled;
+	int ret = 0;
+	u64 id;
+
+	cgrp = cgroup_get_from_path(cgpath);
+	if (IS_ERR(cgrp))
+		return PTR_ERR(cgrp);
+
+	/*
+	 * Not cgroup_get_e_css(), which falls back to an ancestor when the
+	 * memory controller is not enabled here.
+	 */
+	rcu_read_lock();
+	enabled = cgroup_css(cgrp, &memory_cgrp_subsys);
+	rcu_read_unlock();
+	id = cgroup_id(cgrp);
+	cgroup_put(cgrp);
+
+	if (!enabled)
+		return -ENOENT;
+
+	mutex_lock(&swap_tier_lock);
+
+	stc = swap_tier_cgroup_lookup(id);
+	if (stc) {
+		WRITE_ONCE(stc->mask, mask);
+	} else if (mask != TIER_ALL_MASK) {
+		stc = kmalloc_obj(*stc, GFP_KERNEL);
+		if (stc) {
+			stc->id = id;
+			stc->mask = mask;
+			list_add_rcu(&stc->list, &swap_tier_cgroup_list);
+		} else {
+			ret = -ENOMEM;
+		}
+	}
+	swap_tier_cgroup_prune();
+
+	mutex_unlock(&swap_tier_lock);
+	return ret;
+}
+
+/* The tiers that the cgroup @folio is charged to may swap to. */
+unsigned int folio_tier_mask(struct folio *folio)
+{
+	struct swap_tier_cgroup *stc;
+	struct mem_cgroup *memcg;
+	unsigned int mask = TIER_ALL_MASK;
+
+	rcu_read_lock();
+	memcg = folio_memcg(folio);
+	if (memcg) {
+		stc = swap_tier_cgroup_lookup(cgroup_id(memcg->css.cgroup));
+		if (stc)
+			mask = READ_ONCE(stc->mask);
+	}
+	rcu_read_unlock();
+
+	return mask;
+}
+
+/*
+ * When a tier is removed, its index (bit position in the mask) becomes
+ * free for reassignment to a future tier. If a cgroup had previously
+ * disabled this tier (cleared the bit in its memcg_tiers entry), its mask
+ * would keep that bit clear, meaning the new tier at the same index would
+ * be silently unavailable, an invisible cgroup constraint left behind by a
+ * tier that no longer exists.
+ *
+ * To prevent this, OR the removed tier's mask bit into every cgroup's
+ * mask. This resets the bit so the new tier is accessible by default.
+ * Users who want to restrict it must explicitly disable it after the tier
+ * is re-created.
+ */
+void swap_tiers_memcg_propagate(unsigned int mask)
+{
+	struct swap_tier_cgroup *stc;
+
+	mutex_lock(&swap_tier_lock);
+	list_for_each_entry(stc, &swap_tier_cgroup_list, list)
+		WRITE_ONCE(stc->mask, stc->mask | mask);
+	mutex_unlock(&swap_tier_lock);
+}
+
+static int swap_tiers_memcg_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, swap_tiers_memcg_show, NULL);
+}
+
+static ssize_t swap_tiers_memcg_write(struct file *file,
+				      const char __user *ubuf,
+				      size_t count, loff_t *ppos)
+{
+	char *pos, *tmp, *cgpath;
+	unsigned int mask;
+	int ret;
+
+	tmp = memdup_user_nul(ubuf, count);
+	if (IS_ERR(tmp))
+		return PTR_ERR(tmp);
+
+	pos = strstrip(tmp);
+	cgpath = strsep(&pos, " \t\n");
+	if (!cgpath || !*cgpath || !pos ||
+	    kstrtouint(skip_spaces(pos), 16, &mask))
+		ret = -EINVAL;
+	else
+		ret = swap_tiers_memcg_set(cgpath, mask);
+
+	kfree(tmp);
+	return ret ? ret : count;
+}
+
+static const struct file_operations swap_tiers_memcg_fops = {
+	.open		= swap_tiers_memcg_open,
+	.read		= seq_read,
+	.write		= swap_tiers_memcg_write,
+	.llseek		= seq_lseek,
+	.release	= single_release,
+};
+
+static int swap_tiers_show(struct seq_file *m, void *v)
+{
+	struct swap_tier *tier;
+
+	seq_printf(m, "%-5s %s\n", "Idx", "Prio");
+
+	spin_lock(&swap_lock);
+	for_each_active_tier(tier)
+		seq_printf(m, "%-5td %d\n", TIER_IDX(tier), tier->prio);
+	spin_unlock(&swap_lock);
+
+	return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(swap_tiers);
+
+static void swap_tiers_debugfs_init(void)
+{
+	struct dentry *dir = debugfs_create_dir("swap", NULL);
+
+	debugfs_create_file("tiers", 0400, dir, NULL, &swap_tiers_fops);
+	debugfs_create_file("memcg_tiers", 0600, dir, NULL,
+			    &swap_tiers_memcg_fops);
+}
+#endif
diff --git a/mm/swap_tier.h b/mm/swap_tier.h
index c4347c28d8f1..9967cfbcc439 100644
--- a/mm/swap_tier.h
+++ b/mm/swap_tier.h
@@ -9,16 +9,20 @@
 /* Forward declarations */
 struct swap_info_struct;
 
+#define TIER_ALL_MASK		(~0U)
+
 /*
  * struct swap_tier - structure representing a swap tier.
  *
  * @prio: priority of the swap devices in the tier.
+ * @nr_devs: swap devices in the tier, including ones being swapped off.
  * @active_head: swap devices in the tier.
  * @avail_head: available swap devices in the tier.
  * @list: linkage into swap_tier_active_list or swap_tier_inactive_list.
  */
 struct swap_tier {
 	short prio;
+	int nr_devs;
 	struct plist_head active_head;
 	struct plist_head avail_head;
 	struct list_head list;
@@ -35,6 +39,33 @@ void swap_tiers_init(void);
 /* Tier assignment */
 void swap_tiers_assign_dev(struct swap_info_struct *swp);
 void swap_tiers_remove_dev(struct swap_info_struct *swp);
+unsigned int swap_tiers_release_dev(struct swap_info_struct *swp);
 struct plist_head *swap_tiers_avail_head(struct swap_info_struct *swp);
 
+/**
+ * swap_tiers_mask_test - test whether two tier masks overlap
+ * @tier_mask: mask to test, e.g. a swap device's tier bit
+ * @mask: mask to test against, e.g. a cgroup's mask
+ *
+ * Return: true if @tier_mask and @mask share at least one tier bit.
+ */
+static inline bool swap_tiers_mask_test(unsigned int tier_mask,
+					unsigned int mask)
+{
+	return tier_mask & mask;
+}
+
+#if defined(CONFIG_MEMCG) && defined(CONFIG_DEBUG_FS)
+/* Memcg related functions */
+void swap_tiers_memcg_propagate(unsigned int mask);
+unsigned int folio_tier_mask(struct folio *folio);
+#else
+static inline void swap_tiers_memcg_propagate(unsigned int mask) {}
+
+static inline unsigned int folio_tier_mask(struct folio *folio)
+{
+	return TIER_ALL_MASK;
+}
+#endif
+
 #endif /* _SWAP_TIER_H */
diff --git a/mm/swapfile.c b/mm/swapfile.c
index e9d142c0655b..bb953dd33ca0 100644
--- a/mm/swapfile.c
+++ b/mm/swapfile.c
@@ -3191,6 +3191,7 @@ SYSCALL_DEFINE1(swapoff, const char __user *, specialfile)
 	struct address_space *mapping;
 	struct inode *inode;
 	unsigned int maxpages;
+	unsigned int freed_tier;
 	int err, found = 0;
 
 	if (!capable(CAP_SYS_ADMIN))
@@ -3290,7 +3291,11 @@ SYSCALL_DEFINE1(swapoff, const char __user *, specialfile)
 	p->max = 0;
 	p->cluster_info = NULL;
 	spin_unlock(&p->lock);
+	freed_tier = swap_tiers_release_dev(p);
 	spin_unlock(&swap_lock);
+	/* Under swapon_mutex, so a swapon cannot reuse the index before this. */
+	if (freed_tier)
+		swap_tiers_memcg_propagate(freed_tier);
 	arch_swap_invalidate_area(p->type);
 	zswap_swapoff(p->type);
 	mutex_unlock(&swapon_mutex);
-- 
2.48.1


  parent reply	other threads:[~2026-09-16 18:34 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-16 18:34 [RFC PATCH v11 0/4] mm/swap: priority-based swap tiers with per-cgroup selection Youngjun Park
2026-09-16 18:34 ` [RFC PATCH v11 1/4] mm: swap: introduce swap tier infrastructure Youngjun Park
2026-09-16 18:34 ` [RFC PATCH v11 2/4] mm: swap: allocate swap slots from swap tiers Youngjun Park
2026-09-16 18:34 ` Youngjun Park [this message]
2026-09-16 18:34 ` [RFC PATCH v11 4/4] mm: swap: filter swap allocation by memcg tier mask Youngjun Park
2026-09-16 20:04 ` [RFC PATCH v11 0/4] mm/swap: priority-based swap tiers with per-cgroup selection Johannes Weiner

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=20260916183437.2946306-4-youngjun.park@lge.com \
    --to=youngjun.park@lge.com \
    --cc=akpm@linux-foundation.org \
    --cc=baohua@kernel.org \
    --cc=baoquan.he@linux.dev \
    --cc=cgroups@vger.kernel.org \
    --cc=chrisl@kernel.org \
    --cc=hannes@cmpxchg.org \
    --cc=her0gyugyu@gmail.com \
    --cc=joshua.hahnjy@gmail.com \
    --cc=kasong@tencent.com \
    --cc=lianux.mm@gmail.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-mm@kvack.org \
    --cc=mhocko@kernel.org \
    --cc=muchun.song@linux.dev \
    --cc=roman.gushchin@linux.dev \
    --cc=shakeel.butt@linux.dev \
    --cc=shikemeng@huaweicloud.com \
    --cc=taejoon.song@lge.com \
    --cc=yosry@kernel.org \
    /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®