linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
From: George Cherian <gcherian@caviumnetworks.com>
To: Corentin Labbe <clabbe.montjoie@gmail.com>, <george.cherian@cavium.com>
Cc: <herbert@gondor.apana.org.au>, <davem@davemloft.net>,
	<linux-kernel@vger.kernel.org>, <linux-crypto@vger.kernel.org>,
	<david.daney@cavium.com>
Subject: Re: [PATCH v3 1/3] drivers: crypto: Add Support for Octeon-tx CPT Engine
Date: Fri, 6 Jan 2017 11:49:39 +0530	[thread overview]
Message-ID: <586F36FB.8020108@caviumnetworks.com> (raw)
In-Reply-To: <20161221132347.GA21051@Red>

Hi Corentin,

Thank you very much for the review.
I was on vacation and now am back, I will fix your comments and send a 
new version.


On 12/21/2016 06:53 PM, Corentin Labbe wrote:
> Hello
>
> I have some comment inline
>
> On Wed, Dec 21, 2016 at 11:56:11AM +0000, george.cherian@cavium.com wrote:
>> From: George Cherian <george.cherian@cavium.com>
>>
>> Enable the Physical Function diver for the Cavium Crypto Engine (CPT)
>
> typo driver
okay
>
>> found in Octeon-tx series of SoC's. CPT is the Cryptographic Acceleration
>> Unit. CPT includes microcoded GigaCypher symmetric engines (SEs) and
>> asymmetric engines (AEs).
>>
>> Signed-off-by: George Cherian <george.cherian@cavium.com>
>> Reviewed-by: David Daney <david.daney@cavium.com>
>> ---
>>   drivers/crypto/cavium/cpt/Kconfig        |  16 +
>>   drivers/crypto/cavium/cpt/Makefile       |   2 +
>>   drivers/crypto/cavium/cpt/cpt_common.h   | 158 +++++++
>>   drivers/crypto/cavium/cpt/cpt_hw_types.h | 658 +++++++++++++++++++++++++++++
>>   drivers/crypto/cavium/cpt/cptpf.h        |  69 +++
>>   drivers/crypto/cavium/cpt/cptpf_main.c   | 703 +++++++++++++++++++++++++++++++
>>   drivers/crypto/cavium/cpt/cptpf_mbox.c   | 163 +++++++
>>   7 files changed, 1769 insertions(+)
>>   create mode 100644 drivers/crypto/cavium/cpt/Kconfig
>>   create mode 100644 drivers/crypto/cavium/cpt/Makefile
>>   create mode 100644 drivers/crypto/cavium/cpt/cpt_common.h
>>   create mode 100644 drivers/crypto/cavium/cpt/cpt_hw_types.h
>>   create mode 100644 drivers/crypto/cavium/cpt/cptpf.h
>>   create mode 100644 drivers/crypto/cavium/cpt/cptpf_main.c
>>   create mode 100644 drivers/crypto/cavium/cpt/cptpf_mbox.c
>>
>> diff --git a/drivers/crypto/cavium/cpt/Kconfig b/drivers/crypto/cavium/cpt/Kconfig
>> new file mode 100644
>> index 0000000..247f1cb
>> --- /dev/null
>> +++ b/drivers/crypto/cavium/cpt/Kconfig
>> @@ -0,0 +1,16 @@
>> +#
>> +# Cavium crypto device configuration
>> +#
>> +
>> +config CRYPTO_DEV_CPT
>> +	tristate
>> +
>> +config CAVIUM_CPT
>> +	tristate "Cavium Cryptographic Accelerator driver"
>> +	depends on ARCH_THUNDER
>> +	select CRYPTO_DEV_CPT
>
> Could you add some COMPILE_TEST ?

