All of lore.kernel.org
 help / color / mirror / Atom feed
From: Dan Scally <djrscally@gmail.com>
To: Dan Carpenter <dan.carpenter@oracle.com>
Cc: yong.zhi@intel.com, sakari.ailus@linux.intel.com,
	bingbu.cao@intel.com, tian.shu.qiu@intel.com, mchehab@kernel.org,
	gregkh@linuxfoundation.org, davem@davemloft.net, robh@kernel.org,
	devel@driverdev.osuosl.org, jorhand@linux.microsoft.com,
	kieran.bingham@ideasonboard.com, linux-kernel@vger.kernel.org,
	linux-media@vger.kernel.org, kitakar@gmail.com
Subject: Re: [RFC PATCH] Add bridge driver to connect sensors to CIO2 device via software nodes on ACPI platforms
Date: Fri, 18 Sep 2020 23:50:51 +0100	[thread overview]
Message-ID: <8d19e234-2e87-693a-c3e7-a8433ae83d61@gmail.com> (raw)
In-Reply-To: <20200917093407.GK4282@kadam>

Hi Dan -  thanks for all your comments. Sorry it took a while to get to
yours.

On 17/09/2020 10:34, Dan Carpenter wrote:
> On Wed, Sep 16, 2020 at 10:36:18PM +0100, Daniel Scally wrote:
>> diff --git a/drivers/media/pci/intel/ipu3/ipu3-cio2.c b/drivers/media/pci/intel/ipu3/ipu3-cio2.c
>> index 92f5eadf2c99..fd941d2c7581 100644
>> --- a/drivers/media/pci/intel/ipu3/ipu3-cio2.c
>> +++ b/drivers/media/pci/intel/ipu3/ipu3-cio2.c
>> @@ -1719,6 +1719,59 @@ static void cio2_queues_exit(struct cio2_device *cio2)
>>  		cio2_queue_exit(cio2, &cio2->queue[i]);
>>  }
>>  
>> +static int cio2_probe_can_progress(struct pci_dev *pci_dev)
>> +{
>> +	void *sensor;
>> +
>> +	/*
>> +	 * On ACPI platforms, we need to probe _after_ sensors wishing to connect
>> +	 * to cio2 have added a device link. If there are no consumers yet, then
>> +	 * we need to defer. The .sync_state() callback will then be called after
>> +	 * all linked sensors have probed
>> +	 */
>> +
>> +	if (IS_ENABLED(CONFIG_ACPI)) {
> Reverse this condition.
>
> 	if (!IS_ENABLED(CONFIG_ACPI))
> 		return 0;
>
>
Yeah, much better.
>> +		sensor = (struct device *)list_first_entry_or_null(
>> +								&pci_dev->dev.links.consumers,
>> +								struct dev_links_info,
>> +								consumers);
>> +
>> +		if (!sensor)
>> +			return -EPROBE_DEFER;
> Get rid of the cast.
>
> 	if (list_empty(&pci_dev->dev.links.consumers))
> 		return -EPROBE_DEFER;
>
> 	return 0;
>
Also much better, though I think possibly this whole section will be
going away now after some of the other pointers...
>> +		cio2 = dev_get_drvdata(dev);
>> +
>> +		if (!cio2) {
> Delete the blank line between the call and the test.  They're part of
> the same step.  "cio2" can't be NULL anyway, so delete the test.
Thanks - I'll skip blank lines in that situation in future
>> +
>> +	if (ret < 0) {
>> +		dev_err(dev, "Failed to fetch SSDB data\n");
>> +		return ret;
>> +	}
>> +
>> +	sensor->link = sensor_data.link;
>> +	sensor->lanes = sensor_data.lanes;
>> +	sensor->mclkspeed = sensor_data.mclkspeed;
>> +
>> +	return 0;
>> +}
>> +
>> +static int create_endpoint_properties(struct device *dev,
>> +				      struct sensor_bios_data *ssdb,
>> +				      struct property_entry *sensor_props,
>> +				      struct property_entry *cio2_props)
>> +{
>> +		u32 *data_lanes;
>> +		int i;
> Indented too far.
>
>> +
>> +		data_lanes = devm_kmalloc(dev, sizeof(u32) * (int)ssdb->lanes,
> No need for the cast.  Use devm_kmalloc_array().
Ah - TIL that that exists, thanks.
>> +					  GFP_KERNEL);
>> +
>> +		if (!data_lanes) {
>> +			dev_err(dev,
>> +				"Couldn't allocate memory for data lanes array\n");
> Delete the error message (checkpatch.pl --strict).
And that too - I wasn't using the --strict flag, I'll do that next time
>> +
>> +		sensor_props[0] = PROPERTY_ENTRY_U32("clock-frequency",
>> +						     ssdb->mclkspeed);
>> +		sensor_props[1] = PROPERTY_ENTRY_U32("bus-type", 5);
>> +		sensor_props[2] = PROPERTY_ENTRY_U32("clock-lanes", 0);
>> +		sensor_props[3] = PROPERTY_ENTRY_U32_ARRAY_LEN("data-lanes",
>> +							       data_lanes,
>> +							       (int)ssdb->lanes);
>> +		sensor_props[4] = remote_endpoints[(bridge.n_sensors * 2) + ENDPOINT_SENSOR];
>> +		sensor_props[5] = PROPERTY_ENTRY_NULL;
>> +
>> +		cio2_props[0] = PROPERTY_ENTRY_U32_ARRAY_LEN("data-lanes",
>> +							     data_lanes,
>> +							     (int)ssdb->lanes);
>> +		cio2_props[1] = remote_endpoints[(bridge.n_sensors * 2) + ENDPOINT_CIO2];
>> +		cio2_props[2] = PROPERTY_ENTRY_NULL;
>> +
>> +		return 0;
>> +}
>> +
>> +static int connect_supported_devices(void)
>> +{
>> +	struct acpi_device *adev;
>> +	struct device *dev;
>> +	struct sensor_bios_data ssdb;
>> +	struct sensor *sensor;
>> +	struct property_entry *sensor_props;
>> +	struct property_entry *cio2_props;
>> +	struct fwnode_handle *fwnode;
>> +	struct software_node *nodes;
>> +	struct v4l2_subdev *sd;
>> +	int i, ret;
>> +
>> +	for (i = 0; i < ARRAY_SIZE(supported_devices); i++) {
>> +		adev = acpi_dev_get_first_match_dev(supported_devices[i],
>> +						    NULL, -1);
>> +
>> +		if (!adev)
>> +			continue;
>> +
>> +		dev = bus_find_device_by_acpi_dev(&i2c_bus_type, adev);
>> +
>> +		if (!dev) {
>> +			pr_info("ACPI match for %s, but it has no i2c device\n",
>> +				supported_devices[i]);
>> +			continue;
>> +		}
>> +
>> +		if (!dev->driver_data) {
>> +			pr_info("ACPI match for %s, but it has no driver\n",
>> +				supported_devices[i]);
> put_device(dev);
Good catch, thank you.
>> +	}
>> +
>> +	ret = connect_supported_devices();
>> +
>> +	if ((ret < 0) || (bridge.n_sensors <= 0)) {
>> +		pr_err("cio2_bridge: Failed to connect any devices\n");
>> +		goto out;
> If (bridge.n_sensors <= 0) is true then we need to set ret = -EINVAL
> or something.  Really .n_sensors can't be negative.
>
> The name "out" is a crappy label name because it doesn't say what the
> goto does.  When I scroll down then it turns out that "goto out;" calls
> a free_everything() function.  That kind of error handling is always
> buggy.
>
> There are several typical bugs.  1) Something leaks because the error
> handling style is too complicated to be audited.  2)  Dereferencing
> uninitialized pointers.  3)  Undoing something which hasn't been done.
>
> I believe that in this case one bug is with the handling of the
> bridge.cio2_fwnode.  We "restore" it back to the original state
> as soon as we have a non-NULL bridge.cio2 instead of waiting until we
> have stored the original state.
>
> The best way to do error handling is this.
>
> Every function cleans up after itself.  The connect_supported_devices()
> function is a bit special because it's a loop.  I would would write it
> so that if it fails then it cleans up the partial loop iteration and
> then at the end it cleans up all the failed loop iterations.
>
> 	for (i = 0; i < ARRAY_SIZE(supported_devices); i++) {
> 		a = frob();
> 		if (!a)
> 			goto unwind;
> 		b = frob();
> 		if (!b) {
> 			free(a);
> 			goto unwind;
> 		}
> 		...
> 	}
>
> 	return 0;
>
> unwind:
> 	for (i = 0; i < bridge.n_sensors; i++) {
> 		free(b);
> 		free(a);
> 	}
> 	bridge.n_sensors = 0;
>
> 	return ret;
>
> The problem with cio2_bridge_unregister_sensors() is that it doesn't
> clean up partial iterations through the loop.  (Missing calls to
> put_device(dev)).
>
> Loops are complicated but the rest is simple.  1) Every allocation
> function needs a matching cleanup function.  2) Use good label names
> which say what the goto does.  3)  The goto should free the most recent
> successful allocation.
>
> 	a = frob();
> 	if (!a)
> 		return -ENOMEM;
>
> 	b = frob();
> 	if (!b) {
> 		ret = -ENOMEM;
> 		goto free_a;
> 	}
>
> 	c = frob();
> 	if (!c) {
> 		ret = -ENOMEM;
> 		goto free_b;
> 	}
>
> 	return 0;
>
> free_b:
> 	free(b);
> free_a:
> 	free(a);
>
> 	return ret;
>
> The free function doesn't have any if statements.
>
> void free_function()
> {
> 	free(c);
> 	free(b);
> 	free(a);
> }
>
> The reviewer only needs to keep track of the most recent allocation
> and verify that the goto free_foo matches what should be freed.  This
> system means the code is auditable (no leaks), you never free anything
> which wasn't allocated.
>
This  section and the other comments on error handling was really
helpful - I appreciate you taking the time to explain so thoroughly.


