]> git.proxmox.com Git - mirror_edk2.git/blobdiff - MdeModulePkg/Bus/Pci/NvmExpressDxe/NvmExpressPassthru.c
MdeModulePkg NvmExpressDxe: return error code in error path
[mirror_edk2.git] / MdeModulePkg / Bus / Pci / NvmExpressDxe / NvmExpressPassthru.c
index 02219219b66e29808529133d2569f59d63ecce17..cfad1587f299acff738723a73ca94d2a94d5fce2 100644 (file)
@@ -3,7 +3,7 @@
   NVM Express specification.\r
 \r
   (C) Copyright 2014 Hewlett-Packard Development Company, L.P.<BR>\r
-  Copyright (c) 2013 - 2015, Intel Corporation. All rights reserved.<BR>\r
+  Copyright (c) 2013 - 2018, Intel Corporation. All rights reserved.<BR>\r
   This program and the accompanying materials\r
   are licensed and made available under the terms and conditions of the BSD License\r
   which accompanies this distribution.  The full text of the license may be found at\r
@@ -316,6 +316,100 @@ EXIT:
 }\r
 \r
 \r
+/**\r
+  Aborts the asynchronous PassThru requests.\r
+\r
+  @param[in] Private        The pointer to the NVME_CONTROLLER_PRIVATE_DATA\r
+                            data structure.\r
+\r
+  @retval EFI_SUCCESS       The asynchronous PassThru requests have been aborted.\r
+  @return EFI_DEVICE_ERROR  Fail to abort all the asynchronous PassThru requests.\r
+\r
+**/\r
+EFI_STATUS\r
+AbortAsyncPassThruTasks (\r
+  IN NVME_CONTROLLER_PRIVATE_DATA    *Private\r
+  )\r
+{\r
+  EFI_PCI_IO_PROTOCOL                *PciIo;\r
+  LIST_ENTRY                         *Link;\r
+  LIST_ENTRY                         *NextLink;\r
+  NVME_BLKIO2_SUBTASK                *Subtask;\r
+  NVME_BLKIO2_REQUEST                *BlkIo2Request;\r
+  NVME_PASS_THRU_ASYNC_REQ           *AsyncRequest;\r
+  EFI_BLOCK_IO2_TOKEN                *Token;\r
+  EFI_TPL                            OldTpl;\r
+  EFI_STATUS                         Status;\r
+\r
+  PciIo  = Private->PciIo;\r
+  OldTpl = gBS->RaiseTPL (TPL_NOTIFY);\r
+\r
+  //\r
+  // Cancel the unsubmitted subtasks.\r
+  //\r
+  for (Link = GetFirstNode (&Private->UnsubmittedSubtasks);\r
+       !IsNull (&Private->UnsubmittedSubtasks, Link);\r
+       Link = NextLink) {\r
+    NextLink      = GetNextNode (&Private->UnsubmittedSubtasks, Link);\r
+    Subtask       = NVME_BLKIO2_SUBTASK_FROM_LINK (Link);\r
+    BlkIo2Request = Subtask->BlockIo2Request;\r
+    Token         = BlkIo2Request->Token;\r
+\r
+    BlkIo2Request->UnsubmittedSubtaskNum--;\r
+    if (Subtask->IsLast) {\r
+      BlkIo2Request->LastSubtaskSubmitted = TRUE;\r
+    }\r
+    Token->TransactionStatus = EFI_ABORTED;\r
+\r
+    RemoveEntryList (Link);\r
+    InsertTailList (&BlkIo2Request->SubtasksQueue, Link);\r
+    gBS->SignalEvent (Subtask->Event);\r
+  }\r
+\r
+  //\r
+  // Cleanup the resources for the asynchronous PassThru requests.\r
+  //\r
+  for (Link = GetFirstNode (&Private->AsyncPassThruQueue);\r
+       !IsNull (&Private->AsyncPassThruQueue, Link);\r
+       Link = NextLink) {\r
+    NextLink = GetNextNode (&Private->AsyncPassThruQueue, Link);\r
+    AsyncRequest = NVME_PASS_THRU_ASYNC_REQ_FROM_THIS (Link);\r
+\r
+    if (AsyncRequest->MapData != NULL) {\r
+      PciIo->Unmap (PciIo, AsyncRequest->MapData);\r
+    }\r
+    if (AsyncRequest->MapMeta != NULL) {\r
+      PciIo->Unmap (PciIo, AsyncRequest->MapMeta);\r
+    }\r
+    if (AsyncRequest->MapPrpList != NULL) {\r
+      PciIo->Unmap (PciIo, AsyncRequest->MapPrpList);\r
+    }\r
+    if (AsyncRequest->PrpListHost != NULL) {\r
+      PciIo->FreeBuffer (\r
+               PciIo,\r
+               AsyncRequest->PrpListNo,\r
+               AsyncRequest->PrpListHost\r
+               );\r
+    }\r
+\r
+    RemoveEntryList (Link);\r
+    gBS->SignalEvent (AsyncRequest->CallerEvent);\r
+    FreePool (AsyncRequest);\r
+  }\r
+\r
+  if (IsListEmpty (&Private->AsyncPassThruQueue) &&\r
+      IsListEmpty (&Private->UnsubmittedSubtasks)) {\r
+    Status = EFI_SUCCESS;\r
+  } else {\r
+    Status = EFI_DEVICE_ERROR;\r
+  }\r
+\r
+  gBS->RestoreTPL (OldTpl);\r
+\r
+  return Status;\r
+}\r
+\r
+\r
 /**\r
   Sends an NVM Express Command Packet to an NVM Express controller or namespace. This function supports\r
   both blocking I/O and non-blocking I/O. The blocking I/O functionality is required, and the non-blocking\r
@@ -357,27 +451,31 @@ NvmExpressPassThru (
   IN     EFI_EVENT                                   Event OPTIONAL\r
   )\r
 {\r
-  NVME_CONTROLLER_PRIVATE_DATA  *Private;\r
-  EFI_STATUS                    Status;\r
-  EFI_PCI_IO_PROTOCOL           *PciIo;\r
-  NVME_SQ                       *Sq;\r
-  NVME_CQ                       *Cq;\r
-  UINT16                        QueueId;\r
-  UINT32                        Bytes;\r
-  UINT16                        Offset;\r
-  EFI_EVENT                     TimerEvent;\r
-  EFI_PCI_IO_PROTOCOL_OPERATION Flag;\r
-  EFI_PHYSICAL_ADDRESS          PhyAddr;\r
-  VOID                          *MapData;\r
-  VOID                          *MapMeta;\r
-  VOID                          *MapPrpList;\r
-  UINTN                         MapLength;\r
-  UINT64                        *Prp;\r
-  VOID                          *PrpListHost;\r
-  UINTN                         PrpListNo;\r
-  UINT32                        Data;\r
-  NVME_PASS_THRU_ASYNC_REQ      *AsyncRequest;\r
-  EFI_TPL                       OldTpl;\r
+  NVME_CONTROLLER_PRIVATE_DATA   *Private;\r
+  EFI_STATUS                     Status;\r
+  EFI_STATUS                     PreviousStatus;\r
+  EFI_PCI_IO_PROTOCOL            *PciIo;\r
+  NVME_SQ                        *Sq;\r
+  NVME_CQ                        *Cq;\r
+  UINT16                         QueueId;\r
+  UINT32                         Bytes;\r
+  UINT16                         Offset;\r
+  EFI_EVENT                      TimerEvent;\r
+  EFI_PCI_IO_PROTOCOL_OPERATION  Flag;\r
+  EFI_PHYSICAL_ADDRESS           PhyAddr;\r
+  VOID                           *MapData;\r
+  VOID                           *MapMeta;\r
+  VOID                           *MapPrpList;\r
+  UINTN                          MapLength;\r
+  UINT64                         *Prp;\r
+  VOID                           *PrpListHost;\r
+  UINTN                          PrpListNo;\r
+  UINT32                         Attributes;\r
+  UINT32                         IoAlign;\r
+  UINT32                         MaxTransLen;\r
+  UINT32                         Data;\r
+  NVME_PASS_THRU_ASYNC_REQ       *AsyncRequest;\r
+  EFI_TPL                        OldTpl;\r
 \r
   //\r
   // check the data fields in Packet parameter.\r
@@ -394,7 +492,51 @@ NvmExpressPassThru (
     return EFI_INVALID_PARAMETER;\r
   }\r
 \r
+  //\r
+  // 'Attributes' with neither EFI_NVM_EXPRESS_PASS_THRU_ATTRIBUTES_LOGICAL nor\r
+  // EFI_NVM_EXPRESS_PASS_THRU_ATTRIBUTES_PHYSICAL set is an illegal\r
+  // configuration.\r
+  //\r
+  Attributes  = This->Mode->Attributes;\r
+  if ((Attributes & (EFI_NVM_EXPRESS_PASS_THRU_ATTRIBUTES_PHYSICAL |\r
+    EFI_NVM_EXPRESS_PASS_THRU_ATTRIBUTES_LOGICAL)) == 0) {\r
+    return EFI_INVALID_PARAMETER;\r
+  }\r
+\r
+  //\r
+  // Buffer alignment check for TransferBuffer & MetadataBuffer.\r
+  //\r
+  IoAlign     = This->Mode->IoAlign;\r
+  if (IoAlign > 0 && (((UINTN) Packet->TransferBuffer & (IoAlign - 1)) != 0)) {\r
+    return EFI_INVALID_PARAMETER;\r
+  }\r
+\r
+  if (IoAlign > 0 && (((UINTN) Packet->MetadataBuffer & (IoAlign - 1)) != 0)) {\r
+    return EFI_INVALID_PARAMETER;\r
+  }\r
+\r
   Private     = NVME_CONTROLLER_PRIVATE_DATA_FROM_PASS_THRU (This);\r
+\r
+  //\r
+  // Check NamespaceId is valid or not.\r
+  //\r
+  if ((NamespaceId > Private->ControllerData->Nn) &&\r
+      (NamespaceId != (UINT32) -1)) {\r
+    return EFI_INVALID_PARAMETER;\r
+  }\r
+\r
+  //\r
+  // Check whether TransferLength exceeds the maximum data transfer size.\r
+  //\r
+  if (Private->ControllerData->Mdts != 0) {\r
+    MaxTransLen = (1 << (Private->ControllerData->Mdts)) *\r
+                  (1 << (Private->Cap.Mpsmin + 12));\r
+    if (Packet->TransferLength > MaxTransLen) {\r
+      Packet->TransferLength = MaxTransLen;\r
+      return EFI_BAD_BUFFER_SIZE;\r
+    }\r
+  }\r
+\r
   PciIo       = Private->PciIo;\r
   MapData     = NULL;\r
   MapMeta     = NULL;\r
@@ -451,7 +593,12 @@ NvmExpressPassThru (
   // these two cmds are special which requires their data buffer must support simultaneous access by both the\r
   // processor and a PCI Bus Master. It's caller's responsbility to ensure this.\r
   //\r
-  if (((Sq->Opc & (BIT0 | BIT1)) != 0) && (Sq->Opc != NVME_ADMIN_CRIOCQ_CMD) && (Sq->Opc != NVME_ADMIN_CRIOSQ_CMD)) {\r
+  if (((Sq->Opc & (BIT0 | BIT1)) != 0) &&\r
+      !((Packet->QueueType == NVME_ADMIN_QUEUE) && ((Sq->Opc == NVME_ADMIN_CRIOCQ_CMD) || (Sq->Opc == NVME_ADMIN_CRIOSQ_CMD)))) {\r
+    if ((Packet->TransferLength == 0) || (Packet->TransferBuffer == NULL)) {\r
+      return EFI_INVALID_PARAMETER;\r
+    }\r
+\r
     if ((Sq->Opc & BIT0) != 0) {\r
       Flag = EfiPciIoOperationBusMasterRead;\r
     } else {\r
@@ -474,8 +621,7 @@ NvmExpressPassThru (
     Sq->Prp[0] = PhyAddr;\r
     Sq->Prp[1] = 0;\r
 \r
-    MapLength = Packet->MetadataLength;\r
-    if(Packet->MetadataBuffer != NULL) {\r
+    if((Packet->MetadataLength != 0) && (Packet->MetadataBuffer != NULL)) {\r
       MapLength = Packet->MetadataLength;\r
       Status = PciIo->Map (\r
                         PciIo,\r
@@ -510,6 +656,7 @@ NvmExpressPassThru (
     PhyAddr = (Sq->Prp[0] + EFI_PAGE_SIZE) & ~(EFI_PAGE_SIZE - 1);\r
     Prp = NvmeCreatePrpList (PciIo, PhyAddr, EFI_SIZE_TO_PAGES(Offset + Bytes) - 1, &PrpListHost, &PrpListNo, &MapPrpList);\r
     if (Prp == NULL) {\r
+      Status = EFI_OUT_OF_RESOURCES;\r
       goto EXIT;\r
     }\r
 \r
@@ -546,14 +693,14 @@ NvmExpressPassThru (
   //\r
   // Ring the submission queue doorbell.\r
   //\r
-  if (Event != NULL) {\r
+  if ((Event != NULL) && (QueueId != 0)) {\r
     Private->SqTdbl[QueueId].Sqt =\r
       (Private->SqTdbl[QueueId].Sqt + 1) % (NVME_ASYNC_CSQ_SIZE + 1);\r
   } else {\r
     Private->SqTdbl[QueueId].Sqt ^= 1;\r
   }\r
   Data = ReadUnaligned32 ((UINT32*)&Private->SqTdbl[QueueId]);\r
-  PciIo->Mem.Write (\r
+  Status = PciIo->Mem.Write (\r
                PciIo,\r
                EfiPciIoWidthUint32,\r
                NVME_BAR,\r
@@ -562,11 +709,15 @@ NvmExpressPassThru (
                &Data\r
                );\r
 \r
+  if (EFI_ERROR (Status)) {\r
+    goto EXIT;\r
+  }\r
+\r
   //\r
   // For non-blocking requests, return directly if the command is placed\r
   // in the submission queue.\r
   //\r
-  if (Event != NULL) {\r
+  if ((Event != NULL) && (QueueId != 0)) {\r
     AsyncRequest = AllocateZeroPool (sizeof (NVME_PASS_THRU_ASYNC_REQ));\r
     if (AsyncRequest == NULL) {\r
       Status = EFI_DEVICE_ERROR;\r
@@ -577,6 +728,11 @@ NvmExpressPassThru (
     AsyncRequest->Packet        = Packet;\r
     AsyncRequest->CommandId     = Sq->Cid;\r
     AsyncRequest->CallerEvent   = Event;\r
+    AsyncRequest->MapData       = MapData;\r
+    AsyncRequest->MapMeta       = MapMeta;\r
+    AsyncRequest->MapPrpList    = MapPrpList;\r
+    AsyncRequest->PrpListNo     = PrpListNo;\r
+    AsyncRequest->PrpListHost   = PrpListHost;\r
 \r
     OldTpl = gBS->RaiseTPL (TPL_NOTIFY);\r
     InsertTailList (&Private->AsyncPassThruQueue, &AsyncRequest->Link);\r
@@ -633,6 +789,44 @@ NvmExpressPassThru (
         NvmeDumpStatus(Cq);\r
       DEBUG_CODE_END();\r
     }\r
+  } else {\r
+    //\r
+    // Timeout occurs for an NVMe command. Reset the controller to abort the\r
+    // outstanding commands.\r
+    //\r
+    DEBUG ((DEBUG_ERROR, "NvmExpressPassThru: Timeout occurs for an NVMe command.\n"));\r
+\r
+    //\r
+    // Disable the timer to trigger the process of async transfers temporarily.\r
+    //\r
+    Status = gBS->SetTimer (Private->TimerEvent, TimerCancel, 0);\r
+    if (EFI_ERROR (Status)) {\r
+      goto EXIT;\r
+    }\r
+\r
+    //\r
+    // Reset the NVMe controller.\r
+    //\r
+    Status = NvmeControllerInit (Private);\r
+    if (!EFI_ERROR (Status)) {\r
+      Status = AbortAsyncPassThruTasks (Private);\r
+      if (!EFI_ERROR (Status)) {\r
+        //\r
+        // Re-enable the timer to trigger the process of async transfers.\r
+        //\r
+        Status = gBS->SetTimer (Private->TimerEvent, TimerPeriodic, NVME_HC_ASYNC_TIMER);\r
+        if (!EFI_ERROR (Status)) {\r
+          //\r
+          // Return EFI_TIMEOUT to indicate a timeout occurs for NVMe PassThru command.\r
+          //\r
+          Status = EFI_TIMEOUT;\r
+        }\r
+      }\r
+    } else {\r
+      Status = EFI_DEVICE_ERROR;\r
+    }\r
+\r
+    goto EXIT;\r
   }\r
 \r
   if ((Private->CqHdbl[QueueId].Cqh ^= 1) == 0) {\r
@@ -640,7 +834,8 @@ NvmExpressPassThru (
   }\r
 \r
   Data = ReadUnaligned32 ((UINT32*)&Private->CqHdbl[QueueId]);\r
-  PciIo->Mem.Write (\r
+  PreviousStatus = Status;\r
+  Status = PciIo->Mem.Write (\r
                PciIo,\r
                EfiPciIoWidthUint32,\r
                NVME_BAR,\r
@@ -648,6 +843,18 @@ NvmExpressPassThru (
                1,\r
                &Data\r
                );\r
+  // The return status of PciIo->Mem.Write should not override\r
+  // previous status if previous status contains error.\r
+  Status = EFI_ERROR (PreviousStatus) ? PreviousStatus : Status;\r
+\r
+  //\r
+  // For now, the code does not support the non-blocking feature for admin queue.\r
+  // If Event is not NULL for admin queue, signal the caller's event here.\r
+  //\r
+  if (Event != NULL) {\r
+    ASSERT (QueueId == 0);\r
+    gBS->SignalEvent (Event);\r
+  }\r
 \r
 EXIT:\r
   if (MapData != NULL) {\r
@@ -758,11 +965,15 @@ NvmExpressGetNextNamespace (
 \r
     *NamespaceId = NextNamespaceId;\r
   } else {\r
-    if (*NamespaceId >= Private->ControllerData->Nn) {\r
+    if (*NamespaceId > Private->ControllerData->Nn) {\r
       return EFI_INVALID_PARAMETER;\r
     }\r
 \r
     NextNamespaceId = *NamespaceId + 1;\r
+    if (NextNamespaceId > Private->ControllerData->Nn) {\r
+      return EFI_NOT_FOUND;\r
+    }\r
+\r
     //\r
     // Allocate buffer for Identify Namespace data.\r
     //\r
@@ -820,6 +1031,7 @@ NvmExpressGetNamespace (
   )\r
 {\r
   NVME_NAMESPACE_DEVICE_PATH       *Node;\r
+  NVME_CONTROLLER_PRIVATE_DATA     *Private;\r
 \r
   if ((This == NULL) || (DevicePath == NULL) || (NamespaceId == NULL)) {\r
     return EFI_INVALID_PARAMETER;\r
@@ -829,13 +1041,22 @@ NvmExpressGetNamespace (
     return EFI_UNSUPPORTED;\r
   }\r
 \r
-  Node = (NVME_NAMESPACE_DEVICE_PATH *)DevicePath;\r
+  Node    = (NVME_NAMESPACE_DEVICE_PATH *)DevicePath;\r
+  Private = NVME_CONTROLLER_PRIVATE_DATA_FROM_PASS_THRU (This);\r
 \r
   if (DevicePath->SubType == MSG_NVME_NAMESPACE_DP) {\r
     if (DevicePathNodeLength(DevicePath) != sizeof(NVME_NAMESPACE_DEVICE_PATH)) {\r
       return EFI_NOT_FOUND;\r
     }\r
 \r
+    //\r
+    // Check NamespaceId in the device path node is valid or not.\r
+    //\r
+    if ((Node->NamespaceId == 0) ||\r
+      (Node->NamespaceId > Private->ControllerData->Nn)) {\r
+      return EFI_NOT_FOUND;\r
+    }\r
+\r
     *NamespaceId = Node->NamespaceId;\r
 \r
     return EFI_SUCCESS;\r
@@ -895,13 +1116,17 @@ NvmExpressBuildDevicePath (
     return EFI_INVALID_PARAMETER;\r
   }\r
 \r
-  if (NamespaceId == 0) {\r
-    return EFI_NOT_FOUND;\r
-  }\r
-\r
   Status  = EFI_SUCCESS;\r
   Private = NVME_CONTROLLER_PRIVATE_DATA_FROM_PASS_THRU (This);\r
 \r
+  //\r
+  // Check NamespaceId is valid or not.\r
+  //\r
+  if ((NamespaceId == 0) ||\r
+    (NamespaceId > Private->ControllerData->Nn)) {\r
+    return EFI_NOT_FOUND;\r
+  }\r
+\r
   Node = (NVME_NAMESPACE_DEVICE_PATH *)AllocateZeroPool (sizeof (NVME_NAMESPACE_DEVICE_PATH));\r
   if (Node == NULL) {\r
     return EFI_OUT_OF_RESOURCES;\r