X-Git-Url: https://git.proxmox.com/?p=mirror_edk2.git;a=blobdiff_plain;f=MdeModulePkg%2FBus%2FAta%2FAtaAtapiPassThru%2FAhciMode.c;h=1bae1e8e0cfa09c7a11dfc0d12c59547b297d686;hp=1c45312712bb96382f162ce3e871904d7542e3c3;hb=f3100a1a2f575e1a0ce4744cb2722e780135b1cf;hpb=6b13aa602aa1f44743d40691baff2714d842c550 diff --git a/MdeModulePkg/Bus/Ata/AtaAtapiPassThru/AhciMode.c b/MdeModulePkg/Bus/Ata/AtaAtapiPassThru/AhciMode.c index 1c45312712..1bae1e8e0c 100644 --- a/MdeModulePkg/Bus/Ata/AtaAtapiPassThru/AhciMode.c +++ b/MdeModulePkg/Bus/Ata/AtaAtapiPassThru/AhciMode.c @@ -1,7 +1,8 @@ /** @file The file for AHCI mode of ATA host controller. - Copyright (c) 2010 - 2012, Intel Corporation. All rights reserved.
+ Copyright (c) 2010 - 2018, Intel Corporation. All rights reserved.
+ (C) Copyright 2015 Hewlett Packard Enterprise Development LP
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 @@ -156,9 +157,16 @@ AhciWaitMmioSet ( ) { UINT32 Value; - UINT32 Delay; + UINT64 Delay; + BOOLEAN InfiniteWait; - Delay = (UINT32) (DivU64x32 (Timeout, 1000) + 1); + if (Timeout == 0) { + InfiniteWait = TRUE; + } else { + InfiniteWait = FALSE; + } + + Delay = DivU64x32 (Timeout, 1000) + 1; do { // @@ -177,7 +185,7 @@ AhciWaitMmioSet ( Delay--; - } while (Delay > 0); + } while (InfiniteWait || (Delay > 0)); return EFI_TIMEOUT; } @@ -204,9 +212,16 @@ AhciWaitMemSet ( ) { UINT32 Value; - UINT32 Delay; + UINT64 Delay; + BOOLEAN InfiniteWait; - Delay = (UINT32) (DivU64x32 (Timeout, 1000) + 1); + if (Timeout == 0) { + InfiniteWait = TRUE; + } else { + InfiniteWait = FALSE; + } + + Delay = DivU64x32 (Timeout, 1000) + 1; do { // @@ -231,7 +246,7 @@ AhciWaitMemSet ( Delay--; - } while (Delay > 0); + } while (InfiniteWait || (Delay > 0)); return EFI_TIMEOUT; } @@ -242,7 +257,8 @@ AhciWaitMemSet ( @param[in] Address The memory address to test. @param[in] MaskValue The mask value of memory. @param[in] TestValue The test value of memory. - @param[in, out] RetryTimes The retry times value for waitting memory set. If 0, then just try once. + @param[in, out] Task Optional. Pointer to the ATA_NONBLOCK_TASK used by + non-blocking mode. If NULL, then just try once. @retval EFI_NOTREADY The memory is not set. @retval EFI_TIMEOUT The memory setting retry times out. @@ -255,13 +271,13 @@ AhciCheckMemSet ( IN UINTN Address, IN UINT32 MaskValue, IN UINT32 TestValue, - IN OUT UINTN *RetryTimes OPTIONAL + IN OUT ATA_NONBLOCK_TASK *Task ) { UINT32 Value; - if (RetryTimes != NULL) { - (*RetryTimes)--; + if (Task != NULL) { + Task->RetryTimes--; } Value = *(volatile UINT32 *) Address; @@ -271,7 +287,7 @@ AhciCheckMemSet ( return EFI_SUCCESS; } - if ((RetryTimes != NULL) && (*RetryTimes == 0)) { + if ((Task != NULL) && !Task->InfiniteWait && (Task->RetryTimes == 0)) { return EFI_TIMEOUT; } else { return EFI_NOT_READY; @@ -355,6 +371,7 @@ AhciClearPortStatus ( in the Status Register, the Error Register's value is also be dumped. @param PciIo The PCI IO protocol instance. + @param AhciRegisters The pointer to the EFI_AHCI_REGISTERS. @param Port The number of port. @param AtaStatusBlock A pointer to EFI_ATA_STATUS_BLOCK data structure. @@ -363,24 +380,42 @@ VOID EFIAPI AhciDumpPortStatus ( IN EFI_PCI_IO_PROTOCOL *PciIo, + IN EFI_AHCI_REGISTERS *AhciRegisters, IN UINT8 Port, IN OUT EFI_ATA_STATUS_BLOCK *AtaStatusBlock ) { - UINT32 Offset; + UINTN Offset; UINT32 Data; + UINTN FisBaseAddr; + EFI_STATUS Status; ASSERT (PciIo != NULL); - Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_TFD; - Data = AhciReadReg (PciIo, Offset); - if (AtaStatusBlock != NULL) { ZeroMem (AtaStatusBlock, sizeof (EFI_ATA_STATUS_BLOCK)); - AtaStatusBlock->AtaStatus = (UINT8)Data; - if ((AtaStatusBlock->AtaStatus & BIT0) != 0) { - AtaStatusBlock->AtaError = (UINT8)(Data >> 8); + FisBaseAddr = (UINTN)AhciRegisters->AhciRFis + Port * sizeof (EFI_AHCI_RECEIVED_FIS); + Offset = FisBaseAddr + EFI_AHCI_D2H_FIS_OFFSET; + + Status = AhciCheckMemSet (Offset, EFI_AHCI_FIS_TYPE_MASK, EFI_AHCI_FIS_REGISTER_D2H, NULL); + if (!EFI_ERROR (Status)) { + // + // If D2H FIS is received, update StatusBlock with its content. + // + CopyMem (AtaStatusBlock, (UINT8 *)Offset, sizeof (EFI_ATA_STATUS_BLOCK)); + } else { + // + // If D2H FIS is not received, only update Status & Error field through PxTFD + // as there is no other way to get the content of the Shadow Register Block. + // + Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_TFD; + Data = AhciReadReg (PciIo, (UINT32)Offset); + + AtaStatusBlock->AtaStatus = (UINT8)Data; + if ((AtaStatusBlock->AtaStatus & BIT0) != 0) { + AtaStatusBlock->AtaError = (UINT8)(Data >> 8); + } } } } @@ -411,13 +446,7 @@ AhciEnableFisReceive ( Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_CMD; AhciOrReg (PciIo, Offset, EFI_AHCI_PORT_CMD_FRE); - return AhciWaitMmioSet ( - PciIo, - Offset, - EFI_AHCI_PORT_CMD_FR, - EFI_AHCI_PORT_CMD_FR, - Timeout - ); + return EFI_SUCCESS; } /** @@ -517,7 +546,7 @@ AhciBuildCommand ( // // Filling the PRDT // - PrdtNumber = (DataLength + EFI_AHCI_MAX_DATA_PER_PRDT - 1) / EFI_AHCI_MAX_DATA_PER_PRDT; + PrdtNumber = (UINT32)DivU64x32 (((UINT64)DataLength + EFI_AHCI_MAX_DATA_PER_PRDT - 1), EFI_AHCI_MAX_DATA_PER_PRDT); // // According to AHCI 1.3 spec, a PRDT entry can point to a maximum 4MB data block. @@ -548,7 +577,6 @@ AhciBuildCommand ( CommandList->AhciCmdA = 1; CommandList->AhciCmdP = 1; - CommandList->AhciCmdC = (DataLength == 0) ? 1 : 0; AhciOrReg (PciIo, Offset, (EFI_AHCI_PORT_CMD_DLAE | EFI_AHCI_PORT_CMD_ATAPI)); } else { @@ -684,11 +712,20 @@ AhciPioTransfer ( VOID *Map; UINTN MapLength; EFI_PCI_IO_PROTOCOL_OPERATION Flag; - UINT32 Delay; + UINT64 Delay; EFI_AHCI_COMMAND_FIS CFis; EFI_AHCI_COMMAND_LIST CmdList; UINT32 PortTfd; UINT32 PrdCount; + BOOLEAN InfiniteWait; + BOOLEAN PioFisReceived; + BOOLEAN D2hFisReceived; + + if (Timeout == 0) { + InfiniteWait = TRUE; + } else { + InfiniteWait = FALSE; + } if (Read) { Flag = EfiPciIoOperationBusMasterWrite; @@ -757,17 +794,33 @@ AhciPioTransfer ( // Wait device sends the PIO setup fis before data transfer // Status = EFI_TIMEOUT; - Delay = (UINT32) (DivU64x32 (Timeout, 1000) + 1); + Delay = DivU64x32 (Timeout, 1000) + 1; do { + PioFisReceived = FALSE; + D2hFisReceived = FALSE; Offset = FisBaseAddr + EFI_AHCI_PIO_FIS_OFFSET; - - Status = AhciCheckMemSet (Offset, EFI_AHCI_FIS_TYPE_MASK, EFI_AHCI_FIS_PIO_SETUP, 0); + Status = AhciCheckMemSet (Offset, EFI_AHCI_FIS_TYPE_MASK, EFI_AHCI_FIS_PIO_SETUP, NULL); + if (!EFI_ERROR (Status)) { + PioFisReceived = TRUE; + } + // + // According to SATA 2.6 spec section 11.7, D2h FIS means an error encountered. + // But Qemu and Marvel 9230 sata controller may just receive a D2h FIS from device + // after the transaction is finished successfully. + // To get better device compatibilities, we further check if the PxTFD's ERR bit is set. + // By this way, we can know if there is a real error happened. + // + Offset = FisBaseAddr + EFI_AHCI_D2H_FIS_OFFSET; + Status = AhciCheckMemSet (Offset, EFI_AHCI_FIS_TYPE_MASK, EFI_AHCI_FIS_REGISTER_D2H, NULL); if (!EFI_ERROR (Status)) { + D2hFisReceived = TRUE; + } + + if (PioFisReceived || D2hFisReceived) { Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_TFD; PortTfd = AhciReadReg (PciIo, (UINT32) Offset); // // PxTFD will be updated if there is a D2H or SetupFIS received. - // For PIO IN transfer, D2H means a device error. Therefore we only need to check the TFD after receiving a SetupFIS. // if ((PortTfd & EFI_AHCI_PORT_TFD_ERR) != 0) { Status = EFI_DEVICE_ERROR; @@ -776,24 +829,21 @@ AhciPioTransfer ( PrdCount = *(volatile UINT32 *) (&(AhciRegisters->AhciCmdList[0].AhciCmdPrdbc)); if (PrdCount == DataCount) { + Status = EFI_SUCCESS; break; } } - Offset = FisBaseAddr + EFI_AHCI_D2H_FIS_OFFSET; - Status = AhciCheckMemSet (Offset, EFI_AHCI_FIS_TYPE_MASK, EFI_AHCI_FIS_REGISTER_D2H, 0); - if (!EFI_ERROR (Status)) { - Status = EFI_DEVICE_ERROR; - break; - } - // // Stall for 100 microseconds. // MicroSecondDelay(100); Delay--; - } while (Delay > 0); + if (Delay == 0) { + Status = EFI_TIMEOUT; + } + } while (InfiniteWait || (Delay > 0)); } else { // // Wait for D2H Fis is received @@ -835,7 +885,7 @@ Exit: Map ); - AhciDumpPortStatus (PciIo, Port, AtaStatusBlock); + AhciDumpPortStatus (PciIo, AhciRegisters, Port, AtaStatusBlock); return Status; } @@ -925,7 +975,6 @@ AhciDmaTransfer ( // if (Task != NULL) { Task->IsStart = TRUE; - Task->RetryTimes = (UINT32) (DivU64x32(Timeout, 1000) + 1); } if (Read) { Flag = EfiPciIoOperationBusMasterWrite; @@ -1001,7 +1050,7 @@ AhciDmaTransfer ( Offset, EFI_AHCI_FIS_TYPE_MASK, EFI_AHCI_FIS_REGISTER_D2H, - (UINTN *) (&Task->RetryTimes) + Task ); } else { Status = AhciWaitMemSet ( @@ -1055,7 +1104,7 @@ Exit: } } - AhciDumpPortStatus (PciIo, Port, AtaStatusBlock); + AhciDumpPortStatus (PciIo, AhciRegisters, Port, AtaStatusBlock); return Status; } @@ -1171,7 +1220,7 @@ Exit: Timeout ); - AhciDumpPortStatus (PciIo, Port, AtaStatusBlock); + AhciDumpPortStatus (PciIo, AhciRegisters, Port, AtaStatusBlock); return Status; } @@ -1305,10 +1354,6 @@ AhciStartCommand ( // // Setting the command // - Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_SACT; - AhciAndReg (PciIo, Offset, 0); - AhciOrReg (PciIo, Offset, CmdSlotBit); - Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_CI; AhciAndReg (PciIo, Offset, 0); AhciOrReg (PciIo, Offset, CmdSlotBit); @@ -1376,6 +1421,7 @@ AhciPortReset ( ); if (EFI_ERROR (Status)) { + DEBUG ((EFI_D_ERROR, "Port %d COMRESET failed: %r\n", Port, Status)); return Status; } @@ -1403,14 +1449,21 @@ AhciReset ( IN UINT64 Timeout ) { - UINT32 Delay; + UINT64 Delay; UINT32 Value; - AhciOrReg (PciIo, EFI_AHCI_GHC_OFFSET, EFI_AHCI_GHC_ENABLE); + // + // Make sure that GHC.AE bit is set before accessing any AHCI registers. + // + Value = AhciReadReg(PciIo, EFI_AHCI_GHC_OFFSET); + + if ((Value & EFI_AHCI_GHC_ENABLE) == 0) { + AhciOrReg (PciIo, EFI_AHCI_GHC_OFFSET, EFI_AHCI_GHC_ENABLE); + } AhciOrReg (PciIo, EFI_AHCI_GHC_OFFSET, EFI_AHCI_GHC_RESET); - Delay = (UINT32) (DivU64x32(Timeout, 1000) + 1); + Delay = DivU64x32(Timeout, 1000) + 1; do { Value = AhciReadReg(PciIo, EFI_AHCI_GHC_OFFSET); @@ -1440,7 +1493,7 @@ AhciReset ( @param PciIo The PCI IO protocol instance. @param AhciRegisters The pointer to the EFI_AHCI_REGISTERS. @param Port The number of port. - @param PortMultiplier The timeout value of stop. + @param PortMultiplier The port multiplier port number. @param AtaStatusBlock A pointer to EFI_ATA_STATUS_BLOCK data structure. @retval EFI_SUCCESS Successfully get the return status of S.M.A.R.T command execution. @@ -1488,9 +1541,18 @@ AhciAtaSmartReturnStatusCheck ( ); if (EFI_ERROR (Status)) { + REPORT_STATUS_CODE ( + EFI_ERROR_CODE | EFI_ERROR_MINOR, + (EFI_IO_BUS_ATA_ATAPI | EFI_IOB_ATA_BUS_SMART_DISABLED) + ); return EFI_DEVICE_ERROR; } + REPORT_STATUS_CODE ( + EFI_PROGRESS_CODE, + (EFI_IO_BUS_ATA_ATAPI | EFI_IOB_ATA_BUS_SMART_ENABLE) + ); + FisBaseAddr = (UINTN)AhciRegisters->AhciRFis + Port * sizeof (EFI_AHCI_RECEIVED_FIS); Value = *(UINT32 *) (FisBaseAddr + EFI_AHCI_D2H_FIS_OFFSET); @@ -1504,12 +1566,19 @@ AhciAtaSmartReturnStatusCheck ( // The threshold exceeded condition is not detected by the device // DEBUG ((EFI_D_INFO, "The S.M.A.R.T threshold exceeded condition is not detected\n")); - + REPORT_STATUS_CODE ( + EFI_PROGRESS_CODE, + (EFI_IO_BUS_ATA_ATAPI | EFI_IOB_ATA_BUS_SMART_UNDERTHRESHOLD) + ); } else if ((LBAMid == 0xf4) && (LBAHigh == 0x2c)) { // // The threshold exceeded condition is detected by the device // DEBUG ((EFI_D_INFO, "The S.M.A.R.T threshold exceeded condition is detected\n")); + REPORT_STATUS_CODE ( + EFI_PROGRESS_CODE, + (EFI_IO_BUS_ATA_ATAPI | EFI_IOB_ATA_BUS_SMART_OVERTHRESHOLD) + ); } } @@ -1522,7 +1591,7 @@ AhciAtaSmartReturnStatusCheck ( @param PciIo The PCI IO protocol instance. @param AhciRegisters The pointer to the EFI_AHCI_REGISTERS. @param Port The number of port. - @param PortMultiplier The timeout value of stop. + @param PortMultiplier The port multiplier port number. @param IdentifyData A pointer to data buffer which is used to contain IDENTIFY data. @param AtaStatusBlock A pointer to EFI_ATA_STATUS_BLOCK data structure. @@ -1550,11 +1619,21 @@ AhciAtaSmartSupport ( // DEBUG ((EFI_D_INFO, "S.M.A.R.T feature is not supported at port [%d] PortMultiplier [%d]!\n", Port, PortMultiplier)); + REPORT_STATUS_CODE ( + EFI_ERROR_CODE | EFI_ERROR_MINOR, + (EFI_IO_BUS_ATA_ATAPI | EFI_IOB_ATA_BUS_SMART_NOTSUPPORTED) + ); } else { // // Check if the feature is enabled. If not, then enable S.M.A.R.T. // if ((IdentifyData->AtaData.command_set_feature_enb_85 & 0x0001) != 0x0001) { + + REPORT_STATUS_CODE ( + EFI_PROGRESS_CODE, + (EFI_IO_BUS_ATA_ATAPI | EFI_IOB_ATA_BUS_SMART_DISABLE) + ); + ZeroMem (&AtaCommandBlock, sizeof (EFI_ATA_COMMAND_BLOCK)); AtaCommandBlock.AtaCommand = ATA_CMD_SMART; @@ -1628,7 +1707,7 @@ AhciAtaSmartSupport ( @param PciIo The PCI IO protocol instance. @param AhciRegisters The pointer to the EFI_AHCI_REGISTERS. @param Port The number of port. - @param PortMultiplier The timeout value of stop. + @param PortMultiplier The port multiplier port number. @param Buffer The data buffer to store IDENTIFY PACKET data. @retval EFI_DEVICE_ERROR The cmd abort with error occurs. @@ -1686,7 +1765,7 @@ AhciIdentify ( @param PciIo The PCI IO protocol instance. @param AhciRegisters The pointer to the EFI_AHCI_REGISTERS. @param Port The number of port. - @param PortMultiplier The timeout value of stop. + @param PortMultiplier The port multiplier port number. @param Buffer The data buffer to store IDENTIFY PACKET data. @retval EFI_DEVICE_ERROR The cmd abort with error occurs. @@ -1744,9 +1823,10 @@ AhciIdentifyPacket ( @param PciIo The PCI IO protocol instance. @param AhciRegisters The pointer to the EFI_AHCI_REGISTERS. @param Port The number of port. - @param PortMultiplier The timeout value of stop. + @param PortMultiplier The port multiplier port number. @param Feature The data to send Feature register. @param FeatureSpecificData The specific data for SET FEATURE cmd. + @param Timeout The timeout value of SET FEATURE cmd, uses 100ns as a unit. @retval EFI_DEVICE_ERROR The cmd abort with error occurs. @retval EFI_TIMEOUT The operation is time out. @@ -1762,7 +1842,8 @@ AhciDeviceSetFeature ( IN UINT8 Port, IN UINT8 PortMultiplier, IN UINT16 Feature, - IN UINT32 FeatureSpecificData + IN UINT32 FeatureSpecificData, + IN UINT64 Timeout ) { EFI_STATUS Status; @@ -1789,7 +1870,7 @@ AhciDeviceSetFeature ( 0, &AtaCommandBlock, &AtaStatusBlock, - ATA_ATAPI_TIMEOUT, + Timeout, NULL ); @@ -1908,6 +1989,7 @@ AhciCreateTransferDescriptor ( VOID *Buffer; UINT32 Capability; + UINT32 PortImplementBitMap; UINT8 MaxPortNumber; UINT8 MaxCommandSlotNumber; BOOLEAN Support64Bit; @@ -1923,12 +2005,20 @@ AhciCreateTransferDescriptor ( // Collect AHCI controller information // Capability = AhciReadReg(PciIo, EFI_AHCI_CAPABILITY_OFFSET); - MaxPortNumber = (UINT8) ((Capability & 0x1F) + 1); // // Get the number of command slots per port supported by this HBA. // MaxCommandSlotNumber = (UINT8) (((Capability & 0x1F00) >> 8) + 1); Support64Bit = (BOOLEAN) (((Capability & BIT31) != 0) ? TRUE : FALSE); + + PortImplementBitMap = AhciReadReg(PciIo, EFI_AHCI_PI_OFFSET); + // + // Get the highest bit of implemented ports which decides how many bytes are allocated for recived FIS. + // + MaxPortNumber = (UINT8)(UINTN)(HighBitSet32(PortImplementBitMap) + 1); + if (MaxPortNumber == 0) { + return EFI_DEVICE_ERROR; + } MaxReceiveFisSize = MaxPortNumber * sizeof (EFI_AHCI_RECEIVED_FIS); Status = PciIo->AllocateBuffer ( @@ -2128,6 +2218,343 @@ Error6: return Status; } +/** + Read logs from SATA device. + + @param PciIo The PCI IO protocol instance. + @param AhciRegisters The pointer to the EFI_AHCI_REGISTERS. + @param Port The number of port. + @param PortMultiplier The multiplier of port. + @param Buffer The data buffer to store SATA logs. + @param LogNumber The address of the log. + @param PageNumber The page number of the log. + + @retval EFI_INVALID_PARAMETER PciIo, AhciRegisters or Buffer is NULL. + @retval others Return status of AhciPioTransfer(). +**/ +EFI_STATUS +AhciReadLogExt ( + IN EFI_PCI_IO_PROTOCOL *PciIo, + IN EFI_AHCI_REGISTERS *AhciRegisters, + IN UINT8 Port, + IN UINT8 PortMultiplier, + IN OUT UINT8 *Buffer, + IN UINT8 LogNumber, + IN UINT8 PageNumber + ) +{ + EFI_ATA_COMMAND_BLOCK AtaCommandBlock; + EFI_ATA_STATUS_BLOCK AtaStatusBlock; + + if (PciIo == NULL || AhciRegisters == NULL || Buffer == NULL) { + return EFI_INVALID_PARAMETER; + } + + /// + /// Read log from device + /// + ZeroMem (&AtaCommandBlock, sizeof (EFI_ATA_COMMAND_BLOCK)); + ZeroMem (&AtaStatusBlock, sizeof (EFI_ATA_STATUS_BLOCK)); + ZeroMem (Buffer, 512); + + AtaCommandBlock.AtaCommand = ATA_CMD_READ_LOG_EXT; + AtaCommandBlock.AtaSectorCount = 1; + AtaCommandBlock.AtaSectorNumber = LogNumber; + AtaCommandBlock.AtaCylinderLow = PageNumber; + + return AhciPioTransfer ( + PciIo, + AhciRegisters, + Port, + PortMultiplier, + NULL, + 0, + TRUE, + &AtaCommandBlock, + &AtaStatusBlock, + Buffer, + 512, + ATA_ATAPI_TIMEOUT, + NULL + ); +} + +/** + Enable DEVSLP of the disk if supported. + + @param PciIo The PCI IO protocol instance. + @param AhciRegisters The pointer to the EFI_AHCI_REGISTERS. + @param Port The number of port. + @param PortMultiplier The multiplier of port. + @param IdentifyData A pointer to data buffer which is used to contain IDENTIFY data. + + @retval EFI_SUCCESS The DEVSLP is enabled per policy successfully. + @retval EFI_UNSUPPORTED The DEVSLP isn't supported by the controller/device and policy requires to enable it. +**/ +EFI_STATUS +AhciEnableDevSlp ( + IN EFI_PCI_IO_PROTOCOL *PciIo, + IN EFI_AHCI_REGISTERS *AhciRegisters, + IN UINT8 Port, + IN UINT8 PortMultiplier, + IN EFI_IDENTIFY_DATA *IdentifyData + ) +{ + EFI_STATUS Status; + UINT32 Offset; + UINT32 Capability2; + UINT8 LogData[512]; + DEVSLP_TIMING_VARIABLES DevSlpTiming; + UINT32 PortCmd; + UINT32 PortDevSlp; + + if (mAtaAtapiPolicy->DeviceSleepEnable != 1) { + return EFI_SUCCESS; + } + + // + // Do not enable DevSlp if DevSlp is not supported. + // + Capability2 = AhciReadReg (PciIo, AHCI_CAPABILITY2_OFFSET); + DEBUG ((DEBUG_INFO, "AHCI CAPABILITY2 = %08x\n", Capability2)); + if ((Capability2 & AHCI_CAP2_SDS) == 0) { + return EFI_UNSUPPORTED; + } + + // + // Do not enable DevSlp if DevSlp is not present + // Do not enable DevSlp if Hot Plug or Mechanical Presence Switch is supported + // + Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH; + PortCmd = AhciReadReg (PciIo, Offset + EFI_AHCI_PORT_CMD); + PortDevSlp = AhciReadReg (PciIo, Offset + AHCI_PORT_DEVSLP); + DEBUG ((DEBUG_INFO, "Port CMD/DEVSLP = %08x / %08x\n", PortCmd, PortDevSlp)); + if (((PortDevSlp & AHCI_PORT_DEVSLP_DSP) == 0) || + ((PortCmd & (EFI_AHCI_PORT_CMD_HPCP | EFI_AHCI_PORT_CMD_MPSP)) != 0) + ) { + return EFI_UNSUPPORTED; + } + + // + // Do not enable DevSlp if the device doesn't support DevSlp + // + DEBUG ((DEBUG_INFO, "IDENTIFY DEVICE: [77] = %04x, [78] = %04x, [79] = %04x\n", + IdentifyData->AtaData.reserved_77, + IdentifyData->AtaData.serial_ata_features_supported, IdentifyData->AtaData.serial_ata_features_enabled)); + if ((IdentifyData->AtaData.serial_ata_features_supported & BIT8) == 0) { + DEBUG ((DEBUG_INFO, "DevSlp feature is not supported for device at port [%d] PortMultiplier [%d]!\n", + Port, PortMultiplier)); + return EFI_UNSUPPORTED; + } + + // + // Enable DevSlp when it is not enabled. + // + if ((IdentifyData->AtaData.serial_ata_features_enabled & BIT8) != 0) { + Status = AhciDeviceSetFeature ( + PciIo, AhciRegisters, Port, 0, ATA_SUB_CMD_ENABLE_SATA_FEATURE, 0x09, ATA_ATAPI_TIMEOUT + ); + DEBUG ((DEBUG_INFO, "DevSlp set feature for device at port [%d] PortMultiplier [%d] - %r\n", + Port, PortMultiplier, Status)); + if (EFI_ERROR (Status)) { + return Status; + } + } + + Status = AhciReadLogExt(PciIo, AhciRegisters, Port, PortMultiplier, LogData, 0x30, 0x08); + + // + // Clear PxCMD.ST and PxDEVSLP.ADSE before updating PxDEVSLP.DITO and PxDEVSLP.MDAT. + // + AhciWriteReg (PciIo, Offset + EFI_AHCI_PORT_CMD, PortCmd & ~EFI_AHCI_PORT_CMD_ST); + PortDevSlp &= ~AHCI_PORT_DEVSLP_ADSE; + AhciWriteReg (PciIo, Offset + AHCI_PORT_DEVSLP, PortDevSlp); + + // + // Set PxDEVSLP.DETO and PxDEVSLP.MDAT to 0. + // + PortDevSlp &= ~AHCI_PORT_DEVSLP_DETO_MASK; + PortDevSlp &= ~AHCI_PORT_DEVSLP_MDAT_MASK; + AhciWriteReg (PciIo, Offset + AHCI_PORT_DEVSLP, PortDevSlp); + DEBUG ((DEBUG_INFO, "Read Log Ext at port [%d] PortMultiplier [%d] - %r\n", Port, PortMultiplier, Status)); + if (EFI_ERROR (Status)) { + // + // Assume DEVSLP TIMING VARIABLES is not supported if the Identify Device Data log (30h, 8) fails + // + DevSlpTiming.Supported = 0; + } else { + CopyMem (&DevSlpTiming, &LogData[48], sizeof (DevSlpTiming)); + DEBUG ((DEBUG_INFO, "DevSlpTiming: Supported(%d), Deto(%d), Madt(%d)\n", + DevSlpTiming.Supported, DevSlpTiming.Deto, DevSlpTiming.Madt)); + } + + // + // Use 20ms as default DETO when DEVSLP TIMING VARIABLES is not supported or the DETO is 0. + // + if ((DevSlpTiming.Supported == 0) || (DevSlpTiming.Deto == 0)) { + DevSlpTiming.Deto = 20; + } + + // + // Use 10ms as default MADT when DEVSLP TIMING VARIABLES is not supported or the MADT is 0. + // + if ((DevSlpTiming.Supported == 0) || (DevSlpTiming.Madt == 0)) { + DevSlpTiming.Madt = 10; + } + + PortDevSlp |= DevSlpTiming.Deto << 2; + PortDevSlp |= DevSlpTiming.Madt << 10; + AhciOrReg (PciIo, Offset + AHCI_PORT_DEVSLP, PortDevSlp); + + if (mAtaAtapiPolicy->AggressiveDeviceSleepEnable == 1) { + if ((Capability2 & AHCI_CAP2_SADM) != 0) { + PortDevSlp &= ~AHCI_PORT_DEVSLP_DITO_MASK; + PortDevSlp |= (625 << 15); + AhciWriteReg (PciIo, Offset + AHCI_PORT_DEVSLP, PortDevSlp); + + PortDevSlp |= AHCI_PORT_DEVSLP_ADSE; + AhciWriteReg (PciIo, Offset + AHCI_PORT_DEVSLP, PortDevSlp); + } + } + + + AhciWriteReg (PciIo, Offset + EFI_AHCI_PORT_CMD, PortCmd); + + DEBUG ((DEBUG_INFO, "Enabled DevSlp feature at port [%d] PortMultiplier [%d], Port CMD/DEVSLP = %08x / %08x\n", + Port, PortMultiplier, PortCmd, PortDevSlp)); + + return EFI_SUCCESS; +} + +/** + Spin-up disk if IDD was incomplete or PUIS feature is enabled + + @param PciIo The PCI IO protocol instance. + @param AhciRegisters The pointer to the EFI_AHCI_REGISTERS. + @param Port The number of port. + @param PortMultiplier The multiplier of port. + @param IdentifyData A pointer to data buffer which is used to contain IDENTIFY data. + +**/ +EFI_STATUS +AhciSpinUpDisk ( + IN EFI_PCI_IO_PROTOCOL *PciIo, + IN EFI_AHCI_REGISTERS *AhciRegisters, + IN UINT8 Port, + IN UINT8 PortMultiplier, + IN OUT EFI_IDENTIFY_DATA *IdentifyData + ) +{ + EFI_STATUS Status; + EFI_ATA_COMMAND_BLOCK AtaCommandBlock; + EFI_ATA_STATUS_BLOCK AtaStatusBlock; + UINT8 Buffer[512]; + + if (IdentifyData->AtaData.specific_config == ATA_SPINUP_CFG_REQUIRED_IDD_INCOMPLETE) { + // + // Use SET_FEATURE subcommand to spin up the device. + // + Status = AhciDeviceSetFeature ( + PciIo, AhciRegisters, Port, PortMultiplier, + ATA_SUB_CMD_PUIS_SET_DEVICE_SPINUP, 0x00, ATA_SPINUP_TIMEOUT + ); + DEBUG ((DEBUG_INFO, "CMD_PUIS_SET_DEVICE_SPINUP for device at port [%d] PortMultiplier [%d] - %r!\n", + Port, PortMultiplier, Status)); + if (EFI_ERROR (Status)) { + return Status; + } + } else { + ASSERT (IdentifyData->AtaData.specific_config == ATA_SPINUP_CFG_NOT_REQUIRED_IDD_INCOMPLETE); + + // + // Use READ_SECTORS to spin up the device if SpinUp SET FEATURE subcommand is not supported + // + ZeroMem (&AtaCommandBlock, sizeof (EFI_ATA_COMMAND_BLOCK)); + ZeroMem (&AtaStatusBlock, sizeof (EFI_ATA_STATUS_BLOCK)); + // + // Perform READ SECTORS PIO Data-In command to Read LBA 0 + // + AtaCommandBlock.AtaCommand = ATA_CMD_READ_SECTORS; + AtaCommandBlock.AtaSectorCount = 0x1; + + Status = AhciPioTransfer ( + PciIo, + AhciRegisters, + Port, + PortMultiplier, + NULL, + 0, + TRUE, + &AtaCommandBlock, + &AtaStatusBlock, + &Buffer, + sizeof (Buffer), + ATA_SPINUP_TIMEOUT, + NULL + ); + DEBUG ((DEBUG_INFO, "Read LBA 0 for device at port [%d] PortMultiplier [%d] - %r!\n", + Port, PortMultiplier, Status)); + if (EFI_ERROR (Status)) { + return Status; + } + } + + // + // Read the complete IDENTIFY DEVICE data. + // + ZeroMem (IdentifyData, sizeof (*IdentifyData)); + Status = AhciIdentify (PciIo, AhciRegisters, Port, PortMultiplier, IdentifyData); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "Read IDD failed for device at port [%d] PortMultiplier [%d] - %r!\n", + Port, PortMultiplier, Status)); + return Status; + } + + DEBUG ((DEBUG_INFO, "IDENTIFY DEVICE: [0] = %016x, [2] = %016x, [83] = %016x, [86] = %016x\n", + IdentifyData->AtaData.config, IdentifyData->AtaData.specific_config, + IdentifyData->AtaData.command_set_supported_83, IdentifyData->AtaData.command_set_feature_enb_86)); + // + // Check if IDD is incomplete + // + if ((IdentifyData->AtaData.config & BIT2) != 0) { + return EFI_DEVICE_ERROR; + } + + return EFI_SUCCESS; +} + +/** + Enable/disable/skip PUIS of the disk according to policy. + + @param PciIo The PCI IO protocol instance. + @param AhciRegisters The pointer to the EFI_AHCI_REGISTERS. + @param Port The number of port. + @param PortMultiplier The multiplier of port. + +**/ +EFI_STATUS +AhciPuisEnable ( + IN EFI_PCI_IO_PROTOCOL *PciIo, + IN EFI_AHCI_REGISTERS *AhciRegisters, + IN UINT8 Port, + IN UINT8 PortMultiplier + ) +{ + EFI_STATUS Status; + + Status = EFI_SUCCESS; + if (mAtaAtapiPolicy->PuisEnable == 0) { + Status = AhciDeviceSetFeature (PciIo, AhciRegisters, Port, PortMultiplier, ATA_SUB_CMD_DISABLE_PUIS, 0x00, ATA_ATAPI_TIMEOUT); + } else if (mAtaAtapiPolicy->PuisEnable == 1) { + Status = AhciDeviceSetFeature (PciIo, AhciRegisters, Port, PortMultiplier, ATA_SUB_CMD_ENABLE_PUIS, 0x00, ATA_ATAPI_TIMEOUT); + } + DEBUG ((DEBUG_INFO, "%a PUIS feature at port [%d] PortMultiplier [%d] - %r!\n", + (mAtaAtapiPolicy->PuisEnable == 0) ? "Disable" : ( + (mAtaAtapiPolicy->PuisEnable == 1) ? "Enable" : "Skip" + ), Port, PortMultiplier, Status)); + return Status; +} + /** Initialize ATA host controller at AHCI mode. @@ -2160,6 +2587,7 @@ AhciModeInitialization ( EFI_ATA_COLLECTIVE_MODE *SupportedModes; EFI_ATA_TRANSFER_MODE TransferMode; UINT32 PhyDetectDelay; + UINT32 Value; if (Instance == NULL) { return EFI_INVALID_PARAMETER; @@ -2175,14 +2603,36 @@ AhciModeInitialization ( } // - // Enable AE before accessing any AHCI registers + // Collect AHCI controller information // - AhciOrReg (PciIo, EFI_AHCI_GHC_OFFSET, EFI_AHCI_GHC_ENABLE); + Capability = AhciReadReg (PciIo, EFI_AHCI_CAPABILITY_OFFSET); // - // Collect AHCI controller information + // Make sure that GHC.AE bit is set before accessing any AHCI registers. // - Capability = AhciReadReg(PciIo, EFI_AHCI_CAPABILITY_OFFSET); + Value = AhciReadReg(PciIo, EFI_AHCI_GHC_OFFSET); + + if ((Value & EFI_AHCI_GHC_ENABLE) == 0) { + AhciOrReg (PciIo, EFI_AHCI_GHC_OFFSET, EFI_AHCI_GHC_ENABLE); + } + + // + // Enable 64-bit DMA support in the PCI layer if this controller + // supports it. + // + if ((Capability & EFI_AHCI_CAP_S64A) != 0) { + Status = PciIo->Attributes ( + PciIo, + EfiPciIoAttributeOperationEnable, + EFI_PCI_IO_ATTRIBUTE_DUAL_ADDRESS_CYCLE, + NULL + ); + if (EFI_ERROR (Status)) { + DEBUG ((EFI_D_WARN, + "AhciModeInitialization: failed to enable 64-bit DMA on 64-bit capable controller (%r)\n", + Status)); + } + } // // Get the number of command slots per port supported by this HBA. @@ -2203,7 +2653,7 @@ AhciModeInitialization ( } for (Port = 0; Port < EFI_AHCI_MAX_PORTS; Port ++) { - if ((PortImplementBitMap & (BIT0 << Port)) != 0) { + if ((PortImplementBitMap & (((UINT32)BIT0) << Port)) != 0) { // // According to AHCI spec, MaxPortNumber should be equal or greater than the number of implemented ports. // @@ -2263,20 +2713,9 @@ AhciModeInitialization ( // Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_CMD; AhciOrReg (PciIo, Offset, EFI_AHCI_PORT_CMD_FRE); - Status = AhciWaitMmioSet ( - PciIo, - Offset, - EFI_AHCI_PORT_CMD_FR, - EFI_AHCI_PORT_CMD_FR, - EFI_AHCI_PORT_CMD_FR_CLEAR_TIMEOUT - ); - if (EFI_ERROR (Status)) { - continue; - } // - // Wait no longer than 10 ms to wait the Phy to detect the presence of a device. - // It's the requirment from SATA1.0a spec section 5.2. + // Wait for the Phy to detect the presence of a device. // PhyDetectDelay = EFI_AHCI_BUS_PHY_DETECT_TIMEOUT; Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_SSTS; @@ -2322,6 +2761,7 @@ AhciModeInitialization ( } while (PhyDetectDelay > 0); if (PhyDetectDelay == 0) { + DEBUG ((EFI_D_ERROR, "Port %d Device presence detected but phy not ready (TFD=0x%X)\n", Port, Data)); continue; } @@ -2357,6 +2797,28 @@ AhciModeInitialization ( continue; } + DEBUG (( + DEBUG_INFO, "IDENTIFY DEVICE: [0] = %016x, [2] = %016x, [83] = %016x, [86] = %016x\n", + Buffer.AtaData.config, Buffer.AtaData.specific_config, + Buffer.AtaData.command_set_supported_83, Buffer.AtaData.command_set_feature_enb_86 + )); + if ((Buffer.AtaData.config & BIT2) != 0) { + // + // SpinUp disk if device reported incomplete IDENTIFY DEVICE. + // + Status = AhciSpinUpDisk ( + PciIo, + AhciRegisters, + Port, + 0, + &Buffer + ); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "Spin up standby device failed - %r\n", Status)); + continue; + } + } + DeviceType = EfiIdeHarddisk; } else { continue; @@ -2422,7 +2884,7 @@ AhciModeInitialization ( TransferMode.ModeNumber = (UINT8) SupportedModes->MultiWordDmaMode.Mode; } - Status = AhciDeviceSetFeature (PciIo, AhciRegisters, Port, 0, 0x03, (UINT32)(*(UINT8 *)&TransferMode)); + Status = AhciDeviceSetFeature (PciIo, AhciRegisters, Port, 0, 0x03, (UINT32)(*(UINT8 *)&TransferMode), ATA_ATAPI_TIMEOUT); if (EFI_ERROR (Status)) { DEBUG ((EFI_D_ERROR, "Set transfer Mode Fail, Status = %r\n", Status)); continue; @@ -2431,9 +2893,32 @@ AhciModeInitialization ( // // Found a ATA or ATAPI device, add it into the device list. // - CreateNewDeviceInfo (Instance, Port, 0, DeviceType, &Buffer); + CreateNewDeviceInfo (Instance, Port, 0xFFFF, DeviceType, &Buffer); if (DeviceType == EfiIdeHarddisk) { REPORT_STATUS_CODE (EFI_PROGRESS_CODE, (EFI_PERIPHERAL_FIXED_MEDIA | EFI_P_PC_ENABLE)); + AhciEnableDevSlp ( + PciIo, + AhciRegisters, + Port, + 0, + &Buffer + ); + } + + // + // Enable/disable PUIS according to policy setting if PUIS is capable (Word[83].BIT5 is set). + // + if ((Buffer.AtaData.command_set_supported_83 & BIT5) != 0) { + Status = AhciPuisEnable ( + PciIo, + AhciRegisters, + Port, + 0 + ); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "PUIS enable/disable failed, Status = %r\n", Status)); + continue; + } } } }