All of lore.kernel.org
 help / color / mirror / Atom feed
From: Suzuki K Poulose <suzuki.poulose@arm.com>
To: kvmarm@lists.linux.dev
Cc: kvm@vger.kernel.org, linux-coco@lists.linux.dev,
	linux-arm-kernel@lists.infradead.org, maz@kernel.org,
	alexandru.elisei@arm.com, joey.gouly@arm.com,
	steven.price@arm.com, james.morse@arm.com,
	oliver.upton@linux.dev, yuzenghui@huawei.com,
	andrew.jones@linux.dev, eric.auger@redhat.com,
	Suzuki K Poulose <suzuki.poulose@arm.com>
Subject: [kvm-unit-tests PATCH 10/33] arm: realm: Add support for changing the state of memory
Date: Fri, 12 Apr 2024 11:33:45 +0100	[thread overview]
Message-ID: <20240412103408.2706058-11-suzuki.poulose@arm.com> (raw)
In-Reply-To: <20240412103408.2706058-1-suzuki.poulose@arm.com>

For a Realm, the guest physical address (in reality the IPA/GPA of the VM)
has an associated state (Realm IPA State, RIPAS) which is one of :
   RIPAS_RAM
   RIPAS_EMPTY
   RIPAS_DESTROYED

The state of the physical address decides certain behaviors. e.g., any access
to a RIPAS_EMPTY PA will generate a Synchronous External Abort back to the Realm,
from the RMM.

All "PA" that represents RAM for the Realm, must be set to RIPAS_RAM before
an access is made. When the initial image (e.g., test, DTB) of a Realm is
loaded, the hypervisor/VMM can transition the state of the loaded "area" to
RIPAS_RAM. The rest of the "RAM" must be transitioned by the test payload
before any access is made.

Similarly, a Realm could set an "IPA" to RIPAS_EMPTY, when it is about to use
the "unprotected" alias of the IPA. This is a hint for the host to reclaim the
page from the protected "IPA.

RIPAS_DESTROYED indicates that the Host has destroyed a data granule at the IPA,
without the consent from the realm and is not reachable by a Realm action.

This patchs adds supporting helpers for setting the IPA state from Realm. These
will be used later for the Realm.

Co-developed-by: Joey Gouly <joey.gouly@arm.com>
Signed-off-by: Joey Gouly <joey.gouly@arm.com>
Co-developed-by: Suzuki K Poulose <suzuki.poulose@arm.com>
Signed-off-by: Suzuki K Poulose <suzuki.poulose@arm.com>
---
 lib/arm/asm/rsi.h   |  3 +++
 lib/arm/mmu.c       |  1 +
 lib/arm64/asm/rsi.h |  9 +++++++
 lib/arm64/rsi.c     | 63 +++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 76 insertions(+)

diff --git a/lib/arm/asm/rsi.h b/lib/arm/asm/rsi.h
index 5ff8d011..98e75303 100644
--- a/lib/arm/asm/rsi.h
+++ b/lib/arm/asm/rsi.h
@@ -14,5 +14,8 @@ static inline bool is_realm(void)
 }
 
 static inline void arm_rsi_init(void) {}
+static inline void arm_set_memory_protected(unsigned long va, size_t size) {}
+static inline void arm_set_memory_protected_safe(unsigned long va, size_t size) {}
+static inline void arm_set_memory_shared(unsigned long va, size_t size) {}
 
 #endif /* __ASMARM_RSI_H_ */
diff --git a/lib/arm/mmu.c b/lib/arm/mmu.c
index 41a8304d..16ceffcc 100644
--- a/lib/arm/mmu.c
+++ b/lib/arm/mmu.c
@@ -13,6 +13,7 @@
 #include <asm/setup.h>
 #include <asm/page.h>
 #include <asm/io.h>
+#include <asm/rsi.h>
 
 #include "alloc_page.h"
 #include "vmalloc.h"
diff --git a/lib/arm64/asm/rsi.h b/lib/arm64/asm/rsi.h
index 37103210..0b726684 100644
--- a/lib/arm64/asm/rsi.h
+++ b/lib/arm64/asm/rsi.h
@@ -34,4 +34,13 @@ static inline bool is_realm(void)
 	return rsi_present;
 }
 
