blob: 533d2013894fdb850cd00046513978c243c6f6ae [file] [log] [blame]
/** @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;
}