/** @file | |
The file for AHCI mode of ATA host controller. | |
Copyright (c) 2010 - 2020, Intel Corporation. All rights reserved.<BR> | |
(C) Copyright 2015 Hewlett Packard Enterprise Development LP<BR> | |
SPDX-License-Identifier: BSD-2-Clause-Patent | |
**/ | |
#include "AtaAtapiPassThru.h" | |
/** | |
Read AHCI Operation register. | |
@param PciIo The PCI IO protocol instance. | |
@param Offset The operation register offset. | |
@return The register content read. | |
**/ | |
UINT32 | |
EFIAPI | |
AhciReadReg ( | |
IN EFI_PCI_IO_PROTOCOL *PciIo, | |
IN UINT32 Offset | |
) | |
{ | |
UINT32 Data; | |
ASSERT (PciIo != NULL); | |
Data = 0; | |
PciIo->Mem.Read ( | |
PciIo, | |
EfiPciIoWidthUint32, | |
EFI_AHCI_BAR_INDEX, | |
(UINT64)Offset, | |
1, | |
&Data | |
); | |
return Data; | |
} | |
/** | |
Write AHCI Operation register. | |
@param PciIo The PCI IO protocol instance. | |
@param Offset The operation register offset. | |
@param Data The data used to write down. | |
**/ | |
VOID | |
EFIAPI | |
AhciWriteReg ( | |
IN EFI_PCI_IO_PROTOCOL *PciIo, | |
IN UINT32 Offset, | |
IN UINT32 Data | |
) | |
{ | |
ASSERT (PciIo != NULL); | |
PciIo->Mem.Write ( | |
PciIo, | |
EfiPciIoWidthUint32, | |
EFI_AHCI_BAR_INDEX, | |
(UINT64)Offset, | |
1, | |
&Data | |
); | |
return; | |
} | |
/** | |
Do AND operation with the value of AHCI Operation register. | |
@param PciIo The PCI IO protocol instance. | |
@param Offset The operation register offset. | |
@param AndData The data used to do AND operation. | |
**/ | |
VOID | |
EFIAPI | |
AhciAndReg ( | |
IN EFI_PCI_IO_PROTOCOL *PciIo, | |
IN UINT32 Offset, | |
IN UINT32 AndData | |
) | |
{ | |
UINT32 Data; | |
ASSERT (PciIo != NULL); | |
Data = AhciReadReg (PciIo, Offset); | |
Data &= AndData; | |
AhciWriteReg (PciIo, Offset, Data); | |
} | |
/** | |
Do OR operation with the value of AHCI Operation register. | |
@param PciIo The PCI IO protocol instance. | |
@param Offset The operation register offset. | |
@param OrData The data used to do OR operation. | |
**/ | |
VOID | |
EFIAPI | |
AhciOrReg ( | |
IN EFI_PCI_IO_PROTOCOL *PciIo, | |
IN UINT32 Offset, | |
IN UINT32 OrData | |
) | |
{ | |
UINT32 Data; | |
ASSERT (PciIo != NULL); | |
Data = AhciReadReg (PciIo, Offset); | |
Data |= OrData; | |
AhciWriteReg (PciIo, Offset, Data); | |
} | |
/** | |
Wait for the value of the specified MMIO register set to the test value. | |
@param PciIo The PCI IO protocol instance. | |
@param Offset The MMIO address to test. | |
@param MaskValue The mask value of memory. | |
@param TestValue The test value of memory. | |
@param Timeout The time out value for wait memory set, uses 100ns as a unit. | |
@retval EFI_TIMEOUT The MMIO setting is time out. | |
@retval EFI_SUCCESS The MMIO is correct set. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
AhciWaitMmioSet ( | |
IN EFI_PCI_IO_PROTOCOL *PciIo, | |
IN UINTN Offset, | |
IN UINT32 MaskValue, | |
IN UINT32 TestValue, | |
IN UINT64 Timeout | |
) | |
{ | |
UINT32 Value; | |
UINT64 Delay; | |
BOOLEAN InfiniteWait; | |
if (Timeout == 0) { | |
InfiniteWait = TRUE; | |
} else { | |
InfiniteWait = FALSE; | |
} | |
Delay = DivU64x32 (Timeout, 1000) + 1; | |
do { | |
// | |
// Access PCI MMIO space to see if the value is the tested one. | |
// | |
Value = AhciReadReg (PciIo, (UINT32)Offset) & MaskValue; | |
if (Value == TestValue) { | |
return EFI_SUCCESS; | |
} | |
// | |
// Stall for 100 microseconds. | |
// | |
MicroSecondDelay (100); | |
Delay--; | |
} while (InfiniteWait || (Delay > 0)); | |
return EFI_TIMEOUT; | |
} | |
/** | |
Wait for the value of the specified system memory set to the test value. | |
@param Address The system memory address to test. | |
@param MaskValue The mask value of memory. | |
@param TestValue The test value of memory. | |
@param Timeout The time out value for wait memory set, uses 100ns as a unit. | |
@retval EFI_TIMEOUT The system memory setting is time out. | |
@retval EFI_SUCCESS The system memory is correct set. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
AhciWaitMemSet ( | |
IN EFI_PHYSICAL_ADDRESS Address, | |
IN UINT32 MaskValue, | |
IN UINT32 TestValue, | |
IN UINT64 Timeout | |
) | |
{ | |
UINT32 Value; | |
UINT64 Delay; | |
BOOLEAN InfiniteWait; | |
if (Timeout == 0) { | |
InfiniteWait = TRUE; | |
} else { | |
InfiniteWait = FALSE; | |
} | |
Delay = DivU64x32 (Timeout, 1000) + 1; | |
do { | |
// | |
// Access system memory to see if the value is the tested one. | |
// | |
// The system memory pointed by Address will be updated by the | |
// SATA Host Controller, "volatile" is introduced to prevent | |
// compiler from optimizing the access to the memory address | |
// to only read once. | |
// | |
Value = *(volatile UINT32 *)(UINTN)Address; | |
Value &= MaskValue; | |
if (Value == TestValue) { | |
return EFI_SUCCESS; | |
} | |
// | |
// Stall for 100 microseconds. | |
// | |
MicroSecondDelay (100); | |
Delay--; | |
} while (InfiniteWait || (Delay > 0)); | |
return EFI_TIMEOUT; | |
} | |
/** | |
Check the memory status to the test value. | |
@param[in] Address The memory address to test. | |
@param[in] MaskValue The mask value of memory. | |
@param[in] TestValue The test value of memory. | |
@retval EFI_NOT_READY The memory is not set. | |
@retval EFI_SUCCESS The memory is correct set. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
AhciCheckMemSet ( | |
IN UINTN Address, | |
IN UINT32 MaskValue, | |
IN UINT32 TestValue | |
) | |
{ | |
UINT32 Value; | |
Value = *(volatile UINT32 *)Address; | |
Value &= MaskValue; | |
if (Value == TestValue) { | |
return EFI_SUCCESS; | |
} | |
return EFI_NOT_READY; | |
} | |
/** | |
Clear the port interrupt and error status. It will also clear | |
HBA interrupt status. | |
@param PciIo The PCI IO protocol instance. | |
@param Port The number of port. | |
**/ | |
VOID | |
EFIAPI | |
AhciClearPortStatus ( | |
IN EFI_PCI_IO_PROTOCOL *PciIo, | |
IN UINT8 Port | |
) | |
{ | |
UINT32 Offset; | |
// | |
// Clear any error status | |
// | |
Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_SERR; | |
AhciWriteReg (PciIo, Offset, AhciReadReg (PciIo, Offset)); | |
// | |
// Clear any port interrupt status | |
// | |
Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_IS; | |
AhciWriteReg (PciIo, Offset, AhciReadReg (PciIo, Offset)); | |
// | |
// Clear any HBA interrupt status | |
// | |
AhciWriteReg (PciIo, EFI_AHCI_IS_OFFSET, AhciReadReg (PciIo, EFI_AHCI_IS_OFFSET)); | |
} | |
/** | |
This function is used to dump the Status Registers and if there is ERR bit set | |
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. | |
**/ | |
VOID | |
EFIAPI | |
AhciDumpPortStatus ( | |
IN EFI_PCI_IO_PROTOCOL *PciIo, | |
IN EFI_AHCI_REGISTERS *AhciRegisters, | |
IN UINT8 Port, | |
IN OUT EFI_ATA_STATUS_BLOCK *AtaStatusBlock | |
) | |
{ | |
UINTN Offset; | |
UINT32 Data; | |
UINTN FisBaseAddr; | |
EFI_STATUS Status; | |
ASSERT (PciIo != NULL); | |
if (AtaStatusBlock != NULL) { | |
ZeroMem (AtaStatusBlock, sizeof (EFI_ATA_STATUS_BLOCK)); | |
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); | |
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); | |
} | |
} | |
} | |
} | |
/** | |
Enable the FIS running for giving port. | |
@param PciIo The PCI IO protocol instance. | |
@param Port The number of port. | |
@param Timeout The timeout value of enabling FIS, uses 100ns as a unit. | |
@retval EFI_DEVICE_ERROR The FIS enable setting fails. | |
@retval EFI_TIMEOUT The FIS enable setting is time out. | |
@retval EFI_SUCCESS The FIS enable successfully. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
AhciEnableFisReceive ( | |
IN EFI_PCI_IO_PROTOCOL *PciIo, | |
IN UINT8 Port, | |
IN UINT64 Timeout | |
) | |
{ | |
UINT32 Offset; | |
Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_CMD; | |
AhciOrReg (PciIo, Offset, EFI_AHCI_PORT_CMD_FRE); | |
return EFI_SUCCESS; | |
} | |
/** | |
Disable the FIS running for giving port. | |
@param PciIo The PCI IO protocol instance. | |
@param Port The number of port. | |
@param Timeout The timeout value of disabling FIS, uses 100ns as a unit. | |
@retval EFI_DEVICE_ERROR The FIS disable setting fails. | |
@retval EFI_TIMEOUT The FIS disable setting is time out. | |
@retval EFI_UNSUPPORTED The port is in running state. | |
@retval EFI_SUCCESS The FIS disable successfully. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
AhciDisableFisReceive ( | |
IN EFI_PCI_IO_PROTOCOL *PciIo, | |
IN UINT8 Port, | |
IN UINT64 Timeout | |
) | |
{ | |
UINT32 Offset; | |
UINT32 Data; | |
Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_CMD; | |
Data = AhciReadReg (PciIo, Offset); | |
// | |
// Before disabling Fis receive, the DMA engine of the port should NOT be in running status. | |
// | |
if ((Data & (EFI_AHCI_PORT_CMD_ST | EFI_AHCI_PORT_CMD_CR)) != 0) { | |
return EFI_UNSUPPORTED; | |
} | |
// | |
// Check if the Fis receive DMA engine for the port is running. | |
// | |
if ((Data & EFI_AHCI_PORT_CMD_FR) != EFI_AHCI_PORT_CMD_FR) { | |
return EFI_SUCCESS; | |
} | |
AhciAndReg (PciIo, Offset, (UINT32) ~(EFI_AHCI_PORT_CMD_FRE)); | |
return AhciWaitMmioSet ( | |
PciIo, | |
Offset, | |
EFI_AHCI_PORT_CMD_FR, | |
0, | |
Timeout | |
); | |
} | |
/** | |
Build the command list, command table and prepare the fis receiver. | |
@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 CommandFis The control fis will be used for the transfer. | |
@param CommandList The command list will be used for the transfer. | |
@param AtapiCommand The atapi command will be used for the transfer. | |
@param AtapiCommandLength The length of the atapi command. | |
@param CommandSlotNumber The command slot will be used for the transfer. | |
@param DataPhysicalAddr The pointer to the data buffer pci bus master address. | |
@param DataLength The data count to be transferred. | |
**/ | |
VOID | |
EFIAPI | |
AhciBuildCommand ( | |
IN EFI_PCI_IO_PROTOCOL *PciIo, | |
IN EFI_AHCI_REGISTERS *AhciRegisters, | |
IN UINT8 Port, | |
IN UINT8 PortMultiplier, | |
IN EFI_AHCI_COMMAND_FIS *CommandFis, | |
IN EFI_AHCI_COMMAND_LIST *CommandList, | |
IN EFI_AHCI_ATAPI_COMMAND *AtapiCommand OPTIONAL, | |
IN UINT8 AtapiCommandLength, | |
IN UINT8 CommandSlotNumber, | |
IN OUT VOID *DataPhysicalAddr, | |
IN UINT32 DataLength | |
) | |
{ | |
UINT64 BaseAddr; | |
UINT32 PrdtNumber; | |
UINT32 PrdtIndex; | |
UINTN RemainedData; | |
UINTN MemAddr; | |
DATA_64 Data64; | |
UINT32 Offset; | |
// | |
// Filling the 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. | |
// It also limits that the maximum amount of the PRDT entry in the command table | |
// is 65535. | |
// | |
ASSERT (PrdtNumber <= 65535); | |
Data64.Uint64 = (UINTN)(AhciRegisters->AhciRFis) + sizeof (EFI_AHCI_RECEIVED_FIS) * Port; | |
BaseAddr = Data64.Uint64; | |
ZeroMem ((VOID *)((UINTN)BaseAddr), sizeof (EFI_AHCI_RECEIVED_FIS)); | |
ZeroMem (AhciRegisters->AhciCommandTable, sizeof (EFI_AHCI_COMMAND_TABLE)); | |
CommandFis->AhciCFisPmNum = PortMultiplier; | |
CopyMem (&AhciRegisters->AhciCommandTable->CommandFis, CommandFis, sizeof (EFI_AHCI_COMMAND_FIS)); | |
Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_CMD; | |
if (AtapiCommand != NULL) { | |
CopyMem ( | |
&AhciRegisters->AhciCommandTable->AtapiCmd, | |
AtapiCommand, | |
AtapiCommandLength | |
); | |
CommandList->AhciCmdA = 1; | |
CommandList->AhciCmdP = 1; | |
AhciOrReg (PciIo, Offset, (EFI_AHCI_PORT_CMD_DLAE | EFI_AHCI_PORT_CMD_ATAPI)); | |
} else { | |
AhciAndReg (PciIo, Offset, (UINT32) ~(EFI_AHCI_PORT_CMD_DLAE | EFI_AHCI_PORT_CMD_ATAPI)); | |
} | |
RemainedData = (UINTN)DataLength; | |
MemAddr = (UINTN)DataPhysicalAddr; | |
CommandList->AhciCmdPrdtl = PrdtNumber; | |
for (PrdtIndex = 0; PrdtIndex < PrdtNumber; PrdtIndex++) { | |
if (RemainedData < EFI_AHCI_MAX_DATA_PER_PRDT) { | |
AhciRegisters->AhciCommandTable->PrdtTable[PrdtIndex].AhciPrdtDbc = (UINT32)RemainedData - 1; | |
} else { | |
AhciRegisters->AhciCommandTable->PrdtTable[PrdtIndex].AhciPrdtDbc = EFI_AHCI_MAX_DATA_PER_PRDT - 1; | |
} | |
Data64.Uint64 = (UINT64)MemAddr; | |
AhciRegisters->AhciCommandTable->PrdtTable[PrdtIndex].AhciPrdtDba = Data64.Uint32.Lower32; | |
AhciRegisters->AhciCommandTable->PrdtTable[PrdtIndex].AhciPrdtDbau = Data64.Uint32.Upper32; | |
RemainedData -= EFI_AHCI_MAX_DATA_PER_PRDT; | |
MemAddr += EFI_AHCI_MAX_DATA_PER_PRDT; | |
} | |
// | |
// Set the last PRDT to Interrupt On Complete | |
// | |
if (PrdtNumber > 0) { | |
AhciRegisters->AhciCommandTable->PrdtTable[PrdtNumber - 1].AhciPrdtIoc = 1; | |
} | |
CopyMem ( | |
(VOID *)((UINTN)AhciRegisters->AhciCmdList + (UINTN)CommandSlotNumber * sizeof (EFI_AHCI_COMMAND_LIST)), | |
CommandList, | |
sizeof (EFI_AHCI_COMMAND_LIST) | |
); | |
Data64.Uint64 = (UINT64)(UINTN)AhciRegisters->AhciCommandTablePciAddr; | |
AhciRegisters->AhciCmdList[CommandSlotNumber].AhciCmdCtba = Data64.Uint32.Lower32; | |
AhciRegisters->AhciCmdList[CommandSlotNumber].AhciCmdCtbau = Data64.Uint32.Upper32; | |
AhciRegisters->AhciCmdList[CommandSlotNumber].AhciCmdPmp = PortMultiplier; | |
} | |
/** | |
Build a command FIS. | |
@param CmdFis A pointer to the EFI_AHCI_COMMAND_FIS data structure. | |
@param AtaCommandBlock A pointer to the AhciBuildCommandFis data structure. | |
**/ | |
VOID | |
EFIAPI | |
AhciBuildCommandFis ( | |
IN OUT EFI_AHCI_COMMAND_FIS *CmdFis, | |
IN EFI_ATA_COMMAND_BLOCK *AtaCommandBlock | |
) | |
{ | |
ZeroMem (CmdFis, sizeof (EFI_AHCI_COMMAND_FIS)); | |
CmdFis->AhciCFisType = EFI_AHCI_FIS_REGISTER_H2D; | |
// | |
// Indicator it's a command | |
// | |
CmdFis->AhciCFisCmdInd = 0x1; | |
CmdFis->AhciCFisCmd = AtaCommandBlock->AtaCommand; | |
CmdFis->AhciCFisFeature = AtaCommandBlock->AtaFeatures; | |
CmdFis->AhciCFisFeatureExp = AtaCommandBlock->AtaFeaturesExp; | |
CmdFis->AhciCFisSecNum = AtaCommandBlock->AtaSectorNumber; | |
CmdFis->AhciCFisSecNumExp = AtaCommandBlock->AtaSectorNumberExp; | |
CmdFis->AhciCFisClyLow = AtaCommandBlock->AtaCylinderLow; | |
CmdFis->AhciCFisClyLowExp = AtaCommandBlock->AtaCylinderLowExp; | |
CmdFis->AhciCFisClyHigh = AtaCommandBlock->AtaCylinderHigh; | |
CmdFis->AhciCFisClyHighExp = AtaCommandBlock->AtaCylinderHighExp; | |
CmdFis->AhciCFisSecCount = AtaCommandBlock->AtaSectorCount; | |
CmdFis->AhciCFisSecCountExp = AtaCommandBlock->AtaSectorCountExp; | |
CmdFis->AhciCFisDevHead = (UINT8)(AtaCommandBlock->AtaDeviceHead | 0xE0); | |
} | |
/** | |
Wait until SATA device reports it is ready for operation. | |
@param[in] PciIo Pointer to AHCI controller PciIo. | |
@param[in] Port SATA port index on which to reset. | |
@retval EFI_SUCCESS Device ready for operation. | |
@retval EFI_TIMEOUT Device failed to get ready within required period. | |
**/ | |
EFI_STATUS | |
AhciWaitDeviceReady ( | |
IN EFI_PCI_IO_PROTOCOL *PciIo, | |
IN UINT8 Port | |
) | |
{ | |
UINT32 PhyDetectDelay; | |
UINT32 Data; | |
UINT32 Offset; | |
// | |
// According to SATA1.0a spec section 5.2, we need to wait for PxTFD.BSY and PxTFD.DRQ | |
// and PxTFD.ERR to be zero. The maximum wait time is 16s which is defined at ATA spec. | |
// | |
PhyDetectDelay = 16 * 1000; | |
do { | |
Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_SERR; | |
if (AhciReadReg (PciIo, Offset) != 0) { | |
AhciWriteReg (PciIo, Offset, AhciReadReg (PciIo, Offset)); | |
} | |
Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_TFD; | |
Data = AhciReadReg (PciIo, Offset) & EFI_AHCI_PORT_TFD_MASK; | |
if (Data == 0) { | |
break; | |
} | |
MicroSecondDelay (1000); | |
PhyDetectDelay--; | |
} while (PhyDetectDelay > 0); | |
if (PhyDetectDelay == 0) { | |
DEBUG ((DEBUG_ERROR, "Port %d Device not ready (TFD=0x%X)\n", Port, Data)); | |
return EFI_TIMEOUT; | |
} else { | |
return EFI_SUCCESS; | |
} | |
} | |
/** | |
Reset the SATA port. Algorithm follows AHCI spec 1.3.1 section 10.4.2 | |
@param[in] PciIo Pointer to AHCI controller PciIo. | |
@param[in] Port SATA port index on which to reset. | |
@retval EFI_SUCCESS Port reset. | |
@retval Others Failed to reset the port. | |
**/ | |
EFI_STATUS | |
AhciResetPort ( | |
IN EFI_PCI_IO_PROTOCOL *PciIo, | |
IN UINT8 Port | |
) | |
{ | |
UINT32 Offset; | |
EFI_STATUS Status; | |
Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_SCTL; | |
AhciOrReg (PciIo, Offset, EFI_AHCI_PORT_SCTL_DET_INIT); | |
// | |
// SW is required to keep DET set to 0x1 at least for 1 milisecond to ensure that | |
// at least one COMRESET signal is sent. | |
// | |
MicroSecondDelay (1000); | |
AhciAndReg (PciIo, Offset, ~(UINT32)EFI_AHCI_PORT_SSTS_DET_MASK); | |
Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_SSTS; | |
Status = AhciWaitMmioSet (PciIo, Offset, EFI_AHCI_PORT_SSTS_DET_MASK, EFI_AHCI_PORT_SSTS_DET_PCE, ATA_ATAPI_TIMEOUT); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
return AhciWaitDeviceReady (PciIo, Port); | |
} | |
/** | |
Recovers the SATA port from error condition. | |
This function implements algorithm described in | |
AHCI spec 1.3.1 section 6.2.2 | |
@param[in] PciIo Pointer to AHCI controller PciIo. | |
@param[in] Port SATA port index on which to check. | |
@retval EFI_SUCCESS Port recovered. | |
@retval Others Failed to recover port. | |
**/ | |
EFI_STATUS | |
AhciRecoverPortError ( | |
IN EFI_PCI_IO_PROTOCOL *PciIo, | |
IN UINT8 Port | |
) | |
{ | |
UINT32 Offset; | |
UINT32 PortInterrupt; | |
UINT32 PortTfd; | |
EFI_STATUS Status; | |
Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_IS; | |
PortInterrupt = AhciReadReg (PciIo, Offset); | |
if ((PortInterrupt & EFI_AHCI_PORT_IS_FATAL_ERROR_MASK) == 0) { | |
// | |
// No fatal error detected. Exit with success as port should still be operational. | |
// No need to clear IS as it will be cleared when the next command starts. | |
// | |
return EFI_SUCCESS; | |
} | |
Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_CMD; | |
AhciAndReg (PciIo, Offset, ~(UINT32)EFI_AHCI_PORT_CMD_ST); | |
Status = AhciWaitMmioSet (PciIo, Offset, EFI_AHCI_PORT_CMD_CR, 0, ATA_ATAPI_TIMEOUT); | |
if (EFI_ERROR (Status)) { | |
DEBUG ((DEBUG_ERROR, "Ahci port %d is in hung state, aborting recovery\n", Port)); | |
return Status; | |
} | |
// | |
// If TFD.BSY or TFD.DRQ is still set it means that drive is hung and software has | |
// to reset it before sending any additional commands. | |
// | |
Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_TFD; | |
PortTfd = AhciReadReg (PciIo, Offset); | |
if ((PortTfd & (EFI_AHCI_PORT_TFD_BSY | EFI_AHCI_PORT_TFD_DRQ)) != 0) { | |
Status = AhciResetPort (PciIo, Port); | |
if (EFI_ERROR (Status)) { | |
DEBUG ((DEBUG_ERROR, "Failed to reset the port %d\n", Port)); | |
return EFI_DEVICE_ERROR; | |
} | |
} | |
return EFI_SUCCESS; | |
} | |
/** | |
This function will check if the failed command should be retired. Only error | |
conditions which are a result of transient conditions on a link(either to system or to device). | |
@param[in] PciIo Pointer to AHCI controller PciIo. | |
@param[in] Port SATA port index on which to check. | |
@retval TRUE Command failure was caused by transient condition and should be retried | |
@retval FALSE Command should not be retried | |
**/ | |
BOOLEAN | |
AhciShouldCmdBeRetried ( | |
IN EFI_PCI_IO_PROTOCOL *PciIo, | |
IN UINT8 Port | |
) | |
{ | |
UINT32 Offset; | |
UINT32 PortInterrupt; | |
UINT32 Serr; | |
UINT32 Tfd; | |
Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_IS; | |
PortInterrupt = AhciReadReg (PciIo, Offset); | |
Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_SERR; | |
Serr = AhciReadReg (PciIo, Offset); | |
Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_TFD; | |
Tfd = AhciReadReg (PciIo, Offset); | |
// | |
// This can occur if there was a CRC error on a path from system memory to | |
// host controller. | |
// | |
if (PortInterrupt & EFI_AHCI_PORT_IS_HBDS) { | |
return TRUE; | |
// | |
// This can occur if there was a CRC error detected by host during communication | |
// with the device | |
// | |
} else if ((PortInterrupt & (EFI_AHCI_PORT_IS_IFS | EFI_AHCI_PORT_IS_INFS)) && | |
(Serr & EFI_AHCI_PORT_SERR_CRCE)) | |
{ | |
return TRUE; | |
// | |
// This can occur if there was a CRC error detected by device during communication | |
// with the host. Device returns error status to host with D2H FIS. | |
// | |
} else if ((PortInterrupt & EFI_AHCI_PORT_IS_TFES) && | |
(Tfd & EFI_AHCI_PORT_TFD_ERR_INT_CRC)) | |
{ | |
return TRUE; | |
} | |
return FALSE; | |
} | |
/** | |
Checks if specified FIS has been received. | |
@param[in] PciIo Pointer to AHCI controller PciIo. | |
@param[in] Port SATA port index on which to check. | |
@param[in] FisType FIS type for which to check. | |
@retval EFI_SUCCESS FIS received. | |
@retval EFI_NOT_READY FIS not received yet. | |
@retval EFI_DEVICE_ERROR AHCI controller reported an error on port. | |
**/ | |
EFI_STATUS | |
AhciCheckFisReceived ( | |
IN EFI_PCI_IO_PROTOCOL *PciIo, | |
IN UINT8 Port, | |
IN SATA_FIS_TYPE FisType | |
) | |
{ | |
UINT32 Offset; | |
UINT32 PortInterrupt; | |
UINT32 PortTfd; | |
Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_IS; | |
PortInterrupt = AhciReadReg (PciIo, Offset); | |
if ((PortInterrupt & EFI_AHCI_PORT_IS_ERROR_MASK) != 0) { | |
DEBUG ((DEBUG_ERROR, "AHCI: Error interrupt reported PxIS: %X\n", PortInterrupt)); | |
return EFI_DEVICE_ERROR; | |
} | |
// | |
// For PIO setup FIS - 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. | |
// | |
if (((FisType == SataFisD2H) && ((PortInterrupt & EFI_AHCI_PORT_IS_DHRS) != 0)) || | |
((FisType == SataFisPioSetup) && ((PortInterrupt & (EFI_AHCI_PORT_IS_PSS | EFI_AHCI_PORT_IS_DHRS)) != 0)) || | |
((FisType == SataFisDmaSetup) && ((PortInterrupt & (EFI_AHCI_PORT_IS_DSS | EFI_AHCI_PORT_IS_DHRS)) != 0))) | |
{ | |
Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_TFD; | |
PortTfd = AhciReadReg (PciIo, (UINT32)Offset); | |
if ((PortTfd & EFI_AHCI_PORT_TFD_ERR) != 0) { | |
return EFI_DEVICE_ERROR; | |
} else { | |
return EFI_SUCCESS; | |
} | |
} | |
return EFI_NOT_READY; | |
} | |
/** | |
Waits until specified FIS has been received. | |
@param[in] PciIo Pointer to AHCI controller PciIo. | |
@param[in] Port SATA port index on which to check. | |
@param[in] Timeout Time after which function should stop polling. | |
@param[in] FisType FIS type for which to check. | |
@retval EFI_SUCCESS FIS received. | |
@retval EFI_TIMEOUT FIS failed to arrive within a specified time period. | |
@retval EFI_DEVICE_ERROR AHCI controller reported an error on port. | |
**/ | |
EFI_STATUS | |
AhciWaitUntilFisReceived ( | |
IN EFI_PCI_IO_PROTOCOL *PciIo, | |
IN UINT8 Port, | |
IN UINT64 Timeout, | |
IN SATA_FIS_TYPE FisType | |
) | |
{ | |
EFI_STATUS Status; | |
BOOLEAN InfiniteWait; | |
UINT64 Delay; | |
Delay = DivU64x32 (Timeout, 1000) + 1; | |
if (Timeout == 0) { | |
InfiniteWait = TRUE; | |
} else { | |
InfiniteWait = FALSE; | |
} | |
do { | |
Status = AhciCheckFisReceived (PciIo, Port, FisType); | |
if (Status != EFI_NOT_READY) { | |
return Status; | |
} | |
// | |
// Stall for 100 microseconds. | |
// | |
MicroSecondDelay (100); | |
Delay--; | |
} while (InfiniteWait || (Delay > 0)); | |
return EFI_TIMEOUT; | |
} | |
/** | |
Prints contents of the ATA command block into the debug port. | |
@param[in] AtaCommandBlock AtaCommandBlock to print. | |
@param[in] DebugLevel Debug level on which to print. | |
**/ | |
VOID | |
AhciPrintCommandBlock ( | |
IN EFI_ATA_COMMAND_BLOCK *AtaCommandBlock, | |
IN UINT32 DebugLevel | |
) | |
{ | |
DEBUG ((DebugLevel, "ATA COMMAND BLOCK:\n")); | |
DEBUG ((DebugLevel, "AtaCommand: %d\n", AtaCommandBlock->AtaCommand)); | |
DEBUG ((DebugLevel, "AtaFeatures: %X\n", AtaCommandBlock->AtaFeatures)); | |
DEBUG ((DebugLevel, "AtaSectorNumber: %d\n", AtaCommandBlock->AtaSectorNumber)); | |
DEBUG ((DebugLevel, "AtaCylinderLow: %X\n", AtaCommandBlock->AtaCylinderHigh)); | |
DEBUG ((DebugLevel, "AtaCylinderHigh: %X\n", AtaCommandBlock->AtaCylinderHigh)); | |
DEBUG ((DebugLevel, "AtaDeviceHead: %d\n", AtaCommandBlock->AtaDeviceHead)); | |
DEBUG ((DebugLevel, "AtaSectorNumberExp: %d\n", AtaCommandBlock->AtaSectorNumberExp)); | |
DEBUG ((DebugLevel, "AtaCylinderLowExp: %X\n", AtaCommandBlock->AtaCylinderLowExp)); | |
DEBUG ((DebugLevel, "AtaCylinderHighExp: %X\n", AtaCommandBlock->AtaCylinderHighExp)); | |
DEBUG ((DebugLevel, "AtaFeaturesExp: %X\n", AtaCommandBlock->AtaFeaturesExp)); | |
DEBUG ((DebugLevel, "AtaSectorCount: %d\n", AtaCommandBlock->AtaSectorCount)); | |
DEBUG ((DebugLevel, "AtaSectorCountExp: %d\n", AtaCommandBlock->AtaSectorCountExp)); | |
} | |
/** | |
Prints contents of the ATA status block into the debug port. | |
@param[in] AtaStatusBlock AtaStatusBlock to print. | |
@param[in] DebugLevel Debug level on which to print. | |
**/ | |
VOID | |
AhciPrintStatusBlock ( | |
IN EFI_ATA_STATUS_BLOCK *AtaStatusBlock, | |
IN UINT32 DebugLevel | |
) | |
{ | |
// | |
// Skip NULL pointer | |
// | |
if (AtaStatusBlock == NULL) { | |
return; | |
} | |
// | |
// Only print status and error since we have all of the rest printed as | |
// a part of command block print. | |
// | |
DEBUG ((DebugLevel, "ATA STATUS BLOCK:\n")); | |
DEBUG ((DebugLevel, "AtaStatus: %d\n", AtaStatusBlock->AtaStatus)); | |
DEBUG ((DebugLevel, "AtaError: %d\n", AtaStatusBlock->AtaError)); | |
} | |
/** | |
Start a PIO data transfer on specific port. | |
@param[in] PciIo The PCI IO protocol instance. | |
@param[in] AhciRegisters The pointer to the EFI_AHCI_REGISTERS. | |
@param[in] Port The number of port. | |
@param[in] PortMultiplier The timeout value of stop. | |
@param[in] AtapiCommand The atapi command will be used for the | |
transfer. | |
@param[in] AtapiCommandLength The length of the atapi command. | |
@param[in] Read The transfer direction. | |
@param[in] AtaCommandBlock The EFI_ATA_COMMAND_BLOCK data. | |
@param[in, out] AtaStatusBlock The EFI_ATA_STATUS_BLOCK data. | |
@param[in, out] MemoryAddr The pointer to the data buffer. | |
@param[in] DataCount The data count to be transferred. | |
@param[in] Timeout The timeout value of non data transfer, uses 100ns as a unit. | |
@param[in] Task Optional. Pointer to the ATA_NONBLOCK_TASK | |
used by non-blocking mode. | |
@retval EFI_DEVICE_ERROR The PIO data transfer abort with error occurs. | |
@retval EFI_TIMEOUT The operation is time out. | |
@retval EFI_UNSUPPORTED The device is not ready for transfer. | |
@retval EFI_SUCCESS The PIO data transfer executes successfully. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
AhciPioTransfer ( | |
IN EFI_PCI_IO_PROTOCOL *PciIo, | |
IN EFI_AHCI_REGISTERS *AhciRegisters, | |
IN UINT8 Port, | |
IN UINT8 PortMultiplier, | |
IN EFI_AHCI_ATAPI_COMMAND *AtapiCommand OPTIONAL, | |
IN UINT8 AtapiCommandLength, | |
IN BOOLEAN Read, | |
IN EFI_ATA_COMMAND_BLOCK *AtaCommandBlock, | |
IN OUT EFI_ATA_STATUS_BLOCK *AtaStatusBlock, | |
IN OUT VOID *MemoryAddr, | |
IN UINT32 DataCount, | |
IN UINT64 Timeout, | |
IN ATA_NONBLOCK_TASK *Task | |
) | |
{ | |
EFI_STATUS Status; | |
EFI_PHYSICAL_ADDRESS PhyAddr; | |
VOID *Map; | |
UINTN MapLength; | |
EFI_PCI_IO_PROTOCOL_OPERATION Flag; | |
EFI_AHCI_COMMAND_FIS CFis; | |
EFI_AHCI_COMMAND_LIST CmdList; | |
UINT32 PrdCount; | |
UINT32 Retry; | |
EFI_STATUS RecoveryStatus; | |
BOOLEAN DoRetry; | |
if (Read) { | |
Flag = EfiPciIoOperationBusMasterWrite; | |
} else { | |
Flag = EfiPciIoOperationBusMasterRead; | |
} | |
// | |
// construct command list and command table with pci bus address | |
// | |
MapLength = DataCount; | |
Status = PciIo->Map ( | |
PciIo, | |
Flag, | |
MemoryAddr, | |
&MapLength, | |
&PhyAddr, | |
&Map | |
); | |
if (EFI_ERROR (Status) || (DataCount != MapLength)) { | |
return EFI_BAD_BUFFER_SIZE; | |
} | |
// | |
// Package read needed | |
// | |
AhciBuildCommandFis (&CFis, AtaCommandBlock); | |
ZeroMem (&CmdList, sizeof (EFI_AHCI_COMMAND_LIST)); | |
CmdList.AhciCmdCfl = EFI_AHCI_FIS_REGISTER_H2D_LENGTH / 4; | |
CmdList.AhciCmdW = Read ? 0 : 1; | |
for (Retry = 0; Retry < AHCI_COMMAND_RETRIES; Retry++) { | |
AhciBuildCommand ( | |
PciIo, | |
AhciRegisters, | |
Port, | |
PortMultiplier, | |
&CFis, | |
&CmdList, | |
AtapiCommand, | |
AtapiCommandLength, | |
0, | |
(VOID *)(UINTN)PhyAddr, | |
DataCount | |
); | |
DEBUG ((DEBUG_VERBOSE, "Starting command for PIO transfer:\n")); | |
AhciPrintCommandBlock (AtaCommandBlock, DEBUG_VERBOSE); | |
Status = AhciStartCommand ( | |
PciIo, | |
Port, | |
0, | |
Timeout | |
); | |
if (EFI_ERROR (Status)) { | |
break; | |
} | |
if (Read && (AtapiCommand == 0)) { | |
Status = AhciWaitUntilFisReceived (PciIo, Port, Timeout, SataFisPioSetup); | |
if (Status == EFI_SUCCESS) { | |
PrdCount = *(volatile UINT32 *)(&(AhciRegisters->AhciCmdList[0].AhciCmdPrdbc)); | |
if (PrdCount == DataCount) { | |
Status = EFI_SUCCESS; | |
} else { | |
Status = EFI_DEVICE_ERROR; | |
} | |
} | |
} else { | |
Status = AhciWaitUntilFisReceived (PciIo, Port, Timeout, SataFisD2H); | |
} | |
if (Status == EFI_DEVICE_ERROR) { | |
DEBUG ((DEBUG_ERROR, "PIO command failed at retry %d\n", Retry)); | |
DoRetry = AhciShouldCmdBeRetried (PciIo, Port); // needs to be called before error recovery | |
RecoveryStatus = AhciRecoverPortError (PciIo, Port); | |
if (!DoRetry || EFI_ERROR (RecoveryStatus)) { | |
break; | |
} | |
} else { | |
break; | |
} | |
} | |
AhciStopCommand ( | |
PciIo, | |
Port, | |
Timeout | |
); | |
AhciDisableFisReceive ( | |
PciIo, | |
Port, | |
Timeout | |
); | |
PciIo->Unmap ( | |
PciIo, | |
Map | |
); | |
AhciDumpPortStatus (PciIo, AhciRegisters, Port, AtaStatusBlock); | |
if (Status == EFI_DEVICE_ERROR) { | |
DEBUG ((DEBUG_ERROR, "Failed to execute command for PIO transfer:\n")); | |
// | |
// Repeat command block here to make sure it is printed on | |
// device error debug level. | |
// | |
AhciPrintCommandBlock (AtaCommandBlock, DEBUG_ERROR); | |
AhciPrintStatusBlock (AtaStatusBlock, DEBUG_ERROR); | |
} else { | |
AhciPrintStatusBlock (AtaStatusBlock, DEBUG_VERBOSE); | |
} | |
return Status; | |
} | |
/** | |
Start a DMA data transfer on specific port | |
@param[in] Instance The ATA_ATAPI_PASS_THRU_INSTANCE protocol instance. | |
@param[in] AhciRegisters The pointer to the EFI_AHCI_REGISTERS. | |
@param[in] Port The number of port. | |
@param[in] PortMultiplier The timeout value of stop. | |
@param[in] AtapiCommand The atapi command will be used for the | |
transfer. | |
@param[in] AtapiCommandLength The length of the atapi command. | |
@param[in] Read The transfer direction. | |
@param[in] AtaCommandBlock The EFI_ATA_COMMAND_BLOCK data. | |
@param[in, out] AtaStatusBlock The EFI_ATA_STATUS_BLOCK data. | |
@param[in, out] MemoryAddr The pointer to the data buffer. | |
@param[in] DataCount The data count to be transferred. | |
@param[in] Timeout The timeout value of non data transfer, uses 100ns as a unit. | |
@param[in] Task Optional. Pointer to the ATA_NONBLOCK_TASK | |
used by non-blocking mode. | |
@retval EFI_DEVICE_ERROR The DMA data transfer abort with error occurs. | |
@retval EFI_TIMEOUT The operation is time out. | |
@retval EFI_UNSUPPORTED The device is not ready for transfer. | |
@retval EFI_SUCCESS The DMA data transfer executes successfully. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
AhciDmaTransfer ( | |
IN ATA_ATAPI_PASS_THRU_INSTANCE *Instance, | |
IN EFI_AHCI_REGISTERS *AhciRegisters, | |
IN UINT8 Port, | |
IN UINT8 PortMultiplier, | |
IN EFI_AHCI_ATAPI_COMMAND *AtapiCommand OPTIONAL, | |
IN UINT8 AtapiCommandLength, | |
IN BOOLEAN Read, | |
IN EFI_ATA_COMMAND_BLOCK *AtaCommandBlock, | |
IN OUT EFI_ATA_STATUS_BLOCK *AtaStatusBlock, | |
IN OUT VOID *MemoryAddr, | |
IN UINT32 DataCount, | |
IN UINT64 Timeout, | |
IN ATA_NONBLOCK_TASK *Task | |
) | |
{ | |
EFI_STATUS Status; | |
EFI_PHYSICAL_ADDRESS PhyAddr; | |
VOID *Map; | |
UINTN MapLength; | |
EFI_PCI_IO_PROTOCOL_OPERATION Flag; | |
EFI_AHCI_COMMAND_FIS CFis; | |
EFI_AHCI_COMMAND_LIST CmdList; | |
EFI_PCI_IO_PROTOCOL *PciIo; | |
EFI_TPL OldTpl; | |
UINT32 Retry; | |
EFI_STATUS RecoveryStatus; | |
BOOLEAN DoRetry; | |
Map = NULL; | |
PciIo = Instance->PciIo; | |
if (PciIo == NULL) { | |
return EFI_INVALID_PARAMETER; | |
} | |
// | |
// Set Status to suppress incorrect compiler/analyzer warnings | |
// | |
Status = EFI_SUCCESS; | |
// | |
// DMA buffer allocation. Needs to be done only once for both sync and async | |
// DMA transfers irrespective of number of retries. | |
// | |
if ((Task == NULL) || ((Task != NULL) && (Task->Map == NULL))) { | |
if (Read) { | |
Flag = EfiPciIoOperationBusMasterWrite; | |
} else { | |
Flag = EfiPciIoOperationBusMasterRead; | |
} | |
MapLength = DataCount; | |
Status = PciIo->Map ( | |
PciIo, | |
Flag, | |
MemoryAddr, | |
&MapLength, | |
&PhyAddr, | |
&Map | |
); | |
if (EFI_ERROR (Status) || (DataCount != MapLength)) { | |
return EFI_BAD_BUFFER_SIZE; | |
} | |
if (Task != NULL) { | |
Task->Map = Map; | |
} | |
} | |
if ((Task == NULL) || ((Task != NULL) && !Task->IsStart)) { | |
AhciBuildCommandFis (&CFis, AtaCommandBlock); | |
ZeroMem (&CmdList, sizeof (EFI_AHCI_COMMAND_LIST)); | |
CmdList.AhciCmdCfl = EFI_AHCI_FIS_REGISTER_H2D_LENGTH / 4; | |
CmdList.AhciCmdW = Read ? 0 : 1; | |
} | |
if (Task == NULL) { | |
// | |
// Before starting the Blocking BlockIO operation, push to finish all non-blocking | |
// BlockIO tasks. | |
// Delay 100us to simulate the blocking time out checking. | |
// | |
OldTpl = gBS->RaiseTPL (TPL_NOTIFY); | |
while (!IsListEmpty (&Instance->NonBlockingTaskList)) { | |
AsyncNonBlockingTransferRoutine (NULL, Instance); | |
// | |
// Stall for 100us. | |
// | |
MicroSecondDelay (100); | |
} | |
gBS->RestoreTPL (OldTpl); | |
for (Retry = 0; Retry < AHCI_COMMAND_RETRIES; Retry++) { | |
AhciBuildCommand ( | |
PciIo, | |
AhciRegisters, | |
Port, | |
PortMultiplier, | |
&CFis, | |
&CmdList, | |
AtapiCommand, | |
AtapiCommandLength, | |
0, | |
(VOID *)(UINTN)PhyAddr, | |
DataCount | |
); | |
DEBUG ((DEBUG_VERBOSE, "Starting command for sync DMA transfer:\n")); | |
AhciPrintCommandBlock (AtaCommandBlock, DEBUG_VERBOSE); | |
Status = AhciStartCommand ( | |
PciIo, | |
Port, | |
0, | |
Timeout | |
); | |
if (EFI_ERROR (Status)) { | |
break; | |
} | |
Status = AhciWaitUntilFisReceived (PciIo, Port, Timeout, SataFisD2H); | |
if (Status == EFI_DEVICE_ERROR) { | |
DEBUG ((DEBUG_ERROR, "DMA command failed at retry: %d\n", Retry)); | |
DoRetry = AhciShouldCmdBeRetried (PciIo, Port); // needs to be called before error recovery | |
RecoveryStatus = AhciRecoverPortError (PciIo, Port); | |
if (!DoRetry || EFI_ERROR (RecoveryStatus)) { | |
break; | |
} | |
} else { | |
break; | |
} | |
} | |
} else { | |
if (!Task->IsStart) { | |
AhciBuildCommand ( | |
PciIo, | |
AhciRegisters, | |
Port, | |
PortMultiplier, | |
&CFis, | |
&CmdList, | |
AtapiCommand, | |
AtapiCommandLength, | |
0, | |
(VOID *)(UINTN)PhyAddr, | |
DataCount | |
); | |
DEBUG ((DEBUG_VERBOSE, "Starting command for async DMA transfer:\n")); | |
AhciPrintCommandBlock (AtaCommandBlock, DEBUG_VERBOSE); | |
Status = AhciStartCommand ( | |
PciIo, | |
Port, | |
0, | |
Timeout | |
); | |
if (!EFI_ERROR (Status)) { | |
Task->IsStart = TRUE; | |
} | |
} | |
if (Task->IsStart) { | |
Status = AhciCheckFisReceived (PciIo, Port, SataFisD2H); | |
if (Status == EFI_DEVICE_ERROR) { | |
DEBUG ((DEBUG_ERROR, "DMA command failed at retry: %d\n", Task->RetryTimes)); | |
DoRetry = AhciShouldCmdBeRetried (PciIo, Port); // call this before error recovery | |
RecoveryStatus = AhciRecoverPortError (PciIo, Port); | |
// | |
// If recovery passed mark the Task as not started and change the status | |
// to EFI_NOT_READY. This will make the higher level call this function again | |
// and on next call the command will be re-issued due to IsStart being FALSE. | |
// This also makes the next condition decrement the RetryTimes. | |
// | |
if (DoRetry && (RecoveryStatus == EFI_SUCCESS)) { | |
Task->IsStart = FALSE; | |
Status = EFI_NOT_READY; | |
} | |
} | |
if (Status == EFI_NOT_READY) { | |
if (!Task->InfiniteWait && (Task->RetryTimes == 0)) { | |
Status = EFI_TIMEOUT; | |
} else { | |
Task->RetryTimes--; | |
} | |
} | |
} | |
} | |
// | |
// For Blocking mode, the command should be stopped, the Fis should be disabled | |
// and the PciIo should be unmapped. | |
// For non-blocking mode, only when a error is happened (if the return status is | |
// EFI_NOT_READY that means the command doesn't finished, try again.), first do the | |
// context cleanup, then set the packet's Asb status. | |
// | |
if ((Task == NULL) || | |
((Task != NULL) && (Status != EFI_NOT_READY)) | |
) | |
{ | |
AhciStopCommand ( | |
PciIo, | |
Port, | |
Timeout | |
); | |
AhciDisableFisReceive ( | |
PciIo, | |
Port, | |
Timeout | |
); | |
PciIo->Unmap ( | |
PciIo, | |
(Task != NULL) ? Task->Map : Map | |
); | |
if (Task != NULL) { | |
Task->Packet->Asb->AtaStatus = 0x01; | |
} | |
} | |
AhciDumpPortStatus (PciIo, AhciRegisters, Port, AtaStatusBlock); | |
if (Status == EFI_DEVICE_ERROR) { | |
DEBUG ((DEBUG_ERROR, "Failed to execute command for DMA transfer:\n")); | |
// | |
// Repeat command block here to make sure it is printed on | |
// device error debug level. | |
// | |
AhciPrintCommandBlock (AtaCommandBlock, DEBUG_ERROR); | |
AhciPrintStatusBlock (AtaStatusBlock, DEBUG_ERROR); | |
} else { | |
AhciPrintStatusBlock (AtaStatusBlock, DEBUG_VERBOSE); | |
} | |
return Status; | |
} | |
/** | |
Start a non data transfer on specific port. | |
@param[in] PciIo The PCI IO protocol instance. | |
@param[in] AhciRegisters The pointer to the EFI_AHCI_REGISTERS. | |
@param[in] Port The number of port. | |
@param[in] PortMultiplier The timeout value of stop. | |
@param[in] AtapiCommand The atapi command will be used for the | |
transfer. | |
@param[in] AtapiCommandLength The length of the atapi command. | |
@param[in] AtaCommandBlock The EFI_ATA_COMMAND_BLOCK data. | |
@param[in, out] AtaStatusBlock The EFI_ATA_STATUS_BLOCK data. | |
@param[in] Timeout The timeout value of non data transfer, uses 100ns as a unit. | |
@param[in] Task Optional. Pointer to the ATA_NONBLOCK_TASK | |
used by non-blocking mode. | |
@retval EFI_DEVICE_ERROR The non data transfer abort with error occurs. | |
@retval EFI_TIMEOUT The operation is time out. | |
@retval EFI_UNSUPPORTED The device is not ready for transfer. | |
@retval EFI_SUCCESS The non data transfer executes successfully. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
AhciNonDataTransfer ( | |
IN EFI_PCI_IO_PROTOCOL *PciIo, | |
IN EFI_AHCI_REGISTERS *AhciRegisters, | |
IN UINT8 Port, | |
IN UINT8 PortMultiplier, | |
IN EFI_AHCI_ATAPI_COMMAND *AtapiCommand OPTIONAL, | |
IN UINT8 AtapiCommandLength, | |
IN EFI_ATA_COMMAND_BLOCK *AtaCommandBlock, | |
IN OUT EFI_ATA_STATUS_BLOCK *AtaStatusBlock, | |
IN UINT64 Timeout, | |
IN ATA_NONBLOCK_TASK *Task | |
) | |
{ | |
EFI_STATUS Status; | |
EFI_AHCI_COMMAND_FIS CFis; | |
EFI_AHCI_COMMAND_LIST CmdList; | |
UINT32 Retry; | |
EFI_STATUS RecoveryStatus; | |
BOOLEAN DoRetry; | |
// | |
// Package read needed | |
// | |
AhciBuildCommandFis (&CFis, AtaCommandBlock); | |
ZeroMem (&CmdList, sizeof (EFI_AHCI_COMMAND_LIST)); | |
CmdList.AhciCmdCfl = EFI_AHCI_FIS_REGISTER_H2D_LENGTH / 4; | |
for (Retry = 0; Retry < AHCI_COMMAND_RETRIES; Retry++) { | |
AhciBuildCommand ( | |
PciIo, | |
AhciRegisters, | |
Port, | |
PortMultiplier, | |
&CFis, | |
&CmdList, | |
AtapiCommand, | |
AtapiCommandLength, | |
0, | |
NULL, | |
0 | |
); | |
DEBUG ((DEBUG_VERBOSE, "Starting command for non data transfer:\n")); | |
AhciPrintCommandBlock (AtaCommandBlock, DEBUG_VERBOSE); | |
Status = AhciStartCommand ( | |
PciIo, | |
Port, | |
0, | |
Timeout | |
); | |
if (EFI_ERROR (Status)) { | |
break; | |
} | |
Status = AhciWaitUntilFisReceived (PciIo, Port, Timeout, SataFisD2H); | |
if (Status == EFI_DEVICE_ERROR) { | |
DEBUG ((DEBUG_ERROR, "Non data transfer failed at retry %d\n", Retry)); | |
DoRetry = AhciShouldCmdBeRetried (PciIo, Port); // call this before error recovery | |
RecoveryStatus = AhciRecoverPortError (PciIo, Port); | |
if (!DoRetry || EFI_ERROR (RecoveryStatus)) { | |
break; | |
} | |
} else { | |
break; | |
} | |
} | |
AhciStopCommand ( | |
PciIo, | |
Port, | |
Timeout | |
); | |
AhciDisableFisReceive ( | |
PciIo, | |
Port, | |
Timeout | |
); | |
AhciDumpPortStatus (PciIo, AhciRegisters, Port, AtaStatusBlock); | |
if (Status == EFI_DEVICE_ERROR) { | |
DEBUG ((DEBUG_ERROR, "Failed to execute command for non data transfer:\n")); | |
// | |
// Repeat command block here to make sure it is printed on | |
// device error debug level. | |
// | |
AhciPrintCommandBlock (AtaCommandBlock, DEBUG_ERROR); | |
AhciPrintStatusBlock (AtaStatusBlock, DEBUG_ERROR); | |
} else { | |
AhciPrintStatusBlock (AtaStatusBlock, DEBUG_VERBOSE); | |
} | |
return Status; | |
} | |
/** | |
Stop command running for giving port | |
@param PciIo The PCI IO protocol instance. | |
@param Port The number of port. | |
@param Timeout The timeout value of stop, uses 100ns as a unit. | |
@retval EFI_DEVICE_ERROR The command stop unsuccessfully. | |
@retval EFI_TIMEOUT The operation is time out. | |
@retval EFI_SUCCESS The command stop successfully. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
AhciStopCommand ( | |
IN EFI_PCI_IO_PROTOCOL *PciIo, | |
IN UINT8 Port, | |
IN UINT64 Timeout | |
) | |
{ | |
UINT32 Offset; | |
UINT32 Data; | |
Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_CMD; | |
Data = AhciReadReg (PciIo, Offset); | |
if ((Data & (EFI_AHCI_PORT_CMD_ST | EFI_AHCI_PORT_CMD_CR)) == 0) { | |
return EFI_SUCCESS; | |
} | |
if ((Data & EFI_AHCI_PORT_CMD_ST) != 0) { | |
AhciAndReg (PciIo, Offset, (UINT32) ~(EFI_AHCI_PORT_CMD_ST)); | |
} | |
return AhciWaitMmioSet ( | |
PciIo, | |
Offset, | |
EFI_AHCI_PORT_CMD_CR, | |
0, | |
Timeout | |
); | |
} | |
/** | |
Start command for give slot on specific port. | |
@param PciIo The PCI IO protocol instance. | |
@param Port The number of port. | |
@param CommandSlot The number of Command Slot. | |
@param Timeout The timeout value of start, uses 100ns as a unit. | |
@retval EFI_DEVICE_ERROR The command start unsuccessfully. | |
@retval EFI_TIMEOUT The operation is time out. | |
@retval EFI_SUCCESS The command start successfully. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
AhciStartCommand ( | |
IN EFI_PCI_IO_PROTOCOL *PciIo, | |
IN UINT8 Port, | |
IN UINT8 CommandSlot, | |
IN UINT64 Timeout | |
) | |
{ | |
UINT32 CmdSlotBit; | |
EFI_STATUS Status; | |
UINT32 PortStatus; | |
UINT32 StartCmd; | |
UINT32 PortTfd; | |
UINT32 Offset; | |
UINT32 Capability; | |
// | |
// Collect AHCI controller information | |
// | |
Capability = AhciReadReg (PciIo, EFI_AHCI_CAPABILITY_OFFSET); | |
CmdSlotBit = (UINT32)(1 << CommandSlot); | |
AhciClearPortStatus ( | |
PciIo, | |
Port | |
); | |
Status = AhciEnableFisReceive ( | |
PciIo, | |
Port, | |
Timeout | |
); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_CMD; | |
PortStatus = AhciReadReg (PciIo, Offset); | |
StartCmd = 0; | |
if ((PortStatus & EFI_AHCI_PORT_CMD_ALPE) != 0) { | |
StartCmd = AhciReadReg (PciIo, Offset); | |
StartCmd &= ~EFI_AHCI_PORT_CMD_ICC_MASK; | |
StartCmd |= EFI_AHCI_PORT_CMD_ACTIVE; | |
} | |
Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_TFD; | |
PortTfd = AhciReadReg (PciIo, Offset); | |
if ((PortTfd & (EFI_AHCI_PORT_TFD_BSY | EFI_AHCI_PORT_TFD_DRQ)) != 0) { | |
if ((Capability & BIT24) != 0) { | |
Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_CMD; | |
AhciOrReg (PciIo, Offset, EFI_AHCI_PORT_CMD_CLO); | |
AhciWaitMmioSet ( | |
PciIo, | |
Offset, | |
EFI_AHCI_PORT_CMD_CLO, | |
0, | |
Timeout | |
); | |
} | |
} | |
Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_CMD; | |
AhciOrReg (PciIo, Offset, EFI_AHCI_PORT_CMD_ST | StartCmd); | |
// | |
// Setting the command | |
// | |
Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_CI; | |
AhciAndReg (PciIo, Offset, 0); | |
AhciOrReg (PciIo, Offset, CmdSlotBit); | |
return EFI_SUCCESS; | |
} | |
/** | |
Do AHCI HBA reset. | |
@param PciIo The PCI IO protocol instance. | |
@param Timeout The timeout value of reset, uses 100ns as a unit. | |
@retval EFI_DEVICE_ERROR AHCI controller is failed to complete hardware reset. | |
@retval EFI_TIMEOUT The reset operation is time out. | |
@retval EFI_SUCCESS AHCI controller is reset successfully. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
AhciReset ( | |
IN EFI_PCI_IO_PROTOCOL *PciIo, | |
IN UINT64 Timeout | |
) | |
{ | |
UINT64 Delay; | |
UINT32 Value; | |
// | |
// 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 = DivU64x32 (Timeout, 1000) + 1; | |
do { | |
Value = AhciReadReg (PciIo, EFI_AHCI_GHC_OFFSET); | |
if ((Value & EFI_AHCI_GHC_RESET) == 0) { | |
break; | |
} | |
// | |
// Stall for 100 microseconds. | |
// | |
MicroSecondDelay (100); | |
Delay--; | |
} while (Delay > 0); | |
if (Delay == 0) { | |
return EFI_TIMEOUT; | |
} | |
return EFI_SUCCESS; | |
} | |
/** | |
Send SMART Return Status command to check if the execution of SMART cmd is successful or not. | |
@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 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. | |
@retval Others Fail to get return status data. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
AhciAtaSmartReturnStatusCheck ( | |
IN EFI_PCI_IO_PROTOCOL *PciIo, | |
IN EFI_AHCI_REGISTERS *AhciRegisters, | |
IN UINT8 Port, | |
IN UINT8 PortMultiplier, | |
IN OUT EFI_ATA_STATUS_BLOCK *AtaStatusBlock | |
) | |
{ | |
EFI_STATUS Status; | |
EFI_ATA_COMMAND_BLOCK AtaCommandBlock; | |
UINT8 LBAMid; | |
UINT8 LBAHigh; | |
UINTN FisBaseAddr; | |
UINT32 Value; | |
ZeroMem (&AtaCommandBlock, sizeof (EFI_ATA_COMMAND_BLOCK)); | |
AtaCommandBlock.AtaCommand = ATA_CMD_SMART; | |
AtaCommandBlock.AtaFeatures = ATA_SMART_RETURN_STATUS; | |
AtaCommandBlock.AtaCylinderLow = ATA_CONSTANT_4F; | |
AtaCommandBlock.AtaCylinderHigh = ATA_CONSTANT_C2; | |
// | |
// Send S.M.A.R.T Read Return Status command to device | |
// | |
Status = AhciNonDataTransfer ( | |
PciIo, | |
AhciRegisters, | |
(UINT8)Port, | |
(UINT8)PortMultiplier, | |
NULL, | |
0, | |
&AtaCommandBlock, | |
AtaStatusBlock, | |
ATA_ATAPI_TIMEOUT, | |
NULL | |
); | |
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); | |
if ((Value & EFI_AHCI_FIS_TYPE_MASK) == EFI_AHCI_FIS_REGISTER_D2H) { | |
LBAMid = ((UINT8 *)(UINTN)(FisBaseAddr + EFI_AHCI_D2H_FIS_OFFSET))[5]; | |
LBAHigh = ((UINT8 *)(UINTN)(FisBaseAddr + EFI_AHCI_D2H_FIS_OFFSET))[6]; | |
if ((LBAMid == 0x4f) && (LBAHigh == 0xc2)) { | |
// | |
// The threshold exceeded condition is not detected by the device | |
// | |
DEBUG ((DEBUG_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 ((DEBUG_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) | |
); | |
} | |
} | |
return EFI_SUCCESS; | |
} | |
/** | |
Enable SMART command 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 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. | |
**/ | |
VOID | |
EFIAPI | |
AhciAtaSmartSupport ( | |
IN EFI_PCI_IO_PROTOCOL *PciIo, | |
IN EFI_AHCI_REGISTERS *AhciRegisters, | |
IN UINT8 Port, | |
IN UINT8 PortMultiplier, | |
IN EFI_IDENTIFY_DATA *IdentifyData, | |
IN OUT EFI_ATA_STATUS_BLOCK *AtaStatusBlock | |
) | |
{ | |
EFI_STATUS Status; | |
EFI_ATA_COMMAND_BLOCK AtaCommandBlock; | |
// | |
// Detect if the device supports S.M.A.R.T. | |
// | |
if ((IdentifyData->AtaData.command_set_supported_82 & 0x0001) != 0x0001) { | |
// | |
// S.M.A.R.T is not supported by the device | |
// | |
DEBUG (( | |
DEBUG_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; | |
AtaCommandBlock.AtaFeatures = ATA_SMART_ENABLE_OPERATION; | |
AtaCommandBlock.AtaCylinderLow = ATA_CONSTANT_4F; | |
AtaCommandBlock.AtaCylinderHigh = ATA_CONSTANT_C2; | |
// | |
// Send S.M.A.R.T Enable command to device | |
// | |
Status = AhciNonDataTransfer ( | |
PciIo, | |
AhciRegisters, | |
(UINT8)Port, | |
(UINT8)PortMultiplier, | |
NULL, | |
0, | |
&AtaCommandBlock, | |
AtaStatusBlock, | |
ATA_ATAPI_TIMEOUT, | |
NULL | |
); | |
if (!EFI_ERROR (Status)) { | |
// | |
// Send S.M.A.R.T AutoSave command to device | |
// | |
ZeroMem (&AtaCommandBlock, sizeof (EFI_ATA_COMMAND_BLOCK)); | |
AtaCommandBlock.AtaCommand = ATA_CMD_SMART; | |
AtaCommandBlock.AtaFeatures = 0xD2; | |
AtaCommandBlock.AtaSectorCount = 0xF1; | |
AtaCommandBlock.AtaCylinderLow = ATA_CONSTANT_4F; | |
AtaCommandBlock.AtaCylinderHigh = ATA_CONSTANT_C2; | |
Status = AhciNonDataTransfer ( | |
PciIo, | |
AhciRegisters, | |
(UINT8)Port, | |
(UINT8)PortMultiplier, | |
NULL, | |
0, | |
&AtaCommandBlock, | |
AtaStatusBlock, | |
ATA_ATAPI_TIMEOUT, | |
NULL | |
); | |
} | |
} | |
AhciAtaSmartReturnStatusCheck ( | |
PciIo, | |
AhciRegisters, | |
(UINT8)Port, | |
(UINT8)PortMultiplier, | |
AtaStatusBlock | |
); | |
DEBUG (( | |
DEBUG_INFO, | |
"Enabled S.M.A.R.T feature at port [%d] PortMultiplier [%d]!\n", | |
Port, | |
PortMultiplier | |
)); | |
} | |
return; | |
} | |
/** | |
Send Buffer cmd to specific 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 port multiplier port number. | |
@param Buffer The data buffer to store IDENTIFY PACKET data. | |
@retval EFI_DEVICE_ERROR The cmd abort with error occurs. | |
@retval EFI_TIMEOUT The operation is time out. | |
@retval EFI_UNSUPPORTED The device is not ready for executing. | |
@retval EFI_SUCCESS The cmd executes successfully. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
AhciIdentify ( | |
IN EFI_PCI_IO_PROTOCOL *PciIo, | |
IN EFI_AHCI_REGISTERS *AhciRegisters, | |
IN UINT8 Port, | |
IN UINT8 PortMultiplier, | |
IN OUT EFI_IDENTIFY_DATA *Buffer | |
) | |
{ | |
EFI_STATUS Status; | |
EFI_ATA_COMMAND_BLOCK AtaCommandBlock; | |
EFI_ATA_STATUS_BLOCK AtaStatusBlock; | |
if ((PciIo == NULL) || (AhciRegisters == NULL) || (Buffer == NULL)) { | |
return EFI_INVALID_PARAMETER; | |
} | |
ZeroMem (&AtaCommandBlock, sizeof (EFI_ATA_COMMAND_BLOCK)); | |
ZeroMem (&AtaStatusBlock, sizeof (EFI_ATA_STATUS_BLOCK)); | |
AtaCommandBlock.AtaCommand = ATA_CMD_IDENTIFY_DRIVE; | |
AtaCommandBlock.AtaSectorCount = 1; | |
Status = AhciPioTransfer ( | |
PciIo, | |
AhciRegisters, | |
Port, | |
PortMultiplier, | |
NULL, | |
0, | |
TRUE, | |
&AtaCommandBlock, | |
&AtaStatusBlock, | |
Buffer, | |
sizeof (EFI_IDENTIFY_DATA), | |
ATA_ATAPI_TIMEOUT, | |
NULL | |
); | |
return Status; | |
} | |
/** | |
Send Buffer cmd to specific 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 port multiplier port number. | |
@param Buffer The data buffer to store IDENTIFY PACKET data. | |
@retval EFI_DEVICE_ERROR The cmd abort with error occurs. | |
@retval EFI_TIMEOUT The operation is time out. | |
@retval EFI_UNSUPPORTED The device is not ready for executing. | |
@retval EFI_SUCCESS The cmd executes successfully. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
AhciIdentifyPacket ( | |
IN EFI_PCI_IO_PROTOCOL *PciIo, | |
IN EFI_AHCI_REGISTERS *AhciRegisters, | |
IN UINT8 Port, | |
IN UINT8 PortMultiplier, | |
IN OUT EFI_IDENTIFY_DATA *Buffer | |
) | |
{ | |
EFI_STATUS Status; | |
EFI_ATA_COMMAND_BLOCK AtaCommandBlock; | |
EFI_ATA_STATUS_BLOCK AtaStatusBlock; | |
if ((PciIo == NULL) || (AhciRegisters == NULL)) { | |
return EFI_INVALID_PARAMETER; | |
} | |
ZeroMem (&AtaCommandBlock, sizeof (EFI_ATA_COMMAND_BLOCK)); | |
ZeroMem (&AtaStatusBlock, sizeof (EFI_ATA_STATUS_BLOCK)); | |
AtaCommandBlock.AtaCommand = ATA_CMD_IDENTIFY_DEVICE; | |
AtaCommandBlock.AtaSectorCount = 1; | |
Status = AhciPioTransfer ( | |
PciIo, | |
AhciRegisters, | |
Port, | |
PortMultiplier, | |
NULL, | |
0, | |
TRUE, | |
&AtaCommandBlock, | |
&AtaStatusBlock, | |
Buffer, | |
sizeof (EFI_IDENTIFY_DATA), | |
ATA_ATAPI_TIMEOUT, | |
NULL | |
); | |
return Status; | |
} | |
/** | |
Send SET FEATURE cmd on specific 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 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. | |
@retval EFI_UNSUPPORTED The device is not ready for executing. | |
@retval EFI_SUCCESS The cmd executes successfully. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
AhciDeviceSetFeature ( | |
IN EFI_PCI_IO_PROTOCOL *PciIo, | |
IN EFI_AHCI_REGISTERS *AhciRegisters, | |
IN UINT8 Port, | |
IN UINT8 PortMultiplier, | |
IN UINT16 Feature, | |
IN UINT32 FeatureSpecificData, | |
IN UINT64 Timeout | |
) | |
{ | |
EFI_STATUS Status; | |
EFI_ATA_COMMAND_BLOCK AtaCommandBlock; | |
EFI_ATA_STATUS_BLOCK AtaStatusBlock; | |
ZeroMem (&AtaCommandBlock, sizeof (EFI_ATA_COMMAND_BLOCK)); | |
ZeroMem (&AtaStatusBlock, sizeof (EFI_ATA_STATUS_BLOCK)); | |
AtaCommandBlock.AtaCommand = ATA_CMD_SET_FEATURES; | |
AtaCommandBlock.AtaFeatures = (UINT8)Feature; | |
AtaCommandBlock.AtaFeaturesExp = (UINT8)(Feature >> 8); | |
AtaCommandBlock.AtaSectorCount = (UINT8)FeatureSpecificData; | |
AtaCommandBlock.AtaSectorNumber = (UINT8)(FeatureSpecificData >> 8); | |
AtaCommandBlock.AtaCylinderLow = (UINT8)(FeatureSpecificData >> 16); | |
AtaCommandBlock.AtaCylinderHigh = (UINT8)(FeatureSpecificData >> 24); | |
Status = AhciNonDataTransfer ( | |
PciIo, | |
AhciRegisters, | |
(UINT8)Port, | |
(UINT8)PortMultiplier, | |
NULL, | |
0, | |
&AtaCommandBlock, | |
&AtaStatusBlock, | |
Timeout, | |
NULL | |
); | |
return Status; | |
} | |
/** | |
This function is used to send out ATAPI commands conforms to the Packet Command | |
with PIO Protocol. | |
@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 number of port multiplier. | |
@param Packet A pointer to EFI_EXT_SCSI_PASS_THRU_SCSI_REQUEST_PACKET structure. | |
@retval EFI_SUCCESS send out the ATAPI packet command successfully | |
and device sends data successfully. | |
@retval EFI_DEVICE_ERROR the device failed to send data. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
AhciPacketCommandExecute ( | |
IN EFI_PCI_IO_PROTOCOL *PciIo, | |
IN EFI_AHCI_REGISTERS *AhciRegisters, | |
IN UINT8 Port, | |
IN UINT8 PortMultiplier, | |
IN EFI_EXT_SCSI_PASS_THRU_SCSI_REQUEST_PACKET *Packet | |
) | |
{ | |
EFI_STATUS Status; | |
VOID *Buffer; | |
UINT32 Length; | |
EFI_ATA_COMMAND_BLOCK AtaCommandBlock; | |
EFI_ATA_STATUS_BLOCK AtaStatusBlock; | |
BOOLEAN Read; | |
if ((Packet == NULL) || (Packet->Cdb == NULL)) { | |
return EFI_INVALID_PARAMETER; | |
} | |
ZeroMem (&AtaCommandBlock, sizeof (EFI_ATA_COMMAND_BLOCK)); | |
ZeroMem (&AtaStatusBlock, sizeof (EFI_ATA_STATUS_BLOCK)); | |
AtaCommandBlock.AtaCommand = ATA_CMD_PACKET; | |
// | |
// No OVL; No DMA | |
// | |
AtaCommandBlock.AtaFeatures = 0x00; | |
// | |
// set the transfersize to ATAPI_MAX_BYTE_COUNT to let the device | |
// determine how many data should be transferred. | |
// | |
AtaCommandBlock.AtaCylinderLow = (UINT8)(ATAPI_MAX_BYTE_COUNT & 0x00ff); | |
AtaCommandBlock.AtaCylinderHigh = (UINT8)(ATAPI_MAX_BYTE_COUNT >> 8); | |
if (Packet->DataDirection == EFI_EXT_SCSI_DATA_DIRECTION_READ) { | |
Buffer = Packet->InDataBuffer; | |
Length = Packet->InTransferLength; | |
Read = TRUE; | |
} else { | |
Buffer = Packet->OutDataBuffer; | |
Length = Packet->OutTransferLength; | |
Read = FALSE; | |
} | |
if (Length == 0) { | |
Status = AhciNonDataTransfer ( | |
PciIo, | |
AhciRegisters, | |
Port, | |
PortMultiplier, | |
Packet->Cdb, | |
Packet->CdbLength, | |
&AtaCommandBlock, | |
&AtaStatusBlock, | |
Packet->Timeout, | |
NULL | |
); | |
} else { | |
Status = AhciPioTransfer ( | |
PciIo, | |
AhciRegisters, | |
Port, | |
PortMultiplier, | |
Packet->Cdb, | |
Packet->CdbLength, | |
Read, | |
&AtaCommandBlock, | |
&AtaStatusBlock, | |
Buffer, | |
Length, | |
Packet->Timeout, | |
NULL | |
); | |
} | |
return Status; | |
} | |
/** | |
Allocate transfer-related data struct which is used at AHCI mode. | |
@param PciIo The PCI IO protocol instance. | |
@param AhciRegisters The pointer to the EFI_AHCI_REGISTERS. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
AhciCreateTransferDescriptor ( | |
IN EFI_PCI_IO_PROTOCOL *PciIo, | |
IN OUT EFI_AHCI_REGISTERS *AhciRegisters | |
) | |
{ | |
EFI_STATUS Status; | |
UINTN Bytes; | |
VOID *Buffer; | |
UINT32 Capability; | |
UINT32 PortImplementBitMap; | |
UINT8 MaxPortNumber; | |
UINT8 MaxCommandSlotNumber; | |
BOOLEAN Support64Bit; | |
UINT64 MaxReceiveFisSize; | |
UINT64 MaxCommandListSize; | |
UINT64 MaxCommandTableSize; | |
EFI_PHYSICAL_ADDRESS AhciRFisPciAddr; | |
EFI_PHYSICAL_ADDRESS AhciCmdListPciAddr; | |
EFI_PHYSICAL_ADDRESS AhciCommandTablePciAddr; | |
Buffer = NULL; | |
// | |
// Collect AHCI controller information | |
// | |
Capability = AhciReadReg (PciIo, EFI_AHCI_CAPABILITY_OFFSET); | |
// | |
// 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 received FIS. | |
// | |
MaxPortNumber = (UINT8)(UINTN)(HighBitSet32 (PortImplementBitMap) + 1); | |
if (MaxPortNumber == 0) { | |
return EFI_DEVICE_ERROR; | |
} | |
MaxReceiveFisSize = MaxPortNumber * sizeof (EFI_AHCI_RECEIVED_FIS); | |
Status = PciIo->AllocateBuffer ( | |
PciIo, | |
AllocateAnyPages, | |
EfiBootServicesData, | |
EFI_SIZE_TO_PAGES ((UINTN)MaxReceiveFisSize), | |
&Buffer, | |
0 | |
); | |
if (EFI_ERROR (Status)) { | |
return EFI_OUT_OF_RESOURCES; | |
} | |
ZeroMem (Buffer, (UINTN)MaxReceiveFisSize); | |
AhciRegisters->AhciRFis = Buffer; | |
AhciRegisters->MaxReceiveFisSize = MaxReceiveFisSize; | |
Bytes = (UINTN)MaxReceiveFisSize; | |
Status = PciIo->Map ( | |
PciIo, | |
EfiPciIoOperationBusMasterCommonBuffer, | |
Buffer, | |
&Bytes, | |
&AhciRFisPciAddr, | |
&AhciRegisters->MapRFis | |
); | |
if (EFI_ERROR (Status) || (Bytes != MaxReceiveFisSize)) { | |
// | |
// Map error or unable to map the whole RFis buffer into a contiguous region. | |
// | |
Status = EFI_OUT_OF_RESOURCES; | |
goto Error6; | |
} | |
if ((!Support64Bit) && (AhciRFisPciAddr > 0x100000000ULL)) { | |
// | |
// The AHCI HBA doesn't support 64bit addressing, so should not get a >4G pci bus master address. | |
// | |
Status = EFI_DEVICE_ERROR; | |
goto Error5; | |
} | |
AhciRegisters->AhciRFisPciAddr = (EFI_AHCI_RECEIVED_FIS *)(UINTN)AhciRFisPciAddr; | |
// | |
// Allocate memory for command list | |
// Note that the implementation is a single task model which only use a command list for all ports. | |
// | |
Buffer = NULL; | |
MaxCommandListSize = MaxCommandSlotNumber * sizeof (EFI_AHCI_COMMAND_LIST); | |
Status = PciIo->AllocateBuffer ( | |
PciIo, | |
AllocateAnyPages, | |
EfiBootServicesData, | |
EFI_SIZE_TO_PAGES ((UINTN)MaxCommandListSize), | |
&Buffer, | |
0 | |
); | |
if (EFI_ERROR (Status)) { | |
// | |
// Free mapped resource. | |
// | |
Status = EFI_OUT_OF_RESOURCES; | |
goto Error5; | |
} | |
ZeroMem (Buffer, (UINTN)MaxCommandListSize); | |
AhciRegisters->AhciCmdList = Buffer; | |
AhciRegisters->MaxCommandListSize = MaxCommandListSize; | |
Bytes = (UINTN)MaxCommandListSize; | |
Status = PciIo->Map ( | |
PciIo, | |
EfiPciIoOperationBusMasterCommonBuffer, | |
Buffer, | |
&Bytes, | |
&AhciCmdListPciAddr, | |
&AhciRegisters->MapCmdList | |
); | |
if (EFI_ERROR (Status) || (Bytes != MaxCommandListSize)) { | |
// | |
// Map error or unable to map the whole cmd list buffer into a contiguous region. | |
// | |
Status = EFI_OUT_OF_RESOURCES; | |
goto Error4; | |
} | |
if ((!Support64Bit) && (AhciCmdListPciAddr > 0x100000000ULL)) { | |
// | |
// The AHCI HBA doesn't support 64bit addressing, so should not get a >4G pci bus master address. | |
// | |
Status = EFI_DEVICE_ERROR; | |
goto Error3; | |
} | |
AhciRegisters->AhciCmdListPciAddr = (EFI_AHCI_COMMAND_LIST *)(UINTN)AhciCmdListPciAddr; | |
// | |
// Allocate memory for command table | |
// According to AHCI 1.3 spec, a PRD table can contain maximum 65535 entries. | |
// | |
Buffer = NULL; | |
MaxCommandTableSize = sizeof (EFI_AHCI_COMMAND_TABLE); | |
Status = PciIo->AllocateBuffer ( | |
PciIo, | |
AllocateAnyPages, | |
EfiBootServicesData, | |
EFI_SIZE_TO_PAGES ((UINTN)MaxCommandTableSize), | |
&Buffer, | |
0 | |
); | |
if (EFI_ERROR (Status)) { | |
// | |
// Free mapped resource. | |
// | |
Status = EFI_OUT_OF_RESOURCES; | |
goto Error3; | |
} | |
ZeroMem (Buffer, (UINTN)MaxCommandTableSize); | |
AhciRegisters->AhciCommandTable = Buffer; | |
AhciRegisters->MaxCommandTableSize = MaxCommandTableSize; | |
Bytes = (UINTN)MaxCommandTableSize; | |
Status = PciIo->Map ( | |
PciIo, | |
EfiPciIoOperationBusMasterCommonBuffer, | |
Buffer, | |
&Bytes, | |
&AhciCommandTablePciAddr, | |
&AhciRegisters->MapCommandTable | |
); | |
if (EFI_ERROR (Status) || (Bytes != MaxCommandTableSize)) { | |
// | |
// Map error or unable to map the whole cmd list buffer into a contiguous region. | |
// | |
Status = EFI_OUT_OF_RESOURCES; | |
goto Error2; | |
} | |
if ((!Support64Bit) && (AhciCommandTablePciAddr > 0x100000000ULL)) { | |
// | |
// The AHCI HBA doesn't support 64bit addressing, so should not get a >4G pci bus master address. | |
// | |
Status = EFI_DEVICE_ERROR; | |
goto Error1; | |
} | |
AhciRegisters->AhciCommandTablePciAddr = (EFI_AHCI_COMMAND_TABLE *)(UINTN)AhciCommandTablePciAddr; | |
return EFI_SUCCESS; | |
// | |
// Map error or unable to map the whole CmdList buffer into a contiguous region. | |
// | |
Error1: | |
PciIo->Unmap ( | |
PciIo, | |
AhciRegisters->MapCommandTable | |
); | |
Error2: | |
PciIo->FreeBuffer ( | |
PciIo, | |
EFI_SIZE_TO_PAGES ((UINTN)MaxCommandTableSize), | |
AhciRegisters->AhciCommandTable | |
); | |
Error3: | |
PciIo->Unmap ( | |
PciIo, | |
AhciRegisters->MapCmdList | |
); | |
Error4: | |
PciIo->FreeBuffer ( | |
PciIo, | |
EFI_SIZE_TO_PAGES ((UINTN)MaxCommandListSize), | |
AhciRegisters->AhciCmdList | |
); | |
Error5: | |
PciIo->Unmap ( | |
PciIo, | |
AhciRegisters->MapRFis | |
); | |
Error6: | |
PciIo->FreeBuffer ( | |
PciIo, | |
EFI_SIZE_TO_PAGES ((UINTN)MaxReceiveFisSize), | |
AhciRegisters->AhciRFis | |
); | |
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 | |
// | |
ZeroMem (&DevSlpTiming, sizeof (DevSlpTiming)); | |
} 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. | |
The function is designed to initialize ATA host controller. | |
@param[in] Instance A pointer to the ATA_ATAPI_PASS_THRU_INSTANCE instance. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
AhciModeInitialization ( | |
IN ATA_ATAPI_PASS_THRU_INSTANCE *Instance | |
) | |
{ | |
EFI_STATUS Status; | |
EFI_PCI_IO_PROTOCOL *PciIo; | |
EFI_IDE_CONTROLLER_INIT_PROTOCOL *IdeInit; | |
UINT32 Capability; | |
UINT8 MaxPortNumber; | |
UINT32 PortImplementBitMap; | |
EFI_AHCI_REGISTERS *AhciRegisters; | |
UINT8 Port; | |
DATA_64 Data64; | |
UINT32 Offset; | |
UINT32 Data; | |
EFI_IDENTIFY_DATA Buffer; | |
EFI_ATA_DEVICE_TYPE DeviceType; | |
EFI_ATA_COLLECTIVE_MODE *SupportedModes; | |
EFI_ATA_TRANSFER_MODE TransferMode; | |
UINT32 PhyDetectDelay; | |
UINT32 Value; | |
if (Instance == NULL) { | |
return EFI_INVALID_PARAMETER; | |
} | |
PciIo = Instance->PciIo; | |
IdeInit = Instance->IdeControllerInit; | |
Status = AhciReset (PciIo, EFI_AHCI_BUS_RESET_TIMEOUT); | |
if (EFI_ERROR (Status)) { | |
return EFI_DEVICE_ERROR; | |
} | |
// | |
// Collect AHCI controller information | |
// | |
Capability = AhciReadReg (PciIo, EFI_AHCI_CAPABILITY_OFFSET); | |
// | |
// 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); | |
} | |
// | |
// 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 (( | |
DEBUG_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. | |
// | |
MaxPortNumber = (UINT8)((Capability & 0x1F) + 1); | |
// | |
// Get the bit map of those ports exposed by this HBA. | |
// It indicates which ports that the HBA supports are available for software to use. | |
// | |
PortImplementBitMap = AhciReadReg (PciIo, EFI_AHCI_PI_OFFSET); | |
AhciRegisters = &Instance->AhciRegisters; | |
Status = AhciCreateTransferDescriptor (PciIo, AhciRegisters); | |
if (EFI_ERROR (Status)) { | |
return EFI_OUT_OF_RESOURCES; | |
} | |
for (Port = 0; Port < EFI_AHCI_MAX_PORTS; Port++) { | |
if ((PortImplementBitMap & (((UINT32)BIT0) << Port)) != 0) { | |
// | |
// According to AHCI spec, MaxPortNumber should be equal or greater than the number of implemented ports. | |
// | |
if ((MaxPortNumber--) == 0) { | |
// | |
// Should never be here. | |
// | |
ASSERT (FALSE); | |
return EFI_SUCCESS; | |
} | |
IdeInit->NotifyPhase (IdeInit, EfiIdeBeforeChannelEnumeration, Port); | |
// | |
// Initialize FIS Base Address Register and Command List Base Address Register for use. | |
// | |
Data64.Uint64 = (UINTN)(AhciRegisters->AhciRFisPciAddr) + sizeof (EFI_AHCI_RECEIVED_FIS) * Port; | |
Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_FB; | |
AhciWriteReg (PciIo, Offset, Data64.Uint32.Lower32); | |
Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_FBU; | |
AhciWriteReg (PciIo, Offset, Data64.Uint32.Upper32); | |
Data64.Uint64 = (UINTN)(AhciRegisters->AhciCmdListPciAddr); | |
Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_CLB; | |
AhciWriteReg (PciIo, Offset, Data64.Uint32.Lower32); | |
Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_CLBU; | |
AhciWriteReg (PciIo, Offset, Data64.Uint32.Upper32); | |
Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_CMD; | |
Data = AhciReadReg (PciIo, Offset); | |
if ((Data & EFI_AHCI_PORT_CMD_CPD) != 0) { | |
AhciOrReg (PciIo, Offset, EFI_AHCI_PORT_CMD_POD); | |
} | |
if ((Capability & EFI_AHCI_CAP_SSS) != 0) { | |
AhciOrReg (PciIo, Offset, EFI_AHCI_PORT_CMD_SUD); | |
} | |
// | |
// Disable aggressive power management. | |
// | |
Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_SCTL; | |
AhciOrReg (PciIo, Offset, EFI_AHCI_PORT_SCTL_IPM_INIT); | |
// | |
// Disable the reporting of the corresponding interrupt to system software. | |
// | |
Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_IE; | |
AhciAndReg (PciIo, Offset, 0); | |
// | |
// Now inform the IDE Controller Init Module. | |
// | |
IdeInit->NotifyPhase (IdeInit, EfiIdeBusBeforeDevicePresenceDetection, Port); | |
// | |
// Enable FIS Receive DMA engine for the first D2H FIS. | |
// | |
Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_CMD; | |
AhciOrReg (PciIo, Offset, EFI_AHCI_PORT_CMD_FRE); | |
// | |
// 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; | |
do { | |
Data = AhciReadReg (PciIo, Offset) & EFI_AHCI_PORT_SSTS_DET_MASK; | |
if ((Data == EFI_AHCI_PORT_SSTS_DET_PCE) || (Data == EFI_AHCI_PORT_SSTS_DET)) { | |
break; | |
} | |
MicroSecondDelay (1000); | |
PhyDetectDelay--; | |
} while (PhyDetectDelay > 0); | |
if (PhyDetectDelay == 0) { | |
// | |
// No device detected at this port. | |
// Clear PxCMD.SUD for those ports at which there are no device present. | |
// | |
Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_CMD; | |
AhciAndReg (PciIo, Offset, (UINT32) ~(EFI_AHCI_PORT_CMD_SUD)); | |
continue; | |
} | |
Status = AhciWaitDeviceReady (PciIo, Port); | |
if (EFI_ERROR (Status)) { | |
continue; | |
} | |
// | |
// When the first D2H register FIS is received, the content of PxSIG register is updated. | |
// | |
Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_SIG; | |
Status = AhciWaitMmioSet ( | |
PciIo, | |
Offset, | |
0x0000FFFF, | |
0x00000101, | |
EFI_TIMER_PERIOD_SECONDS (16) | |
); | |
if (EFI_ERROR (Status)) { | |
continue; | |
} | |
Data = AhciReadReg (PciIo, Offset); | |
if ((Data & EFI_AHCI_ATAPI_SIG_MASK) == EFI_AHCI_ATAPI_DEVICE_SIG) { | |
Status = AhciIdentifyPacket (PciIo, AhciRegisters, Port, 0, &Buffer); | |
if (EFI_ERROR (Status)) { | |
continue; | |
} | |
DeviceType = EfiIdeCdrom; | |
} else if ((Data & EFI_AHCI_ATAPI_SIG_MASK) == EFI_AHCI_ATA_DEVICE_SIG) { | |
Status = AhciIdentify (PciIo, AhciRegisters, Port, 0, &Buffer); | |
if (EFI_ERROR (Status)) { | |
REPORT_STATUS_CODE (EFI_PROGRESS_CODE, (EFI_PERIPHERAL_FIXED_MEDIA | EFI_P_EC_NOT_DETECTED)); | |
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; | |
} | |
DEBUG (( | |
DEBUG_INFO, | |
"port [%d] port multitplier [%d] has a [%a]\n", | |
Port, | |
0, | |
DeviceType == EfiIdeCdrom ? "cdrom" : "harddisk" | |
)); | |
// | |
// If the device is a hard disk, then try to enable S.M.A.R.T feature | |
// | |
if ((DeviceType == EfiIdeHarddisk) && PcdGetBool (PcdAtaSmartEnable)) { | |
AhciAtaSmartSupport ( | |
PciIo, | |
AhciRegisters, | |
Port, | |
0, | |
&Buffer, | |
NULL | |
); | |
} | |
// | |
// Submit identify data to IDE controller init driver | |
// | |
IdeInit->SubmitData (IdeInit, Port, 0, &Buffer); | |
// | |
// Now start to config ide device parameter and transfer mode. | |
// | |
Status = IdeInit->CalculateMode ( | |
IdeInit, | |
Port, | |
0, | |
&SupportedModes | |
); | |
if (EFI_ERROR (Status)) { | |
DEBUG ((DEBUG_ERROR, "Calculate Mode Fail, Status = %r\n", Status)); | |
continue; | |
} | |
// | |
// Set best supported PIO mode on this IDE device | |
// | |
if (SupportedModes->PioMode.Mode <= EfiAtaPioMode2) { | |
TransferMode.ModeCategory = EFI_ATA_MODE_DEFAULT_PIO; | |
} else { | |
TransferMode.ModeCategory = EFI_ATA_MODE_FLOW_PIO; | |
} | |
TransferMode.ModeNumber = (UINT8)(SupportedModes->PioMode.Mode); | |
// | |
// Set supported DMA mode on this IDE device. Note that UDMA & MDMA can't | |
// be set together. Only one DMA mode can be set to a device. If setting | |
// DMA mode operation fails, we can continue moving on because we only use | |
// PIO mode at boot time. DMA modes are used by certain kind of OS booting | |
// | |
if (SupportedModes->UdmaMode.Valid) { | |
TransferMode.ModeCategory = EFI_ATA_MODE_UDMA; | |
TransferMode.ModeNumber = (UINT8)(SupportedModes->UdmaMode.Mode); | |
} else if (SupportedModes->MultiWordDmaMode.Valid) { | |
TransferMode.ModeCategory = EFI_ATA_MODE_MDMA; | |
TransferMode.ModeNumber = (UINT8)SupportedModes->MultiWordDmaMode.Mode; | |
} | |
Status = AhciDeviceSetFeature (PciIo, AhciRegisters, Port, 0, 0x03, (UINT32)(*(UINT8 *)&TransferMode), ATA_ATAPI_TIMEOUT); | |
if (EFI_ERROR (Status)) { | |
DEBUG ((DEBUG_ERROR, "Set transfer Mode Fail, Status = %r\n", Status)); | |
continue; | |
} | |
// | |
// Found a ATA or ATAPI device, add it into the device list. | |
// | |
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; | |
} | |
} | |
} | |
} | |
return EFI_SUCCESS; | |
} |