+enum ripas_t {
+	RIPAS_EMPTY,
+	RIPAS_RAM,
+};
+
+void arm_set_memory_protected(unsigned long va, size_t size);
+void arm_set_memory_protected_safe(unsigned long va, size_t size);
+void arm_set_memory_shared(unsigned long va, size_t size);
+
 #endif /* __ASMARM64_RSI_H_ */
diff --git a/lib/arm64/rsi.c b/lib/arm64/rsi.c
index c4560866..e58d9660 100644
--- a/lib/arm64/rsi.c
+++ b/lib/arm64/rsi.c
@@ -71,3 +71,66 @@ void arm_rsi_init(void)
 	/* Set the upper bit of the IPA as the NS_SHARED pte attribute */
 	prot_ns_shared = (1UL << phys_mask_shift);
 }
+
+static unsigned rsi_set_addr_range_state(unsigned long start, unsigned long end,
+					 enum ripas_t state, unsigned int flags,
+					 unsigned long *top)
+{
+	struct smccc_result res;
+
+	rsi_invoke(SMC_RSI_IPA_STATE_SET, start, end, state, flags,
+		   0, 0, 0, 0, 0, 0, 0, &res);
+	*top = res.r1;
+	return res.r0;
+}
+
+static void arm_set_memory_state(unsigned long start,
+				 unsigned long size,
+				 unsigned int ripas,
+				 unsigned int flags)
+{
+	int ret;
+	unsigned long end, top;
+	unsigned long old_start = start;
+
+	if (!is_realm())
+		return;
+
+	start = ALIGN_DOWN(start, RSI_GRANULE_SIZE);
+	if (start != old_start)
+		size += old_start - start;
+	end = ALIGN(start + size, RSI_GRANULE_SIZE);
+	while (start != end) {
+		ret = rsi_set_addr_range_state(start, end, ripas, flags, &top);
+		assert(!ret);
+		assert(top <= end);
+		start = top;
+	}
+}
+
+/*
+ * Convert the IPA state of the given range to RIPAS_RAM, ignoring the
+ * fact that the host could have destroyed the contents and we don't
+ * rely on the previous state of the contents.
+ */
+void arm_set_memory_protected(unsigned long start, unsigned long size)
+{
+	arm_set_memory_state(start, size, RIPAS_RAM, RSI_CHANGE_DESTROYED);
+}
+
+/*
+ * Convert the IPA state of the given range to RSI_RAM, ensuring that the
+ * host has not destroyed any of the contents in the IPA range. Useful in
+ * converting a range of addresses where some of the IPA may already be in
+ * RSI_RAM state (e.g., images loaded at boot) and we want to make sure the
+ * host hasn't modified (by destroying them) the contents.
+ */
+void arm_set_memory_protected_safe(unsigned long start, unsigned long size)
+{
+	arm_set_memory_state(start, size, RIPAS_RAM, RSI_NO_CHANGE_DESTROYED);
+}
+
+void arm_set_memory_shared(unsigned long start, unsigned long size)
+{
+	arm_set_memory_state(start, size, RIPAS_EMPTY, RSI_CHANGE_DESTROYED);
+}
-- 
2.34.1


WARNING: multiple messages have this Message-ID (diff)
From: Suzuki K Poulose <suzuki.poulose@arm.com>
To: kvmarm@lists.linux.dev
Cc: kvm@vger.kernel.org, linux-coco@lists.linux.dev,
	linux-arm-kernel@lists.infradead.org, maz@kernel.org,
	alexandru.elisei@arm.com, joey.gouly@arm.com,
	steven.price@arm.com, james.morse@arm.com,
	oliver.upton@linux.dev, yuzenghui@huawei.com,
	andrew.jones@linux.dev, eric.auger@redhat.com,
	Suzuki K Poulose <suzuki.poulose@arm.com>
