From: "Ard Biesheuvel" <ard.biesheuvel@linaro.org>
To: Laszlo Ersek <lersek@redhat.com>
Cc: edk2-devel-groups-io <devel@edk2.groups.io>,
"Igor Mammedov" <imammedo@redhat.com>,
"Jiewen Yao" <jiewen.yao@intel.com>,
"Jordan Justen" <jordan.l.justen@intel.com>,
"Michael Kinney" <michael.d.kinney@intel.com>,
"Philippe Mathieu-Daudé" <philmd@redhat.com>
Subject: Re: [PATCH v2 16/16] OvmfPkg/CpuS3DataDxe: enable S3 resume after CPU hotplug
Date: Mon, 2 Mar 2020 15:16:46 +0100 [thread overview]
Message-ID: <CAKv+Gu-s3a4uEWTJ0VSZotkL7zQruCiHVGSYRfX8uR7+-MmkwA@mail.gmail.com> (raw)
In-Reply-To: <20200226221156.29589-17-lersek@redhat.com>
On Wed, 26 Feb 2020 at 23:12, Laszlo Ersek <lersek@redhat.com> wrote:
>
> During normal boot, CpuS3DataDxe allocates
>
> - an empty CPU_REGISTER_TABLE entry in the
> "ACPI_CPU_DATA.PreSmmInitRegisterTable" array, and
>
> - an empty CPU_REGISTER_TABLE entry in the "ACPI_CPU_DATA.RegisterTable"
> array,
>
> for every CPU whose APIC ID CpuS3DataDxe can learn.
>
> Currently EFI_MP_SERVICES_PROTOCOL is used for both determining the number
> of CPUs -- the protocol reports the present-at-boot CPU count --, and for
> retrieving the APIC IDs of those CPUs.
>
> Consequently, if a CPU is hot-plugged at OS runtime, then S3 resume
> breaks. That's because PiSmmCpuDxeSmm will not find the hot-added CPU's
> APIC ID associated with any CPU_REGISTER_TABLE object, in the SMRAM copies
> of either of the "RegisterTable" and "PreSmmInitRegisterTable" arrays. The
> failure to match the hot-added CPU's APIC ID trips the ASSERT() in
> SetRegister() [UefiCpuPkg/PiSmmCpuDxeSmm/CpuS3.c].
>
> If "PcdQ35SmramAtDefaultSmbase" is TRUE, then:
>
> - prepare CPU_REGISTER_TABLE objects for all possible CPUs, not just the
> present-at-boot CPUs (PlatformPei stored the possible CPU count to
> "PcdCpuMaxLogicalProcessorNumber");
>
> - use QEMU_CPUHP_CMD_GET_ARCH_ID for filling in the "InitialApicId" fields
> of the CPU_REGISTER_TABLE objects.
>
> This provides full APIC ID coverage for PiSmmCpuDxeSmm during S3 resume,
> accommodating CPUs hot-added at OS runtime.
>
> This patch is best reviewed with
>
> $ git show -b
>
> Cc: Ard Biesheuvel <ard.biesheuvel@linaro.org>
> Cc: Igor Mammedov <imammedo@redhat.com>
> Cc: Jiewen Yao <jiewen.yao@intel.com>
> Cc: Jordan Justen <jordan.l.justen@intel.com>
> Cc: Michael Kinney <michael.d.kinney@intel.com>
> Cc: Philippe Mathieu-Daudé <philmd@redhat.com>
> Ref: https://bugzilla.tianocore.org/show_bug.cgi?id=1512
> Signed-off-by: Laszlo Ersek <lersek@redhat.com>
> Acked-by: Ard Biesheuvel <ard.biesheuvel@linaro.org>
Reviewed-by: Ard Biesheuvel <ard.biesheuvel@linaro.org>
> ---
>
> Notes:
> v2:
>
> - Pick up Ard's Acked-by, which is conditional on approval from Intel
> reviewers on Cc. (I'd like to save Ard the churn of re-acking
> unmodified patches.)
>
> OvmfPkg/CpuS3DataDxe/CpuS3DataDxe.inf | 4 +
> OvmfPkg/CpuS3DataDxe/CpuS3Data.c | 91 ++++++++++++++------
> 2 files changed, 70 insertions(+), 25 deletions(-)
>
> diff --git a/OvmfPkg/CpuS3DataDxe/CpuS3DataDxe.inf b/OvmfPkg/CpuS3DataDxe/CpuS3DataDxe.inf
> index f9679e0c33b3..ceae1d4078c7 100644
> --- a/OvmfPkg/CpuS3DataDxe/CpuS3DataDxe.inf
> +++ b/OvmfPkg/CpuS3DataDxe/CpuS3DataDxe.inf
> @@ -16,46 +16,50 @@
> #
> ##
>
> [Defines]
> INF_VERSION = 1.29
> BASE_NAME = CpuS3DataDxe
> FILE_GUID = 229B7EFD-DA02-46B9-93F4-E20C009F94E9
> MODULE_TYPE = DXE_DRIVER
> VERSION_STRING = 1.0
> ENTRY_POINT = CpuS3DataInitialize
>
> # The following information is for reference only and not required by the build
> # tools.
> #
> # VALID_ARCHITECTURES = IA32 X64
>
> [Sources]
> CpuS3Data.c
>
> [Packages]
> MdeModulePkg/MdeModulePkg.dec
> MdePkg/MdePkg.dec
> + OvmfPkg/OvmfPkg.dec
> UefiCpuPkg/UefiCpuPkg.dec
>
> [LibraryClasses]
> BaseLib
> BaseMemoryLib
> DebugLib
> + IoLib
> MemoryAllocationLib
> MtrrLib
> UefiBootServicesTableLib
> UefiDriverEntryPoint
>
> [Guids]
> gEfiEndOfDxeEventGroupGuid ## CONSUMES ## Event
>
> [Protocols]
> gEfiMpServiceProtocolGuid ## CONSUMES
>
> [Pcd]
> gEfiMdeModulePkgTokenSpaceGuid.PcdAcpiS3Enable ## CONSUMES
> gUefiCpuPkgTokenSpaceGuid.PcdCpuApStackSize ## CONSUMES
> + gUefiCpuPkgTokenSpaceGuid.PcdCpuMaxLogicalProcessorNumber ## CONSUMES
> gUefiCpuPkgTokenSpaceGuid.PcdCpuS3DataAddress ## PRODUCES
> + gUefiOvmfPkgTokenSpaceGuid.PcdQ35SmramAtDefaultSmbase ## CONSUMES
>
> [Depex]
> gEfiMpServiceProtocolGuid
> diff --git a/OvmfPkg/CpuS3DataDxe/CpuS3Data.c b/OvmfPkg/CpuS3DataDxe/CpuS3Data.c
> index 8bb9807cd501..bac7285aa2f3 100644
> --- a/OvmfPkg/CpuS3DataDxe/CpuS3Data.c
> +++ b/OvmfPkg/CpuS3DataDxe/CpuS3Data.c
> @@ -4,51 +4,55 @@ ACPI CPU Data initialization module
> This module initializes the ACPI_CPU_DATA structure and registers the address
> of this structure in the PcdCpuS3DataAddress PCD. This is a generic/simple
> version of this module. It does not provide a machine check handler or CPU
> register initialization tables for ACPI S3 resume. It also only supports the
> number of CPUs reported by the MP Services Protocol, so this module does not
> support hot plug CPUs. This module can be copied into a CPU specific package
> and customized if these additional features are required.
>
> Copyright (c) 2013 - 2017, Intel Corporation. All rights reserved.<BR>
> Copyright (c) 2015 - 2020, Red Hat, Inc.
>
> SPDX-License-Identifier: BSD-2-Clause-Patent
>
> **/
>
> #include <PiDxe.h>
>
> #include <AcpiCpuData.h>
>
> #include <Library/BaseLib.h>
> #include <Library/BaseMemoryLib.h>
> #include <Library/DebugLib.h>
> +#include <Library/IoLib.h>
> #include <Library/MemoryAllocationLib.h>
> #include <Library/MtrrLib.h>
> #include <Library/UefiBootServicesTableLib.h>
>
> #include <Protocol/MpService.h>
> #include <Guid/EventGroup.h>
>
> +#include <IndustryStandard/Q35MchIch9.h>
> +#include <IndustryStandard/QemuCpuHotplug.h>
> +
> //
> // Data structure used to allocate ACPI_CPU_DATA and its supporting structures
> //
> typedef struct {
> ACPI_CPU_DATA AcpiCpuData;
> MTRR_SETTINGS MtrrTable;
> IA32_DESCRIPTOR GdtrProfile;
> IA32_DESCRIPTOR IdtrProfile;
> } ACPI_CPU_DATA_EX;
>
> /**
> Allocate EfiACPIMemoryNVS memory.
>
> @param[in] Size Size of memory to allocate.
>
> @return Allocated address for output.
>
> **/
> VOID *
> AllocateAcpiNvsMemory (
> IN UINTN Size
> )
> @@ -144,89 +148,101 @@ CpuS3DataOnEndOfDxe (
> to the address that ACPI_CPU_DATA is allocated at.
>
> @param[in] ImageHandle The firmware allocated handle for the EFI image.
> @param[in] SystemTable A pointer to the EFI System Table.
>
> @retval EFI_SUCCESS The entry point is executed successfully.
> @retval EFI_UNSUPPORTED Do not support ACPI S3.
> @retval other Some error occurs when executing this entry point.
>
> **/
> EFI_STATUS
> EFIAPI
> CpuS3DataInitialize (
> IN EFI_HANDLE ImageHandle,
> IN EFI_SYSTEM_TABLE *SystemTable
> )
> {
> EFI_STATUS Status;
> ACPI_CPU_DATA_EX *AcpiCpuDataEx;
> ACPI_CPU_DATA *AcpiCpuData;
> EFI_MP_SERVICES_PROTOCOL *MpServices;
> UINTN NumberOfCpus;
> - UINTN NumberOfEnabledProcessors;
> VOID *Stack;
> UINTN TableSize;
> CPU_REGISTER_TABLE *RegisterTable;
> UINTN Index;
> EFI_PROCESSOR_INFORMATION ProcessorInfoBuffer;
> UINTN GdtSize;
> UINTN IdtSize;
> VOID *Gdt;
> VOID *Idt;
> EFI_EVENT Event;
> ACPI_CPU_DATA *OldAcpiCpuData;
> + BOOLEAN FetchPossibleApicIds;
>
> if (!PcdGetBool (PcdAcpiS3Enable)) {
> return EFI_UNSUPPORTED;
> }
>
> //
> // Set PcdCpuS3DataAddress to the base address of the ACPI_CPU_DATA structure
> //
> OldAcpiCpuData = (ACPI_CPU_DATA *) (UINTN) PcdGet64 (PcdCpuS3DataAddress);
>
> AcpiCpuDataEx = AllocateZeroPages (sizeof (ACPI_CPU_DATA_EX));
> ASSERT (AcpiCpuDataEx != NULL);
> AcpiCpuData = &AcpiCpuDataEx->AcpiCpuData;
>
> //
> - // Get MP Services Protocol
> + // The "SMRAM at default SMBASE" feature guarantees that
> + // QEMU_CPUHP_CMD_GET_ARCH_ID too is available.
> //
> - Status = gBS->LocateProtocol (
> - &gEfiMpServiceProtocolGuid,
> - NULL,
> - (VOID **)&MpServices
> - );
> - ASSERT_EFI_ERROR (Status);
> + FetchPossibleApicIds = PcdGetBool (PcdQ35SmramAtDefaultSmbase);
>
> - //
> - // Get the number of CPUs
> - //
> - Status = MpServices->GetNumberOfProcessors (
> - MpServices,
> - &NumberOfCpus,
> - &NumberOfEnabledProcessors
> - );
> - ASSERT_EFI_ERROR (Status);
> + if (FetchPossibleApicIds) {
> + NumberOfCpus = PcdGet32 (PcdCpuMaxLogicalProcessorNumber);
> + } else {
> + UINTN NumberOfEnabledProcessors;
> +
> + //
> + // Get MP Services Protocol
> + //
> + Status = gBS->LocateProtocol (
> + &gEfiMpServiceProtocolGuid,
> + NULL,
> + (VOID **)&MpServices
> + );
> + ASSERT_EFI_ERROR (Status);
> +
> + //
> + // Get the number of CPUs
> + //
> + Status = MpServices->GetNumberOfProcessors (
> + MpServices,
> + &NumberOfCpus,
> + &NumberOfEnabledProcessors
> + );
> + ASSERT_EFI_ERROR (Status);
> + }
> AcpiCpuData->NumberOfCpus = (UINT32)NumberOfCpus;
>
> //
> // Initialize ACPI_CPU_DATA fields
> //
> AcpiCpuData->StackSize = PcdGet32 (PcdCpuApStackSize);
> AcpiCpuData->ApMachineCheckHandlerBase = 0;
> AcpiCpuData->ApMachineCheckHandlerSize = 0;
> AcpiCpuData->GdtrProfile = (EFI_PHYSICAL_ADDRESS)(UINTN)&AcpiCpuDataEx->GdtrProfile;
> AcpiCpuData->IdtrProfile = (EFI_PHYSICAL_ADDRESS)(UINTN)&AcpiCpuDataEx->IdtrProfile;
> AcpiCpuData->MtrrTable = (EFI_PHYSICAL_ADDRESS)(UINTN)&AcpiCpuDataEx->MtrrTable;
>
> //
> // Allocate stack space for all CPUs.
> // Use ACPI NVS memory type because this data will be directly used by APs
> // in S3 resume phase in long mode. Also during S3 resume, the stack buffer
> // will only be used as scratch space. i.e. we won't read anything from it
> // before we write to it, in PiSmmCpuDxeSmm.
> //
> Stack = AllocateAcpiNvsMemory (NumberOfCpus * AcpiCpuData->StackSize);
> ASSERT (Stack != NULL);
> AcpiCpuData->StackAddress = (EFI_PHYSICAL_ADDRESS)(UINTN)Stack;
> @@ -244,58 +260,83 @@ CpuS3DataInitialize (
> IdtSize = AcpiCpuDataEx->IdtrProfile.Limit + 1;
> Gdt = AllocateZeroPages (GdtSize + IdtSize);
> ASSERT (Gdt != NULL);
> Idt = (VOID *)((UINTN)Gdt + GdtSize);
> CopyMem (Gdt, (VOID *)AcpiCpuDataEx->GdtrProfile.Base, GdtSize);
> CopyMem (Idt, (VOID *)AcpiCpuDataEx->IdtrProfile.Base, IdtSize);
> AcpiCpuDataEx->GdtrProfile.Base = (UINTN)Gdt;
> AcpiCpuDataEx->IdtrProfile.Base = (UINTN)Idt;
>
> if (OldAcpiCpuData != NULL) {
> AcpiCpuData->RegisterTable = OldAcpiCpuData->RegisterTable;
> AcpiCpuData->PreSmmInitRegisterTable = OldAcpiCpuData->PreSmmInitRegisterTable;
> AcpiCpuData->ApLocation = OldAcpiCpuData->ApLocation;
> CopyMem (&AcpiCpuData->CpuStatus, &OldAcpiCpuData->CpuStatus, sizeof (CPU_STATUS_INFORMATION));
> } else {
> //
> // Allocate buffer for empty RegisterTable and PreSmmInitRegisterTable for all CPUs
> //
> TableSize = 2 * NumberOfCpus * sizeof (CPU_REGISTER_TABLE);
> RegisterTable = (CPU_REGISTER_TABLE *)AllocateZeroPages (TableSize);
> ASSERT (RegisterTable != NULL);
>
> + if (FetchPossibleApicIds) {
> + //
> + // Write a valid selector so that other hotplug registers can be
> + // accessed.
> + //
> + IoWrite32 (ICH9_CPU_HOTPLUG_BASE + QEMU_CPUHP_W_CPU_SEL, 0);
> + //
> + // We'll be fetching the APIC IDs.
> + //
> + IoWrite8 (ICH9_CPU_HOTPLUG_BASE + QEMU_CPUHP_W_CMD,
> + QEMU_CPUHP_CMD_GET_ARCH_ID);
> + }
> for (Index = 0; Index < NumberOfCpus; Index++) {
> - Status = MpServices->GetProcessorInfo (
> - MpServices,
> - Index,
> - &ProcessorInfoBuffer
> - );
> - ASSERT_EFI_ERROR (Status);
> + UINT32 InitialApicId;
>
> - RegisterTable[Index].InitialApicId = (UINT32)ProcessorInfoBuffer.ProcessorId;
> + if (FetchPossibleApicIds) {
> + IoWrite32 (ICH9_CPU_HOTPLUG_BASE + QEMU_CPUHP_W_CPU_SEL,
> + (UINT32)Index);
> + InitialApicId = IoRead32 (
> + ICH9_CPU_HOTPLUG_BASE + QEMU_CPUHP_RW_CMD_DATA);
> + } else {
> + Status = MpServices->GetProcessorInfo (
> + MpServices,
> + Index,
> + &ProcessorInfoBuffer
> + );
> + ASSERT_EFI_ERROR (Status);
> + InitialApicId = (UINT32)ProcessorInfoBuffer.ProcessorId;
> + }
> +
> + DEBUG ((DEBUG_VERBOSE, "%a: Index=%05Lu ApicId=0x%08x\n", __FUNCTION__,
> + (UINT64)Index, InitialApicId));
> +
> + RegisterTable[Index].InitialApicId = InitialApicId;
> RegisterTable[Index].TableLength = 0;
> RegisterTable[Index].AllocatedSize = 0;
> RegisterTable[Index].RegisterTableEntry = 0;
>
> - RegisterTable[NumberOfCpus + Index].InitialApicId = (UINT32)ProcessorInfoBuffer.ProcessorId;
> + RegisterTable[NumberOfCpus + Index].InitialApicId = InitialApicId;
> RegisterTable[NumberOfCpus + Index].TableLength = 0;
> RegisterTable[NumberOfCpus + Index].AllocatedSize = 0;
> RegisterTable[NumberOfCpus + Index].RegisterTableEntry = 0;
> }
> AcpiCpuData->RegisterTable = (EFI_PHYSICAL_ADDRESS)(UINTN)RegisterTable;
> AcpiCpuData->PreSmmInitRegisterTable = (EFI_PHYSICAL_ADDRESS)(UINTN)(RegisterTable + NumberOfCpus);
> }
>
> //
> // Set PcdCpuS3DataAddress to the base address of the ACPI_CPU_DATA structure
> //
> Status = PcdSet64S (PcdCpuS3DataAddress, (UINT64)(UINTN)AcpiCpuData);
> ASSERT_EFI_ERROR (Status);
>
> //
> // Register EFI_END_OF_DXE_EVENT_GROUP_GUID event.
> // The notification function allocates StartupVector and saves MTRRs for ACPI_CPU_DATA
> //
> Status = gBS->CreateEventEx (
> EVT_NOTIFY_SIGNAL,
> TPL_CALLBACK,
> CpuS3DataOnEndOfDxe,
> --
> 2.19.1.3.g30247aa5d201
>
next prev parent reply other threads:[~2020-03-02 14:16 UTC|newest]
Thread overview: 53+ messages / expand[flat|nested] mbox.gz Atom feed top
2020-02-26 22:11 [PATCH v2 00/16] OvmfPkg: support VCPU hotplug with -D SMM_REQUIRE Laszlo Ersek
2020-02-26 22:11 ` [PATCH v2 01/16] MdeModulePkg/PiSmmCore: log SMM image start failure Laszlo Ersek
2020-03-02 12:47 ` [edk2-devel] " Laszlo Ersek
2020-03-02 12:55 ` Liming Gao
2020-03-02 13:46 ` Philippe Mathieu-Daudé
2020-03-03 0:46 ` Dong, Eric
2020-02-26 22:11 ` [PATCH v2 02/16] UefiCpuPkg/PiSmmCpuDxeSmm: fix S3 Resume for CPU hotplug Laszlo Ersek
2020-02-28 3:05 ` [edk2-devel] " Dong, Eric
2020-02-28 10:50 ` Laszlo Ersek
2020-03-04 12:23 ` Laszlo Ersek
2020-03-04 14:36 ` Dong, Eric
2020-02-26 22:11 ` [PATCH v2 03/16] OvmfPkg: clone SmmCpuPlatformHookLib from UefiCpuPkg Laszlo Ersek
2020-03-02 13:27 ` Ard Biesheuvel
2020-03-02 13:49 ` Philippe Mathieu-Daudé
2020-02-26 22:11 ` [PATCH v2 04/16] OvmfPkg: enable SMM Monarch Election in PiSmmCpuDxeSmm Laszlo Ersek
2020-03-02 13:32 ` Ard Biesheuvel
2020-02-26 22:11 ` [PATCH v2 05/16] OvmfPkg: enable CPU hotplug support " Laszlo Ersek
2020-03-02 13:33 ` Ard Biesheuvel
2020-02-26 22:11 ` [PATCH v2 06/16] OvmfPkg/CpuHotplugSmm: introduce skeleton for CPU Hotplug SMM driver Laszlo Ersek
2020-03-02 13:44 ` Ard Biesheuvel
2020-02-26 22:11 ` [PATCH v2 07/16] OvmfPkg/CpuHotplugSmm: add hotplug register block helper functions Laszlo Ersek
2020-03-02 13:24 ` Philippe Mathieu-Daudé
2020-03-02 13:45 ` [edk2-devel] " Ard Biesheuvel
2020-02-26 22:11 ` [PATCH v2 08/16] OvmfPkg/CpuHotplugSmm: define the QEMU_CPUHP_CMD_GET_ARCH_ID macro Laszlo Ersek
2020-03-02 13:22 ` Philippe Mathieu-Daudé
2020-03-02 13:45 ` Ard Biesheuvel
2020-02-26 22:11 ` [PATCH v2 09/16] OvmfPkg/CpuHotplugSmm: add function for collecting CPUs with events Laszlo Ersek
2020-03-02 13:49 ` Ard Biesheuvel
2020-03-02 20:34 ` Philippe Mathieu-Daudé
2020-03-03 10:31 ` Laszlo Ersek
2020-02-26 22:11 ` [PATCH v2 10/16] OvmfPkg/CpuHotplugSmm: collect " Laszlo Ersek
2020-03-02 13:58 ` Ard Biesheuvel
2020-02-26 22:11 ` [PATCH v2 11/16] OvmfPkg/CpuHotplugSmm: introduce Post-SMM Pen for hot-added CPUs Laszlo Ersek
2020-03-02 14:02 ` [edk2-devel] " Ard Biesheuvel
2020-02-26 22:11 ` [PATCH v2 12/16] OvmfPkg/CpuHotplugSmm: introduce First SMI Handler " Laszlo Ersek
2020-03-02 14:03 ` Ard Biesheuvel
2020-02-26 22:11 ` [PATCH v2 13/16] OvmfPkg/CpuHotplugSmm: complete root MMI handler for CPU hotplug Laszlo Ersek
2020-03-02 14:05 ` Ard Biesheuvel
2020-02-26 22:11 ` [PATCH v2 14/16] OvmfPkg: clone CpuS3DataDxe from UefiCpuPkg Laszlo Ersek
2020-03-02 13:44 ` Philippe Mathieu-Daudé
2020-03-02 14:06 ` Ard Biesheuvel
2020-02-26 22:11 ` [PATCH v2 15/16] OvmfPkg/CpuS3DataDxe: superficial cleanups Laszlo Ersek
2020-03-02 13:25 ` Philippe Mathieu-Daudé
2020-03-02 14:06 ` Ard Biesheuvel
2020-02-26 22:11 ` [PATCH v2 16/16] OvmfPkg/CpuS3DataDxe: enable S3 resume after CPU hotplug Laszlo Ersek
2020-03-02 14:16 ` Ard Biesheuvel [this message]
2020-03-02 15:46 ` [edk2-devel] [PATCH v2 00/16] OvmfPkg: support VCPU hotplug with -D SMM_REQUIRE Boris Ostrovsky
2020-03-02 19:22 ` Laszlo Ersek
2020-03-02 19:59 ` Laszlo Ersek
2020-03-04 13:29 ` Philippe Mathieu-Daudé
2020-03-04 18:09 ` Laszlo Ersek
2020-03-04 12:29 ` Laszlo Ersek
2020-03-05 8:32 ` Laszlo Ersek
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-list 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=CAKv+Gu-s3a4uEWTJ0VSZotkL7zQruCiHVGSYRfX8uR7+-MmkwA@mail.gmail.com \
--to=devel@edk2.groups.io \
/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