audk/SecurityPkg/Tcg/Opal/OpalPasswordSmm/OpalAhciMode.c

1268 lines
36 KiB
C

/** @file
This driver is used for Opal Password Feature support at AHCI mode.
Copyright (c) 2016, Intel Corporation. All rights reserved.<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 "OpalPasswordSmm.h"
/**
Start command for give slot on specific port.
@param Port The number of port.
@param CommandSlot The number of CommandSlot.
@param Timeout The timeout Value of start.
@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 UINT8 Port,
IN UINT8 CommandSlot,
IN UINT64 Timeout
);
/**
Stop command running for giving port
@param Port The number of port.
@param Timeout The timeout Value of stop.
@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 UINT8 Port,
IN UINT64 Timeout
);
/**
Read AHCI Operation register.
@param Offset The operation register offset.
@return The register content read.
**/
UINT32
EFIAPI
AhciReadReg (
IN UINT32 Offset
)
{
UINT32 Data;
Data = 0;
Data = MmioRead32 (mAhciBar + Offset);
return Data;
}
/**
Write AHCI Operation register.
@param Offset The operation register offset.
@param Data The Data used to write down.
**/
VOID
EFIAPI
AhciWriteReg (
IN UINT32 Offset,
IN UINT32 Data
)
{
MmioWrite32 (mAhciBar + Offset, Data);
return ;
}
/**
Do AND operation with the Value of AHCI Operation register.
@param Offset The operation register offset.
@param AndData The Data used to do AND operation.
**/
VOID
EFIAPI
AhciAndReg (
IN UINT32 Offset,
IN UINT32 AndData
)
{
UINT32 Data;
Data = AhciReadReg (Offset);
Data &= AndData;
AhciWriteReg (Offset, Data);
}
/**
Do OR operation with the Value of AHCI Operation register.
@param Offset The operation register offset.
@param OrData The Data used to do OR operation.
**/
VOID
EFIAPI
AhciOrReg (
IN UINT32 Offset,
IN UINT32 OrData
)
{
UINT32 Data;
Data = AhciReadReg (Offset);
Data |= OrData;
AhciWriteReg (Offset, Data);
}
/**
Wait for memory set to the test Value.
@param Offset The 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.
@retval EFI_DEVICE_ERROR The memory is not set.
@retval EFI_TIMEOUT The memory setting is time out.
@retval EFI_SUCCESS The memory is correct set.
**/
EFI_STATUS
EFIAPI
AhciWaitMmioSet (
IN UINT32 Offset,
IN UINT32 MaskValue,
IN UINT32 TestValue,
IN UINT64 Timeout
)
{
UINT32 Value;
UINT32 Delay;
Delay = (UINT32) (DivU64x32(Timeout, 1000) + 1);
do {
Value = AhciReadReg (Offset) & MaskValue;
if (Value == TestValue) {
return EFI_SUCCESS;
}
//
// Stall for 100 microseconds.
//
MicroSecondDelay (100);
Delay--;
} while (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;
UINT32 Delay;
Delay = (UINT32) (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 (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] RetryTimes The retry times Value for waitting memory set. If 0, 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 UINTN *RetryTimes OPTIONAL
)
{
UINT32 Value;
if (RetryTimes != NULL) {
(*RetryTimes)--;
}
Value = *(volatile UINT32 *) Address;
Value &= MaskValue;
if (Value == TestValue) {
return EFI_SUCCESS;
}
if ((RetryTimes != NULL) && (*RetryTimes == 0)) {
return EFI_TIMEOUT;
} else {
return EFI_NOT_READY;
}
}
/**
Clear the port interrupt and error status. It will also clear
HBA interrupt status.
@param Port The number of port.
**/
VOID
EFIAPI
AhciClearPortStatus (
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 (Offset, AhciReadReg (Offset));
//
// Clear any port interrupt status
//
Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_IS;
AhciWriteReg (Offset, AhciReadReg (Offset));
//
// Clear any HBA interrupt status
//
AhciWriteReg (EFI_AHCI_IS_OFFSET, AhciReadReg (EFI_AHCI_IS_OFFSET));
}
/**
Enable the FIS running for giving port.
@param Port The number of port.
@param Timeout The timeout Value of enabling FIS.
@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 UINT8 Port,
IN UINT64 Timeout
)
{
UINT32 Offset;
Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_CMD;
AhciOrReg (Offset, EFI_AHCI_PORT_CMD_FRE);
return AhciWaitMmioSet (
Offset,
EFI_AHCI_PORT_CMD_FR,
EFI_AHCI_PORT_CMD_FR,
Timeout
);
}
/**
Disable the FIS running for giving port.
@param Port The number of port.
@param Timeout The timeout Value of disabling FIS.
@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 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 (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 (Offset, (UINT32)~(EFI_AHCI_PORT_CMD_FRE));
return AhciWaitMmioSet (
Offset,
EFI_AHCI_PORT_CMD_FR,
0,
Timeout
);
}
/**
Build the command list, command table and prepare the fis receiver.
@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_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 UINT64 DataLength
)
{
UINT64 BaseAddr;
UINT64 PrdtNumber;
UINTN RemainedData;
UINTN MemAddr;
DATA_64 Data64;
UINT32 Offset;
//
// Filling the PRDT
//
PrdtNumber = DivU64x32 (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 <= 1);
Data64.Uint64 = (UINTN) (AhciRegisters->AhciRFis);
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 (Offset, (EFI_AHCI_PORT_CMD_DLAE | EFI_AHCI_PORT_CMD_ATAPI));
} else {
AhciAndReg (Offset, (UINT32)~(EFI_AHCI_PORT_CMD_DLAE | EFI_AHCI_PORT_CMD_ATAPI));
}
RemainedData = (UINTN) DataLength;
MemAddr = (UINTN) DataPhysicalAddr;
CommandList->AhciCmdPrdtl = (UINT32)PrdtNumber;
AhciRegisters->AhciCommandTable->PrdtTable.AhciPrdtDbc = (UINT32)RemainedData - 1;
Data64.Uint64 = (UINT64)MemAddr;
AhciRegisters->AhciCommandTable->PrdtTable.AhciPrdtDba = Data64.Uint32.Lower32;
AhciRegisters->AhciCommandTable->PrdtTable.AhciPrdtDbau = Data64.Uint32.Upper32;
//
// Set the last PRDT to Interrupt On Complete
//
AhciRegisters->AhciCommandTable->PrdtTable.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->AhciCommandTable;
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 AhciRegisters The pointer to the EFI_AHCI_REGISTERS.
@param Port The number of port.
@param PortMultiplier The timeout Value of stop.
@param AtapiCommand The atapi command will be used for the transfer.
@param AtapiCommandLength The Length of the atapi command.
@param Read The transfer direction.
@param AtaCommandBlock The EFI_ATA_COMMAND_BLOCK Data.
@param AtaStatusBlock The EFI_ATA_STATUS_BLOCK Data.
@param MemoryAddr The pointer to the Data Buffer.
@param DataCount The Data count to be transferred.
@param Timeout The timeout Value of non Data transfer.
@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_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
)
{
EFI_STATUS Status;
UINT32 FisBaseAddr;
UINT32 Offset;
UINT32 Delay;
EFI_AHCI_COMMAND_FIS CFis;
EFI_AHCI_COMMAND_LIST CmdList;
UINT32 PortTfd;
UINT32 PrdCount;
UINT32 OldRfisLo;
UINT32 OldRfisHi;
UINT32 OldCmdListLo;
UINT32 OldCmdListHi;
Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_FB;
OldRfisLo = AhciReadReg (Offset);
Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_FBU;
OldRfisHi = AhciReadReg (Offset);
Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_FB;
AhciWriteReg (Offset, (UINT32)(UINTN)AhciRegisters->AhciRFis);
Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_FBU;
AhciWriteReg (Offset, 0);
//
// Single task envrionment, we only use one command table for all port
//
Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_CLB;
OldCmdListLo = AhciReadReg (Offset);
Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_CLBU;
OldCmdListHi = AhciReadReg (Offset);
Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_CLB;
AhciWriteReg (Offset, (UINT32)(UINTN)AhciRegisters->AhciCmdList);
Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_CLBU;
AhciWriteReg (Offset, 0);
//
// 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 (
AhciRegisters,
Port,
PortMultiplier,
&CFis,
&CmdList,
AtapiCommand,
AtapiCommandLength,
0,
(VOID *)(UINTN)MemoryAddr,
DataCount
);
Status = AhciStartCommand (
Port,
0,
Timeout
);
if (EFI_ERROR (Status)) {
goto Exit;
}
//
// Checking the status and wait the driver sending Data
//
FisBaseAddr = (UINT32)(UINTN)AhciRegisters->AhciRFis;
if (Read && (AtapiCommand == 0)) {
//
// Wait device sends the PIO setup fis before Data transfer
//
Status = EFI_TIMEOUT;
Delay = (UINT32) (DivU64x32 (Timeout, 1000) + 1);
do {
Offset = FisBaseAddr + EFI_AHCI_PIO_FIS_OFFSET;
Status = AhciCheckMemSet (Offset, EFI_AHCI_FIS_TYPE_MASK, EFI_AHCI_FIS_PIO_SETUP, 0);
if (!EFI_ERROR (Status)) {
Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_TFD;
PortTfd = AhciReadReg ((UINT32) Offset);
//
// PxTFD will be updated if there is a D2H or SetupFIS received.
// For PIO IN transfer, D2H means a device error. Therefore we only need to check the TFD after receiving a SetupFIS.
//
if ((PortTfd & EFI_AHCI_PORT_TFD_ERR) != 0) {
Status = EFI_DEVICE_ERROR;
break;
}
PrdCount = *(volatile UINT32 *) (&(AhciRegisters->AhciCmdList[0].AhciCmdPrdbc));
if (PrdCount == DataCount) {
break;
}
}
Offset = FisBaseAddr + EFI_AHCI_D2H_FIS_OFFSET;
Status = AhciCheckMemSet (Offset, EFI_AHCI_FIS_TYPE_MASK, EFI_AHCI_FIS_REGISTER_D2H, 0);
if (!EFI_ERROR (Status)) {
Status = EFI_DEVICE_ERROR;
break;
}
//
// Stall for 100 microseconds.
//
MicroSecondDelay(100);
Delay--;
} while (Delay > 0);
} 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 ((UINT32) Offset);
if ((PortTfd & EFI_AHCI_PORT_TFD_ERR) != 0) {
Status = EFI_DEVICE_ERROR;
}
}
Exit:
AhciStopCommand (
Port,
Timeout
);
AhciDisableFisReceive (
Port,
Timeout
);
Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_FB;
AhciWriteReg (Offset, OldRfisLo);
Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_FBU;
AhciWriteReg (Offset, OldRfisHi);
Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_CLB;
AhciWriteReg (Offset, OldCmdListLo);
Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_CLBU;
AhciWriteReg (Offset, OldCmdListHi);
return Status;
}
/**
Stop command running for giving port
@param Port The number of port.
@param Timeout The timeout Value of stop.
@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 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 (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 (Offset, (UINT32)~(EFI_AHCI_PORT_CMD_ST));
}
return AhciWaitMmioSet (
Offset,
EFI_AHCI_PORT_CMD_CR,
0,
Timeout
);
}
/**
Start command for give slot on specific port.
@param Port The number of port.
@param CommandSlot The number of CommandSlot.
@param Timeout The timeout Value of start.
@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 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(EFI_AHCI_CAPABILITY_OFFSET);
CmdSlotBit = (UINT32) (1 << CommandSlot);
AhciClearPortStatus (
Port
);
Status = AhciEnableFisReceive (
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 (Offset);
StartCmd = 0;
if ((PortStatus & EFI_AHCI_PORT_CMD_ALPE) != 0) {
StartCmd = AhciReadReg (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 (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 (Offset, EFI_AHCI_PORT_CMD_COL);
AhciWaitMmioSet (
Offset,
EFI_AHCI_PORT_CMD_COL,
0,
Timeout
);
}
}
Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_CMD;
AhciOrReg (Offset, EFI_AHCI_PORT_CMD_ST | StartCmd);
//
// Setting the command
//
Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_SACT;
AhciAndReg (Offset, 0);
AhciOrReg (Offset, CmdSlotBit);
Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_CI;
AhciAndReg (Offset, 0);
AhciOrReg (Offset, CmdSlotBit);
return EFI_SUCCESS;
}
/**
Do AHCI HBA reset.
@param[in] Timeout The timeout Value of reset.
@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 UINT64 Timeout
)
{
UINT32 Delay;
UINT32 Value;
UINT32 Capability;
//
// Collect AHCI controller information
//
Capability = AhciReadReg (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 (EFI_AHCI_GHC_OFFSET, EFI_AHCI_GHC_ENABLE);
}
AhciOrReg (EFI_AHCI_GHC_OFFSET, EFI_AHCI_GHC_RESET);
Delay = (UINT32) (DivU64x32(Timeout, 1000) + 1);
do {
Value = AhciReadReg(EFI_AHCI_GHC_OFFSET);
if ((Value & EFI_AHCI_GHC_RESET) == 0) {
return EFI_SUCCESS;
}
//
// Stall for 100 microseconds.
//
MicroSecondDelay(100);
Delay--;
} while (Delay > 0);
return EFI_TIMEOUT;
}
/**
Send Buffer cmd to specific device.
@param[in] AhciRegisters The pointer to the EFI_AHCI_REGISTERS.
@param[in] Port The port number of attached ATA device.
@param[in] PortMultiplier The port number of port multiplier of attached ATA device.
@param[in, out] 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_AHCI_REGISTERS *AhciRegisters,
IN UINT8 Port,
IN UINT8 PortMultiplier,
IN OUT ATA_IDENTIFY_DATA *Buffer
)
{
EFI_STATUS Status;
EFI_ATA_COMMAND_BLOCK AtaCommandBlock;
if (AhciRegisters == NULL || Buffer == NULL) {
return EFI_INVALID_PARAMETER;
}
ZeroMem (&AtaCommandBlock, sizeof (EFI_ATA_COMMAND_BLOCK));
AtaCommandBlock.AtaCommand = ATA_CMD_IDENTIFY_DRIVE;
AtaCommandBlock.AtaSectorCount = 1;
Status = AhciPioTransfer (
AhciRegisters,
Port,
PortMultiplier,
NULL,
0,
TRUE,
&AtaCommandBlock,
NULL,
Buffer,
sizeof (ATA_IDENTIFY_DATA),
ATA_TIMEOUT
);
return Status;
}
/**
Get AHCI mode MMIO Bar Size.
@param[in] Bus The bus number of ata host controller.
@param[in] Device The device number of ata host controller.
@param[in] Function The function number of ata host controller.
@retval The Size of AHCI MMIO BAR.
**/
UINT32
EFIAPI
GetAhciBarSize (
IN UINTN Bus,
IN UINTN Device,
IN UINTN Function
)
{
UINT32 Size;
UINT32 OldBar;
OldBar = PciRead32 (PCI_LIB_ADDRESS (Bus, Device, Function, 0x24));
//
// Disable PCI CMD.MSE bit before calculating MMIO Bar Size as it needs write all 1 to BAR register.
//
PciAnd32 (PCI_LIB_ADDRESS (Bus, Device, Function, 0x04), (UINT32)~BIT1);
//
// Get AHCI MMIO Bar Size.
//
PciWrite32 (PCI_LIB_ADDRESS (Bus, Device, Function, 0x24), 0xFFFFFFFF);
Size = PciRead32 (PCI_LIB_ADDRESS (Bus, Device, Function, 0x24));
Size = (~(Size & 0xFFFFFFF0)) + 1;
//
// Restore old MMIO Bar.
//
PciWrite32 (PCI_LIB_ADDRESS (Bus, Device, Function, 0x24), OldBar);
//
// Enable PCI CMD.MSE bit after restoring MMIO Bar.
//
PciOr32 (PCI_LIB_ADDRESS (Bus, Device, Function, 0x04), BIT1);
return Size;
}
/**
Get AHCI mode base address registers' Value.
@param[in] Bus The bus number of ata host controller.
@param[in] Device The device number of ata host controller.
@param[in] Function The function number of ata host controller.
@retval EFI_UNSUPPORTED Return this Value when the BARs is not IO type
@retval EFI_SUCCESS Get the Base address successfully
@retval Other Read the pci configureation Data error
**/
EFI_STATUS
EFIAPI
GetAhciBaseAddress (
IN UINTN Bus,
IN UINTN Device,
IN UINTN Function
)
{
UINT32 Size;
//
// Get AHCI MMIO Bar
//
mAhciBar = PciRead32 (PCI_LIB_ADDRESS (Bus, Device, Function, 0x24));
//
// Get AHCI MMIO Bar Size
//
Size = GetAhciBarSize (Bus, Device, Function);
//
// Check if the AHCI Bar region is in SMRAM to avoid malicious attack by modifying MMIO Bar to point to SMRAM.
//
if (!SmmIsMmioValid ((EFI_PHYSICAL_ADDRESS)mAhciBar, Size, NULL)) {
return EFI_UNSUPPORTED;
}
return EFI_SUCCESS;
}
/**
Allocate transfer-related Data struct which is used at AHCI mode.
@retval EFI_OUT_OF_RESOURCE The allocation is failure.
@retval EFI_SUCCESS Successful to allocate memory.
**/
EFI_STATUS
EFIAPI
AhciAllocateResource (
VOID
)
{
EFI_STATUS Status;
EFI_PHYSICAL_ADDRESS Base;
//
// Allocate resources required by AHCI host controller.
//
Base = 0xFFFFFFFF;
Status = gBS->AllocatePages (
AllocateMaxAddress,
EfiACPIMemoryNVS,
EFI_SIZE_TO_PAGES (sizeof (EFI_AHCI_RECEIVED_FIS)),
&Base
);
if (EFI_ERROR (Status)) {
return EFI_OUT_OF_RESOURCES;
}
ZeroMem ((VOID *)(UINTN)Base, EFI_PAGE_SIZE * EFI_SIZE_TO_PAGES (sizeof (EFI_AHCI_RECEIVED_FIS)));
mAhciRegisters.AhciRFis = (VOID *)(UINTN)Base;
Base = 0xFFFFFFFF;
Status = gBS->AllocatePages (
AllocateMaxAddress,
EfiACPIMemoryNVS,
EFI_SIZE_TO_PAGES (sizeof (EFI_AHCI_COMMAND_LIST)),
&Base
);
if (EFI_ERROR (Status)) {
gBS->FreePages ((EFI_PHYSICAL_ADDRESS)(UINTN)mAhciRegisters.AhciRFis, EFI_SIZE_TO_PAGES (sizeof (EFI_AHCI_RECEIVED_FIS)));
return EFI_OUT_OF_RESOURCES;
}
ZeroMem ((VOID *)(UINTN)Base, EFI_PAGE_SIZE * EFI_SIZE_TO_PAGES (sizeof (EFI_AHCI_COMMAND_LIST)));
mAhciRegisters.AhciCmdList = (VOID *)(UINTN)Base;
Base = 0xFFFFFFFF;
Status = gBS->AllocatePages (
AllocateMaxAddress,
EfiACPIMemoryNVS,
EFI_SIZE_TO_PAGES (sizeof (EFI_AHCI_COMMAND_TABLE)),
&Base
);
if (EFI_ERROR (Status)) {
gBS->FreePages ((EFI_PHYSICAL_ADDRESS)(UINTN)mAhciRegisters.AhciRFis, EFI_SIZE_TO_PAGES (sizeof (EFI_AHCI_RECEIVED_FIS)));
gBS->FreePages ((EFI_PHYSICAL_ADDRESS)(UINTN)mAhciRegisters.AhciCmdList, EFI_SIZE_TO_PAGES (sizeof (EFI_AHCI_COMMAND_LIST)));
return EFI_OUT_OF_RESOURCES;
}
ZeroMem ((VOID *)(UINTN)Base, EFI_PAGE_SIZE * EFI_SIZE_TO_PAGES (sizeof (EFI_AHCI_COMMAND_TABLE)));
mAhciRegisters.AhciCommandTable = (VOID *)(UINTN)Base;
return EFI_SUCCESS;
}
/**
Free allocated transfer-related Data struct which is used at AHCI mode.
**/
VOID
EFIAPI
AhciFreeResource (
VOID
)
{
if (mAhciRegisters.AhciRFis != NULL) {
gBS->FreePages ((EFI_PHYSICAL_ADDRESS)(UINTN)mAhciRegisters.AhciRFis, EFI_SIZE_TO_PAGES (sizeof (EFI_AHCI_RECEIVED_FIS)));
}
if (mAhciRegisters.AhciCmdList != NULL) {
gBS->FreePages ((EFI_PHYSICAL_ADDRESS)(UINTN)mAhciRegisters.AhciCmdList, EFI_SIZE_TO_PAGES (sizeof (EFI_AHCI_COMMAND_LIST)));
}
if (mAhciRegisters.AhciCommandTable != NULL) {
gBS->FreePages ((EFI_PHYSICAL_ADDRESS)(UINTN)mAhciRegisters.AhciCommandTable, EFI_SIZE_TO_PAGES (sizeof (EFI_AHCI_COMMAND_TABLE)));
}
}
/**
Initialize ATA host controller at AHCI mode.
The function is designed to initialize ATA host controller.
@param[in] Port The port number to do initialization.
**/
EFI_STATUS
EFIAPI
AhciModeInitialize (
UINT8 Port
)
{
EFI_STATUS Status;
UINT32 Capability;
UINT32 Offset;
UINT32 Data;
UINT32 PhyDetectDelay;
Status = AhciReset (ATA_TIMEOUT);
if (EFI_ERROR (Status)) {
return Status;
}
//
// Collect AHCI controller information
//
Capability = AhciReadReg (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 (EFI_AHCI_GHC_OFFSET, EFI_AHCI_GHC_ENABLE);
}
Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_FB;
AhciWriteReg (Offset, (UINT32)(UINTN)mAhciRegisters.AhciRFis);
//
// Single task envrionment, we only use one command table for all port
//
Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_CLB;
AhciWriteReg (Offset, (UINT32)(UINTN)mAhciRegisters.AhciCmdList);
Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_CMD;
Data = AhciReadReg (Offset);
if ((Data & EFI_AHCI_PORT_CMD_CPD) != 0) {
AhciOrReg (Offset, EFI_AHCI_PORT_CMD_POD);
}
if ((Capability & BIT27) != 0) {
AhciOrReg (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 (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 (Offset, 0);
Status = AhciEnableFisReceive (
Port,
EFI_TIMER_PERIOD_MILLISECONDS(500)
);
ASSERT_EFI_ERROR (Status);
if (EFI_ERROR (Status)) {
return Status;
}
//
// 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(Offset) != 0) {
AhciWriteReg (Offset, AhciReadReg(Offset));
}
Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_TFD;
Data = AhciReadReg (Offset) & EFI_AHCI_PORT_TFD_MASK;
if (Data == 0) {
break;
}
MicroSecondDelay (1000);
PhyDetectDelay--;
} while (PhyDetectDelay > 0);
if (PhyDetectDelay == 0) {
return EFI_NOT_FOUND;
}
Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_SIG;
Status = AhciWaitMmioSet (
Offset,
0x0000FFFF,
0x00000101,
EFI_TIMER_PERIOD_SECONDS(16)
);
if (EFI_ERROR (Status)) {
return Status;
}
return Status;
}