/** @file | |
The file for AHCI mode of ATA host controller. | |
Copyright (c) 2010 - 2016, Intel Corporation. All rights reserved.<BR> | |
(C) Copyright 2015 Hewlett Packard Enterprise Development LP<BR> | |
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 | |
http://opensource.org/licenses/bsd-license.php | |
THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, | |
WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. | |
**/ | |
#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 sytem 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. | |
@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. | |
@retval EFI_SUCCESS The memory is correct set. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
AhciCheckMemSet ( | |
IN UINTN Address, | |
IN UINT32 MaskValue, | |
IN UINT32 TestValue, | |
IN OUT ATA_NONBLOCK_TASK *Task | |
) | |
{ | |
UINT32 Value; | |
if (Task != NULL) { | |
Task->RetryTimes--; | |
} | |
Value = *(volatile UINT32 *) Address; | |
Value &= MaskValue; | |
if (Value == TestValue) { | |
return EFI_SUCCESS; | |
} | |
if ((Task != NULL) && !Task->InfiniteWait && (Task->RetryTimes == 0)) { | |
return EFI_TIMEOUT; | |
} else { | |
return EFI_NOT_READY; | |
} | |
} | |
/** | |
Check if the device is still on port. It also checks if the AHCI controller | |
supports the address and data count will be transferred. | |
@param PciIo The PCI IO protocol instance. | |
@param Port The number of port. | |
@retval EFI_SUCCESS The device is attached to port and the transfer data is | |
supported by AHCI controller. | |
@retval EFI_UNSUPPORTED The transfer address and count is not supported by AHCI | |
controller. | |
@retval EFI_NOT_READY The physical communication between AHCI controller and device | |
is not ready. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
AhciCheckDeviceStatus ( | |
IN EFI_PCI_IO_PROTOCOL *PciIo, | |
IN UINT8 Port | |
) | |
{ | |
UINT32 Data; | |
UINT32 Offset; | |
Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_SSTS; | |
Data = AhciReadReg (PciIo, Offset) & EFI_AHCI_PORT_SSTS_DET_MASK; | |
if (Data == EFI_AHCI_PORT_SSTS_DET_PCE) { | |
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, 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); | |
} | |
} | |
} | |
} | |
/** | |
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; | |
} | |
/** | |
Buid 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); | |
} | |
/** | |
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; | |
UINTN FisBaseAddr; | |
UINTN Offset; | |
EFI_PHYSICAL_ADDRESS PhyAddr; | |
VOID *Map; | |
UINTN MapLength; | |
EFI_PCI_IO_PROTOCOL_OPERATION Flag; | |
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; | |
} 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; | |
AhciBuildCommand ( | |
PciIo, | |
AhciRegisters, | |
Port, | |
PortMultiplier, | |
&CFis, | |
&CmdList, | |
AtapiCommand, | |
AtapiCommandLength, | |
0, | |
(VOID *)(UINTN)PhyAddr, | |
DataCount | |
); | |
Status = AhciStartCommand ( | |
PciIo, | |
Port, | |
0, | |
Timeout | |
); | |
if (EFI_ERROR (Status)) { | |
goto Exit; | |
} | |
// | |
// Check the status and wait the driver sending data | |
// | |
FisBaseAddr = (UINTN)AhciRegisters->AhciRFis + Port * sizeof (EFI_AHCI_RECEIVED_FIS); | |
if (Read && (AtapiCommand == 0)) { | |
// | |
// Wait device sends the PIO setup fis before data transfer | |
// | |
Status = EFI_TIMEOUT; | |
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, 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. | |
// | |
if ((PortTfd & EFI_AHCI_PORT_TFD_ERR) != 0) { | |
Status = EFI_DEVICE_ERROR; | |
break; | |
} | |
PrdCount = *(volatile UINT32 *) (&(AhciRegisters->AhciCmdList[0].AhciCmdPrdbc)); | |
if (PrdCount == DataCount) { | |
Status = EFI_SUCCESS; | |
break; | |
} | |
} | |
// | |
// Stall for 100 microseconds. | |
// | |
MicroSecondDelay(100); | |
Delay--; | |
if (Delay == 0) { | |
Status = EFI_TIMEOUT; | |
} | |
} while (InfiniteWait || (Delay > 0)); | |
} else { | |
// | |
// Wait for D2H Fis is received | |
// | |
Offset = FisBaseAddr + EFI_AHCI_D2H_FIS_OFFSET; | |
Status = AhciWaitMemSet ( | |
Offset, | |
EFI_AHCI_FIS_TYPE_MASK, | |
EFI_AHCI_FIS_REGISTER_D2H, | |
Timeout | |
); | |
if (EFI_ERROR (Status)) { | |
goto Exit; | |
} | |
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) { | |
Status = EFI_DEVICE_ERROR; | |
} | |
} | |
Exit: | |
AhciStopCommand ( | |
PciIo, | |
Port, | |
Timeout | |
); | |
AhciDisableFisReceive ( | |
PciIo, | |
Port, | |
Timeout | |
); | |
PciIo->Unmap ( | |
PciIo, | |
Map | |
); | |
AhciDumpPortStatus (PciIo, AhciRegisters, Port, AtaStatusBlock); | |
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; | |
UINTN Offset; | |
EFI_PHYSICAL_ADDRESS PhyAddr; | |
VOID *Map; | |
UINTN MapLength; | |
EFI_PCI_IO_PROTOCOL_OPERATION Flag; | |
EFI_AHCI_COMMAND_FIS CFis; | |
EFI_AHCI_COMMAND_LIST CmdList; | |
UINTN FisBaseAddr; | |
UINT32 PortTfd; | |
EFI_PCI_IO_PROTOCOL *PciIo; | |
EFI_TPL OldTpl; | |
Map = NULL; | |
PciIo = Instance->PciIo; | |
if (PciIo == NULL) { | |
return EFI_INVALID_PARAMETER; | |
} | |
// | |
// 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 ((Task == NULL) && (!IsListEmpty (&Instance->NonBlockingTaskList))) { | |
AsyncNonBlockingTransferRoutine (NULL, Instance); | |
// | |
// Stall for 100us. | |
// | |
MicroSecondDelay (100); | |
} | |
gBS->RestoreTPL (OldTpl); | |
if ((Task == NULL) || ((Task != NULL) && (!Task->IsStart))) { | |
// | |
// Mark the Task to indicate that it has been started. | |
// | |
if (Task != NULL) { | |
Task->IsStart = TRUE; | |
} | |
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; | |
} | |
if (Task != NULL) { | |
Task->Map = Map; | |
} | |
// | |
// 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; | |
AhciBuildCommand ( | |
PciIo, | |
AhciRegisters, | |
Port, | |
PortMultiplier, | |
&CFis, | |
&CmdList, | |
AtapiCommand, | |
AtapiCommandLength, | |
0, | |
(VOID *)(UINTN)PhyAddr, | |
DataCount | |
); | |
Status = AhciStartCommand ( | |
PciIo, | |
Port, | |
0, | |
Timeout | |
); | |
if (EFI_ERROR (Status)) { | |
goto Exit; | |
} | |
} | |
// | |
// Wait for command compelte | |
// | |
FisBaseAddr = (UINTN)AhciRegisters->AhciRFis + Port * sizeof (EFI_AHCI_RECEIVED_FIS); | |
Offset = FisBaseAddr + EFI_AHCI_D2H_FIS_OFFSET; | |
if (Task != NULL) { | |
// | |
// For Non-blocking | |
// | |
Status = AhciCheckMemSet ( | |
Offset, | |
EFI_AHCI_FIS_TYPE_MASK, | |
EFI_AHCI_FIS_REGISTER_D2H, | |
Task | |
); | |
} else { | |
Status = AhciWaitMemSet ( | |
Offset, | |
EFI_AHCI_FIS_TYPE_MASK, | |
EFI_AHCI_FIS_REGISTER_D2H, | |
Timeout | |
); | |
} | |
if (EFI_ERROR (Status)) { | |
goto Exit; | |
} | |
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) { | |
Status = EFI_DEVICE_ERROR; | |
} | |
Exit: | |
// | |
// 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); | |
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; | |
UINTN FisBaseAddr; | |
UINTN Offset; | |
UINT32 PortTfd; | |
EFI_AHCI_COMMAND_FIS CFis; | |
EFI_AHCI_COMMAND_LIST CmdList; | |
// | |
// Package read needed | |
// | |
AhciBuildCommandFis (&CFis, AtaCommandBlock); | |
ZeroMem (&CmdList, sizeof (EFI_AHCI_COMMAND_LIST)); | |
CmdList.AhciCmdCfl = EFI_AHCI_FIS_REGISTER_H2D_LENGTH / 4; | |
AhciBuildCommand ( | |
PciIo, | |
AhciRegisters, | |
Port, | |
PortMultiplier, | |
&CFis, | |
&CmdList, | |
AtapiCommand, | |
AtapiCommandLength, | |
0, | |
NULL, | |
0 | |
); | |
Status = AhciStartCommand ( | |
PciIo, | |
Port, | |
0, | |
Timeout | |
); | |
if (EFI_ERROR (Status)) { | |
goto Exit; | |
} | |
// | |
// Wait device sends the Response Fis | |
// | |
FisBaseAddr = (UINTN)AhciRegisters->AhciRFis + Port * sizeof (EFI_AHCI_RECEIVED_FIS); | |
Offset = FisBaseAddr + EFI_AHCI_D2H_FIS_OFFSET; | |
Status = AhciWaitMemSet ( | |
Offset, | |
EFI_AHCI_FIS_TYPE_MASK, | |
EFI_AHCI_FIS_REGISTER_D2H, | |
Timeout | |
); | |
if (EFI_ERROR (Status)) { | |
goto Exit; | |
} | |
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) { | |
Status = EFI_DEVICE_ERROR; | |
} | |
Exit: | |
AhciStopCommand ( | |
PciIo, | |
Port, | |
Timeout | |
); | |
AhciDisableFisReceive ( | |
PciIo, | |
Port, | |
Timeout | |
); | |
AhciDumpPortStatus (PciIo, AhciRegisters, Port, AtaStatusBlock); | |
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 port reset. | |
@param PciIo The PCI IO protocol instance. | |
@param Port The number of port. | |
@param Timeout The timeout value of reset, uses 100ns as a unit. | |
@retval EFI_DEVICE_ERROR The port reset unsuccessfully | |
@retval EFI_TIMEOUT The reset operation is time out. | |
@retval EFI_SUCCESS The port reset successfully. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
AhciPortReset ( | |
IN EFI_PCI_IO_PROTOCOL *PciIo, | |
IN UINT8 Port, | |
IN UINT64 Timeout | |
) | |
{ | |
EFI_STATUS Status; | |
UINT32 Offset; | |
AhciClearPortStatus (PciIo, Port); | |
AhciStopCommand (PciIo, Port, Timeout); | |
AhciDisableFisReceive (PciIo, Port, Timeout); | |
AhciEnableFisReceive (PciIo, Port, Timeout); | |
Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_SCTL; | |
AhciOrReg (PciIo, Offset, EFI_AHCI_PORT_SCTL_DET_INIT); | |
// | |
// wait 5 millisecond before de-assert DET | |
// | |
MicroSecondDelay (5000); | |
AhciAndReg (PciIo, Offset, (UINT32)EFI_AHCI_PORT_SCTL_MASK); | |
// | |
// wait 5 millisecond before de-assert DET | |
// | |
MicroSecondDelay (5000); | |
// | |
// Wait for communication to be re-established | |
// | |
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, | |
Timeout | |
); | |
if (EFI_ERROR (Status)) { | |
DEBUG ((EFI_D_ERROR, "Port %d COMRESET failed: %r\n", Port, Status)); | |
return Status; | |
} | |
Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_SERR; | |
AhciOrReg (PciIo, Offset, EFI_AHCI_PORT_ERR_CLEAR); | |
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; | |
UINT32 Capability; | |
// | |
// Collect AHCI controller information | |
// | |
Capability = AhciReadReg (PciIo, EFI_AHCI_CAPABILITY_OFFSET); | |
// | |
// Enable AE before accessing any AHCI registers if Supports AHCI Mode Only is not set | |
// | |
if ((Capability & EFI_AHCI_CAP_SAM) == 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 ((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) | |
); | |
} | |
} | |
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 ((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; | |
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 | |
); | |
if (!EFI_ERROR (Status)) { | |
Status = AhciAtaSmartReturnStatusCheck ( | |
PciIo, | |
AhciRegisters, | |
(UINT8)Port, | |
(UINT8)PortMultiplier, | |
AtaStatusBlock | |
); | |
} | |
} | |
} | |
DEBUG ((EFI_D_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. | |
@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 | |
) | |
{ | |
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, | |
ATA_ATAPI_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 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 ( | |
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 implemenation 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; | |
} | |
/** | |
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; | |
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); | |
// | |
// Enable AE before accessing any AHCI registers if Supports AHCI Mode Only is not set | |
// | |
if ((Capability & EFI_AHCI_CAP_SAM) == 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. | |
// | |
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 & (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 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. | |
// | |
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; | |
} | |
// | |
// 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 ((EFI_D_ERROR, "Port %d Device presence detected but phy not ready (TFD=0x%X)\n", Port, Data)); | |
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; | |
} | |
DeviceType = EfiIdeHarddisk; | |
} else { | |
continue; | |
} | |
DEBUG ((EFI_D_INFO, "port [%d] port mulitplier [%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 ((EFI_D_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 cann'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)); | |
if (EFI_ERROR (Status)) { | |
DEBUG ((EFI_D_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)); | |
} | |
} | |
} | |
return EFI_SUCCESS; | |
} | |