]> git.proxmox.com Git - mirror_edk2.git/commitdiff
MdeModulePkg/AtaAtapiPassThru: Add SATA error recovery flow
authorAlbecki, Mateusz <mateusz.albecki@intel.com>
Thu, 5 Nov 2020 12:48:45 +0000 (20:48 +0800)
committermergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
Wed, 11 Nov 2020 02:27:59 +0000 (02:27 +0000)
BZ: https://bugzilla.tianocore.org/show_bug.cgi?id=3025

This commit adds error recovery flow on SATA port when the error
condition is reported. Commit only implements SATA port reset flow which
is executed when PxTFD indicates BSY or DRQ. Commit does not implement
HBA level reset.

Signed-off-by: Mateusz Albecki <mateusz.albecki@intel.com>
Reviewed-by: Hao A Wu <hao.a.wu@intel.com>
Cc: Ray Ni <ray.ni@intel.com>
Cc: Hao A Wu <hao.a.wu@intel.com>
MdeModulePkg/Bus/Ata/AtaAtapiPassThru/AhciMode.c
MdeModulePkg/Bus/Ata/AtaAtapiPassThru/AhciMode.h

index 180a60b5aac16f7172ddc39f687f9b228640a21a..0b7141c4f1f377ba29def0060ce1fa406ef144bd 100644 (file)
@@ -608,6 +608,148 @@ AhciBuildCommandFis (
   CmdFis->AhciCFisDevHead     = (UINT8) (AtaCommandBlock->AtaDeviceHead | 0xE0);\r
 }\r
 \r