Subject: [kvm-unit-tests PATCH 10/33] arm: realm: Add support for changing the state of memory
Date: Fri, 12 Apr 2024 11:33:45 +0100	[thread overview]
Message-ID: <20240412103408.2706058-11-suzuki.poulose@arm.com> (raw)
In-Reply-To: <20240412103408.2706058-1-suzuki.poulose@arm.com>

For a Realm, the guest physical address (in reality the IPA/GPA of the VM)
has an associated state (Realm IPA State, RIPAS) which is one of :
   RIPAS_RAM
   RIPAS_EMPTY
   RIPAS_DESTROYED

The state of the physical address decides certain behaviors. e.g., any access
to a RIPAS_EMPTY PA will generate a Synchronous External Abort back to the Realm,
from the RMM.

All "PA" that represents RAM for the Realm, must be set to RIPAS_RAM before
an access is made. When the initial image (e.g., test, DTB) of a Realm is
loaded, the hypervisor/VMM can transition the state of the loaded "area" to
RIPAS_RAM. The rest of the "RAM" must be transitioned by the test payload
before any access is made.

Similarly, a Realm could set an "IPA" to RIPAS_EMPTY, when it is about to use
the "unprotected" alias of the IPA. This is a hint for the host to reclaim the
page from the protected "IPA.

RIPAS_DESTROYED indicates that the Host has destroyed a data granule at the IPA,
without the consent from the realm and is not reachable by a Realm action.

This patchs adds supporting helpers for setting the IPA state from Realm. These
will be used later for the Realm.

Co-developed-by: Joey Gouly <joey.gouly@arm.com>
Signed-off-by: Joey Gouly <joey.gouly@arm.com>
Co-developed-by: Suzuki K Poulose <suzuki.poulose@arm.com>
Signed-off-by: Suzuki K Poulose <suzuki.poulose@arm.com>
---
 lib/arm/asm/rsi.h   |  3 +++
 lib/arm/mmu.c       |  1 +
 lib/arm64/asm/rsi.h |  9 +++++++
 lib/arm64/rsi.c     | 63 +++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 76 insertions(+)

diff --git a/lib/arm/asm/rsi.h b/lib/arm/asm/rsi.h
index 5ff8d011..98e75303 100644
--- a/lib/arm/asm/rsi.h
+++ b/lib/arm/asm/rsi.h
@@ -14,5 +14,8 @@ static inline bool is_realm(void)
 }
 
 static inline void arm_rsi_init(void) {}
+static inline void arm_set_memory_protected(unsigned long va, size_t size) {}
+static inline void arm_set_memory_protected_safe(unsigned long va, size_t size) {}
+static inline void arm_set_memory_shared(unsigned long va, size_t size) {}
 
 #endif /* __ASMARM_RSI_H_ */
diff --git a/lib/arm/mmu.c b/lib/arm/mmu.c
index 41a8304d..16ceffcc 100644
--- a/lib/arm/mmu.c
+++ b/lib/arm/mmu.c
@@ -13,6 +13,7 @@
 #include <asm/setup.h>
 #include <asm/page.h>
 #include <asm/io.h>
+#include <asm/rsi.h>
 
 #include "alloc_page.h"
 #include "vmalloc.h"
diff --git a/lib/arm64/asm/rsi.h b/lib/arm64/asm/rsi.h
index 37103210..0b726684 100644
--- a/lib/arm64/asm/rsi.h
+++ b/lib/arm64/asm/rsi.h
@@ -34,4 +34,13 @@ static inline bool is_realm(void)
 	return rsi_present;
 }
 
+enum ripas_t {
+	RIPAS_EMPTY,
+	RIPAS_RAM,
+};
+
+void arm_set_memory_protected(unsigned long va, size_t size);
+void arm_set_memory_protected_safe(unsigned long va, size_t size);
+void arm_set_memory_shared(unsigned long va, size_t size);
+
 #endif /* __ASMARM64_RSI_H_ */
diff --git a/lib/arm64/rsi.c b/lib/arm64/rsi.c
index c4560866..e58d9660 100644
--- a/lib/arm64/rsi.c
+++ b/lib/arm64/rsi.c
@@ -71,3 +71,66 @@ void arm_rsi_init(void)
 	/* Set the upper bit of the IPA as the NS_SHARED pte attribute */
 	prot_ns_shared = (1UL << phys_mask_shift);
 }
