public inbox for devel@edk2.groups.io
 help / color / mirror / Atom feed
From: "Nikita Leshenko" <nikita.leshchenko@oracle.com>
To: devel@edk2.groups.io
Cc: liran.alon@oracle.com, aaron.young@oracle.com,
	jordan.l.justen@intel.com, lersek@redhat.com,
	ard.biesheuvel@linaro.org,
	Nikita Leshenko <nikita.leshchenko@oracle.com>
Subject: [PATCH v2 12/13] OvmfPkg/MptScsiDxe: Implement the PassThru method
Date: Wed, 26 Feb 2020 18:41:50 +0200	[thread overview]
Message-ID: <20200226164151.125182-13-nikita.leshchenko@oracle.com> (raw)
In-Reply-To: <20200226164151.125182-1-nikita.leshchenko@oracle.com>

Machines should be able to boot after this commit. Tested with different Linux
distributions (Ubuntu, CentOS) and different Windows versions (Windows 7,
Windows 10, Server 2016).

Ref: https://bugzilla.tianocore.org/show_bug.cgi?id=2390
Contributed-under: TianoCore Contribution Agreement 1.1
Signed-off-by: Nikita Leshenko <nikita.leshchenko@oracle.com>
Reviewed-by: Aaron Young <aaron.young@oracle.com>
Reviewed-by: Liran Alon <liran.alon@oracle.com>
---
 .../Include/IndustryStandard/FusionMptScsi.h  |  17 +
 OvmfPkg/MptScsiDxe/MptScsi.c                  | 335 +++++++++++++++++-
 OvmfPkg/MptScsiDxe/MptScsiDxe.inf             |   3 +
 OvmfPkg/OvmfPkg.dec                           |   3 +
 4 files changed, 357 insertions(+), 1 deletion(-)

diff --git a/OvmfPkg/Include/IndustryStandard/FusionMptScsi.h b/OvmfPkg/Include/IndustryStandard/FusionMptScsi.h
index 20f535a2c8..83aff3ea3f 100644
--- a/OvmfPkg/Include/IndustryStandard/FusionMptScsi.h
+++ b/OvmfPkg/Include/IndustryStandard/FusionMptScsi.h
@@ -47,6 +47,11 @@
 
 #define MPT_IOC_WHOINIT_ROM_BIOS 0x02
 
+#define MPT_SCSIIO_REQUEST_CONTROL_TXDIR_WRITE (0x01 << 24)
+#define MPT_SCSIIO_REQUEST_CONTROL_TXDIR_READ  (0x02 << 24)
+
+#define MPT_SCSI_IO_ERROR_IOCSTATUS_DEVICE_NOT_THERE 0x0043
+
 //
 // Device structures
 //