+/**\r
+  Wait until SATA device reports it is ready for operation.\r
+\r
+  @param[in] PciIo    Pointer to AHCI controller PciIo.\r
+  @param[in] Port     SATA port index on which to reset.\r
+\r
+  @retval EFI_SUCCESS  Device ready for operation.\r
+  @retval EFI_TIMEOUT  Device failed to get ready within required period.\r
+**/\r
+EFI_STATUS\r
+AhciWaitDeviceReady (\r
+  IN EFI_PCI_IO_PROTOCOL  *PciIo,\r
+  IN UINT8                Port\r
+   )\r
+{\r
+  UINT32      PhyDetectDelay;\r
+  UINT32      Data;\r
+  UINT32      Offset;\r
+\r
+  //\r
+  // According to SATA1.0a spec section 5.2, we need to wait for PxTFD.BSY and PxTFD.DRQ\r
+  // and PxTFD.ERR to be zero. The maximum wait time is 16s which is defined at ATA spec.\r
+  //\r
+  PhyDetectDelay = 16 * 1000;\r
+  do {\r
+    Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_SERR;\r
+    if (AhciReadReg(PciIo, Offset) != 0) {\r
+      AhciWriteReg (PciIo, Offset, AhciReadReg(PciIo, Offset));\r
+    }\r
+    Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_TFD;\r
+\r
+    Data = AhciReadReg (PciIo, Offset) & EFI_AHCI_PORT_TFD_MASK;\r
+    if (Data == 0) {\r
+      break;\r
+    }\r
+\r
+    MicroSecondDelay (1000);\r
+    PhyDetectDelay--;\r
+  } while (PhyDetectDelay > 0);\r
+\r
+  if (PhyDetectDelay == 0) {\r
+    DEBUG ((DEBUG_ERROR, "Port %d Device not ready (TFD=0x%X)\n", Port, Data));\r
+    return EFI_TIMEOUT;\r
+  } else {\r
+    return EFI_SUCCESS;\r
+  }\r
+}\r
+\r
+\r
+/**\r
+  Reset the SATA port. Algorithm follows AHCI spec 1.3.1 section 10.4.2\r
+\r
+  @param[in] PciIo    Pointer to AHCI controller PciIo.\r
+  @param[in] Port     SATA port index on which to reset.\r
+\r
+  @retval EFI_SUCCESS  Port reset.\r
+  @retval Others       Failed to reset the port.\r
+**/\r
+EFI_STATUS\r
+AhciResetPort (\r
+  IN EFI_PCI_IO_PROTOCOL  *PciIo,\r
+  IN UINT8                Port\r
+  )\r
+{\r
+  UINT32      Offset;\r
+  EFI_STATUS  Status;\r
+\r
+  Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_SCTL;\r
+  AhciOrReg (PciIo, Offset, EFI_AHCI_PORT_SCTL_DET_INIT);\r
+  //\r
+  // SW is required to keep DET set to 0x1 at least for 1 milisecond to ensure that\r
+  // at least one COMRESET signal is sent.\r
+  //\r
+  MicroSecondDelay(1000);\r
+  AhciAndReg (PciIo, Offset, ~(UINT32)EFI_AHCI_PORT_SSTS_DET_MASK);\r
+\r
+  Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_SSTS;\r
+  Status = AhciWaitMmioSet (PciIo, Offset, EFI_AHCI_PORT_SSTS_DET_MASK, EFI_AHCI_PORT_SSTS_DET_PCE, ATA_ATAPI_TIMEOUT);\r
+  if (EFI_ERROR (Status)) {\r
+    return Status;\r
+  }\r
+\r
+  return AhciWaitDeviceReady (PciIo, Port);\r
+}\r
+\r
+/**\r
+  Recovers the SATA port from error condition.\r
+  This function implements algorithm described in\r
+  AHCI spec 1.3.1 section 6.2.2\r
+\r
+  @param[in] PciIo    Pointer to AHCI controller PciIo.\r
+  @param[in] Port     SATA port index on which to check.\r
+\r
+  @retval EFI_SUCCESS  Port recovered.\r
+  @retval Others       Failed to recover port.\r
+**/\r
+EFI_STATUS\r
+AhciRecoverPortError (\r
+  IN EFI_PCI_IO_PROTOCOL  *PciIo,\r
+  IN UINT8                Port\r
+  )\r
+{\r
+  UINT32      Offset;\r
+  UINT32      PortInterrupt;\r
+  UINT32      PortTfd;\r
+  EFI_STATUS  Status;\r
+\r
+  Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_IS;\r
+  PortInterrupt = AhciReadReg (PciIo, Offset);\r
+  if ((PortInterrupt & EFI_AHCI_PORT_IS_FATAL_ERROR_MASK) == 0) {\r
+    //\r
+    // No fatal error detected. Exit with success as port should still be operational.\r
+    // No need to clear IS as it will be cleared when the next command starts.\r
+    //\r
+    return EFI_SUCCESS;\r
+  }\r
+\r
+  Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_CMD;\r
+  AhciAndReg (PciIo, Offset, ~(UINT32)EFI_AHCI_PORT_CMD_ST);\r
+\r
+  Status = AhciWaitMmioSet (PciIo, Offset, EFI_AHCI_PORT_CMD_CR, 0, ATA_ATAPI_TIMEOUT);\r
+  if (EFI_ERROR (Status)) {\r
+    DEBUG ((DEBUG_ERROR, "Ahci port %d is in hung state, aborting recovery\n", Port));\r
+    return Status;\r
+  }\r
+\r
+  //\r
+  // If TFD.BSY or TFD.DRQ is still set it means that drive is hung and software has\r
+  // to reset it before sending any additional commands.\r
+  //\r
+  Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_TFD;\r
+  PortTfd = AhciReadReg (PciIo, Offset);\r
+  if ((PortTfd & (EFI_AHCI_PORT_TFD_BSY | EFI_AHCI_PORT_TFD_DRQ)) != 0) {\r
+    Status = AhciResetPort (PciIo, Port);\r
+    if (EFI_ERROR (Status)) {\r
+      DEBUG ((DEBUG_ERROR, "Failed to reset the port %d\n", Port));\r
+    }\r
+  }\r
+\r
+  return EFI_SUCCESS;\r
+}\r
+\r
 /**\r
   Checks if specified FIS has been received.\r
 \r
@@ -827,6 +969,10 @@ AhciPioTransfer (
     Status = AhciWaitUntilFisReceived (PciIo, Port, Timeout, SataFisD2H);\r
   }\r
 \r
+  if (Status == EFI_DEVICE_ERROR) {\r
+    AhciRecoverPortError (PciIo, Port);\r
+  }\r
+\r
 Exit:\r
   AhciStopCommand (\r
     PciIo,\r
@@ -1007,6 +1153,10 @@ AhciDmaTransfer (
     Status = AhciWaitUntilFisReceived (PciIo, Port, Timeout, SataFisD2H);\r
   }\r
 \r
+  if (Status == EFI_DEVICE_ERROR) {\r
+    AhciRecoverPortError (PciIo, Port);\r
+  }\r
+\r
 Exit:\r
   //\r
   // For Blocking mode, the command should be stopped, the Fis should be disabled\r
@@ -1119,6 +1269,9 @@ AhciNonDataTransfer (
   }\r
 \r
   Status = AhciWaitUntilFisReceived (PciIo, Port, Timeout, SataFisD2H);\r
+  if (Status == EFI_DEVICE_ERROR) {\r
+    AhciRecoverPortError (PciIo, Port);\r
+  }\r
 \r
 Exit:\r
   AhciStopCommand (\r
@@ -2583,29 +2736,8 @@ AhciModeInitialization (
         continue;\r
       }\r
 \r
-      //\r
-      // According to SATA1.0a spec section 5.2, we need to wait for PxTFD.BSY and PxTFD.DRQ\r
-      // and PxTFD.ERR to be zero. The maximum wait time is 16s which is defined at ATA spec.\r
-      //\r
-      PhyDetectDelay = 16 * 1000;\r
-      do {\r
-        Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_SERR;\r
-        if (AhciReadReg(PciIo, Offset) != 0) {\r
-          AhciWriteReg (PciIo, Offset, AhciReadReg(PciIo, Offset));\r
-        }\r
-        Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_TFD;\r
-\r
-        Data = AhciReadReg (PciIo, Offset) & EFI_AHCI_PORT_TFD_MASK;\r
-        if (Data == 0) {\r
-          break;\r
-        }\r
-\r
-        MicroSecondDelay (1000);\r
-        PhyDetectDelay--;\r
-      } while (PhyDetectDelay > 0);\r
-\r
-      if (PhyDetectDelay == 0) {\r
-        DEBUG ((EFI_D_ERROR, "Port %d Device presence detected but phy not ready (TFD=0x%X)\n", Port, Data));\r
+      Status = AhciWaitDeviceReady (PciIo, Port);\r
+      if (EFI_ERROR (Status)) {\r
         continue;\r
       }\r
 \r
index 6bcff1bb7b550950b15cf84da87805e408460cbb..338447a55fd44f85aa3b1bed99afbfc0921c3ba4 100644 (file)
@@ -114,6 +114,7 @@ typedef union {
 #define   EFI_AHCI_PORT_IS_CLEAR               0xFFFFFFFF\r
 #define   EFI_AHCI_PORT_IS_FIS_CLEAR           0x0000000F\r
 #define   EFI_AHCI_PORT_IS_ERROR_MASK          (EFI_AHCI_PORT_IS_INFS | EFI_AHCI_PORT_IS_IFS | EFI_AHCI_PORT_IS_HBDS | EFI_AHCI_PORT_IS_HBFS | EFI_AHCI_PORT_IS_TFES)\r
+#define   EFI_AHCI_PORT_IS_FATAL_ERROR_MASK    (EFI_AHCI_PORT_IS_IFS | EFI_AHCI_PORT_IS_HBDS | EFI_AHCI_PORT_IS_HBFS | EFI_AHCI_PORT_IS_TFES)\r
 \r
 #define EFI_AHCI_PORT_IE                       0x0014\r
 #define EFI_AHCI_PORT_CMD                      0x0018\r
@@ -122,9 +123,11 @@ typedef union {
 #define   EFI_AHCI_PORT_CMD_SUD                BIT1\r
 #define   EFI_AHCI_PORT_CMD_POD                BIT2\r
 #define   EFI_AHCI_PORT_CMD_CLO                BIT3\r
-#define   EFI_AHCI_PORT_CMD_CR                 BIT15\r
 #define   EFI_AHCI_PORT_CMD_FRE                BIT4\r
+#define   EFI_AHCI_PORT_CMD_CCS_MASK           (BIT8 | BIT9 | BIT10 | BIT11 | BIT12)\r
+#define   EFI_AHCI_PORT_CMD_CCS_SHIFT          8\r
 #define   EFI_AHCI_PORT_CMD_FR                 BIT14\r
+#define   EFI_AHCI_PORT_CMD_CR                 BIT15\r
 #define   EFI_AHCI_PORT_CMD_MASK               ~(EFI_AHCI_PORT_CMD_ST | EFI_AHCI_PORT_CMD_FRE | EFI_AHCI_PORT_CMD_COL)\r
 #define   EFI_AHCI_PORT_CMD_PMA                BIT17\r
 #define   EFI_AHCI_PORT_CMD_HPCP               BIT18\r