+
+static unsigned rsi_set_addr_range_state(unsigned long start, unsigned long end,
+					 enum ripas_t state, unsigned int flags,
+					 unsigned long *top)
+{
+	struct smccc_result res;
+
+	rsi_invoke(SMC_RSI_IPA_STATE_SET, start, end, state, flags,
+		   0, 0, 0, 0, 0, 0, 0, &res);
+	*top = res.r1;
+	return res.r0;
+}
+
+static void arm_set_memory_state(unsigned long start,
+				 unsigned long size,
+				 unsigned int ripas,
+				 unsigned int flags)
+{
+	int ret;
+	unsigned long end, top;
+	unsigned long old_start = start;
+
+	if (!is_realm())
+		return;
+
+	start = ALIGN_DOWN(start, RSI_GRANULE_SIZE);
+	if (start != old_start)
+		size += old_start - start;
+	end = ALIGN(start + size, RSI_GRANULE_SIZE);
+	while (start != end) {
+		ret = rsi_set_addr_range_state(start, end, ripas, flags, &top);
+		assert(!ret);
+		assert(top <= end);
+		start = top;
+	}
+}
+
+/*
+ * Convert the IPA state of the given range to RIPAS_RAM, ignoring the
+ * fact that the host could have destroyed the contents and we don't
+ * rely on the previous state of the contents.
+ */
+void arm_set_memory_protected(unsigned long start, unsigned long size)
+{
+	arm_set_memory_state(start, size, RIPAS_RAM, RSI_CHANGE_DESTROYED);
+}
+
+/*
+ * Convert the IPA state of the given range to RSI_RAM, ensuring that the
+ * host has not destroyed any of the contents in the IPA range. Useful in
+ * converting a range of addresses where some of the IPA may already be in
+ * RSI_RAM state (e.g., images loaded at boot) and we want to make sure the
+ * host hasn't modified (by destroying them) the contents.
+ */
+void arm_set_memory_protected_safe(unsigned long start, unsigned long size)
+{
+	arm_set_memory_state(start, size, RIPAS_RAM, RSI_NO_CHANGE_DESTROYED);
+}
+
+void arm_set_memory_shared(unsigned long start, unsigned long size)
+{
+	arm_set_memory_state(start, size, RIPAS_EMPTY, RSI_CHANGE_DESTROYED);
+}
-- 
2.34.1


_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

  parent reply	other threads:[~2024-04-12 10:34 UTC|newest]