@@ -106,6 +111,10 @@ typedef struct {
   UINT32    Length:             24;
   UINT32    EndOfList:          1;
   UINT32    Is64BitAddress:     1;
+  //
+  // True when the buffer contains data to be transfered. Otherwise it's the
+  // destination buffer
+  //
   UINT32    BufferContainsData: 1;
   UINT32    LocalAddress:       1;
   UINT32    ElementType:        2;
@@ -137,3 +146,11 @@ typedef struct {
 #pragma pack ()
   UINT64 Uint64; // 8 byte alignment required by HW
 } MPT_SCSI_IO_ERROR_REPLY;
+
+typedef union {
+  struct {
+    MPT_SCSI_IO_REQUEST Header;
+    MPT_SG_ENTRY_SIMPLE Sg;
+  } Data;
+  UINT64 Uint64; // 8 byte alignment required by HW
+} MPT_SCSI_REQUEST_WITH_SG;
diff --git a/OvmfPkg/MptScsiDxe/MptScsi.c b/OvmfPkg/MptScsiDxe/MptScsi.c
index 4cb35046c4..6c70112bbc 100644
--- a/OvmfPkg/MptScsiDxe/MptScsi.c
+++ b/OvmfPkg/MptScsiDxe/MptScsi.c
@@ -23,6 +23,7 @@
 #include <Library/UefiBootServicesTableLib.h>
 #include <Library/UefiLib.h>
 #include <Protocol/PciIo.h>
+#include <Protocol/PciRootBridgeIo.h>
 #include <Protocol/ScsiPassThruExt.h>
 
 //
@@ -35,6 +36,13 @@
 // Runtime Structures
 //
 
+typedef struct {
+  MPT_SCSI_IO_ERROR_REPLY         IoErrorReply;
+  MPT_SCSI_REQUEST_WITH_SG        IoRequest;
+  UINT8                           Sense[MAX_UINT8];
+  UINT8                           Data[0x2000];
+} MPT_SCSI_DMA_BUFFER;
+
 #define MPT_SCSI_DEV_SIGNATURE SIGNATURE_32 ('M','P','T','S')
 typedef struct {
   UINT32                          Signature;
@@ -42,11 +50,18 @@ typedef struct {
   EFI_EXT_SCSI_PASS_THRU_MODE     PassThruMode;
   EFI_PCI_IO_PROTOCOL             *PciIo;
   UINT64                          OriginalPciAttributes;
+  UINT32                          StallPerPollUsec;
+  MPT_SCSI_DMA_BUFFER             *Dma;
+  EFI_PHYSICAL_ADDRESS            DmaPhysical;
+  VOID                            *DmaMapping;
 } MPT_SCSI_DEV;
 
 #define MPT_SCSI_FROM_PASS_THRU(PassThruPtr) \
   CR (PassThruPtr, MPT_SCSI_DEV, PassThru, MPT_SCSI_DEV_SIGNATURE)
 
+#define MPT_SCSI_DMA_ADDR(Dev, MemberName) \
+  (Dev->DmaPhysical + OFFSET_OF(MPT_SCSI_DMA_BUFFER, MemberName))
+
 //
 // Hardware functions
 //
@@ -147,6 +162,8 @@ MptScsiInit (
   UINT8                          *ReplyBytes;
   UINT32                         Reply32;
 
+  Dev->StallPerPollUsec = PcdGet32 (PcdMptScsiStallPerPollUsec);
+
   Status = MptScsiReset (Dev);
   if (EFI_ERROR (Status)) {
     return Status;
@@ -205,6 +222,220 @@ MptScsiInit (
     return Status;
   }
 
+  //
+  // Put one free reply frame on the reply queue, the hardware may use it to
+  // report an error to us.
+  //
+  Status = Out32 (Dev, MPT_REG_REP_Q, MPT_SCSI_DMA_ADDR (Dev, IoErrorReply));
+  if (EFI_ERROR (Status)) {
+    return Status;
+  }
+
+  return EFI_SUCCESS;
+}
+
+STATIC
+EFI_STATUS
+MptScsiPopulateRequest (
+  IN MPT_SCSI_DEV                                   *Dev,
+  IN UINT8                                          Target,
+  IN UINT64                                         Lun,
+  IN OUT EFI_EXT_SCSI_PASS_THRU_SCSI_REQUEST_PACKET *Packet
+  )
+{
+  MPT_SCSI_REQUEST_WITH_SG *Request;
+
+  Request = &Dev->Dma->IoRequest;
+
+  if (Packet->DataDirection == EFI_EXT_SCSI_DATA_DIRECTION_BIDIRECTIONAL ||
+      Packet->CdbLength > sizeof (Request->Data.Header.CDB)) {
+    return EFI_UNSUPPORTED;
+  }
+
+  if (Target > 0 || Lun > 0) {
+    return EFI_INVALID_PARAMETER;
+  }
+
+  if (Packet->InTransferLength > sizeof (Dev->Dma->Data)) {
+    Packet->InTransferLength = sizeof (Dev->Dma->Data);
+    return EFI_BAD_BUFFER_SIZE;
+  }
+  if (Packet->OutTransferLength > sizeof (Dev->Dma->Data)) {
+    Packet->OutTransferLength = sizeof (Dev->Dma->Data);
+    return EFI_BAD_BUFFER_SIZE;
+  }
+
+  ZeroMem (Request, sizeof (*Request));
+  Request->Data.Header.TargetID = Target;
+  //
+  // It's 1 and not 0, for some reason...
+  //
+  Request->Data.Header.LUN[1] = Lun;
+  Request->Data.Header.Function = MPT_MESSAGE_HDR_FUNCTION_SCSI_IO_REQUEST;
+  Request->Data.Header.MessageContext = 1; // We handle one request at a time
+
+  Request->Data.Header.CDBLength = Packet->CdbLength;
+  CopyMem (Request->Data.Header.CDB, Packet->Cdb, Packet->CdbLength);
+
+  //
+  // SenseDataLength is UINT8, Sense[] is MAX_UINT8, so we can't overflow
+  //
+  ZeroMem (&Dev->Dma->Sense, Packet->SenseDataLength);
+  Request->Data.Header.SenseBufferLength = Packet->SenseDataLength;
+  Request->Data.Header.SenseBufferLowAddress = MPT_SCSI_DMA_ADDR (Dev, Sense);
+
+  Request->Data.Sg.EndOfList = 1;
+  Request->Data.Sg.EndOfBuffer = 1;
+  Request->Data.Sg.LastElement = 1;
+  Request->Data.Sg.ElementType = MPT_SG_ENTRY_TYPE_SIMPLE;
+  Request->Data.Sg.DataBufferAddress = MPT_SCSI_DMA_ADDR (Dev, Data);
+
+  switch (Packet->DataDirection)
+  {
+  case EFI_EXT_SCSI_DATA_DIRECTION_READ:
+    Request->Data.Header.DataLength = Packet->InTransferLength;
+    Request->Data.Sg.Length = Packet->InTransferLength;
+    Request->Data.Header.Control = MPT_SCSIIO_REQUEST_CONTROL_TXDIR_READ;
+    break;
+  case EFI_EXT_SCSI_DATA_DIRECTION_WRITE:
+    Request->Data.Header.DataLength = Packet->OutTransferLength;
+    Request->Data.Sg.Length = Packet->OutTransferLength;
+    Request->Data.Header.Control = MPT_SCSIIO_REQUEST_CONTROL_TXDIR_WRITE;
+
+    CopyMem (Dev->Dma->Data, Packet->OutDataBuffer, Packet->OutTransferLength);
+    Request->Data.Sg.BufferContainsData = 1;
+    break;
+  }
+
+  return EFI_SUCCESS;
+}
+
+STATIC
+EFI_STATUS
+MptScsiSendRequest (
+  IN MPT_SCSI_DEV                                   *Dev,
+  IN OUT EFI_EXT_SCSI_PASS_THRU_SCSI_REQUEST_PACKET *Packet
+  )
+{
+  EFI_STATUS Status;
+
+  //
+  // Make sure Request is fully written
+  //
+  MemoryFence ();
+
+  Status = Out32 (Dev, MPT_REG_REQ_Q, MPT_SCSI_DMA_ADDR (Dev, IoRequest));
+  if (EFI_ERROR (Status)) {
+    //
+    // We couldn't enqueue the request, report it as an adapter error
+    //
+    Packet->InTransferLength  = 0;
+    Packet->OutTransferLength = 0;
+    Packet->HostAdapterStatus = EFI_EXT_SCSI_STATUS_HOST_ADAPTER_OTHER;
+    Packet->TargetStatus      = EFI_EXT_SCSI_STATUS_TARGET_GOOD;
+    Packet->SenseDataLength   = 0;
+    return EFI_DEVICE_ERROR;
+  }
+
+  return EFI_SUCCESS;
+}
+
+STATIC
+EFI_STATUS
+MptScsiGetReply (
+  IN MPT_SCSI_DEV                                   *Dev,
+  OUT UINT32                                        *Reply
+  )
+{
+  EFI_STATUS Status;
+  UINT32     Istatus;
+  UINT32     EmptyReply;
+
+  for (;;) {
+    Status = In32 (Dev, MPT_REG_ISTATUS, &Istatus);
+    if (EFI_ERROR (Status)) {
+      return Status;
+    }
+
+    //
+    // Interrupt raised
+    //
+    if (Istatus & MPT_IMASK_REPLY) {
+      break;
+    }
+
+    gBS->Stall (Dev->StallPerPollUsec);
+  }
+
+  Status = In32 (Dev, MPT_REG_REP_Q, Reply);
+  if (EFI_ERROR (Status)) {
+    return Status;
+  }
+
+  //
+  // The driver is supposed to fetch replies until 0xffffffff is returned, which
+  // will reset the interrupt status. We put only one request, so we expect the
+  // next read reply to be the last.
+  //
+  Status = In32 (Dev, MPT_REG_REP_Q, &EmptyReply);
+  if (EFI_ERROR (Status) || EmptyReply != MAX_UINT32) {
+    return EFI_DEVICE_ERROR;
+  }
+
+  return EFI_SUCCESS;
+}
+
+STATIC
+EFI_STATUS
+MptScsiHandleReply (
+  IN MPT_SCSI_DEV                                   *Dev,
+  IN UINT32                                         Reply,
+  OUT EFI_EXT_SCSI_PASS_THRU_SCSI_REQUEST_PACKET    *Packet
+  )
+{
+  EFI_STATUS Status;
+
+  CopyMem (Packet->SenseData, Dev->Dma->Sense, Packet->SenseDataLength);
+  if (Packet->DataDirection == EFI_EXT_SCSI_DATA_DIRECTION_READ) {
+    CopyMem (Packet->InDataBuffer, Dev->Dma->Data, Packet->InTransferLength);
+  }
+
+  if (Reply == Dev->Dma->IoRequest.Data.Header.MessageContext) {
+    //
+    // Everything is good
+    //
+    Packet->HostAdapterStatus = EFI_EXT_SCSI_STATUS_HOST_ADAPTER_OK;
+    Packet->TargetStatus = EFI_EXT_SCSI_STATUS_TARGET_GOOD;
+
+  } else if (Reply & (1 << 31)) {
+    DEBUG ((DEBUG_ERROR, "%a: request failed\n", __FUNCTION__));
+    //
+    // When reply MSB is set, it's an error frame.
+    //
+
+    switch (Dev->Dma->IoErrorReply.Data.IOCStatus) {
+    case MPT_SCSI_IO_ERROR_IOCSTATUS_DEVICE_NOT_THERE:
+      Packet->HostAdapterStatus =
+        EFI_EXT_SCSI_STATUS_HOST_ADAPTER_SELECTION_TIMEOUT;
+      break;
+    default:
+      Packet->HostAdapterStatus = EFI_EXT_SCSI_STATUS_HOST_ADAPTER_OTHER;
+      break;
+    }
+
+    //
+    // Resubmit the reply frame to the reply queue
+    //
+    Status = Out32 (Dev, MPT_REG_REP_Q, MPT_SCSI_DMA_ADDR (Dev, IoErrorReply));
+    if (EFI_ERROR (Status)) {
+      return Status;
+    }
+
+  } else {
+    DEBUG ((DEBUG_ERROR, "%a: unexpected reply\n", __FUNCTION__));
+    return EFI_DEVICE_ERROR;
+  }
+
   return EFI_SUCCESS;
 }
 
@@ -223,7 +454,49 @@ MptScsiPassThru (
   IN EFI_EVENT                                      Event     OPTIONAL
   )
 {
-  return EFI_UNSUPPORTED;
+  EFI_STATUS   Status;
+  MPT_SCSI_DEV *Dev = MPT_SCSI_FROM_PASS_THRU (This);
+  UINT32       Reply;
+
+  Status = MptScsiPopulateRequest (Dev, *Target, Lun, Packet);
+  if (EFI_ERROR (Status)) {
+    return Status;
+  }
+
+  Status = MptScsiSendRequest (Dev, Packet);
+  if (EFI_ERROR (Status)) {
+    return Status;
+  }
+
+  Packet->HostAdapterStatus = EFI_EXT_SCSI_STATUS_HOST_ADAPTER_OK;
+
+  Status = MptScsiGetReply (Dev, &Reply);
+  if (EFI_ERROR (Status)) {
+    goto Fatal;
+  }
+
+  Status = MptScsiHandleReply (Dev, Reply, Packet);
+  if (EFI_ERROR (Status)) {
+    goto Fatal;
+  }
+
+  return Status;
+
+Fatal:
+  //
+  // We erred in the middle of a transaction, a very serious problem has occured
+  // and it's not clear if it's possible to recover without leaving the hardware
+  // in an inconsistent state. Perhaps we would want to reset the device...
+  //
+  DEBUG ((DEBUG_ERROR, "%a: fatal error in scsi request\n", __FUNCTION__));
+  Packet->InTransferLength  = 0;
+  Packet->OutTransferLength = 0;
+  if (Packet->HostAdapterStatus == EFI_EXT_SCSI_STATUS_HOST_ADAPTER_OK) {
+    Packet->HostAdapterStatus = EFI_EXT_SCSI_STATUS_HOST_ADAPTER_OTHER;
+  }
+  Packet->TargetStatus      = EFI_EXT_SCSI_STATUS_TARGET_TASK_ABORTED;
+  Packet->SenseDataLength   = 0;
+  return EFI_DEVICE_ERROR;
 }
 
 STATIC
@@ -426,6 +699,7 @@ MptScsiControllerStart (
   EFI_STATUS           Status;
   MPT_SCSI_DEV         *Dev;
   BOOLEAN              PciAttributesChanged;
+  UINTN                BytesMapped;
 
   Dev = AllocateZeroPool (sizeof (*Dev));
   if (Dev == NULL) {
@@ -472,6 +746,39 @@ MptScsiControllerStart (
   }
   PciAttributesChanged = TRUE;
 
+  //
+  // Create buffers for data transfer
+  //
+  Status = Dev->PciIo->AllocateBuffer (
+                         Dev->PciIo,
+                         AllocateAnyPages,
+                         EfiBootServicesData,
+                         EFI_SIZE_TO_PAGES (sizeof (*Dev->Dma)),
+                         (VOID **)&Dev->Dma,
+                         EFI_PCI_ATTRIBUTE_MEMORY_CACHED
+                         );
+  if (EFI_ERROR (Status)) {
+    goto Done;
+  }
+
+  BytesMapped = sizeof (*Dev->Dma);
+  Status = Dev->PciIo->Map (
+                         Dev->PciIo,
+                         EfiPciIoOperationBusMasterCommonBuffer,
+                         Dev->Dma,
+                         &BytesMapped,
+                         &Dev->DmaPhysical,
+                         &Dev->DmaMapping
+                         );
+  if (EFI_ERROR (Status)) {
+    goto Done;
+  }
+
+  if (BytesMapped != sizeof (*Dev->Dma)) {
+    Status = EFI_OUT_OF_RESOURCES;
+    goto Done;
+  }
+
   Status = MptScsiInit (Dev);
   if (EFI_ERROR (Status)) {
     goto Done;
@@ -506,6 +813,21 @@ MptScsiControllerStart (
 
 Done:
   if (EFI_ERROR (Status)) {
+    if (Dev->DmaMapping) {
+      Dev->PciIo->Unmap (
+                    Dev->PciIo,
+                    Dev->DmaMapping
+                    );
+    }
+
+    if (Dev->Dma) {
+      Dev->PciIo->FreeBuffer (
+                    Dev->PciIo,
+                    EFI_SIZE_TO_PAGES (sizeof (*Dev->Dma)),
+                    Dev->Dma
+                    );
+    }
+
     if (PciAttributesChanged) {
       Dev->PciIo->Attributes (
                     Dev->PciIo,
@@ -566,6 +888,17 @@ MptScsiControllerStop (
 
   MptScsiReset (Dev);
 
+  Dev->PciIo->Unmap (
+                Dev->PciIo,
+                Dev->DmaMapping
+                );
+
+  Dev->PciIo->FreeBuffer (
+                Dev->PciIo,
+                EFI_SIZE_TO_PAGES (sizeof (*Dev->Dma)),
+                Dev->Dma
+                );
+
   Dev->PciIo->Attributes (
                 Dev->PciIo,
                 EfiPciIoAttributeOperationEnable,
diff --git a/OvmfPkg/MptScsiDxe/MptScsiDxe.inf b/OvmfPkg/MptScsiDxe/MptScsiDxe.inf
index 1cb5df4233..9b090921b6 100644
--- a/OvmfPkg/MptScsiDxe/MptScsiDxe.inf
+++ b/OvmfPkg/MptScsiDxe/MptScsiDxe.inf
@@ -40,3 +40,6 @@
 [Protocols]
   gEfiPciIoProtocolGuid                  ## TO_START
   gEfiExtScsiPassThruProtocolGuid        ## BY_START
+
+[Pcd]
+  gUefiOvmfPkgTokenSpaceGuid.PcdMptScsiStallPerPollUsec ## CONSUMES
diff --git a/OvmfPkg/OvmfPkg.dec b/OvmfPkg/OvmfPkg.dec
index 4c5b6511cb..7e8097f995 100644
--- a/OvmfPkg/OvmfPkg.dec
+++ b/OvmfPkg/OvmfPkg.dec
@@ -228,6 +228,9 @@
   ## Number of page frames to use for storing grant table entries.
   gUefiOvmfPkgTokenSpaceGuid.PcdXenGrantFrames|4|UINT32|0x33
 
+  ## Microseconds to stall between polling for MptScsi request result
+  gUefiOvmfPkgTokenSpaceGuid.PcdMptScsiStallPerPollUsec|5|UINT32|0x36
+
 [PcdsDynamic, PcdsDynamicEx]
   gUefiOvmfPkgTokenSpaceGuid.PcdEmuVariableEvent|0|UINT64|2
   gUefiOvmfPkgTokenSpaceGuid.PcdOvmfFlashVariablesEnable|FALSE|BOOLEAN|0x10
-- 
2.20.1


  parent reply	other threads:[~2020-02-26 16:40 UTC|newest]

Thread overview: 27+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-02-26 16:41 [PATCH v2 00/13] OvmfPkg: Support booting from Fusion-MPT SCSI controllers Nikita Leshenko
2020-02-26 16:41 ` [PATCH v2 01/13] OvmfPkg/MptScsiDxe: Create empty driver Nikita Leshenko
2020-02-28  8:12   ` Laszlo Ersek
2020-02-26 16:41 ` [PATCH v2 02/13] OvmfPkg/MptScsiDxe: Install DriverBinding Protocol Nikita Leshenko
2020-02-28  8:16   ` Laszlo Ersek
2020-02-26 16:41 ` [PATCH v2 03/13] OvmfPkg/MptScsiDxe: Report name of driver Nikita Leshenko
2020-02-28  8:17   ` Laszlo Ersek
2020-02-26 16:41 ` [PATCH v2 04/13] OvmfPkg/MptScsiDxe: Probe PCI devices and look for MptScsi Nikita Leshenko
2020-02-28  8:26   ` Laszlo Ersek
2020-02-26 16:41 ` [PATCH v2 05/13] OvmfPkg/MptScsiDxe: Install stubbed EXT_SCSI_PASS_THRU Nikita Leshenko
2020-02-28  8:35   ` Laszlo Ersek
2020-02-26 16:41 ` [PATCH v2 06/13] OvmfPkg/MptScsiDxe: Report one Target and one LUN Nikita Leshenko
2020-02-28  8:41   ` Laszlo Ersek
2020-02-26 16:41 ` [PATCH v2 07/13] OvmfPkg/MptScsiDxe: Build DevicePath for discovered devices Nikita Leshenko
2020-02-28  9:03   ` Laszlo Ersek
2020-02-26 16:41 ` [PATCH v2 08/13] OvmfPkg/MptScsiDxe: Implement GetTargetLun Nikita Leshenko
2020-02-28  9:16   ` Laszlo Ersek
2020-02-26 16:41 ` [PATCH v2 09/13] OvmfPkg/MptScsiDxe: Open PciIo protocol for later use Nikita Leshenko
2020-02-28  9:50   ` Laszlo Ersek
2020-02-28  9:53     ` Laszlo Ersek
2020-02-26 16:41 ` [PATCH v2 10/13] OvmfPkg/MptScsiDxe: Set and restore PCI attributes Nikita Leshenko
2020-02-26 16:41 ` [PATCH v2 11/13] OvmfPkg/MptScsiDxe: Initialize hardware Nikita Leshenko
2020-02-26 16:41 ` Nikita Leshenko [this message]
2020-02-26 16:41 ` [PATCH v2 13/13] OvmfPkg/MptScsiDxe: Report multiple targets Nikita Leshenko
2020-02-27  9:52 ` [edk2-devel] [PATCH v2 00/13] OvmfPkg: Support booting from Fusion-MPT SCSI controllers Laszlo Ersek
2020-02-28  7:51 ` Laszlo Ersek
2020-02-28  8:06   ` 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=20200226164151.125182-13-nikita.leshchenko@oracle.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