From: Lorenzo Pieralisi <lorenzo.pieralisi-5wv7dgnIgG8@public.gmane.org>
To: Robin Murphy <robin.murphy-5wv7dgnIgG8@public.gmane.org>
Cc: joro-zLv9SwRftAIdnm+yROfE0A@public.gmane.org,
will.deacon-5wv7dgnIgG8@public.gmane.org,
iommu-cunTk1MwBs9QetFLy7KEm3xJsTq8ys+cHZ5vskTnxNA@public.gmane.org,
devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
jean-philippe.brucker-5wv7dgnIgG8@public.gmane.org,
punit.agrawal-5wv7dgnIgG8@public.gmane.org,
thunder.leizhen-hv44wF8Li93QT0dZR+AlfA@public.gmane.org,
eric.auger-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org
Subject: Re: [PATCH v5 14/19] iommu/arm-smmu: Intelligent SMR allocation
Date: Thu, 1 Sep 2016 16:17:02 +0100 [thread overview]
Message-ID: <20160901151702.GC28180@red-moon> (raw)
In-Reply-To: <693b7fdd58be254297eb43ac8f5e035beb5226b2.1471975357.git.robin.murphy-5wv7dgnIgG8@public.gmane.org>
On Tue, Aug 23, 2016 at 08:05:25PM +0100, Robin Murphy wrote:
> Stream Match Registers are one of the more awkward parts of the SMMUv2
> architecture; there are typically never enough to assign one to each
> stream ID in the system, and configuring them such that a single ID
> matches multiple entries is catastrophically bad - at best, every
> transaction raises a global fault; at worst, they go *somewhere*.
>
> To address the former issue, we can mask ID bits such that a single
> register may be used to match multiple IDs belonging to the same device
> or group, but doing so also heightens the risk of the latter problem
> (which can be nasty to debug).
>
> Tackle both problems at once by replacing the simple bitmap allocator
> with something much cleverer. Now that we have convenient in-memory
> representations of the stream mapping table, it becomes straightforward
> to properly validate new SMR entries against the current state, opening
> the door to arbitrary masking and SMR sharing.
>
> Another feature which falls out of this is that with IDs shared by
> separate devices being automatically accounted for, simply associating a
> group pointer with the S2CR offers appropriate group allocation almost
> for free, so hook that up in the process.
>
> Signed-off-by: Robin Murphy <robin.murphy-5wv7dgnIgG8@public.gmane.org>
> ---
> drivers/iommu/arm-smmu.c | 192 ++++++++++++++++++++++++++++-------------------
> 1 file changed, 114 insertions(+), 78 deletions(-)
>
> diff --git a/drivers/iommu/arm-smmu.c b/drivers/iommu/arm-smmu.c
> index 17bf871030c6..88f82eb8d1fe 100644
> --- a/drivers/iommu/arm-smmu.c
> +++ b/drivers/iommu/arm-smmu.c
> @@ -302,6 +302,8 @@ enum arm_smmu_implementation {
> };
>
> struct arm_smmu_s2cr {
> + struct iommu_group *group;
> + int count;
> enum arm_smmu_s2cr_type type;
> enum arm_smmu_s2cr_privcfg privcfg;
> u8 cbndx;
> @@ -363,6 +365,7 @@ struct arm_smmu_device {
> u16 smr_mask_mask;
> struct arm_smmu_smr *smrs;
> struct arm_smmu_s2cr *s2crs;
> + struct mutex stream_map_mutex;
>
> unsigned long va_size;
> unsigned long ipa_size;
> @@ -1016,23 +1019,6 @@ static void arm_smmu_domain_free(struct iommu_domain *domain)
> kfree(smmu_domain);
> }
>
> -static int arm_smmu_alloc_smr(struct arm_smmu_device *smmu)
> -{
> - int i;
> -
> - for (i = 0; i < smmu->num_mapping_groups; i++)
> - if (!cmpxchg(&smmu->smrs[i].valid, false, true))
> - return i;
> -
> - return INVALID_SMENDX;
> -}
> -
> -static void arm_smmu_free_smr(struct arm_smmu_device *smmu, int idx)
> -{
> - writel_relaxed(~SMR_VALID, ARM_SMMU_GR0(smmu) + ARM_SMMU_GR0_SMR(idx));
> - WRITE_ONCE(smmu->smrs[idx].valid, false);
> -}
> -
> static void arm_smmu_write_smr(struct arm_smmu_device *smmu, int idx)
> {
> struct arm_smmu_smr *smr = smmu->smrs + idx;
> @@ -1060,49 +1046,110 @@ static void arm_smmu_write_sme(struct arm_smmu_device *smmu, int idx)
> arm_smmu_write_smr(smmu, idx);
> }
>
> -static int arm_smmu_master_alloc_smes(struct arm_smmu_device *smmu,
> - struct arm_smmu_master_cfg *cfg)
> +static int arm_smmu_find_sme(struct arm_smmu_device *smmu, u16 id, u16 mask)
> {
> struct arm_smmu_smr *smrs = smmu->smrs;
> - int i, idx;
> + int i, idx = -ENOSPC;
>
> - /* Allocate the SMRs on the SMMU */
> - for_each_cfg_sme(cfg, i, idx) {
> - if (idx >= 0)
> - return -EEXIST;
> + /* Stream indexing is blissfully easy */
> + if (!smrs)
> + return id;
>
> - /* ...except on stream indexing hardware, of course */
> - if (!smrs) {
> - cfg->smendx[i] = cfg->streamids[i];
> + /* Validating SMRs is... less so */
> + for (i = 0; i < smmu->num_mapping_groups; ++i) {
> + if (!smrs[i].valid) {
> + if (idx < 0)
> + idx = i;
This is to stash an "empty" entry index in case none matches, right ?
Let me say it is not that obvious, that deserves a comment since it
is hard to follow.
> continue;
> }
>
> - idx = arm_smmu_alloc_smr(smmu);
> - if (idx < 0) {
> - dev_err(smmu->dev, "failed to allocate free SMR\n");
> - goto err_free_smrs;
> - }
> - cfg->smendx[i] = idx;
> + /* Exact matches are good */
> + if (mask == smrs[i].mask && id == smrs[i].id)
> + return i;
>
> - smrs[idx].id = cfg->streamids[i];
> - smrs[idx].mask = 0; /* We don't currently share SMRs */
> + /* New unmasked IDs matching existing masks we can cope with */
> + if (!mask && !((smrs[i].id ^ id) & ~smrs[i].mask))
> + return i;
> +
> + /* Overlapping masks are right out */
> + if (mask & smrs[i].mask)
> + return -EINVAL;
> +
> + /* Distinct masks must match unambiguous ranges */
> + if (mask && !((smrs[i].id ^ id) & ~(smrs[i].mask | mask)))
> + return -EINVAL;
And this is to _prevent_ an entry to match the input streamid
range (masked streamid) because it would create an ambiguous match ?
Basically here you keep looping because this function is at the
same time allocating and validating the SMRS (ie you want to make
sure there are no valid entries that clash with the streamid you
are currently allocating).
It is a tad complicated and a bit hard to parse. I wonder if it would
not be better to split into two SMRS validation and allocation
functions, I think I fully grasped what you want to achieve but it is
not that trivial.
Thanks,
Lorenzo
> }
>
> - if (!smrs)
> - return 0;
> + return idx;
> +}
> +
> +static bool arm_smmu_free_sme(struct arm_smmu_device *smmu, int idx)
> +{
> + if (--smmu->s2crs[idx].count)
> + return false;
> +
> + smmu->s2crs[idx] = s2cr_init_val;
> + if (smmu->smrs)
> + smmu->smrs[idx].valid = false;
> +
> + return true;
> +}
> +
> +static int arm_smmu_master_alloc_smes(struct device *dev)
> +{
> + struct arm_smmu_master_cfg *cfg = dev->archdata.iommu;
> + struct arm_smmu_device *smmu = cfg->smmu;
> + struct arm_smmu_smr *smrs = smmu->smrs;
> + struct iommu_group *group;
> + int i, idx, ret;
> +
> + mutex_lock(&smmu->stream_map_mutex);
> + /* Figure out a viable stream map entry allocation */
> + for_each_cfg_sme(cfg, i, idx) {
> + if (idx >= 0) {
> + ret = -EEXIST;
> + goto out_err;
> + }
> +
> + ret = arm_smmu_find_sme(smmu, cfg->streamids[i], 0);
> + if (ret < 0)
> + goto out_err;
> +
> + idx = ret;
> + if (smrs && smmu->s2crs[idx].count == 0) {
> + smrs[idx].id = cfg->streamids[i];
> + smrs[idx].mask = 0; /* We don't currently share SMRs */
> + smrs[idx].valid = true;
> + }
> + smmu->s2crs[idx].count++;
> + cfg->smendx[i] = (s16)idx;
> + }
> +
> + group = iommu_group_get_for_dev(dev);
> + if (!group)
> + group = ERR_PTR(-ENOMEM);
> + if (IS_ERR(group)) {
> + ret = PTR_ERR(group);
> + goto out_err;
> + }
> + iommu_group_put(group);
>
> /* It worked! Now, poke the actual hardware */
> - for_each_cfg_sme(cfg, i, idx)
> - arm_smmu_write_smr(smmu, idx);
> + for_each_cfg_sme(cfg, i, idx) {
> + arm_smmu_write_sme(smmu, idx);
> + smmu->s2crs[idx].group = group;
> + }
>
> + mutex_unlock(&smmu->stream_map_mutex);
> return 0;
>
> -err_free_smrs:
> +out_err:
> while (i--) {
> - arm_smmu_free_smr(smmu, cfg->smendx[i]);
> + arm_smmu_free_sme(smmu, cfg->smendx[i]);
> cfg->smendx[i] = INVALID_SMENDX;
> }
> - return -ENOSPC;
> + mutex_unlock(&smmu->stream_map_mutex);
> + return ret;
> }
>
> static void arm_smmu_master_free_smes(struct arm_smmu_master_cfg *cfg)
> @@ -1110,43 +1157,23 @@ static void arm_smmu_master_free_smes(struct arm_smmu_master_cfg *cfg)
> struct arm_smmu_device *smmu = cfg->smmu;
> int i, idx;
>
> - /*
> - * We *must* clear the S2CR first, because freeing the SMR means
> - * that it can be re-allocated immediately.
> - */
> + mutex_lock(&smmu->stream_map_mutex);
> for_each_cfg_sme(cfg, i, idx) {
> - /* An IOMMU group is torn down by the first device to be removed */
> - if (idx < 0)
> - return;
> -
> - smmu->s2crs[idx] = s2cr_init_val;
> - arm_smmu_write_s2cr(smmu, idx);
> - }
> - /* Sync S2CR updates before touching anything else */
> - __iowmb();
> -
> - /* Invalidate the SMRs before freeing back to the allocator */
> - for_each_cfg_sme(cfg, i, idx) {
> - if (smmu->smrs)
> - arm_smmu_free_smr(smmu, idx);
> -
> + if (arm_smmu_free_sme(smmu, idx))
> + arm_smmu_write_sme(smmu, idx);
> cfg->smendx[i] = INVALID_SMENDX;
> }
> + mutex_unlock(&smmu->stream_map_mutex);
> }
>
> static int arm_smmu_domain_add_master(struct arm_smmu_domain *smmu_domain,
> struct arm_smmu_master_cfg *cfg)
> {
> - int i, idx, ret = 0;
> struct arm_smmu_device *smmu = smmu_domain->smmu;
> struct arm_smmu_s2cr *s2cr = smmu->s2crs;
> enum arm_smmu_s2cr_type type = S2CR_TYPE_TRANS;
> u8 cbndx = smmu_domain->cfg.cbndx;
> -
> - if (cfg->smendx[0] < 0)
> - ret = arm_smmu_master_alloc_smes(smmu, cfg);
> - if (ret)
> - return ret;
> + int i, idx;
>
> /*
> * FIXME: This won't be needed once we have IOMMU-backed DMA ops
> @@ -1158,9 +1185,8 @@ static int arm_smmu_domain_add_master(struct arm_smmu_domain *smmu_domain,
> type = S2CR_TYPE_BYPASS;
>
> for_each_cfg_sme(cfg, i, idx) {
> - /* Devices in an IOMMU group may already be configured */
> if (type == s2cr[idx].type && cbndx == s2cr[idx].cbndx)
> - break;
> + continue;
>
> s2cr[idx].type = type;
> s2cr[idx].privcfg = S2CR_PRIVCFG_UNPRIV;
> @@ -1320,7 +1346,6 @@ static bool arm_smmu_capable(enum iommu_cap cap)
> static int arm_smmu_add_device(struct device *dev)
> {
> struct arm_smmu_master_cfg *cfg;
> - struct iommu_group *group;
> int i, ret;
>
> ret = arm_smmu_register_legacy_master(dev);
> @@ -1340,13 +1365,9 @@ static int arm_smmu_add_device(struct device *dev)
> cfg->smendx[i] = INVALID_SMENDX;
> }
>
> - group = iommu_group_get_for_dev(dev);
> - if (IS_ERR(group)) {
> - ret = PTR_ERR(group);
> - goto out_free;
> - }
> - iommu_group_put(group);
> - return 0;
> + ret = arm_smmu_master_alloc_smes(dev);
> + if (!ret)
> + return ret;
>
> out_free:
> kfree(cfg);
> @@ -1369,7 +1390,21 @@ static void arm_smmu_remove_device(struct device *dev)
>
> static struct iommu_group *arm_smmu_device_group(struct device *dev)
> {
> - struct iommu_group *group;
> + struct arm_smmu_master_cfg *cfg = dev->archdata.iommu;
> + struct arm_smmu_device *smmu = cfg->smmu;
> + struct iommu_group *group = NULL;
> + int i, idx;
> +
> + for_each_cfg_sme(cfg, i, idx) {
> + if (group && smmu->s2crs[idx].group &&
> + group != smmu->s2crs[idx].group)
> + return ERR_PTR(-EINVAL);
> +
> + group = smmu->s2crs[idx].group;
> + }
> +
> + if (group)
> + return group;
>
> if (dev_is_pci(dev))
> group = pci_device_group(dev);
> @@ -1652,6 +1687,7 @@ static int arm_smmu_device_cfg_probe(struct arm_smmu_device *smmu)
> smmu->s2crs[i] = s2cr_init_val;
>
> smmu->num_mapping_groups = size;
> + mutex_init(&smmu->stream_map_mutex);
>
> if (smmu->version < ARM_SMMU_V2 || !(id & ID0_PTFS_NO_AARCH32)) {
> smmu->features |= ARM_SMMU_FEAT_FMT_AARCH32_L;
> --
> 2.8.1.dirty
>
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
next prev parent reply other threads:[~2016-09-01 15:17 UTC|newest]
Thread overview: 52+ messages / expand[flat|nested] mbox.gz Atom feed top
2016-08-23 19:05 [PATCH v5 00/19] Generic DT bindings for PCI IOMMUs and ARM SMMU Robin Murphy
[not found] ` <cover.1471975357.git.robin.murphy-5wv7dgnIgG8@public.gmane.org>
2016-08-23 19:05 ` [PATCH v5 01/19] Docs: dt: add PCI IOMMU map bindings Robin Murphy
2016-08-23 19:05 ` [PATCH v5 02/19] of/irq: Break out msi-map lookup (again) Robin Murphy
2016-08-23 19:05 ` [PATCH v5 03/19] iommu/of: Handle iommu-map property for PCI Robin Murphy
[not found] ` <93909648835867008b21cb688a1d7db238d3641a.1471975357.git.robin.murphy-5wv7dgnIgG8@public.gmane.org>
2016-08-31 15:43 ` Will Deacon
2016-08-23 19:05 ` [PATCH v5 04/19] iommu/of: Introduce iommu_fwspec Robin Murphy
[not found] ` <3e8eaf4fd65833fecc62828214aee81f6ca6c190.1471975357.git.robin.murphy-5wv7dgnIgG8@public.gmane.org>
2016-08-31 17:28 ` Will Deacon
[not found] ` <20160831172856.GI29505-5wv7dgnIgG8@public.gmane.org>
2016-09-01 12:07 ` Robin Murphy
[not found] ` <900f3dcb-217c-4fb3-2f7d-15572f31a0c0-5wv7dgnIgG8@public.gmane.org>
2016-09-01 12:31 ` Will Deacon
[not found] ` <20160901123158.GE6721-5wv7dgnIgG8@public.gmane.org>
2016-09-01 13:25 ` Robin Murphy
2016-08-23 19:05 ` [PATCH v5 05/19] iommu/arm-smmu: Implement of_xlate() for SMMUv3 Robin Murphy
[not found] ` <6088007f60a24b36a3bf965b62521f99cd908019.1471975357.git.robin.murphy-5wv7dgnIgG8@public.gmane.org>
2016-09-01 17:06 ` Will Deacon
[not found] ` <20160901170604.GP6721-5wv7dgnIgG8@public.gmane.org>
2016-09-01 17:40 ` Robin Murphy
2016-08-23 19:05 ` [PATCH v5 06/19] iommu/arm-smmu: Support non-PCI devices with SMMUv3 Robin Murphy
[not found] ` <207d0ae38c5b01b7cf7e48231a4d01bac453b57c.1471975357.git.robin.murphy-5wv7dgnIgG8@public.gmane.org>
2016-09-01 17:08 ` Will Deacon
2016-08-23 19:05 ` [PATCH v5 07/19] iommu/arm-smmu: Set PRIVCFG in stage 1 STEs Robin Murphy
[not found] ` <1cda9861ce3ede6c2de9c6c4f2294549808b421b.1471975357.git.robin.murphy-5wv7dgnIgG8@public.gmane.org>
2016-09-01 17:19 ` Will Deacon
2016-08-23 19:05 ` [PATCH v5 08/19] iommu/arm-smmu: Handle stream IDs more dynamically Robin Murphy
[not found] ` <36f71a07fbc6037ca664bdcc540650f893081dd1.1471975357.git.robin.murphy-5wv7dgnIgG8@public.gmane.org>
2016-09-01 18:17 ` Will Deacon
2016-08-23 19:05 ` [PATCH v5 09/19] iommu/arm-smmu: Consolidate stream map entry state Robin Murphy
[not found] ` <26fcf7d3138816b9546a3dcc2bbbc2f229f34c91.1471975357.git.robin.murphy-5wv7dgnIgG8@public.gmane.org>
2016-09-01 18:32 ` Will Deacon
[not found] ` <20160901183257.GT6721-5wv7dgnIgG8@public.gmane.org>
2016-09-01 18:45 ` Robin Murphy
[not found] ` <6d3209ff-51ad-30ca-867b-ce62105e6699-5wv7dgnIgG8@public.gmane.org>
2016-09-01 18:54 ` Will Deacon
2016-08-23 19:05 ` [PATCH v5 10/19] iommu/arm-smmu: Keep track of S2CR state Robin Murphy
[not found] ` <e086741acfd0959671d184203ef758c824c8d7da.1471975357.git.robin.murphy-5wv7dgnIgG8@public.gmane.org>
2016-09-01 18:42 ` Will Deacon
[not found] ` <20160901184259.GU6721-5wv7dgnIgG8@public.gmane.org>
2016-09-01 19:00 ` Robin Murphy
2016-08-23 19:05 ` [PATCH v5 11/19] iommu/arm-smmu: Refactor mmu-masters handling Robin Murphy
[not found] ` <00301aa60323bb94588d078f2962feea0cb45c72.1471975357.git.robin.murphy-5wv7dgnIgG8@public.gmane.org>
2016-09-01 18:47 ` Will Deacon
2016-08-23 19:05 ` [PATCH v5 12/19] iommu/arm-smmu: Streamline SMMU data lookups Robin Murphy
2016-08-23 19:05 ` [PATCH v5 13/19] iommu/arm-smmu: Add a stream map entry iterator Robin Murphy
2016-08-23 19:05 ` [PATCH v5 14/19] iommu/arm-smmu: Intelligent SMR allocation Robin Murphy
[not found] ` <693b7fdd58be254297eb43ac8f5e035beb5226b2.1471975357.git.robin.murphy-5wv7dgnIgG8@public.gmane.org>
2016-09-01 15:17 ` Lorenzo Pieralisi [this message]
2016-09-01 17:59 ` Robin Murphy
2016-08-23 19:05 ` [PATCH v5 15/19] iommu/arm-smmu: Convert to iommu_fwspec Robin Murphy
[not found] ` <221f668d606abdfb4d6ee6da2c5f568c57ceccdd.1471975357.git.robin.murphy-5wv7dgnIgG8@public.gmane.org>
2016-09-01 18:53 ` Will Deacon
2016-08-23 19:05 ` [PATCH v5 16/19] Docs: dt: document ARM SMMU generic binding usage Robin Murphy
[not found] ` <b4f0eca93ac944c3430297b97c703e1bc54846d7.1471975357.git.robin.murphy-5wv7dgnIgG8@public.gmane.org>
2016-08-29 15:44 ` Rob Herring
2016-08-23 19:05 ` [PATCH v5 17/19] iommu/arm-smmu: Wire up generic configuration support Robin Murphy
[not found] ` <4439250e01ac071bae8f03a5ccf107ed7ddc0b49.1471975357.git.robin.murphy-5wv7dgnIgG8@public.gmane.org>
2016-09-01 19:02 ` Will Deacon
2016-08-23 19:05 ` [PATCH v5 18/19] iommu/arm-smmu: Set domain geometry Robin Murphy
[not found] ` <d6cedec16fe96a081ea2f9f27378dd1a6f406c72.1471975357.git.robin.murphy-5wv7dgnIgG8@public.gmane.org>
2016-08-31 21:00 ` Auger Eric
2016-08-23 19:05 ` [PATCH v5 19/19] iommu/dma: Add support for mapping MSIs Robin Murphy
[not found] ` <4c901ff0f6355039de55b0bc0df283065f02efa1.1471975357.git.robin.murphy-5wv7dgnIgG8@public.gmane.org>
2016-08-24 8:16 ` Thomas Gleixner
2016-08-24 10:06 ` Robin Murphy
2016-08-25 22:25 ` Auger Eric
[not found] ` <5d8d4ae0-846a-2499-eb46-6f215b87ebd4-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
2016-08-26 1:17 ` Robin Murphy
[not found] ` <20160826021736.571a732f-h2/QxWiDqNo@public.gmane.org>
2016-08-31 20:51 ` Auger Eric
2016-08-23 19:15 ` [PATCH v5 00/19] Generic DT bindings for PCI IOMMUs and ARM SMMU Robin Murphy
[not found] ` <3a9a9369-d8cd-66f4-9344-965c80894bb6-5wv7dgnIgG8@public.gmane.org>
2016-09-01 3:49 ` Anup Patel
[not found] ` <CAALAos-OzPG=+aU8eKEZtx6EFXytPXq09k3QweHvtYCD=mN0mQ-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
2016-09-01 10:10 ` Robin Murphy
2016-09-01 15:22 ` Lorenzo Pieralisi
2016-09-01 19:05 ` Will Deacon
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=20160901151702.GC28180@red-moon \
--to=lorenzo.pieralisi-5wv7dgnigg8@public.gmane.org \
--cc=devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org \
--cc=eric.auger-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org \
--cc=iommu-cunTk1MwBs9QetFLy7KEm3xJsTq8ys+cHZ5vskTnxNA@public.gmane.org \
--cc=jean-philippe.brucker-5wv7dgnIgG8@public.gmane.org \
--cc=joro-zLv9SwRftAIdnm+yROfE0A@public.gmane.org \
--cc=punit.agrawal-5wv7dgnIgG8@public.gmane.org \
--cc=robin.murphy-5wv7dgnIgG8@public.gmane.org \
--cc=thunder.leizhen-hv44wF8Li93QT0dZR+AlfA@public.gmane.org \
--cc=will.deacon-5wv7dgnIgG8@public.gmane.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;
as well as URLs for NNTP newsgroup(s).