WARNING: multiple messages have this Message-ID (diff)
From: Dan Scally <djrscally@gmail.com>
To: Dan Carpenter <dan.carpenter@oracle.com>
Cc: devel@driverdev.osuosl.org, robh@kernel.org,
	jorhand@linux.microsoft.com, linux-media@vger.kernel.org,
	gregkh@linuxfoundation.org, kieran.bingham@ideasonboard.com,
	linux-kernel@vger.kernel.org, kitakar@gmail.com,
	sakari.ailus@linux.intel.com, bingbu.cao@intel.com,
	mchehab@kernel.org, davem@davemloft.net, tian.shu.qiu@intel.com,
	yong.zhi@intel.com
Subject: Re: [RFC PATCH] Add bridge driver to connect sensors to CIO2 device via software nodes on ACPI platforms
Date: Fri, 18 Sep 2020 23:50:51 +0100	[thread overview]
Message-ID: <8d19e234-2e87-693a-c3e7-a8433ae83d61@gmail.com> (raw)
In-Reply-To: <20200917093407.GK4282@kadam>

Hi Dan -  thanks for all your comments. Sorry it took a while to get to
yours.

On 17/09/2020 10:34, Dan Carpenter wrote:
> On Wed, Sep 16, 2020 at 10:36:18PM +0100, Daniel Scally wrote:
>> diff --git a/drivers/media/pci/intel/ipu3/ipu3-cio2.c b/drivers/media/pci/intel/ipu3/ipu3-cio2.c
>> index 92f5eadf2c99..fd941d2c7581 100644
>> --- a/drivers/media/pci/intel/ipu3/ipu3-cio2.c
>> +++ b/drivers/media/pci/intel/ipu3/ipu3-cio2.c
>> @@ -1719,6 +1719,59 @@ static void cio2_queues_exit(struct cio2_device *cio2)
>>  		cio2_queue_exit(cio2, &cio2->queue[i]);
>>  }
>>  
>> +static int cio2_probe_can_progress(struct pci_dev *pci_dev)
>> +{
>> +	void *sensor;
>> +
>> +	/*
>> +	 * On ACPI platforms, we need to probe _after_ sensors wishing to connect
>> +	 * to cio2 have added a device link. If there are no consumers yet, then
>> +	 * we need to defer. The .sync_state() callback will then be called after
>> +	 * all linked sensors have probed
>> +	 */
>> +
>> +	if (IS_ENABLED(CONFIG_ACPI)) {
> Reverse this condition.
>
> 	if (!IS_ENABLED(CONFIG_ACPI))
> 		return 0;
>
>
Yeah, much better.
>> +		sensor = (struct device *)list_first_entry_or_null(
>> +								&pci_dev->dev.links.consumers,
>> +								struct dev_links_info,
>> +								consumers);
>> +
>> +		if (!sensor)
>> +			return -EPROBE_DEFER;
> Get rid of the cast.
>
> 	if (list_empty(&pci_dev->dev.links.consumers))
> 		return -EPROBE_DEFER;
>
> 	return 0;
>
Also much better, though I think possibly this whole section will be
going away now after some of the other pointers...
>> +		cio2 = dev_get_drvdata(dev);
>> +
>> +		if (!cio2) {
> Delete the blank line between the call and the test.  They're part of
> the same step.  "cio2" can't be NULL anyway, so delete the test.
Thanks - I'll skip blank lines in that situation in future
>> +
>> +	if (ret < 0) {
>> +		dev_err(dev, "Failed to fetch SSDB data\n");
>> +		return ret;
>> +	}
>> +
>> +	sensor->link = sensor_data.link;
>> +	sensor->lanes = sensor_data.lanes;
>> +	sensor->mclkspeed = sensor_data.mclkspeed;
>> +
>> +	return 0;
>> +}
>> +
>> +static int create_endpoint_properties(struct device *dev,
>> +				      struct sensor_bios_data *ssdb,
>> +				      struct property_entry *sensor_props,
>> +				      struct property_entry *cio2_props)
>> +{
>> +		u32 *data_lanes;
>> +		int i;
> Indented too far.
>
>> +
>> +		data_lanes = devm_kmalloc(dev, sizeof(u32) * (int)ssdb->lanes,
> No need for the cast.  Use devm_kmalloc_array().
Ah - TIL that that exists, thanks.
>> +					  GFP_KERNEL);
>> +
>> +		if (!data_lanes) {
>> +			dev_err(dev,
>> +				"Couldn't allocate memory for data lanes array\n");
> Delete the error message (checkpatch.pl --strict).
And that too - I wasn't using the --strict flag, I'll do that next time
>> +
>> +		sensor_props[0] = PROPERTY_ENTRY_U32("clock-frequency",
>> +						     ssdb->mclkspeed);
>> +		sensor_props[1] = PROPERTY_ENTRY_U32("bus-type", 5);
>> +		sensor_props[2] = PROPERTY_ENTRY_U32("clock-lanes", 0);
>> +		sensor_props[3] = PROPERTY_ENTRY_U32_ARRAY_LEN("data-lanes",
>> +							       data_lanes,
>> +							       (int)ssdb->lanes);
>> +		sensor_props[4] = remote_endpoints[(bridge.n_sensors * 2) + ENDPOINT_SENSOR];
>> +		sensor_props[5] = PROPERTY_ENTRY_NULL;
>> +
>> +		cio2_props[0] = PROPERTY_ENTRY_U32_ARRAY_LEN("data-lanes",
>> +							     data_lanes,
>> +							     (int)ssdb->lanes);
>> +		cio2_props[1] = remote_endpoints[(bridge.n_sensors * 2) + ENDPOINT_CIO2];
>> +		cio2_props[2] = PROPERTY_ENTRY_NULL;
>> +
>> +		return 0;
>> +}
>> +
>> +static int connect_supported_devices(void)
>> +{
>> +	struct acpi_device *adev;
>> +	struct device *dev;
>> +	struct sensor_bios_data ssdb;
>> +	struct sensor *sensor;
>> +	struct property_entry *sensor_props;
>> +	struct property_entry *cio2_props;
>> +	struct fwnode_handle *fwnode;
>> +	struct software_node *nodes;
>> +	struct v4l2_subdev *sd;
>> +	int i, ret;
>> +
>> +	for (i = 0; i < ARRAY_SIZE(supported_devices); i++) {
>> +		adev = acpi_dev_get_first_match_dev(supported_devices[i],
>> +						    NULL, -1);
>> +
>> +		if (!adev)
>> +			continue;
>> +
>> +		dev = bus_find_device_by_acpi_dev(&i2c_bus_type, adev);
>> +
>> +		if (!dev) {
>> +			pr_info("ACPI match for %s, but it has no i2c device\n",
>> +				supported_devices[i]);
>> +			continue;
>> +		}
>> +
>> +		if (!dev->driver_data) {
>> +			pr_info("ACPI match for %s, but it has no driver\n",
>> +				supported_devices[i]);
> put_device(dev);
Good catch, thank you.
>> +	}
>> +
>> +	ret = connect_supported_devices();
>> +
>> +	if ((ret < 0) || (bridge.n_sensors <= 0)) {
>> +		pr_err("cio2_bridge: Failed to connect any devices\n");
>> +		goto out;
> If (bridge.n_sensors <= 0) is true then we need to set ret = -EINVAL
> or something.  Really .n_sensors can't be negative.
>
> The name "out" is a crappy label name because it doesn't say what the
> goto does.  When I scroll down then it turns out that "goto out;" calls
> a free_everything() function.  That kind of error handling is always
> buggy.
>
> There are several typical bugs.  1) Something leaks because the error
> handling style is too complicated to be audited.  2)  Dereferencing
> uninitialized pointers.  3)  Undoing something which hasn't been done.
>
> I believe that in this case one bug is with the handling of the
> bridge.cio2_fwnode.  We "restore" it back to the original state
> as soon as we have a non-NULL bridge.cio2 instead of waiting until we
> have stored the original state.
>
> The best way to do error handling is this.
>
> Every function cleans up after itself.  The connect_supported_devices()
> function is a bit special because it's a loop.  I would would write it
> so that if it fails then it cleans up the partial loop iteration and
> then at the end it cleans up all the failed loop iterations.
>
> 	for (i = 0; i < ARRAY_SIZE(supported_devices); i++) {
> 		a = frob();
> 		if (!a)
> 			goto unwind;
> 		b = frob();
> 		if (!b) {
> 			free(a);
> 			goto unwind;
> 		}
> 		...
> 	}
>
> 	return 0;
>
> unwind:
> 	for (i = 0; i < bridge.n_sensors; i++) {
> 		free(b);
> 		free(a);
> 	}
> 	bridge.n_sensors = 0;
>
> 	return ret;
>
> The problem with cio2_bridge_unregister_sensors() is that it doesn't
> clean up partial iterations through the loop.  (Missing calls to
> put_device(dev)).
>
> Loops are complicated but the rest is simple.  1) Every allocation
> function needs a matching cleanup function.  2) Use good label names
> which say what the goto does.  3)  The goto should free the most recent
> successful allocation.
>
> 	a = frob();
> 	if (!a)
> 		return -ENOMEM;
>
> 	b = frob();
> 	if (!b) {
> 		ret = -ENOMEM;
> 		goto free_a;
> 	}
>
> 	c = frob();
> 	if (!c) {
> 		ret = -ENOMEM;
> 		goto free_b;
> 	}
>
> 	return 0;
>
> free_b:
> 	free(b);
> free_a:
> 	free(a);
>
> 	return ret;
>
> The free function doesn't have any if statements.
>
> void free_function()
> {
> 	free(c);
> 	free(b);
> 	free(a);
> }
>
> The reviewer only needs to keep track of the most recent allocation
> and verify that the goto free_foo matches what should be freed.  This
> system means the code is auditable (no leaks), you never free anything
> which wasn't allocated.
>
This  section and the other comments on error handling was really
helpful - I appreciate you taking the time to explain so thoroughly.