Thread overview: 102+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2024-04-12 10:33 [kvm-unit-tests PATCH 00/33] Support for Arm Confidential Compute Architecture Suzuki K Poulose
2024-04-12 10:33 ` Suzuki K Poulose
2024-04-10 16:17 ` Itaru Kitayama
2024-04-10 16:17   ` Itaru Kitayama
2024-04-15  8:59   ` Suzuki K Poulose
2024-04-15  8:59     ` Suzuki K Poulose
2024-04-12 10:33 ` [kvm-unit-tests PATCH 01/33] arm: Add necessary header files in asm/pgtable.h Suzuki K Poulose
2024-04-12 10:33   ` Suzuki K Poulose
2024-04-12 10:33 ` [kvm-unit-tests PATCH 02/33] arm: Detect FDT overlap with uninitialised data Suzuki K Poulose
2024-04-12 10:33   ` Suzuki K Poulose
2024-04-12 10:33 ` [kvm-unit-tests PATCH 03/33] arm64: Expand SMCCC arguments and return values Suzuki K Poulose
2024-04-12 10:33   ` Suzuki K Poulose
2024-04-12 10:33 ` [kvm-unit-tests PATCH 04/33] arm: Make physical address mask dynamic Suzuki K Poulose
2024-04-12 10:33   ` Suzuki K Poulose
2024-04-12 10:33 ` [kvm-unit-tests PATCH 05/33] arm64: Introduce NS_SHARED PTE attribute Suzuki K Poulose
2024-04-12 10:33   ` Suzuki K Poulose
2024-04-12 10:33 ` [kvm-unit-tests PATCH 06/33] arm: Move io_init after vm initialization Suzuki K Poulose
2024-04-12 10:33   ` Suzuki K Poulose
2024-04-12 10:33 ` [kvm-unit-tests PATCH 07/33] arm: realm: Add RSI interface header Suzuki K Poulose
2024-04-12 10:33   ` Suzuki K Poulose
2024-04-12 10:33 ` [kvm-unit-tests PATCH 08/33] arm: realm: Make uart available before MMU is enabled Suzuki K Poulose
2024-04-12 10:33   ` Suzuki K Poulose
2024-04-22 11:58   ` Alexandru Elisei
2024-04-22 11:58     ` Alexandru Elisei
2024-04-22 12:09     ` Suzuki K Poulose
2024-04-22 12:09       ` Suzuki K Poulose
2024-04-22 12:23       ` Alexandru Elisei
2024-04-22 12:23         ` Alexandru Elisei
2024-04-22 12:36         ` Alexandru Elisei
2024-04-22 12:36           ` Alexandru Elisei
2024-04-22 13:09           ` Suzuki K Poulose
2024-04-22 13:09             ` Suzuki K Poulose
2024-04-22 15:38   ` Alexandru Elisei
2024-04-22 15:38     ` Alexandru Elisei
2024-04-22 16:05     ` Suzuki K Poulose
2024-04-22 16:05       ` Suzuki K Poulose
2024-04-22 16:15       ` Alexandru Elisei
2024-04-22 16:15         ` Alexandru Elisei
2024-04-26 11:15         ` Suzuki K Poulose
2024-04-26 11:15           ` Suzuki K Poulose
2024-04-26 13:51           ` Alexandru Elisei
2024-04-26 13:51             ` Alexandru Elisei
2024-04-12 10:33 ` [kvm-unit-tests PATCH 09/33] arm: realm: Realm initialisation Suzuki K Poulose
2024-04-12 10:33   ` Suzuki K Poulose
2024-04-12 10:33 ` Suzuki K Poulose [this message]
2024-04-12 10:33   ` [kvm-unit-tests PATCH 10/33] arm: realm: Add support for changing the state of memory Suzuki K Poulose
2024-04-12 10:33 ` [kvm-unit-tests PATCH 11/33] arm: realm: Set RIPAS state for RAM Suzuki K Poulose
2024-04-12 10:33   ` Suzuki K Poulose
2024-04-12 10:33 ` [kvm-unit-tests PATCH 12/33] arm: realm: Early memory setup Suzuki K Poulose
2024-04-12 10:33   ` Suzuki K Poulose
2024-04-12 10:33 ` [kvm-unit-tests PATCH 13/33] arm: realm: Add RSI version test Suzuki K Poulose
2024-04-12 10:33   ` Suzuki K Poulose
2024-04-12 10:33 ` [kvm-unit-tests PATCH 14/33] arm: selftest: realm: skip pabt test when running in a realm Suzuki K Poulose
2024-04-12 10:33   ` Suzuki K Poulose
2024-04-22 15:48   ` Alexandru Elisei
2024-04-22 15:48     ` Alexandru Elisei
2024-04-12 10:33 ` [kvm-unit-tests PATCH 15/33] arm: realm: add hvc and RSI_HOST_CALL tests Suzuki K Poulose
2024-04-12 10:33   ` Suzuki K Poulose
2024-04-12 10:33 ` [kvm-unit-tests PATCH 16/33] arm64: add ESR_ELx EC.SVE Suzuki K Poulose
2024-04-12 10:33   ` Suzuki K Poulose
2024-04-12 10:33 ` [kvm-unit-tests PATCH 17/33] arm64: enable SVE at startup Suzuki K Poulose
2024-04-12 10:33   ` Suzuki K Poulose
2024-04-12 10:33 ` [kvm-unit-tests PATCH 18/33] arm: realm: Add test for FPU/SIMD context save/restore Suzuki K Poulose
2024-04-12 10:33   ` Suzuki K Poulose
2024-05-10 15:28   ` Andrew Jones
2024-05-10 15:28     ` Andrew Jones
2024-05-14 10:27     ` Suzuki K Poulose
2024-05-14 10:27       ` Suzuki K Poulose
2024-04-12 10:33 ` [kvm-unit-tests PATCH 19/33] arm64: selftest: add realm SVE VL test Suzuki K Poulose
2024-04-12 10:33   ` Suzuki K Poulose
2024-04-12 10:33 ` [kvm-unit-tests PATCH 20/33] arm: realm: Add tests for in realm SEA Suzuki K Poulose
2024-04-12 10:33   ` Suzuki K Poulose
2024-04-12 10:33 ` [kvm-unit-tests PATCH 21/33] lib/alloc_page: Add shared page allocation support Suzuki K Poulose
2024-04-12 10:33   ` Suzuki K Poulose
2024-04-12 10:33 ` [kvm-unit-tests PATCH 22/33] arm: gic-v3-its: Use shared pages wherever needed Suzuki K Poulose
2024-04-12 10:33   ` Suzuki K Poulose
2024-04-12 10:33 ` [kvm-unit-tests PATCH 23/33] arm: realm: Enable memory encryption Suzuki K Poulose
2024-04-12 10:33   ` Suzuki K Poulose
2024-04-12 10:33 ` [kvm-unit-tests PATCH 24/33] qcbor: Add QCBOR as a submodule Suzuki K Poulose
2024-04-12 10:33   ` Suzuki K Poulose
2024-04-12 10:34 ` [kvm-unit-tests PATCH 25/33] arm: Add build steps for QCBOR library Suzuki K Poulose
2024-04-12 10:34   ` Suzuki K Poulose
2024-04-12 10:34 ` [kvm-unit-tests PATCH 26/33] arm: Add a library to verify tokens using the " Suzuki K Poulose
2024-04-12 10:34   ` Suzuki K Poulose
2024-04-12 10:34 ` [kvm-unit-tests PATCH 27/33] arm: realm: add RSI interface for attestation measurements Suzuki K Poulose
2024-04-12 10:34   ` Suzuki K Poulose
2024-04-12 10:34 ` [kvm-unit-tests PATCH 28/33] arm: realm: Add helpers to decode RSI return codes Suzuki K Poulose
2024-04-12 10:34   ` Suzuki K Poulose
2024-04-12 10:34 ` [kvm-unit-tests PATCH 29/33] arm: realm: Add Realm attestation tests Suzuki K Poulose
2024-04-12 10:34   ` Suzuki K Poulose
2024-04-12 10:34 ` [kvm-unit-tests PATCH 30/33] " Suzuki K Poulose
2024-04-12 10:34   ` Suzuki K Poulose
2024-04-12 10:34 ` [kvm-unit-tests PATCH 31/33] arm: realm: Add a test for shared memory Suzuki K Poulose
2024-04-12 10:34   ` Suzuki K Poulose
2024-04-12 10:34 ` [kvm-unit-tests PATCH 32/33] arm: Add memtest support Suzuki K Poulose
2024-04-12 10:34   ` Suzuki K Poulose
2024-04-12 10:34 ` [kvm-unit-tests PATCH 33/33] NOT-FOR-MERGING: add run-realm-tests Suzuki K Poulose
2024-04-12 10:34   ` Suzuki K Poulose
2024-04-16 14:28 ` [kvm-unit-tests PATCH 00/33] Support for Arm Confidential Compute Architecture Jean-Philippe Brucker
2024-04-16 14:28   ` Jean-Philippe Brucker
2024-05-10 15:23 ` Andrew Jones
2024-05-10 15:23   ` Andrew Jones

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=20240412103408.2706058-11-suzuki.poulose@arm.com \
    --to=suzuki.poulose@arm.com \
    --cc=alexandru.elisei@arm.com \
    --cc=andrew.jones@linux.dev \
    --cc=eric.auger@redhat.com \
    --cc=james.morse@arm.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-coco@lists.linux.dev \
    --cc=maz@kernel.org \
    --cc=oliver.upton@linux.dev \
    --cc=steven.price@arm.com \
    --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 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.