From: Ard Biesheuvel <ard.biesheuvel@linaro.org>
To: "Ni, Ruiyu" <ruiyu.ni@intel.com>
Cc: "edk2-devel@lists.01.org" <edk2-devel@lists.01.org>,
"Gao, Liming" <liming.gao@intel.com>,
"Kinney, Michael D" <michael.d.kinney@intel.com>,
"afish@apple.com" <afish@apple.com>,
"mw@semihalf.com" <mw@semihalf.com>,
"leif.lindholm@linaro.org" <leif.lindholm@linaro.org>
Subject: Re: [PATCH v3 3/5] MdeModulePkg: implement generic PCI I/O driver for non-discoverable devices
Date: Fri, 18 Nov 2016 12:30:08 +0000 [thread overview]
Message-ID: <CAKv+Gu91SCK-H=jjK=i0ULeHaXjREsZhamhsM+hJH5kzqyR4Hw@mail.gmail.com> (raw)
In-Reply-To: <734D49CCEBEEF84792F5B80ED585239D58E7C886@SHSMSX104.ccr.corp.intel.com>
On 17 November 2016 at 03:29, Ni, Ruiyu <ruiyu.ni@intel.com> wrote:
>
>
> Thanks/Ray
>
>> -----Original Message-----
>> From: Ard Biesheuvel [mailto:ard.biesheuvel@linaro.org]
>> Sent: Thursday, November 17, 2016 12:59 AM
>> To: edk2-devel@lists.01.org; Gao, Liming <liming.gao@intel.com>; Ni, Ruiyu
>> <ruiyu.ni@intel.com>; Kinney, Michael D <michael.d.kinney@intel.com>
>> Cc: afish@apple.com; mw@semihalf.com; leif.lindholm@linaro.org; Ard
>> Biesheuvel <ard.biesheuvel@linaro.org>
>> Subject: [PATCH v3 3/5] MdeModulePkg: implement generic PCI I/O driver
>> for non-discoverable devices
>>
>> This implements support for non-discoverable PCI compatible devices, i.e,
>> devices that are not on a PCI bus but that can be controlled by generic PCI
>> drivers in EDK2.
>>
>> This is implemented as a UEFI driver, which means we take full advantage of
>> the UEFI driver model, and only instantiate those devices that are necessary
>> for booting.
>>
>> Care is taken to deal with DMA addressing limitations: DMA mappings and
>> allocations are moved below 4 GB if the PCI driver has not informed us that
>> the device being driven is 64-bit DMA capable. DMA is implemented as
>> coherent, support for non-coherent DMA is implemented by a subsequent
>> patch.
>>
>> Contributed-under: TianoCore Contribution Agreement 1.0
>> Signed-off-by: Ard Biesheuvel <ard.biesheuvel@linaro.org>
>> ---
>>
>> MdeModulePkg/Bus/Pci/NonDiscoverablePciDeviceDxe/ComponentName.c
>> | 75 ++
>>
>> MdeModulePkg/Bus/Pci/NonDiscoverablePciDeviceDxe/NonDiscoverablePci
>> DeviceDxe.c | 223 +++++
>>
>> MdeModulePkg/Bus/Pci/NonDiscoverablePciDeviceDxe/NonDiscoverablePci
>> DeviceDxe.inf | 42 +
>>
>> MdeModulePkg/Bus/Pci/NonDiscoverablePciDeviceDxe/NonDiscoverablePci
>> DeviceIo.c | 868 ++++++++++++++++++++
>>
>> MdeModulePkg/Bus/Pci/NonDiscoverablePciDeviceDxe/NonDiscoverablePci
>> DeviceIo.h | 84 ++
>> MdeModulePkg/MdeModulePkg.dsc | 1 +
>> 6 files changed, 1293 insertions(+)
>>
[...]
>> diff --git
>> a/MdeModulePkg/Bus/Pci/NonDiscoverablePciDeviceDxe/NonDiscoverable
>> PciDeviceIo.c
>> b/MdeModulePkg/Bus/Pci/NonDiscoverablePciDeviceDxe/NonDiscoverable
>> PciDeviceIo.c
>> new file mode 100644
>> index 000000000000..23466318bfc1
>> --- /dev/null
>> +++
>> b/MdeModulePkg/Bus/Pci/NonDiscoverablePciDeviceDxe/NonDiscoverable
>> Pc
>> +++ iDeviceIo.c
>> @@ -0,0 +1,868 @@
>> +/** @file
>> +
>> + Copyright (c) 2008 - 2009, Apple Inc. All rights reserved.<BR>
>> + Copyright (c) 2016, Linaro, Ltd. All rights reserved.<BR>
>> +
>> + This program and the accompanying materials are licensed and made
>> + available under the terms and conditions of the BSD License which
>> + accompanies this distribution. The full text of the license may be
>> + found at http://opensource.org/licenses/bsd-license.php
>> +
>> + THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS"
>> BASIS,
>> + WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER
>> EXPRESS OR IMPLIED.
>> +
>> +**/
>> +
>> +#include "NonDiscoverablePciDeviceIo.h"
>> +
>> +#include <IndustryStandard/Acpi.h>
>> +
>> +#include <Protocol/PciRootBridgeIo.h>
>> +
>> +typedef struct {
>> + EFI_PHYSICAL_ADDRESS AllocAddress;
>> + VOID *HostAddress;
>> + EFI_PCI_IO_PROTOCOL_OPERATION Operation;
>> + UINTN NumberOfBytes;
>> +} NON_DISCOVERABLE_PCI_DEVICE_MAP_INFO;
>> +
>> +//
>> +// Get the resource associated with BAR number 'BarIndex'. This takes
>> +into // account that 64-bit resource use up 2 BAR slots, which means
>> +that BAR // n + 1 is reported as not found if BAR n refers to a 64-bit resource.
>> +//
>> +STATIC
>> +EFI_STATUS
>> +GetBarResource (
>> + IN NON_DISCOVERABLE_PCI_DEVICE *Dev,
>> + IN UINT8 BarIndex,
>> + OUT EFI_ACPI_ADDRESS_SPACE_DESCRIPTOR **Descriptor
>> + )
>> +{
>> + EFI_ACPI_ADDRESS_SPACE_DESCRIPTOR *Desc;
>> +
>> + if (BarIndex < Dev->BarOffset) {
>> + return EFI_NOT_FOUND;
>> + }
>> +
>> + BarIndex -= Dev->BarOffset;
>> +
>> + for (Desc = Dev->Device->Resources;
>> + Desc->Desc != ACPI_END_TAG_DESCRIPTOR;
>> + Desc = (VOID *)((UINT8 *)Desc + Desc->Len + 3)) {
>> +
>> + if (BarIndex == 0) {
>> + *Descriptor = Desc;
>> + return EFI_SUCCESS;
>> + }
>> +
>> + if (Desc->AddrSpaceGranularity == 64) {
>> + if (BarIndex == 1) {
>> + break;
>> + }
>> + BarIndex -= 2;
>
> 1. I think we only need to decrease *one* from BarIndex instead of two,
> even for 64bit BAR.
> I remember MdeModulePkg/PciBusDxe ever had a bug to treat 64bit
> BAR as 2 logic BARs but it should have fixed long ago.
>
Right. So does that mean BarIndex '2' refers to BARs 4 and 5 if we
have 3 64-bit BARs in total?
>> + } else {
>> + BarIndex -= 1;
>> + }
>> + }
>> + return EFI_NOT_FOUND;
>> +}
>> +
>> +STATIC
>> +EFI_STATUS
>> +PciIoPollMem (
>> + IN EFI_PCI_IO_PROTOCOL *This,
>> + IN EFI_PCI_IO_PROTOCOL_WIDTH Width,
>> + IN UINT8 BarIndex,
>> + IN UINT64 Offset,
>> + IN UINT64 Mask,
>> + IN UINT64 Value,
>> + IN UINT64 Delay,
>> + OUT UINT64 *Result
>> + )
>> +{
>> + ASSERT (FALSE);
>> + return EFI_UNSUPPORTED;
>> +}
>> +
>> +STATIC
>> +EFI_STATUS
>> +PciIoPollIo (
>> + IN EFI_PCI_IO_PROTOCOL *This,
>> + IN EFI_PCI_IO_PROTOCOL_WIDTH Width,
>> + IN UINT8 BarIndex,
>> + IN UINT64 Offset,
>> + IN UINT64 Mask,
>> + IN UINT64 Value,
>> + IN UINT64 Delay,
>> + OUT UINT64 *Result
>> + )
>> +{
>> + ASSERT (FALSE);
>> + return EFI_UNSUPPORTED;
>> +}
>> +
>> +STATIC
>> +EFI_STATUS
>> +PciIoMemRW (
>> + IN EFI_PCI_IO_PROTOCOL_WIDTH Width,
>> + IN UINTN Count,
>> + IN UINTN DstStride,
>> + IN VOID *Dst,
>> + IN UINTN SrcStride,
>> + OUT CONST VOID *Src
>> + )
>> +{
>> + volatile UINT8 *Dst8;
>> + volatile UINT16 *Dst16;
>> + volatile UINT32 *Dst32;
>> + volatile CONST UINT8 *Src8;
>> + volatile CONST UINT16 *Src16;
>> + volatile CONST UINT32 *Src32;
>> +
>> + //
>> + // Loop for each iteration and move the data // switch (Width &
>> + 0x3) { case EfiPciWidthUint8:
>> + Dst8 = (UINT8 *)Dst;
>> + Src8 = (UINT8 *)Src;
>> + for (;Count > 0; Count--, Dst8 += DstStride, Src8 += SrcStride) {
>> + *Dst8 = *Src8;
>> + }
>> + break;
>> + case EfiPciWidthUint16:
>> + Dst16 = (UINT16 *)Dst;
>> + Src16 = (UINT16 *)Src;
>> + for (;Count > 0; Count--, Dst16 += DstStride, Src16 += SrcStride) {
>> + *Dst16 = *Src16;
>> + }
>> + break;
>> + case EfiPciWidthUint32:
>> + Dst32 = (UINT32 *)Dst;
>> + Src32 = (UINT32 *)Src;
>> + for (;Count > 0; Count--, Dst32 += DstStride, Src32 += SrcStride) {
>> + *Dst32 = *Src32;
>> + }
>> + break;
>> + default:
>> + return EFI_INVALID_PARAMETER;
>> + }
>> +
>> + return EFI_SUCCESS;
>> +}
>> +
>> +STATIC
>> +EFI_STATUS
>> +PciIoMemRead (
>> + IN EFI_PCI_IO_PROTOCOL *This,
>> + IN EFI_PCI_IO_PROTOCOL_WIDTH Width,
>> + IN UINT8 BarIndex,
>> + IN UINT64 Offset,
>> + IN UINTN Count,
>> + IN OUT VOID *Buffer
>> + )
>> +{
>> + NON_DISCOVERABLE_PCI_DEVICE *Dev;
>> + UINTN AlignMask;
>> + VOID *Address;
>> + EFI_ACPI_ADDRESS_SPACE_DESCRIPTOR *Desc;
>> + EFI_STATUS Status;
>> +
>> + if (Buffer == NULL) {
>> + return EFI_INVALID_PARAMETER;
>> + }
>> +
>> + Dev = NON_DISCOVERABLE_PCI_DEVICE_FROM_PCI_IO(This);
>> +
>> + //
>> + // Only allow accesses to the BARs we emulate // Status =
>> + GetBarResource (Dev, BarIndex, &Desc); if (EFI_ERROR (Status)) {
>> + return Status;
>> + }
>> +
>> + if (Offset + (Count << (Width & 0x3)) > Desc->AddrLen) {
>> + return EFI_UNSUPPORTED;
>> + }
>> +
>> + Address = (VOID *)((UINTN)Desc->AddrRangeMin + Offset); AlignMask =
>> + (1 << (Width & 0x03)) - 1; if ((UINTN)Address & AlignMask) {
>> + return EFI_INVALID_PARAMETER;
>> + }
>> +
>> + switch (Width) {
>> + case EfiPciWidthUint8:
>> + case EfiPciWidthUint16:
>> + case EfiPciWidthUint32:
>> + case EfiPciWidthUint64:
>> + return PciIoMemRW (Width, Count, 1, Buffer, 1, Address);
>> +
>> + case EfiPciWidthFifoUint8:
>> + case EfiPciWidthFifoUint16:
>> + case EfiPciWidthFifoUint32:
>> + case EfiPciWidthFifoUint64:
>> + return PciIoMemRW (Width, Count, 1, Buffer, 0, Address);
>> +
>> + case EfiPciWidthFillUint8:
>> + case EfiPciWidthFillUint16:
>> + case EfiPciWidthFillUint32:
>> + case EfiPciWidthFillUint64:
>> + return PciIoMemRW (Width, Count, 0, Buffer, 1, Address);
>> +
>> + default:
>> + break;
>> + }
>> + return EFI_INVALID_PARAMETER;
>> +}
>> +
>> +STATIC
>> +EFI_STATUS
>> +PciIoMemWrite (
>> + IN EFI_PCI_IO_PROTOCOL *This,
>> + IN EFI_PCI_IO_PROTOCOL_WIDTH Width,
>> + IN UINT8 BarIndex,
>> + IN UINT64 Offset,
>> + IN UINTN Count,
>> + IN OUT VOID *Buffer
>> + )
>> +{
>> + NON_DISCOVERABLE_PCI_DEVICE *Dev;
>> + UINTN AlignMask;
>> + VOID *Address;
>> + EFI_ACPI_ADDRESS_SPACE_DESCRIPTOR *Desc;
>> + EFI_STATUS Status;
>> +
>> + if (Buffer == NULL) {
>> + return EFI_INVALID_PARAMETER;
>> + }
>> +
>> + Dev = NON_DISCOVERABLE_PCI_DEVICE_FROM_PCI_IO(This);
>> +
>> + //
>> + // Only allow accesses to the BARs we emulate // Status =
>> + GetBarResource (Dev, BarIndex, &Desc); if (EFI_ERROR (Status)) {
>> + return Status;
>> + }
>> +
>> + if (Offset + (Count << (Width & 0x3)) > Desc->AddrLen) {
>> + return EFI_UNSUPPORTED;
>> + }
>> +
>> + Address = (VOID *)((UINTN)Desc->AddrRangeMin + Offset); AlignMask =
>> + (1 << (Width & 0x03)) - 1; if ((UINTN)Address & AlignMask) {
>> + return EFI_INVALID_PARAMETER;
>> + }
>> +
>> + switch (Width) {
>> + case EfiPciWidthUint8:
>> + case EfiPciWidthUint16:
>> + case EfiPciWidthUint32:
>> + case EfiPciWidthUint64:
>> + return PciIoMemRW (Width, Count, 1, Address, 1, Buffer);
>> +
>> + case EfiPciWidthFifoUint8:
>> + case EfiPciWidthFifoUint16:
>> + case EfiPciWidthFifoUint32:
>> + case EfiPciWidthFifoUint64:
>> + return PciIoMemRW (Width, Count, 0, Address, 1, Buffer);
>> +
>> + case EfiPciWidthFillUint8:
>> + case EfiPciWidthFillUint16:
>> + case EfiPciWidthFillUint32:
>> + case EfiPciWidthFillUint64:
>> + return PciIoMemRW (Width, Count, 1, Address, 0, Buffer);
>> +
>> + default:
>> + break;
>> + }
>> + return EFI_INVALID_PARAMETER;
>> +}
>> +
>> +STATIC
>> +EFI_STATUS
>> +PciIoIoRead (
>> + IN EFI_PCI_IO_PROTOCOL *This,
>> + IN EFI_PCI_IO_PROTOCOL_WIDTH Width,
>> + IN UINT8 BarIndex,
>> + IN UINT64 Offset,
>> + IN UINTN Count,
>> + IN OUT VOID *Buffer
>> + )
>> +{
>> + ASSERT (FALSE);
>> + return EFI_UNSUPPORTED;
>> +}
>> +
>> +STATIC
>> +EFI_STATUS
>> +PciIoIoWrite (
>> + IN EFI_PCI_IO_PROTOCOL *This,
>> + IN EFI_PCI_IO_PROTOCOL_WIDTH Width,
>> + IN UINT8 BarIndex,
>> + IN UINT64 Offset,
>> + IN UINTN Count,
>> + IN OUT VOID *Buffer
>> + )
>> +{
>> + ASSERT (FALSE);
>> + return EFI_UNSUPPORTED;
>> +}
>> +
>> +STATIC
>> +EFI_STATUS
>> +PciIoPciRead (
>> + IN EFI_PCI_IO_PROTOCOL *This,
>> + IN EFI_PCI_IO_PROTOCOL_WIDTH Width,
>> + IN UINT32 Offset,
>> + IN UINTN Count,
>> + IN OUT VOID *Buffer
>> + )
>> +{
>> + NON_DISCOVERABLE_PCI_DEVICE *Dev;
>> + UINTN Length;
>> +
>> + if (Width < 0 || Width >= EfiPciIoWidthMaximum || Buffer == NULL) {
>> + return EFI_INVALID_PARAMETER;
>> + }
>> +
>> + Dev = NON_DISCOVERABLE_PCI_DEVICE_FROM_PCI_IO(This);
>> + Length = Count << ((UINTN)Width & 0x3);
>> +
>> + if (Offset + Length > sizeof (Dev->ConfigSpace)) {
>> + return EFI_UNSUPPORTED;
>> + }
>> +
>> + return PciIoMemRW (Width, Count, 1, Buffer, 1,
>> + (UINT8 *)&Dev->ConfigSpace + Offset); }
>> +
>> +//
>> +// taken from MdeModulePkg/Bus/Pci/SdMmcPciHcDxe/SdMmcPciHci.h
>> +//
>> +#define SD_MMC_HC_SLOT_OFFSET 0x40
>> +
>> +typedef struct {
>> + UINT8 FirstBar:3; // bit 0:2
>> + UINT8 Reserved:1; // bit 3
>> + UINT8 SlotNum:3; // bit 4:6
>> + UINT8 Reserved1:1; // bit 7
>> +} SD_MMC_HC_SLOT_INFO;
>> +
>> +STATIC
>> +EFI_STATUS
>> +PciIoPciReadSdhci (
>> + IN EFI_PCI_IO_PROTOCOL *This,
>> + IN EFI_PCI_IO_PROTOCOL_WIDTH Width,
>> + IN UINT32 Offset,
>> + IN UINTN Count,
>> + IN OUT VOID *Buffer
>> + )
>> +{
>> + NON_DISCOVERABLE_PCI_DEVICE *Dev;
>> + UINTN Length;
>> + SD_MMC_HC_SLOT_INFO *SlotInfo;
>> +
>> + if (Width < 0 || Width >= EfiPciIoWidthMaximum || Buffer == NULL) {
>> + return EFI_INVALID_PARAMETER;
>> + }
>> +
>> + Dev = NON_DISCOVERABLE_PCI_DEVICE_FROM_PCI_IO(This);
>> + Length = Count << ((UINTN)Width & 0x3);
>> +
>> + //
>> + // The SDHCI driver reads beyond the 64-byte PCI config space to read
>> + // the number of slots and the BAR offset. So allow that // if
>> + (Offset == SD_MMC_HC_SLOT_OFFSET && Length == 1) {
>> + SlotInfo = Buffer;
>> +
>> + SlotInfo->FirstBar = Dev->BarOffset;
>> + SlotInfo->SlotNum = Dev->BarCount - 1;
>
> 2. It's a little bit tricky to hook the SDHCI access.
> We need to make this driver generic enough and stable
> for future possible requirements.
> We now see the needs for SDHCI access. We may have other similar needs.
> Can we pass such information from NonDiscoverredPciDevice?
> The information like array of <Offset, Uint8Value> can be included in the protocol.
> Just my rough idea. Feel free to reject it.
>
Well, this is another side effect of the design decision of folding
SDHCI support into a SDHCI-PCI driver.
For other host controller interfaces, there is always a 1:1
relationship between a PCI device and a ?HCI device. However, for
SDHCI, the correct way to implement this on the driver side would have
been to implement a SDHCI-PCI bus driver that allocates a SHDCI device
handle for each slot on the PCI device. That would allow the SDHCI
*driver* to only worry about a single device, and there would be no
need for this hack.
In the first iteration of this series, I did not have this since there
was a 1:1 relationship between non-discoverable devices and PCI
drivers, but due to the move to ACPI resource descriptors, this is
different now.
>> +
>> + return EFI_SUCCESS;
>> + }
>> +
>> + if (Offset + Length > sizeof (Dev->ConfigSpace)) {
>> + return EFI_UNSUPPORTED;
>> + }
>> +
>> + return PciIoMemRW (Width, Count, 1, Buffer, 1,
>> + (UINT8 *)&Dev->ConfigSpace + Offset); }
>> +
>> +STATIC
>> +EFI_STATUS
>> +PciIoPciWrite (
>> + IN EFI_PCI_IO_PROTOCOL *This,
>> + IN EFI_PCI_IO_PROTOCOL_WIDTH Width,
>> + IN UINT32 Offset,
>> + IN UINTN Count,
>> + IN OUT VOID *Buffer
>> + )
>> +{
>> + NON_DISCOVERABLE_PCI_DEVICE *Dev;
>> + VOID *Address;
>> +
>> + if (Width < 0 || Width >= EfiPciIoWidthMaximum || Buffer == NULL) {
>> + return EFI_INVALID_PARAMETER;
>> + }
>> +
>> + Dev = NON_DISCOVERABLE_PCI_DEVICE_FROM_PCI_IO(This);
>> + Address = (UINT8 *)&Dev->ConfigSpace + Offset;
>> +
>> + if (Offset + Count * (1UL << (Width & 0x3)) > sizeof (Dev->ConfigSpace)) {
>> + return EFI_UNSUPPORTED;
>> + }
>> +
>> + return PciIoMemRW (Width, Count, 1, Address, 1, Buffer); }
>> +
>> +STATIC
>> +EFI_STATUS
>> +PciIoCopyMem (
>> + IN EFI_PCI_IO_PROTOCOL *This,
>> + IN EFI_PCI_IO_PROTOCOL_WIDTH Width,
>> + IN UINT8 DestBarIndex,
>> + IN UINT64 DestOffset,
>> + IN UINT8 SrcBarIndex,
>> + IN UINT64 SrcOffset,
>> + IN UINTN Count
>> + )
>> +{
>> + ASSERT (FALSE);
>> + return EFI_UNSUPPORTED;
>> +}
>> +
>> +STATIC
>> +EFI_STATUS
>> +CoherentPciIoMap (
>> + IN EFI_PCI_IO_PROTOCOL *This,
>> + IN EFI_PCI_IO_PROTOCOL_OPERATION Operation,
>> + IN VOID *HostAddress,
>> + IN OUT UINTN *NumberOfBytes,
>> + OUT EFI_PHYSICAL_ADDRESS *DeviceAddress,
>> + OUT VOID **Mapping
>> + )
>> +{
>> + NON_DISCOVERABLE_PCI_DEVICE *Dev;
>> + EFI_STATUS Status;
>> + NON_DISCOVERABLE_PCI_DEVICE_MAP_INFO *MapInfo;
>> +
>> + //
>> + // If HostAddress exceeds 4 GB, and this device does not support
>> + 64-bit DMA // addressing, we need to allocate a bounce buffer and copy
>> over the data.
>> + //
>> + Dev = NON_DISCOVERABLE_PCI_DEVICE_FROM_PCI_IO(This);
>> + if ((Dev->Attributes & EFI_PCI_IO_ATTRIBUTE_DUAL_ADDRESS_CYCLE) ==
>> 0 &&
>> + (UINTN)HostAddress + *NumberOfBytes > SIZE_4GB) {
>> +
>> + //
>> + // Bounce buffering is not possible for consistent mappings
>> + //
>> + if (Operation == EfiPciIoOperationBusMasterCommonBuffer) {
>> + return EFI_UNSUPPORTED;
>> + }
>> +
>> + MapInfo = AllocatePool (sizeof *MapInfo);
>> + if (MapInfo == NULL) {
>> + return EFI_OUT_OF_RESOURCES;
>> + }
>> +
>> + MapInfo->AllocAddress = MAX_UINT32;
>> + MapInfo->HostAddress = HostAddress;
>> + MapInfo->Operation = Operation;
>> + MapInfo->NumberOfBytes = *NumberOfBytes;
>> +
>> + Status = gBS->AllocatePages (AllocateMaxAddress, EfiBootServicesData,
>> + EFI_SIZE_TO_PAGES (MapInfo->NumberOfBytes),
>> + &MapInfo->AllocAddress);
>> + if (EFI_ERROR (Status)) {
>> + //
>> + // If we fail here, it is likely because the system has no memory below
>> + // 4 GB to begin with. There is not much we can do about that other than
>> + // fail the map request.
>> + //
>> + FreePool (MapInfo);
>> + return EFI_DEVICE_ERROR;
>> + }
>> + if (Operation == EfiPciIoOperationBusMasterRead) {
>> + gBS->CopyMem ((VOID *)(UINTN)MapInfo->AllocAddress, HostAddress,
>> + *NumberOfBytes);
>> + }
>> + *DeviceAddress = MapInfo->AllocAddress;
>> + *Mapping = MapInfo;
>> + } else {
>> + *DeviceAddress = (EFI_PHYSICAL_ADDRESS)(UINTN)HostAddress;
>> + *Mapping = NULL;
>> + }
>> + return EFI_SUCCESS;
>> +}
>> +
>> +STATIC
>> +EFI_STATUS
>> +CoherentPciIoUnmap (
>> + IN EFI_PCI_IO_PROTOCOL *This,
>> + IN VOID *Mapping
>> + )
>> +{
>> + NON_DISCOVERABLE_PCI_DEVICE_MAP_INFO *MapInfo;
>> +
>> + MapInfo = Mapping;
>> + if (MapInfo != NULL) {
>> + if (MapInfo->Operation == EfiPciIoOperationBusMasterWrite) {
>> + gBS->CopyMem (MapInfo->HostAddress, (VOID *)(UINTN)MapInfo-
>> >AllocAddress,
>> + MapInfo->NumberOfBytes);
>> + }
>> + gBS->FreePages (MapInfo->AllocAddress,
>> + EFI_SIZE_TO_PAGES (MapInfo->NumberOfBytes));
>> + FreePool (MapInfo);
>> + }
>> + return EFI_SUCCESS;
>> +}
>> +
>> +STATIC
>> +EFI_STATUS
>> +CoherentPciIoAllocateBuffer (
>> + IN EFI_PCI_IO_PROTOCOL *This,
>> + IN EFI_ALLOCATE_TYPE Type,
>> + IN EFI_MEMORY_TYPE MemoryType,
>> + IN UINTN Pages,
>> + OUT VOID **HostAddress,
>> + IN UINT64 Attributes
>> + )
>> +{
>> + NON_DISCOVERABLE_PCI_DEVICE *Dev;
>> + EFI_PHYSICAL_ADDRESS AllocAddress;
>> + EFI_ALLOCATE_TYPE AllocType;
>> + EFI_STATUS Status;
>> +
>> + if ((Attributes & ~(EFI_PCI_ATTRIBUTE_MEMORY_WRITE_COMBINE |
>> + EFI_PCI_ATTRIBUTE_MEMORY_CACHED)) != 0) {
>> + return EFI_UNSUPPORTED;
>> + }
>> +
>> + //
>> + // Allocate below 4 GB if the dual address cycle attribute has not
>> + // been set. If the system has no memory available below 4 GB, there
>> + // is little we can do except propagate the error.
>> + //
>> + Dev = NON_DISCOVERABLE_PCI_DEVICE_FROM_PCI_IO(This);
>> + if ((Dev->Attributes & EFI_PCI_IO_ATTRIBUTE_DUAL_ADDRESS_CYCLE) ==
>> 0) {
>> + AllocAddress = MAX_UINT32;
>
> 3. better to use BASE_4GB -1.
>
>> + AllocType = AllocateMaxAddress;
>> + } else {
>> + AllocType = AllocateAnyPages;
>> + }
>> +
>> + Status = gBS->AllocatePages (AllocType, MemoryType, Pages,
>> +&AllocAddress);
>> + if (!EFI_ERROR (Status)) {
>> + *HostAddress = (VOID *)(UINTN)AllocAddress;
>> + }
>> + return Status;
>> +}
>> +
>> +STATIC
>> +EFI_STATUS
>> +CoherentPciIoFreeBuffer (
>> + IN EFI_PCI_IO_PROTOCOL *This,
>> + IN UINTN Pages,
>> + IN VOID *HostAddress
>> + )
>> +{
>> + FreePages (HostAddress, Pages);
>> + return EFI_SUCCESS;
>> +}
>> +
>> +
>> +STATIC
>> +EFI_STATUS
>> +PciIoFlush (
>> + IN EFI_PCI_IO_PROTOCOL *This
>> + )
>> +{
>> + return EFI_SUCCESS;
>> +}
>> +
>> +STATIC
>> +EFI_STATUS
>> +PciIoGetLocation (
>> + IN EFI_PCI_IO_PROTOCOL *This,
>> + OUT UINTN *SegmentNumber,
>> + OUT UINTN *BusNumber,
>> + OUT UINTN *DeviceNumber,
>> + OUT UINTN *FunctionNumber
>> + )
>> +{
>> + if (SegmentNumber == NULL ||
>> + BusNumber == NULL ||
>> + DeviceNumber == NULL ||
>> + FunctionNumber == NULL) {
>> + return EFI_INVALID_PARAMETER;
>> + }
>> +
>> + *SegmentNumber = 0;
>> + *BusNumber = 0xff;
>> + *DeviceNumber = 0;
>> + *FunctionNumber = 0;
>> +
>> + return EFI_SUCCESS;
>> +}
>> +
>> +STATIC
>> +EFI_STATUS
>> +PciIoAttributes (
>> + IN EFI_PCI_IO_PROTOCOL *This,
>> + IN EFI_PCI_IO_PROTOCOL_ATTRIBUTE_OPERATION Operation,
>> + IN UINT64 Attributes,
>> + OUT UINT64 *Result OPTIONAL
>> + )
>> +{
>> + NON_DISCOVERABLE_PCI_DEVICE *Dev;
>> + BOOLEAN Enable;
>> +
>> + Dev = NON_DISCOVERABLE_PCI_DEVICE_FROM_PCI_IO(This);
>> +
>> + Enable = FALSE;
>> + switch (Operation) {
>> + case EfiPciIoAttributeOperationGet:
>> + if (Result == NULL) {
>> + return EFI_INVALID_PARAMETER;
>> + }
>> + *Result = Dev->Attributes;
>> + break;
>> +
>> + case EfiPciIoAttributeOperationSupported:
>> + if (Result == NULL) {
>> + return EFI_INVALID_PARAMETER;
>> + }
>> + *Result = EFI_PCI_DEVICE_ENABLE |
>> EFI_PCI_IO_ATTRIBUTE_DUAL_ADDRESS_CYCLE;
>> + break;
>> +
>> + case EfiPciIoAttributeOperationEnable:
>> + Attributes |= Dev->Attributes;
>> + case EfiPciIoAttributeOperationSet:
>> + Enable = ((~Dev->Attributes & Attributes) & EFI_PCI_DEVICE_ENABLE) !=
>> 0;
>> + Dev->Attributes = Attributes;
>> + break;
>> +
>> + case EfiPciIoAttributeOperationDisable:
>> + Dev->Attributes &= ~Attributes;
>> + break;
>> +
>> + default:
>> + return EFI_INVALID_PARAMETER;
>> + };
>> +
>> + //
>> + // If we're setting any of the EFI_PCI_DEVICE_ENABLE bits, perform
>> + // the device specific initialization now.
>> + //
>> + if (Enable && !Dev->Enabled && Dev->Device->Initialize != NULL) {
>> + Dev->Device->Initialize (Dev->Device);
>> + Dev->Enabled = TRUE;
>> + }
>> + return EFI_SUCCESS;
>> +}
>> +
>> +STATIC
>> +EFI_STATUS
>> +PciIoGetBarAttributes (
>> + IN EFI_PCI_IO_PROTOCOL *This,
>> + IN UINT8 BarIndex,
>> + OUT UINT64 *Supports OPTIONAL,
>> + OUT VOID **Resources OPTIONAL
>> + )
>> +{
>> + NON_DISCOVERABLE_PCI_DEVICE *Dev;
>> + EFI_ACPI_ADDRESS_SPACE_DESCRIPTOR *Descriptor, *BarDesc;
>> + EFI_ACPI_END_TAG_DESCRIPTOR *End;
>> + EFI_STATUS Status;
>> +
>> + if (Supports == NULL && Resources == NULL) {
>> + return EFI_INVALID_PARAMETER;
>> + }
>> +
>> + Dev = NON_DISCOVERABLE_PCI_DEVICE_FROM_PCI_IO(This);
>> +
>> + Status = GetBarResource (Dev, BarIndex, &BarDesc); if (EFI_ERROR
>> + (Status)) {
>> + return Status;
>> + }
>> +
>> + //
>> + // Don't expose any configurable attributes for our emulated BAR //
>> + if (Supports != NULL) {
>> + *Supports = 0;
>> + }
>> +
>> + if (Resources != NULL) {
>> + Descriptor = AllocatePool (sizeof
>> (EFI_ACPI_ADDRESS_SPACE_DESCRIPTOR) +
>> + sizeof (EFI_ACPI_END_TAG_DESCRIPTOR));
>> + if (Descriptor == NULL) {
>> + return EFI_OUT_OF_RESOURCES;
>> + }
>> +
>> + CopyMem (Descriptor, BarDesc, sizeof *Descriptor);
>> +
>> + End = (EFI_ACPI_END_TAG_DESCRIPTOR *) (Descriptor + 1);
>> + End->Desc = ACPI_END_TAG_DESCRIPTOR;
>> + End->Checksum = 0;
>> +
>> + *Resources = Descriptor;
>> + }
>> + return EFI_SUCCESS;
>> +}
>> +
>> +STATIC
>> +EFI_STATUS
>> +PciIoSetBarAttributes (
>> + IN EFI_PCI_IO_PROTOCOL *This,
>> + IN UINT64 Attributes,
>> + IN UINT8 BarIndex,
>> + IN OUT UINT64 *Offset,
>> + IN OUT UINT64 *Length
>> + )
>> +{
>> + ASSERT (FALSE);
>> + return EFI_UNSUPPORTED;
>> +}
>> +
>> +STATIC CONST EFI_PCI_IO_PROTOCOL PciIoTemplate = {
>> + PciIoPollMem,
>> + PciIoPollIo,
>> + { PciIoMemRead, PciIoMemWrite },
>> + { PciIoIoRead, PciIoIoWrite },
>> + { PciIoPciRead, PciIoPciWrite },
>> + PciIoCopyMem,
>> + CoherentPciIoMap,
>> + CoherentPciIoUnmap,
>> + CoherentPciIoAllocateBuffer,
>> + CoherentPciIoFreeBuffer,
>> + PciIoFlush,
>> + PciIoGetLocation,
>> + PciIoAttributes,
>> + PciIoGetBarAttributes,
>> + PciIoSetBarAttributes,
>> + 0,
>> + 0
>> +};
>> +
>> +VOID
>> +InitializePciIoProtocol (
>> + NON_DISCOVERABLE_PCI_DEVICE *Dev
>> + )
>> +{
>> + EFI_ACPI_ADDRESS_SPACE_DESCRIPTOR *Desc;
>> + INTN Idx;
>> +
>> + InitializeListHead (&Dev->UncachedAllocationList);
>> +
>> + Dev->ConfigSpace.Hdr.VendorId = PCI_ID_VENDOR_UNKNOWN;
>> + Dev->ConfigSpace.Hdr.DeviceId = PCI_ID_DEVICE_DONTCARE;
>> +
>> + // Copy protocol structure
>> + CopyMem(&Dev->PciIo, &PciIoTemplate, sizeof PciIoTemplate);
>> +
>> + switch (Dev->Device->Type) {
>> + case NonDiscoverableDeviceTypeOhci:
>> + Dev->ConfigSpace.Hdr.ClassCode[0] = PCI_IF_OHCI;
>> + Dev->ConfigSpace.Hdr.ClassCode[1] = PCI_CLASS_SERIAL_USB;
>> + Dev->ConfigSpace.Hdr.ClassCode[2] = PCI_CLASS_SERIAL;
>> + Dev->BarOffset = 0;
>> + break;
>> +
>> + case NonDiscoverableDeviceTypeUhci:
>> + Dev->ConfigSpace.Hdr.ClassCode[0] = PCI_IF_UHCI;
>> + Dev->ConfigSpace.Hdr.ClassCode[1] = PCI_CLASS_SERIAL_USB;
>> + Dev->ConfigSpace.Hdr.ClassCode[2] = PCI_CLASS_SERIAL;
>> + Dev->BarOffset = 0;
>> + break;
>> +
>> + case NonDiscoverableDeviceTypeEhci:
>> + Dev->ConfigSpace.Hdr.ClassCode[0] = PCI_IF_EHCI;
>> + Dev->ConfigSpace.Hdr.ClassCode[1] = PCI_CLASS_SERIAL_USB;
>> + Dev->ConfigSpace.Hdr.ClassCode[2] = PCI_CLASS_SERIAL;
>> + Dev->BarOffset = 0;
>> + break;
>> +
>> + case NonDiscoverableDeviceTypeXhci:
>> + Dev->ConfigSpace.Hdr.ClassCode[0] = PCI_IF_XHCI;
>> + Dev->ConfigSpace.Hdr.ClassCode[1] = PCI_CLASS_SERIAL_USB;
>> + Dev->ConfigSpace.Hdr.ClassCode[2] = PCI_CLASS_SERIAL;
>> + Dev->BarOffset = 0;
>> + break;
>> +
>> + case NonDiscoverableDeviceTypeAhci:
>> + Dev->ConfigSpace.Hdr.ClassCode[0] = PCI_IF_MASS_STORAGE_AHCI;
>> + Dev->ConfigSpace.Hdr.ClassCode[1] =
>> PCI_CLASS_MASS_STORAGE_SATADPA;
>> + Dev->ConfigSpace.Hdr.ClassCode[2] = PCI_CLASS_MASS_STORAGE;
>> + Dev->BarOffset = 5;
>> + break;
>> +
>> + case NonDiscoverableDeviceTypeSdhci:
>> + Dev->ConfigSpace.Hdr.ClassCode[0] = 0x0; // don't care
>> + Dev->ConfigSpace.Hdr.ClassCode[1] =
>> PCI_SUBCLASS_SD_HOST_CONTROLLER;
>> + Dev->ConfigSpace.Hdr.ClassCode[2] = PCI_CLASS_SYSTEM_PERIPHERAL;
>> + Dev->BarOffset = 0;
>> +
>> + //
>> + // Use the special config space accessor so that the SDHCI driver
>> + // is able to retrieve the number of slots and the BAR offset
>> + //
>> + Dev->PciIo.Pci.Read = PciIoPciReadSdhci;
>> + break;
>> +
>> + case NonDiscoverableDeviceTypeUfs:
>> + Dev->ConfigSpace.Hdr.ClassCode[0] = 0x0; // don't care
>> + Dev->ConfigSpace.Hdr.ClassCode[1] = 0x9; // UFS controller subclass;
>> + Dev->ConfigSpace.Hdr.ClassCode[2] = PCI_CLASS_MASS_STORAGE;
>> + Dev->BarOffset = 0;
>> + break;
>> +
>> + case NonDiscoverableDeviceTypeNvme:
>> + Dev->ConfigSpace.Hdr.ClassCode[0] = 0x2; // PCI_IF_NVMHCI
>> + Dev->ConfigSpace.Hdr.ClassCode[1] = 0x8; //
>> PCI_CLASS_MASS_STORAGE_NVM
>> + Dev->ConfigSpace.Hdr.ClassCode[2] = PCI_CLASS_MASS_STORAGE;
>> + Dev->BarOffset = 0;
>> +
>> + default:
>> + ASSERT_EFI_ERROR (EFI_INVALID_PARAMETER); }
>> +
>> + //
>> + // Iterate over the resources to populate the virtual BARs // Idx =
>> + Dev->BarOffset; for (Desc = Dev->Device->Resources, Dev->BarCount =
>> + 0;
>> + Desc->Desc != ACPI_END_TAG_DESCRIPTOR;
>> + Desc = (VOID *)((UINT8 *)Desc + Desc->Len + 3)) {
>> +
>> + ASSERT (Desc->Desc == ACPI_ADDRESS_SPACE_DESCRIPTOR);
>> + ASSERT (Desc->ResType == ACPI_ADDRESS_SPACE_TYPE_MEM);
>> +
>> + if (Idx >= PCI_MAX_BARS ||
>> + (Idx == PCI_MAX_BARS - 1 && Desc->AddrSpaceGranularity == 64)) {
>> + DEBUG ((DEBUG_ERROR,
>> + "%a: resource count exceeds number of emulated BARs\n",
>> + __FUNCTION__));
>> + ASSERT (FALSE);
>> + break;
>> + }
>> +
>> + Dev->ConfigSpace.Device.Bar[Idx] = (UINT32)Desc->AddrRangeMin;
>> + Dev->BarCount++;
>> +
>> + if (Desc->AddrSpaceGranularity == 64) {
>> + Dev->ConfigSpace.Device.Bar[Idx] |= 0x4;
>> + Dev->ConfigSpace.Device.Bar[++Idx] = (UINT32)(Desc-
>> >AddrRangeMin >> 32);
>> + }
>> + }
>> +}
>> diff --git
>> a/MdeModulePkg/Bus/Pci/NonDiscoverablePciDeviceDxe/NonDiscoverable
>> PciDeviceIo.h
>> b/MdeModulePkg/Bus/Pci/NonDiscoverablePciDeviceDxe/NonDiscoverable
>> PciDeviceIo.h
>> new file mode 100644
>> index 000000000000..bc0a3d3258f9
>> --- /dev/null
>> +++
>> b/MdeModulePkg/Bus/Pci/NonDiscoverablePciDeviceDxe/NonDiscoverable
>> Pc
>> +++ iDeviceIo.h
>> @@ -0,0 +1,84 @@
>> +/** @file
>> +
>> + Copyright (C) 2016, Linaro Ltd. All rights reserved.<BR>
>> +
>> + This program and the accompanying materials are licensed and made
>> + available under the terms and conditions of the BSD License which
>> + accompanies this distribution. The full text of the license may be
>> + found at http://opensource.org/licenses/bsd-license.php
>> +
>> + THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS"
>> BASIS,
>> + WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER
>> EXPRESS OR IMPLIED.
>> +
>> +**/
>> +
>> +#ifndef __NON_DISCOVERABLE_PCI_DEVICE_IO_H__
>> +#define __NON_DISCOVERABLE_PCI_DEVICE_IO_H__
>> +
>> +#include <Library/BaseMemoryLib.h>
>> +#include <Library/DebugLib.h>
>> +#include <Library/MemoryAllocationLib.h> #include
>> +<Library/UefiBootServicesTableLib.h>
>> +#include <Library/UefiLib.h>
>> +
>> +#include <IndustryStandard/Pci.h>
>> +
>> +#include <Protocol/ComponentName.h>
>> +#include <Protocol/NonDiscoverableDevice.h>
>> +#include <Protocol/PciIo.h>
>> +
>> +#define NON_DISCOVERABLE_PCI_DEVICE_SIG SIGNATURE_32 ('P', 'P', 'I',
>> +'D')
>> +
>> +#define NON_DISCOVERABLE_PCI_DEVICE_FROM_PCI_IO(PciIoPointer) \
>> + CR (PciIoPointer, NON_DISCOVERABLE_PCI_DEVICE, PciIo, \
>> + NON_DISCOVERABLE_PCI_DEVICE_SIG)
>> +
>> +#define PCI_ID_VENDOR_UNKNOWN 0xffff
>> +#define PCI_ID_DEVICE_DONTCARE 0x0000
>> +
>> +#define PCI_MAX_BARS 6
>> +
>> +typedef struct {
>> + UINT32 Signature;
>> + //
>> + // The bound non-discoverable device protocol instance
>> + //
>> + NON_DISCOVERABLE_DEVICE *Device;
>> + //
>> + // The exposed PCI I/O protocol instance.
>> + //
>> + EFI_PCI_IO_PROTOCOL PciIo;
>> + //
>> + // The emulated PCI config space of the device. Only the minimally
>> +required
>> + // items are assigned.
>> + //
>> + PCI_TYPE00 ConfigSpace;
>> + //
>> + // The first virtual BAR to assign based on the resources described
>> + // by the non-discoverable device.
>> + //
>> + UINT32 BarOffset;
>> + //
>> + // The number of virtual BARs we expose based on the number of
>> + // resources
>> + //
>> + UINT32 BarCount;
>> + //
>> + // The PCI I/O attributes for this device
>> + //
>> + UINT64 Attributes;
>> + //
>> + // Whether this device has been enabled
>> + //
>> + BOOLEAN Enabled;
>> +} NON_DISCOVERABLE_PCI_DEVICE;
>> +
>> +VOID
>> +InitializePciIoProtocol (
>> + NON_DISCOVERABLE_PCI_DEVICE *Device
>> + );
>> +
>> +extern EFI_COMPONENT_NAME_PROTOCOL gComponentName; extern
>> +EFI_COMPONENT_NAME2_PROTOCOL gComponentName2;
>> +
>> +#endif
>> diff --git a/MdeModulePkg/MdeModulePkg.dsc
>> b/MdeModulePkg/MdeModulePkg.dsc index 43421d610ede..aac05408599d
>> 100644
>> --- a/MdeModulePkg/MdeModulePkg.dsc
>> +++ b/MdeModulePkg/MdeModulePkg.dsc
>> @@ -260,6 +260,7 @@ [Components]
>> MdeModulePkg/Bus/Isa/IsaBusDxe/IsaBusDxe.inf
>> MdeModulePkg/Bus/Isa/Ps2KeyboardDxe/Ps2KeyboardDxe.inf
>> MdeModulePkg/Bus/Isa/Ps2MouseDxe/Ps2MouseDxe.inf
>> +
>> +
>> MdeModulePkg/Bus/Pci/NonDiscoverablePciDeviceDxe/NonDiscoverablePci
>> Dev
>> + iceDxe.inf
>>
>> MdeModulePkg/Core/Dxe/DxeMain.inf {
>> <LibraryClasses>
>> --
>> 2.7.4
>
next prev parent reply other threads:[~2016-11-18 12:30 UTC|newest]
Thread overview: 27+ messages / expand[flat|nested] mbox.gz Atom feed top
2016-11-16 16:59 [PATCH v3 0/5] MdeModulePkg: add support for non-discoverable devices Ard Biesheuvel
2016-11-16 16:59 ` [PATCH v3 1/5] MdeModulePkg: introduce non-discoverable device protocol Ard Biesheuvel
2016-11-16 17:48 ` Leif Lindholm
2016-11-17 2:53 ` Ni, Ruiyu
2016-11-17 6:07 ` Ard Biesheuvel
2016-11-17 7:52 ` Ni, Ruiyu
2016-11-17 10:43 ` Ard Biesheuvel
2016-11-18 2:11 ` Ni, Ruiyu
2016-11-18 4:59 ` Ard Biesheuvel
2016-11-18 5:24 ` Tian, Feng
2016-11-18 6:57 ` Ard Biesheuvel
2016-11-18 8:39 ` Tian, Feng
2016-11-18 8:52 ` Ard Biesheuvel
2016-11-18 6:13 ` Ni, Ruiyu
2016-11-18 7:04 ` Ard Biesheuvel
2016-11-18 13:39 ` Ni, Ruiyu
2016-11-18 13:50 ` Ard Biesheuvel
2016-11-25 15:21 ` Ard Biesheuvel
2016-11-16 16:59 ` [PATCH v3 2/5] MdeModule: introduce helper library to register non-discoverable devices Ard Biesheuvel
2016-11-16 16:59 ` [PATCH v3 3/5] MdeModulePkg: implement generic PCI I/O driver for " Ard Biesheuvel
2016-11-17 3:29 ` Ni, Ruiyu
2016-11-18 12:30 ` Ard Biesheuvel [this message]
2016-11-24 18:14 ` Ard Biesheuvel
2016-11-16 16:59 ` [PATCH v3 4/5] MdeModulePkg/NonDiscoverablePciDeviceDxe: add support for non-coherent DMA Ard Biesheuvel
2016-11-16 16:59 ` [PATCH v3 5/5] Omap35xxPkg/PciEmulation: port to new non-discoverable device infrastructure Ard Biesheuvel
2016-11-17 4:36 ` [PATCH v3 0/5] MdeModulePkg: add support for non-discoverable devices Marcin Wojtas
2016-11-23 14:31 ` Marcin Wojtas
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+Gu91SCK-H=jjK=i0ULeHaXjREsZhamhsM+hJH5kzqyR4Hw@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