_______________________________________________
devel mailing list
devel@linuxdriverproject.org
http://driverdev.linuxdriverproject.org/mailman/listinfo/driverdev-devel

  parent reply	other threads:[~2020-09-18 22:50 UTC|newest]

Thread overview: 68+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-09-16 21:36 [RFC PATCH] Add bridge driver to connect sensors to CIO2 device via software nodes on ACPI platforms Daniel Scally
2020-09-16 21:36 ` Daniel Scally
2020-09-17  0:17 ` kernel test robot
2020-09-17  3:02 ` kernel test robot
2020-09-17  7:53 ` Greg KH
2020-09-17  7:53   ` Greg KH
2020-09-17  9:47   ` Dan Scally
2020-09-17  9:47     ` Dan Scally
2020-09-17 10:15     ` Dan Carpenter
2020-09-17 10:15       ` Dan Carpenter
2020-09-17 10:24       ` Dan Scally
2020-09-17 10:24         ` Dan Scally
2020-09-17 13:28     ` Kieran Bingham
2020-09-17 13:28       ` Kieran Bingham
2020-09-17 14:08       ` Andy Shevchenko
2020-09-17 14:08         ` Andy Shevchenko
2020-09-17 14:19         ` Kieran Bingham
2020-09-17 14:19           ` Kieran Bingham
2020-09-17 14:36           ` Andy Shevchenko
2020-09-17 14:36             ` Andy Shevchenko
2020-09-17  9:34 ` Dan Carpenter
2020-09-17  9:34   ` Dan Carpenter
2020-09-17 10:19   ` Joe Perches
2020-09-17 10:19     ` Joe Perches
2020-09-18 22:50   ` Dan Scally [this message]
2020-09-18 22:50     ` Dan Scally
2020-09-17 10:33 ` Sakari Ailus
2020-09-17 10:33   ` Sakari Ailus
2020-09-17 10:49   ` Dan Carpenter
2020-09-17 10:49     ` Dan Carpenter
2020-09-17 12:25     ` Andy Shevchenko
2020-09-17 12:25       ` Andy Shevchenko
2020-09-17 13:15       ` Dan Carpenter
2020-09-17 13:15         ` Dan Carpenter
2020-09-18  6:40     ` Sakari Ailus
2020-09-18  6:40       ` Sakari Ailus
2020-09-18  8:16       ` Dan Carpenter
2020-09-18  8:16         ` Dan Carpenter
2020-09-17 10:52   ` Dan Scally
2020-09-17 10:52     ` Dan Scally
2020-09-17 12:45     ` Andy Shevchenko
2020-09-17 12:45       ` Andy Shevchenko
2020-09-17 13:36       ` Dan Scally
2020-09-17 13:36         ` Dan Scally
2020-09-17 14:14         ` Andy Shevchenko
2020-09-17 14:14           ` Andy Shevchenko
2020-09-17 21:25           ` Daniel Scally
2020-09-17 21:25             ` Daniel Scally
2020-09-17 14:44         ` Andy Shevchenko
2020-09-17 14:44           ` Andy Shevchenko
2020-09-18  7:51       ` Sakari Ailus
2020-09-18  7:51         ` Sakari Ailus
2020-09-18 13:07         ` Andy Shevchenko
2020-09-18 13:07           ` Andy Shevchenko
2020-09-21 13:33           ` Dan Scally
2020-09-21 13:33             ` Dan Scally
2020-09-21 14:33             ` Andy Shevchenko
2020-09-21 14:33               ` Andy Shevchenko
2020-09-23  9:39   ` Dan Scally
2020-09-23  9:39     ` Dan Scally
2020-09-28 11:37   ` Dan Scally
2020-09-28 11:37     ` Dan Scally
2020-09-18  8:03 ` Dan Carpenter
2020-09-18  8:03   ` Dan Carpenter
2020-09-18  8:09   ` Dan Scally
2020-09-18  8:09     ` Dan Scally
2020-09-20 21:34 ` kernel test robot
2020-09-20 21:34 ` [RFC PATCH] cio2_sync_state() can be static kernel test robot

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=8d19e234-2e87-693a-c3e7-a8433ae83d61@gmail.com \
    --to=djrscally@gmail.com \
    --cc=bingbu.cao@intel.com \
    --cc=dan.carpenter@oracle.com \
    --cc=davem@davemloft.net \
    --cc=devel@driverdev.osuosl.org \
    --cc=gregkh@linuxfoundation.org \
    --cc=jorhand@linux.microsoft.com \
    --cc=kieran.bingham@ideasonboard.com \
    --cc=kitakar@gmail.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-media@vger.kernel.org \
    --cc=mchehab@kernel.org \
    --cc=robh@kernel.org \
    --cc=sakari.ailus@linux.intel.com \
    --cc=tian.shu.qiu@intel.com \
    --cc=yong.zhi@intel.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.