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
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
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
}\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
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