DPDK patches and discussions
 help / color / mirror / Atom feed
From: Akhil Goyal <gakhil@marvell.com>
To: Andrew Boyer <andrew.boyer@amd.com>, "dev@dpdk.org" <dev@dpdk.org>
Subject: RE: [EXTERNAL] [PATCH v2 1/9] crypto/ionic: introduce AMD Pensando ionic crypto driver
Date: Thu, 30 May 2024 12:02:46 +0000	[thread overview]
Message-ID: <CO6PR18MB44843FADBAB2C37CAFF351EED8F32@CO6PR18MB4484.namprd18.prod.outlook.com> (raw)
In-Reply-To: <20240430202144.49899-2-andrew.boyer@amd.com>

> Subject: [EXTERNAL] [PATCH v2 1/9] crypto/ionic: introduce AMD Pensando ionic
> crypto driver
> 
Title should be "crypto/ionic: introduce AMD Pensando driver"
Do not repeat words in title.

> +
> +Device Support
> +--------------
> +
> +The ionic crypto PMD currently supports running directly on the device's
> embedded
> +processors. It does not yet support host-side access via PCI.

This can be part of driver limitations.

> +For help running the PMD, please contact AMD Pensando support.
> +
> +Runtime Configuration
> +---------------------
> +
> +None
> +
> diff --git a/drivers/common/ionic/ionic_common.h
> b/drivers/common/ionic/ionic_common.h
> index eb4850e24c..c4a15fdf2b 100644
> --- a/drivers/common/ionic/ionic_common.h
> +++ b/drivers/common/ionic/ionic_common.h
> @@ -32,6 +32,8 @@ struct ionic_dev_bar {
> 
>  __rte_internal
>  void ionic_uio_scan_mnet_devices(void);
> +__rte_internal
> +void ionic_uio_scan_mcrypt_devices(void);
> 
>  __rte_internal
>  void ionic_uio_get_rsrc(const char *name, int idx, struct ionic_dev_bar *bar);
> diff --git a/drivers/common/ionic/ionic_common_uio.c
> b/drivers/common/ionic/ionic_common_uio.c
> index e5c73faf96..c647b22eaf 100644
> --- a/drivers/common/ionic/ionic_common_uio.c
> +++ b/drivers/common/ionic/ionic_common_uio.c
> @@ -23,10 +23,12 @@
> 
>  #define IONIC_MDEV_UNK      "mdev_unknown"
>  #define IONIC_MNIC          "cpu_mnic"
> +#define IONIC_MCRYPT        "cpu_mcrypt"

Any specific reason for using the word mcrypt. Can we use mcrypto everywhere?

> 
>  #define IONIC_MAX_NAME_LEN  20
>  #define IONIC_MAX_MNETS     5
> -#define IONIC_MAX_DEVICES   (IONIC_MAX_MNETS)
> +#define IONIC_MAX_MCPTS     1
> +#define IONIC_MAX_DEVICES   (IONIC_MAX_MNETS + IONIC_MAX_MCPTS)
>  #define IONIC_MAX_U16_IDX   0xFFFF
>  #define IONIC_UIO_MAX_TRIES 32
> 
> @@ -49,6 +51,7 @@ struct ionic_map_tbl
> ionic_mdev_map[IONIC_MAX_DEVICES] = {
>  	{ "net_ionic2", 2, IONIC_MAX_U16_IDX, IONIC_MDEV_UNK },
>  	{ "net_ionic3", 3, IONIC_MAX_U16_IDX, IONIC_MDEV_UNK },
>  	{ "net_ionic4", 4, IONIC_MAX_U16_IDX, IONIC_MDEV_UNK },
> +	{ "crypto_ionic0", 5, IONIC_MAX_U16_IDX, IONIC_MDEV_UNK },
>  };
> 
>  struct uio_name {
> @@ -143,6 +146,49 @@ ionic_uio_scan_mnet_devices(void)
>  	}
>  }
> 
> +void
> +ionic_uio_scan_mcrypt_devices(void)
> +{
> +	struct ionic_map_tbl *map;
> +	char devname[IONIC_MAX_NAME_LEN];
> +	struct uio_name name_cache[IONIC_MAX_DEVICES];
> +	bool done;
> +	int mdev_idx = 0;
> +	int uio_idx;
> +	int i;
> +	static bool scan_done;
> +
> +	if (scan_done)
> +		return;
> +
> +	scan_done = true;
> +
> +	uio_fill_name_cache(name_cache, IONIC_MCRYPT);
> +
> +	for (i = IONIC_MAX_MNETS; i < IONIC_MAX_DEVICES; i++) {
> +		done = false;
> +
> +		while (!done) {
> +			if (mdev_idx > IONIC_MAX_MDEV_SCAN)
> +				break;
> +
> +			/* Look for a matching mcrypt */
> +			snprintf(devname, IONIC_MAX_NAME_LEN,
> +				IONIC_MCRYPT "%d", mdev_idx);
> +			uio_idx = uio_get_idx_for_devname(name_cache,
> devname);
> +			if (uio_idx >= 0) {
> +				map = &ionic_mdev_map[i];
> +				map->uio_idx = (uint16_t)uio_idx;
> +				strlcpy(map->mdev_name, devname,
> +					IONIC_MAX_NAME_LEN);
> +				done = true;
> +			}
> +
> +			mdev_idx++;
> +		}
> +	}
> +}
> +
>  static int
>  uio_get_multi_dev_uionum(const char *name)
>  {
> diff --git a/drivers/common/ionic/version.map
> b/drivers/common/ionic/version.map
> index 484330c437..db532d4ffc 100644
> --- a/drivers/common/ionic/version.map
> +++ b/drivers/common/ionic/version.map
> @@ -2,6 +2,7 @@ INTERNAL {
>  	global:
> 
>  	ionic_uio_scan_mnet_devices;
> +	ionic_uio_scan_mcrypt_devices;
>  	ionic_uio_get_rsrc;
>  	ionic_uio_rel_rsrc;
> 
> diff --git a/drivers/crypto/ionic/ionic_crypto.h
> b/drivers/crypto/ionic/ionic_crypto.h
> new file mode 100644
> index 0000000000..86750f0cbd
> --- /dev/null
> +++ b/drivers/crypto/ionic/ionic_crypto.h
> @@ -0,0 +1,92 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright 2021-2024 Advanced Micro Devices, Inc.
> + */
> +
> +#ifndef _IONIC_CRYPTO_H_
> +#define _IONIC_CRYPTO_H_
> +
> +#include <stdint.h>
> +#include <stdbool.h>
> +#include <inttypes.h>
> +
> +#include <rte_common.h>
> +#include <rte_dev.h>
> +#include <rte_cryptodev.h>
> +#include <cryptodev_pmd.h>
> +#include <rte_log.h>
> +
> +#include "ionic_common.h"
> +#include "ionic_regs.h"
> +
> +/* Devargs */
> +/* NONE */
> +
> +extern int iocpt_logtype;
> +#define RTE_LOGTYPE_IOCPT iocpt_logtype
> +
> +#define IOCPT_PRINT(level, ...)						\
> +	RTE_LOG_LINE_PREFIX(level, IOCPT, "%s(): ", __func__, __VA_ARGS__)
> +
> +#define IOCPT_PRINT_CALL() IOCPT_PRINT(DEBUG, " >>")
> +
> +struct iocpt_dev_bars {
> +	struct ionic_dev_bar bar[IONIC_BARS_MAX];
> +	uint32_t num_bars;
> +};
> +
> +#define IOCPT_DEV_F_INITED		BIT(0)
> +#define IOCPT_DEV_F_UP			BIT(1)
> +#define IOCPT_DEV_F_FW_RESET		BIT(2)
> +
> +/* Combined dev / LIF object */
> +struct iocpt_dev {
> +	const char *name;
> +	struct iocpt_dev_bars bars;
> +
> +	const struct iocpt_dev_intf *intf;
> +	void *bus_dev;
> +	struct rte_cryptodev *crypto_dev;
> +
> +	uint32_t max_qps;
> +	uint32_t max_sessions;
> +	uint16_t state;
> +	uint8_t driver_id;
> +	uint8_t socket_id;
> +
> +	uint64_t features;
> +	uint32_t hw_features;
> +};
> +
> +struct iocpt_dev_intf {
> +	int  (*setup_bars)(struct iocpt_dev *dev);
> +	void (*unmap_bars)(struct iocpt_dev *dev);
> +};
> +
> +static inline int
> +iocpt_setup_bars(struct iocpt_dev *dev)
> +{
> +	if (dev->intf->setup_bars == NULL)
> +		return -EINVAL;
> +
> +	return (*dev->intf->setup_bars)(dev);
> +}
> +
> +int iocpt_probe(void *bus_dev, struct rte_device *rte_dev,
> +	struct iocpt_dev_bars *bars, const struct iocpt_dev_intf *intf,
> +	uint8_t driver_id, uint8_t socket_id);
> +int iocpt_remove(struct rte_device *rte_dev);
> +
> +void iocpt_configure(struct iocpt_dev *dev);
> +void iocpt_deinit(struct iocpt_dev *dev);
> +
> +static inline bool
> +iocpt_is_embedded(void)
> +{
> +#if defined(RTE_LIBRTE_IONIC_PMD_EMBEDDED)
> +	return true;
> +#else
> +	return false;
> +#endif
> +}
> +
> +#endif /* _IONIC_CRYPTO_H_ */
> diff --git a/drivers/crypto/ionic/ionic_crypto_main.c
> b/drivers/crypto/ionic/ionic_crypto_main.c
> new file mode 100644
> index 0000000000..ecbb1cb161
> --- /dev/null
> +++ b/drivers/crypto/ionic/ionic_crypto_main.c
> @@ -0,0 +1,148 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright 2021-2024 Advanced Micro Devices, Inc.
> + */
> +
> +#include <inttypes.h>
> +
> +#include <rte_common.h>
> +#include <rte_malloc.h>
> +#include <rte_bitops.h>
> +
> +#include "ionic_crypto.h"
> +
> +static int
> +iocpt_init(struct iocpt_dev *dev)
> +{
> +	dev->state |= IOCPT_DEV_F_INITED;
> +
> +	return 0;
> +}
> +
> +void
> +iocpt_configure(struct iocpt_dev *dev)
> +{
> +	RTE_SET_USED(dev);
> +}
> +
> +void
> +iocpt_deinit(struct iocpt_dev *dev)
> +{
> +	IOCPT_PRINT_CALL();
> +
> +	if (!(dev->state & IOCPT_DEV_F_INITED))
> +		return;
> +
> +	dev->state &= ~IOCPT_DEV_F_INITED;
> +}
> +
> +static int
> +iocpt_devargs(struct rte_devargs *devargs, struct iocpt_dev *dev)
> +{
> +	RTE_SET_USED(devargs);
> +	RTE_SET_USED(dev);
> +
> +	return 0;
> +}
> +
> +int
> +iocpt_probe(void *bus_dev, struct rte_device *rte_dev,
> +	struct iocpt_dev_bars *bars, const struct iocpt_dev_intf *intf,
> +	uint8_t driver_id, uint8_t socket_id)
> +{
> +	struct rte_cryptodev_pmd_init_params init_params = {
> +		"iocpt",
> +		sizeof(struct iocpt_dev),
> +		socket_id,
> +		RTE_CRYPTODEV_PMD_DEFAULT_MAX_NB_QUEUE_PAIRS
> +	};
> +	struct rte_cryptodev *cdev;
> +	struct iocpt_dev *dev;
> +	uint32_t i;
> +	int err;
> +
> +	/* Multi-process not supported */

It may be highlighted in ionic.rst under limitations.

> +	if (rte_eal_process_type() != RTE_PROC_PRIMARY) {

Print missing here?

> +		err = -EPERM;
> +		goto err;
> +	}
> +
> +	cdev = rte_cryptodev_pmd_create(rte_dev->name, rte_dev,
> &init_params);
> +	if (cdev == NULL) {
> +		IOCPT_PRINT(ERR, "OOM");

Better to use Out of memory.

> +		err = -ENOMEM;
> +		goto err;
> +	}
> +
> +	dev = cdev->data->dev_private;
> +	dev->crypto_dev = cdev;
> +	dev->bus_dev = bus_dev;
> +	dev->intf = intf;
> +	dev->driver_id = driver_id;
> +	dev->socket_id = socket_id;
> +
> +	for (i = 0; i < bars->num_bars; i++) {
> +		struct ionic_dev_bar *bar = &bars->bar[i];
> +
> +		IOCPT_PRINT(DEBUG,
> +			"bar[%u] = { .va = %p, .pa = %#jx, .len = %lu }",
> +			i, bar->vaddr, bar->bus_addr, bar->len);
> +		if (bar->vaddr == NULL) {
> +			IOCPT_PRINT(ERR, "Null bar found, aborting");
> +			err = -EFAULT;
> +			goto err_destroy_crypto_dev;
> +		}
> +
> +		dev->bars.bar[i].vaddr = bar->vaddr;
> +		dev->bars.bar[i].bus_addr = bar->bus_addr;
> +		dev->bars.bar[i].len = bar->len;
> +	}
> +	dev->bars.num_bars = bars->num_bars;
> +
> +	err = iocpt_devargs(rte_dev->devargs, dev);
> +	if (err != 0) {
> +		IOCPT_PRINT(ERR, "Cannot parse device arguments");
> +		goto err_destroy_crypto_dev;
> +	}
> +
> +	err = iocpt_setup_bars(dev);
> +	if (err != 0) {
> +		IOCPT_PRINT(ERR, "Cannot setup BARs: %d, aborting", err);
> +		goto err_destroy_crypto_dev;
> +	}
> +
> +	err = iocpt_init(dev);
> +	if (err != 0) {
> +		IOCPT_PRINT(ERR, "Cannot init device: %d, aborting", err);
> +		goto err_destroy_crypto_dev;
> +	}
> +
> +	return 0;
> +
> +err_destroy_crypto_dev:
> +	rte_cryptodev_pmd_destroy(cdev);
> +err:
> +	return err;
> +}
> +
> +int
> +iocpt_remove(struct rte_device *rte_dev)
> +{
> +	struct rte_cryptodev *cdev;
> +	struct iocpt_dev *dev;
> +
> +	cdev = rte_cryptodev_pmd_get_named_dev(rte_dev->name);
> +	if (cdev == NULL) {
> +		IOCPT_PRINT(DEBUG, "Cannot find device %s", rte_dev->name);
> +		return -ENODEV;
> +	}
> +
> +	dev = cdev->data->dev_private;
> +
> +	iocpt_deinit(dev);
> +
> +	rte_cryptodev_pmd_destroy(cdev);
> +
> +	return 0;
> +}
> +
> +RTE_LOG_REGISTER_DEFAULT(iocpt_logtype, NOTICE);
> diff --git a/drivers/crypto/ionic/ionic_crypto_vdev.c
> b/drivers/crypto/ionic/ionic_crypto_vdev.c
> new file mode 100644
> index 0000000000..a915aa06aa
> --- /dev/null
> +++ b/drivers/crypto/ionic/ionic_crypto_vdev.c
> @@ -0,0 +1,91 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright 2021-2024 Advanced Micro Devices, Inc.
> + */
> +
> +#include <stdint.h>
> +#include <errno.h>
> +
> +#include <rte_errno.h>
> +#include <rte_common.h>
> +#include <rte_log.h>
> +#include <rte_eal.h>
> +#include <bus_vdev_driver.h>
> +#include <rte_dev.h>
> +#include <rte_string_fns.h>
> +
> +#include "ionic_crypto.h"
> +
> +#define IOCPT_VDEV_DEV_BAR          0
> +#define IOCPT_VDEV_INTR_CTL_BAR     1
> +#define IOCPT_VDEV_INTR_CFG_BAR     2
> +#define IOCPT_VDEV_DB_BAR           3
> +#define IOCPT_VDEV_BARS_MAX         4
> +
> +#define IOCPT_VDEV_DEV_INFO_REGS_OFFSET      0x0000
> +#define IOCPT_VDEV_DEV_CMD_REGS_OFFSET       0x0800
> +
> +static int
> +iocpt_vdev_setup_bars(struct iocpt_dev *dev)
> +{
> +	IOCPT_PRINT_CALL();
> +
> +	dev->name = rte_vdev_device_name(dev->bus_dev);
> +
> +	return 0;
> +}
> +
> +static void
> +iocpt_vdev_unmap_bars(struct iocpt_dev *dev)
> +{
> +	struct iocpt_dev_bars *bars = &dev->bars;
> +	uint32_t i;
> +
> +	for (i = 0; i < IOCPT_VDEV_BARS_MAX; i++)
> +		ionic_uio_rel_rsrc(dev->name, i, &bars->bar[i]);
> +}
> +
> +static uint8_t iocpt_vdev_driver_id;
> +static const struct iocpt_dev_intf iocpt_vdev_intf = {
> +	.setup_bars = iocpt_vdev_setup_bars,
> +	.unmap_bars = iocpt_vdev_unmap_bars,
> +};
> +
> +static int
> +iocpt_vdev_probe(struct rte_vdev_device *vdev)
> +{
> +	struct iocpt_dev_bars bars = {};
> +	const char *name = rte_vdev_device_name(vdev);
> +	unsigned int i;
> +
> +	IOCPT_PRINT(NOTICE, "Initializing device %s%s", name,
> +		rte_eal_process_type() == RTE_PROC_SECONDARY ?
> +			" [SECONDARY]" : "");
> +
> +	ionic_uio_scan_mcrypt_devices();
> +
> +	for (i = 0; i < IOCPT_VDEV_BARS_MAX; i++)
> +		ionic_uio_get_rsrc(name, i, &bars.bar[i]);
> +
> +	bars.num_bars = IOCPT_VDEV_BARS_MAX;
> +
> +	return iocpt_probe((void *)vdev, &vdev->device,
> +			&bars, &iocpt_vdev_intf,
> +			iocpt_vdev_driver_id, rte_socket_id());
> +}
> +
> +static int
> +iocpt_vdev_remove(struct rte_vdev_device *vdev)
> +{
> +	return iocpt_remove(&vdev->device);
> +}
> +
> +static struct rte_vdev_driver rte_vdev_iocpt_pmd = {
> +	.probe = iocpt_vdev_probe,
> +	.remove = iocpt_vdev_remove,
> +};
> +
> +static struct cryptodev_driver rte_vdev_iocpt_drv;
> +
> +RTE_PMD_REGISTER_VDEV(crypto_ionic, rte_vdev_iocpt_pmd);
> +RTE_PMD_REGISTER_CRYPTO_DRIVER(rte_vdev_iocpt_drv,
> rte_vdev_iocpt_pmd.driver,
> +		iocpt_vdev_driver_id);
> diff --git a/drivers/crypto/ionic/meson.build b/drivers/crypto/ionic/meson.build
> new file mode 100644
> index 0000000000..4114e13e53
> --- /dev/null
> +++ b/drivers/crypto/ionic/meson.build
> @@ -0,0 +1,13 @@
> +# SPDX-License-Identifier: BSD-3-Clause
> +# Copyright 2021-2024 Advanced Micro Devices, Inc.
> +
> +deps += ['bus_vdev']
> +deps += ['common_ionic']
> +
> +sources = files(
> +        'ionic_crypto_main.c',
> +        'ionic_crypto_vdev.c',
> +)
> +name = 'ionic_crypto'
> +
> +includes += include_directories('../../common/ionic')
> diff --git a/drivers/crypto/meson.build b/drivers/crypto/meson.build
> index ee5377deff..e799861bb6 100644
> --- a/drivers/crypto/meson.build
> +++ b/drivers/crypto/meson.build
> @@ -10,6 +10,7 @@ drivers = [
>          'cnxk',
>          'dpaa_sec',
>          'dpaa2_sec',
> +        'ionic',
>          'ipsec_mb',
>          'mlx5',
>          'mvsam',
> --
> 2.17.1


  reply	other threads:[~2024-05-30 12:02 UTC|newest]

Thread overview: 40+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2024-04-19 19:53 [PATCH 0/6] " Andrew Boyer
2024-04-19 19:53 ` [PATCH 1/6] " Andrew Boyer
2024-04-19 19:53 ` [PATCH 2/6] crypto/ionic: add device and admin command handlers Andrew Boyer
2024-04-22 12:29   ` Boyer, Andrew
2024-04-19 19:53 ` [PATCH 3/6] common/ionic: add crypto vdev support Andrew Boyer
2024-04-19 19:53 ` [PATCH 4/6] crypto/ionic: add device object and " Andrew Boyer
2024-04-19 19:53 ` [PATCH 5/6] crypto/ionic: add datapath and capabilities support Andrew Boyer
2024-04-19 19:53 ` [PATCH 6/6] crypto/ionic: add documentation and connect to build Andrew Boyer
2024-04-24 18:21 ` [EXTERNAL] [PATCH 0/6] crypto/ionic: introduce AMD Pensando ionic crypto driver Akhil Goyal
2024-04-30 20:21 ` [PATCH v2 0/9] " Andrew Boyer
2024-04-30 20:21   ` [PATCH v2 1/9] " Andrew Boyer
2024-05-30 12:02     ` Akhil Goyal [this message]
2024-06-05  1:27       ` [EXTERNAL] " Boyer, Andrew
2024-06-06 10:47         ` Akhil Goyal
2024-06-07 14:27     ` [PATCH v3 0/9] crypto/ionic: introduce AMD Pensando driver Andrew Boyer
2024-06-07 14:27       ` [PATCH v3 1/9] " Andrew Boyer
2024-06-13 10:07         ` [EXTERNAL] " Akhil Goyal
2024-06-13 14:45           ` Boyer, Andrew
2024-06-13 17:58             ` Akhil Goyal
2024-06-07 14:27       ` [PATCH v3 2/9] crypto/ionic: add the firmware interface definition file Andrew Boyer
2024-06-07 14:27       ` [PATCH v3 3/9] crypto/ionic: add device commands Andrew Boyer
2024-06-07 14:27       ` [PATCH v3 4/9] crypto/ionic: add adminq command support Andrew Boyer
2024-06-07 14:27       ` [PATCH v3 5/9] crypto/ionic: add capabilities and basic ops Andrew Boyer
2024-06-07 14:27       ` [PATCH v3 6/9] crypto/ionic: add session support Andrew Boyer
2024-06-07 14:27       ` [PATCH v3 7/9] crypto/ionic: add datapath Andrew Boyer
2024-06-07 14:27       ` [PATCH v3 8/9] crypto/ionic: add a watchdog operation Andrew Boyer
2024-06-07 14:27       ` [PATCH v3 9/9] crypto/ionic: add stats support Andrew Boyer
2024-04-30 20:21   ` [PATCH v2 2/9] crypto/ionic: add the firmware interface definition file Andrew Boyer
2024-04-30 20:21   ` [PATCH v2 3/9] crypto/ionic: add device commands Andrew Boyer
2024-06-01 13:57     ` [EXTERNAL] " Akhil Goyal
2024-06-05  1:23       ` Boyer, Andrew
2024-04-30 20:21   ` [PATCH v2 4/9] crypto/ionic: add adminq command support Andrew Boyer
2024-04-30 20:21   ` [PATCH v2 5/9] crypto/ionic: add capabilities and basic ops Andrew Boyer
2024-04-30 20:21   ` [PATCH v2 6/9] crypto/ionic: add session support Andrew Boyer
2024-04-30 20:21   ` [PATCH v2 7/9] crypto/ionic: add datapath Andrew Boyer
2024-04-30 20:21   ` [PATCH v2 8/9] crypto/ionic: add a watchdog operation Andrew Boyer
2024-04-30 20:21   ` [PATCH v2 9/9] crypto/ionic: add stats support Andrew Boyer
2024-06-04 13:28   ` [PATCH v2 0/9] crypto/ionic: introduce AMD Pensando ionic crypto driver Boyer, Andrew
2024-06-04 14:49     ` Boyer, Andrew
2024-06-06 10:57       ` Akhil Goyal

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=CO6PR18MB44843FADBAB2C37CAFF351EED8F32@CO6PR18MB4484.namprd18.prod.outlook.com \
    --to=gakhil@marvell.com \
    --cc=andrew.boyer@amd.com \
    --cc=dev@dpdk.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).