You meant depends on ARCH_THUNDER || COMPILE_TEST?
>
> [...]
>> +struct microcode {
>> +	u8 is_mc_valid;
>> +	u8 is_ae;
>> +	u8 group;
>> +	u8 num_cores;
>> +	u32 code_size;
>> +	u64 core_mask;
>> +	u8 version[32];
>
> I see this "32" in some other place, perhaps you could use a define
okay
>
> [...]
>> +#include <linux/version.h>
>> +#include <linux/module.h>
>> +#include <linux/moduleparam.h>
>> +#include <linux/printk.h>
>> +#include <linux/device.h>
>> +#include <linux/interrupt.h>
>> +#include <linux/firmware.h>
>> +#include <linux/pci.h>
>
> Header need to be sorted
will do
>
> [...]
>> +static void cpt_disable_cores(struct cpt_device *cpt, u64 coremask,
>> +			      u8 type, u8 grp)
>> +{
>> +	u64 pf_exe_ctl;
>> +	u32 timeout = 0xFFFFFFFF;
>> +	u64 grpmask = 0;
>> +	struct device *dev = &cpt->pdev->dev;
>> +
>> +	if (type == AE_TYPES)
>> +		coremask = (coremask << cpt->max_se_cores);
>> +
>> +	/* Disengage the cores from groups */
>> +	grpmask = cpt_read_csr64(cpt->reg_base, CPTX_PF_GX_EN(0, grp));
>> +	cpt_write_csr64(cpt->reg_base, CPTX_PF_GX_EN(0, grp),
>> +			(grpmask & ~coremask));
>> +	udelay(CSR_DELAY);
>> +	grp = cpt_read_csr64(cpt->reg_base, CPTX_PF_EXEC_BUSY(0));
>> +	while (grp & coremask) {
>> +		dev_err(dev, "Cores still busy %llx", coremask);
>> +		grp = cpt_read_csr64(cpt->reg_base,
>> +				     CPTX_PF_EXEC_BUSY(0));
>> +		if (timeout--)
>> +			break;
>
> The timeout seems enormous and you will flooding syslog with dev_err()
will reduce.
>
>> +	}
>> +
>> +	/* Disable the cores */
>> +	pf_exe_ctl = cpt_read_csr64(cpt->reg_base, CPTX_PF_EXE_CTL(0));
>> +	cpt_write_csr64(cpt->reg_base, CPTX_PF_EXE_CTL(0),
>> +			(pf_exe_ctl & ~coremask));
>> +	udelay(CSR_DELAY);
>> +}
>> +
>> +/*
>> + * Enable cores specified by coremask
>> + */
>> +static void cpt_enable_cores(struct cpt_device *cpt, u64 coremask,
>> +			     u8 type)
>> +{
>> +	u64 pf_exe_ctl;
>> +
>> +	if (type == AE_TYPES)
>> +		coremask = (coremask << cpt->max_se_cores);
>> +
>> +	pf_exe_ctl = cpt_read_csr64(cpt->reg_base, CPTX_PF_EXE_CTL(0));
>> +	cpt_write_csr64(cpt->reg_base, CPTX_PF_EXE_CTL(0),
>> +			(pf_exe_ctl | coremask));
>> +	udelay(CSR_DELAY);
>> +}
>> +
>> +static void cpt_configure_group(struct cpt_device *cpt, u8 grp,
>> +				u64 coremask, u8 type)
>> +{
>> +	u64 pf_gx_en = 0;
>> +
>> +	if (type == AE_TYPES)
>> +		coremask = (coremask << cpt->max_se_cores);
>> +
>> +	pf_gx_en = cpt_read_csr64(cpt->reg_base, CPTX_PF_GX_EN(0, grp));
>> +	cpt_write_csr64(cpt->reg_base, CPTX_PF_GX_EN(0, grp),
>> +			(pf_gx_en | coremask));
>> +	udelay(CSR_DELAY);
>> +}
>> +
>> +static void cpt_disable_mbox_interrupts(struct cpt_device *cpt)
>> +{
>> +	/* Clear mbox(0) interupts for all vfs */
>> +	cpt_write_csr64(cpt->reg_base, CPTX_PF_MBOX_ENA_W1CX(0, 0), ~0ull);
>> +}
>> +
>> +static void cpt_disable_ecc_interrupts(struct cpt_device *cpt)
>> +{
>> +	/* Clear ecc(0) interupts for all vfs */
>> +	cpt_write_csr64(cpt->reg_base, CPTX_PF_ECC0_ENA_W1C(0), ~0ull);
>> +}
>> +
>> +static void cpt_disable_exec_interrupts(struct cpt_device *cpt)
>> +{
>> +	/* Clear exec interupts for all vfs */
>> +	cpt_write_csr64(cpt->reg_base, CPTX_PF_EXEC_ENA_W1C(0), ~0ull);
>> +}
>> +
>> +static void cpt_disable_all_interrupts(struct cpt_device *cpt)
>> +{
>> +	cpt_disable_mbox_interrupts(cpt);
>> +	cpt_disable_ecc_interrupts(cpt);
>> +	cpt_disable_exec_interrupts(cpt);
>> +}
>> +
>> +static void cpt_enable_mbox_interrupts(struct cpt_device *cpt)
>> +{
>> +	/* Set mbox(0) interupts for all vfs */
>> +	cpt_write_csr64(cpt->reg_base, CPTX_PF_MBOX_ENA_W1SX(0, 0), ~0ull);
>> +}
>> +
>> +static int cpt_load_microcode(struct cpt_device *cpt, struct microcode *mcode)
>> +{
>> +	int ret = 0, core = 0, shift = 0;
>> +	u32 total_cores = 0;
>> +	struct device *dev = &cpt->pdev->dev;
>> +
>> +	if (!mcode || !mcode->code) {
>> +		dev_err(dev, "Either the mcode is null or data is NULL\n");
>> +		return 1;
>
> This is not a standard error code
>
Yes will return standard error codes.
>> +	}
>> +
>> +	if (mcode->code_size == 0) {
>> +		dev_err(dev, "microcode size is 0\n");
>> +		return 1;
>
> the same
>
>> +	}
>> +
>> +	/* Assumes 0-9 are SE cores for UCODE_BASE registers and
>> +	 * AE core bases follow
>> +	 */
>> +	if (mcode->is_ae) {
>> +		core = CPT_MAX_SE_CORES; /* start couting from 10 */
>> +		total_cores = CPT_MAX_TOTAL_CORES; /* upto 15 */
>> +	} else {
>> +		core = 0; /* start couting from 0 */
>> +		total_cores = CPT_MAX_SE_CORES; /* upto 9 */
>> +	}
>> +
>> +	/* Point to microcode for each core of the group */
>> +	for (; core < total_cores ; core++, shift++) {
>> +		if (mcode->core_mask & (1 << shift)) {
>> +			cpt_write_csr64(cpt->reg_base,
>> +					CPTX_PF_ENGX_UCODE_BASE(0, core),
>> +					(u64)mcode->phys_base);
>> +		}
>> +	}
>> +	return ret;
>> +}
>> +
>> +static int do_cpt_init(struct cpt_device *cpt, struct microcode *mcode)
>> +{
>> +	int ret = 0;
>> +	struct device *dev = &cpt->pdev->dev;
>> +
>> +	/* Make device not ready */
>> +	cpt->flags &= ~CPT_FLAG_DEVICE_READY;
>> +	/* Disable All PF interrupts */
>> +	cpt_disable_all_interrupts(cpt);
>> +	/* Calculate mcode group and coremasks */
>> +	if (mcode->is_ae) {
>> +		if (mcode->num_cores > cpt->max_ae_cores) {
>> +			dev_err(dev, "Requested for more cores than available AE cores\n");
>> +			ret = -1;
>
> This is not a standard error code
>
>> +			goto cpt_init_fail;
>> +		}
>> +
>> +		if (cpt->next_group >= CPT_MAX_CORE_GROUPS) {
>> +			dev_err(dev, "Can't load, all eight microcode groups in use");
>> +			return -ENFILE;
>> +		}
>> +
>> +		mcode->group = cpt->next_group;
>> +		/* Convert requested cores to mask */
>> +		mcode->core_mask = GENMASK(mcode->num_cores, 0);
>> +		cpt_disable_cores(cpt, mcode->core_mask, AE_TYPES,
>> +				  mcode->group);
>> +		/* Load microcode for AE engines */
>> +		if (cpt_load_microcode(cpt, mcode)) {
>> +			dev_err(dev, "Microcode load Failed for %s\n",
>> +				mcode->version);
>> +			ret = -1;
>
> again and you loose the error code given by cpt_load_microcode
okay
>
>> +			goto cpt_init_fail;
>> +		}
>> +		cpt->next_group++;
>> +		/* Configure group mask for the mcode */
>> +		cpt_configure_group(cpt, mcode->group, mcode->core_mask,
>> +				    AE_TYPES);
>> +		/* Enable AE cores for the group mask */
>> +		cpt_enable_cores(cpt, mcode->core_mask, AE_TYPES);
>> +	} else {
>> +		if (mcode->num_cores > cpt->max_se_cores) {
>> +			dev_err(dev, "Requested for more cores than available SE cores\n");
>> +			ret = -1;
>
> Again
>
>> +			goto cpt_init_fail;
>> +		}
>> +		if (cpt->next_group >= CPT_MAX_CORE_GROUPS) {
>> +			dev_err(dev, "Can't load, all eight microcode groups in use");
>> +			return -ENFILE;
>> +		}
>> +
>> +		mcode->group = cpt->next_group;
>> +		/* Covert requested cores to mask */
>> +		mcode->core_mask = GENMASK(mcode->num_cores, 0);
>> +		cpt_disable_cores(cpt, mcode->core_mask, SE_TYPES,
>> +				  mcode->group);
>> +		/* Load microcode for SE engines */
>> +		if (cpt_load_microcode(cpt, mcode)) {
>> +			dev_err(dev, "Microcode load Failed for %s\n",
>> +				mcode->version);
>> +			ret = -1;
>
> Again
>
>> +			goto cpt_init_fail;
>> +		}
>> +		cpt->next_group++;
>> +		/* Configure group mask for the mcode */
>> +		cpt_configure_group(cpt, mcode->group, mcode->core_mask,
>> +				    SE_TYPES);
>> +		/* Enable SE cores for the group mask */
>> +		cpt_enable_cores(cpt, mcode->core_mask, SE_TYPES);
>> +	}
>> +
>> +	/* Enabled PF mailbox interrupts */
>> +	cpt_enable_mbox_interrupts(cpt);
>> +	cpt->flags |= CPT_FLAG_DEVICE_READY;
>> +
>> +	return ret;
>> +
>> +cpt_init_fail:
>> +	/* Enabled PF mailbox interrupts */
>> +	cpt_enable_mbox_interrupts(cpt);
>> +
>> +	return ret;
>> +}
>> +
>> +struct ucode_header {
>> +	u8 version[32];
>> +	u32 code_length;
>> +	u32 data_length;
>> +	u64 sram_address;
>> +};
>> +
>> +static int cpt_ucode_load_fw(struct cpt_device *cpt, const u8 *fw, bool is_ae)
>> +{
>> +	const struct firmware *fw_entry;
>> +	struct device *dev = &cpt->pdev->dev;
>> +	struct ucode_header *ucode;
>> +	struct microcode *mcode;
>> +	int j, ret = 0;
>> +
>> +	ret = request_firmware(&fw_entry, fw, dev);
>> +	if (ret)
>> +		return ret;
>
> I think you could also check for a minimal firmware size

Yes will add the check.
>
> [...]
>> +static void cpt_disable_all_cores(struct cpt_device *cpt)
>> +{
>> +	u32 grp, timeout = 0xFFFFFFFF;
>> +	struct device *dev = &cpt->pdev->dev;
>> +
>> +	/* Disengage the cores from groups */
>> +	for (grp = 0; grp < CPT_MAX_CORE_GROUPS; grp++) {
>> +		cpt_write_csr64(cpt->reg_base, CPTX_PF_GX_EN(0, grp), 0);
>> +		udelay(CSR_DELAY);
>> +	}
>> +
>> +	grp = cpt_read_csr64(cpt->reg_base, CPTX_PF_EXEC_BUSY(0));
>> +	while (grp) {
>> +		dev_err(dev, "Cores still busy");
>> +		grp = cpt_read_csr64(cpt->reg_base,
>> +				     CPTX_PF_EXEC_BUSY(0));
>> +		if (timeout--)
>> +			break;
>> +	}
>
> Same problem than cpt_disable_cores
Will adjust the timeout.
>
>> +	/* Disable the cores */
>> +	cpt_write_csr64(cpt->reg_base, CPTX_PF_EXE_CTL(0), 0);
>> +}
>> +
>> +/**
>> + * Ensure all cores are disenganed from all groups by
>
> typo engaged
>
>> + * calling cpt_disable_all_cores() before calling this
>> + * function.
>> + */
>> +static void cpt_unload_microcode(struct cpt_device *cpt)
>> +{
>> +	u32 grp = 0, core;
>> +
>> +	/* Free microcode bases and reset group masks */
>> +	for (grp = 0; grp < CPT_MAX_CORE_GROUPS; grp++) {
>> +		struct microcode *mcode = &cpt->mcode[grp];
>> +
>> +		if (cpt->mcode[grp].code)
>> +			dma_free_coherent(&cpt->pdev->dev, mcode->code_size,
>> +					  mcode->code, mcode->phys_base);
>> +		mcode->code = NULL;
>> +		//mcode->base = NULL;
>
> This is not a standard comment
Will get this removed.
>
>> +	}
>> +	/* Clear UCODE_BASE registers for all engines */
>> +	for (core = 0; core < CPT_MAX_TOTAL_CORES; core++)
>> +		cpt_write_csr64(cpt->reg_base,
>> +				CPTX_PF_ENGX_UCODE_BASE(0, core), 0ull);
>> +}
>> +
>> +static int cpt_device_init(struct cpt_device *cpt)
>> +{
>> +	u64 bist;
>> +	struct device *dev = &cpt->pdev->dev;
>> +
>> +	/* Reset the PF when probed first */
>> +	cpt_reset(cpt);
>> +	mdelay((100));
>
> double parenthesis
okay
>
>> +
>> +	/*Check BIST status*/
>> +	bist = (u64)cpt_check_bist_status(cpt);
>> +	if (bist) {
>> +		dev_err(dev, "RAM BIST failed with code 0x%llx", bist);
>> +		return -ENODEV;
>> +	}
>> +
>> +	bist = cpt_check_exe_bist_status(cpt);
>> +	if (bist) {
>> +		dev_err(dev, "Engine BIST failed with code 0x%llx", bist);
>> +		return -ENODEV;
>> +	}
>> +
>> +	/*Get CLK frequency*/
>> +	/*Get max enabled cores */
>> +	cpt_find_max_enabled_cores(cpt);
>> +	/*Disable all cores*/
>> +	cpt_disable_all_cores(cpt);
>> +	/*Reset device parameters*/
>> +	cpt->next_mc_idx   = 0;
>> +	cpt->next_group = 0;
>> +	/* PF is ready */
>> +	cpt->flags |= CPT_FLAG_DEVICE_READY;
>> +
>> +	return 0;
>> +}
>> +
>> +static int cpt_register_interrupts(struct cpt_device *cpt)
>> +{
>> +	int ret;
>> +	struct device *dev = &cpt->pdev->dev;
>> +
>> +	/* Enable MSI-X */
>> +	ret = cpt_enable_msix(cpt);
>> +	if (ret)
>> +		return ret;
>> +
>> +	/* Register mailbox interrupt handlers */
>> +	ret = request_irq(cpt->msix_entries[CPT_PF_INT_VEC_E_MBOXX(0)].vector,
>> +			  cpt_mbx0_intr_handler, 0, "CPT Mbox0", cpt);
>> +	if (ret)
>> +		goto fail;
>> +
>> +	cpt->irq_allocated[CPT_PF_INT_VEC_E_MBOXX(0)] = true;
>> +
>> +	/* Enable mailbox interrupt */
>> +	cpt_enable_mbox_interrupts(cpt);
>> +	return 0;
>> +
>> +fail:
>> +	dev_err(dev, "Request irq failed\n");
>> +	cpt_free_all_interrupts(cpt);
>> +	return ret;
>> +}
>> +
>> +static void cpt_unregister_interrupts(struct cpt_device *cpt)
>> +{
>> +	cpt_free_all_interrupts(cpt);
>> +	cpt_disable_msix(cpt);
>> +}
>> +
>> +static int cpt_sriov_init(struct cpt_device *cpt, int num_vfs)
>> +{
>> +	int pos = 0;
>> +	int err;
>> +	u16 total_vf_cnt;
>> +	struct pci_dev *pdev = cpt->pdev;
>> +
>> +	pos = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_SRIOV);
>> +	if (!pos) {
>> +		dev_err(&pdev->dev, "SRIOV capability is not found in PCIe config space\n");
>> +		return -ENODEV;
>> +	}
>> +
>> +	cpt->num_vf_en = num_vfs; /* User requested VFs */
>> +	pci_read_config_word(pdev, (pos + PCI_SRIOV_TOTAL_VF), &total_vf_cnt);
>> +	if (total_vf_cnt < cpt->num_vf_en)
>> +		cpt->num_vf_en = total_vf_cnt;
>> +
>> +	if (!total_vf_cnt)
>> +		return 0;
>> +
>> +	/*Enabled the available VFs */
>> +	err = pci_enable_sriov(pdev, cpt->num_vf_en);
>> +	if (err) {
>> +		dev_err(&pdev->dev, "SRIOV enable failed, num VF is %d\n",
>> +			cpt->num_vf_en);
>> +		cpt->num_vf_en = 0;
>> +		return err;
>> +	}
>> +
>> +	/* TODO: Optionally enable static VQ priorities feature */
>> +
>> +	dev_info(&pdev->dev, "SRIOV enabled, number of VF available %d\n",
>> +		 cpt->num_vf_en);
>> +
>> +	cpt->flags |= CPT_FLAG_SRIOV_ENABLED;
>> +
>> +	return 0;
>> +}
>> +
>> +static int cpt_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
>> +{
>> +	struct device *dev = &pdev->dev;
>> +	struct cpt_device *cpt;
>> +	int err;
>> +
>> +	if (num_vfs > 16) {
>> +		pr_warn("Invalid vf count %d, Resetting it to 4(default)\n",
>> +			num_vfs);
>
> Why not using dev_warn ?
>
>> +		num_vfs = 4;
>> +	}
>> +
>> +	cpt = devm_kzalloc(dev, sizeof(struct cpt_device), GFP_KERNEL);
>
> Use sizeof(*cpt) like checkpatch will said.
>
> [...]
>> +static void cpt_shutdown(struct pci_dev *pdev)
>> +{
>> +	struct cpt_device *cpt = pci_get_drvdata(pdev);
>> +
>> +	if (!cpt)
>> +		return;
>> +
>> +	dev_info(&pdev->dev, "Shutdown device %x:%x.\n",
>> +		 (u32)pdev->vendor, (u32)pdev->device);
>> +
>> +	cpt_unregister_interrupts(cpt);
>> +	pci_release_regions(pdev);
>> +	pci_disable_device(pdev);
>> +	pci_set_drvdata(pdev, NULL);
>> +	kzfree(cpt);
>
> since cpt is allocated with devm_, this kzfree is unnecessary
Noted!!
>
> Thanks
> Regards
> Corentin Labbe
>

  reply	other threads:[~2017-01-06  6:20 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2016-12-21 11:56 [PATCH v3 0/3] Add Support for Cavium Cryptographic Acceleration Unit george.cherian
2016-12-21 11:56 ` [PATCH v3 1/3] drivers: crypto: Add Support for Octeon-tx CPT Engine george.cherian
2016-12-21 13:23   ` Corentin Labbe
2017-01-06  6:19     ` George Cherian [this message]
2016-12-21 11:56 ` [PATCH v3 2/3] drivers: crypto: Add the Virtual Function driver for CPT george.cherian
2016-12-21 14:01   ` Corentin Labbe
2017-01-06  7:03     ` George Cherian
2016-12-21 11:56 ` [PATCH v3 3/3] drivers: crypto: Enable CPT options crypto for build george.cherian

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=586F36FB.8020108@caviumnetworks.com \
    --to=gcherian@caviumnetworks.com \
    --cc=clabbe.montjoie@gmail.com \
    --cc=davem@davemloft.net \
    --cc=david.daney@cavium.com \
    --cc=george.cherian@cavium.com \
    --cc=herbert@gondor.apana.org.au \
    --cc=linux-crypto@vger.kernel.org \
    --cc=linux-kernel@vger.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;
as well as URLs for NNTP newsgroup(s).