X-Git-Url: https://git.proxmox.com/?p=mirror_edk2.git;a=blobdiff_plain;f=MdeModulePkg%2FBus%2FAta%2FAhciPei%2FAhciMode.c;fp=MdeModulePkg%2FBus%2FAta%2FAhciPei%2FAhciMode.c;h=54df31906fe3efde736e3675ac667c3e7ff0ee9b;hp=0000000000000000000000000000000000000000;hb=87bc3f192e17cd8f7caf0f3b7a87e039ceab323f;hpb=05fd2a9268332d770dd082be4af076ba0217cf54 diff --git a/MdeModulePkg/Bus/Ata/AhciPei/AhciMode.c b/MdeModulePkg/Bus/Ata/AhciPei/AhciMode.c new file mode 100644 index 0000000000..54df31906f --- /dev/null +++ b/MdeModulePkg/Bus/Ata/AhciPei/AhciMode.c @@ -0,0 +1,2015 @@ +/** @file + The AhciPei driver is used to manage ATA hard disk device working under AHCI + mode at PEI phase. + + Copyright (c) 2019, Intel Corporation. All rights reserved.
+ + 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 "AhciPei.h" + +#define ATA_CMD_TRUST_NON_DATA 0x5B +#define ATA_CMD_TRUST_RECEIVE 0x5C +#define ATA_CMD_TRUST_SEND 0x5E + +// +// Look up table (IsWrite) for EFI_ATA_PASS_THRU_CMD_PROTOCOL +// +EFI_ATA_PASS_THRU_CMD_PROTOCOL mAtaPassThruCmdProtocols[2] = { + EFI_ATA_PASS_THRU_PROTOCOL_PIO_DATA_IN, + EFI_ATA_PASS_THRU_PROTOCOL_PIO_DATA_OUT +}; + +// +// Look up table (Lba48Bit, IsIsWrite) for ATA_CMD +// +UINT8 mAtaCommands[2][2] = { + { + ATA_CMD_READ_SECTORS, // 28-bit LBA; PIO read + ATA_CMD_WRITE_SECTORS // 28-bit LBA; PIO write + }, + { + ATA_CMD_READ_SECTORS_EXT, // 48-bit LBA; PIO read + ATA_CMD_WRITE_SECTORS_EXT // 48-bit LBA; PIO write + } +}; + +// +// Look up table (IsTrustSend) for ATA_CMD +// +UINT8 mAtaTrustCommands[2] = { + ATA_CMD_TRUST_RECEIVE, // PIO read + ATA_CMD_TRUST_SEND // PIO write +}; + +// +// Look up table (Lba48Bit) for maximum transfer block number +// +#define MAX_28BIT_TRANSFER_BLOCK_NUM 0x100 +#define MAX_48BIT_TRANSFER_BLOCK_NUM 0xFFFF + +UINT32 mMaxTransferBlockNumber[2] = { + MAX_28BIT_TRANSFER_BLOCK_NUM, + MAX_48BIT_TRANSFER_BLOCK_NUM +}; + +// +// The maximum total sectors count in 28 bit addressing mode +// +#define MAX_28BIT_ADDRESSING_CAPACITY 0xfffffff + + +/** + Read AHCI Operation register. + + @param[in] AhciBar AHCI bar address. + @param[in] Offset The operation register offset. + + @return The register content read. + +**/ +UINT32 +AhciReadReg ( + IN UINTN AhciBar, + IN UINT32 Offset + ) +{ + UINT32 Data; + + Data = 0; + Data = MmioRead32 (AhciBar + Offset); + + return Data; +} + +/** + Write AHCI Operation register. + + @param[in] AhciBar AHCI bar address. + @param[in] Offset The operation register offset. + @param[in] Data The Data used to write down. + +**/ +VOID +AhciWriteReg ( + IN UINTN AhciBar, + IN UINT32 Offset, + IN UINT32 Data + ) +{ + MmioWrite32 (AhciBar + Offset, Data); +} + +/** + Do AND operation with the value of AHCI Operation register. + + @param[in] AhciBar AHCI bar address. + @param[in] Offset The operation register offset. + @param[in] AndData The data used to do AND operation. + +**/ +VOID +AhciAndReg ( + IN UINTN AhciBar, + IN UINT32 Offset, + IN UINT32 AndData + ) +{ + UINT32 Data; + + Data = AhciReadReg (AhciBar, Offset); + Data &= AndData; + + AhciWriteReg (AhciBar, Offset, Data); +} + +/** + Do OR operation with the Value of AHCI Operation register. + + @param[in] AhciBar AHCI bar address. + @param[in] Offset The operation register offset. + @param[in] OrData The Data used to do OR operation. + +**/ +VOID +AhciOrReg ( + IN UINTN AhciBar, + IN UINT32 Offset, + IN UINT32 OrData + ) +{ + UINT32 Data; + + Data = AhciReadReg (AhciBar, Offset); + Data |= OrData; + + AhciWriteReg (AhciBar, Offset, Data); +} + +/** + Wait for memory set to the test Value. + + @param[in] AhciBar AHCI bar address. + @param[in] Offset The memory offset to test. + @param[in] MaskValue The mask Value of memory. + @param[in] TestValue The test Value of memory. + @param[in] Timeout The timeout, in 100ns units, 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 UINTN AhciBar, + 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 (AhciBar, Offset) & 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. + + @retval EFI_NOT_READY The memory is not set. + @retval EFI_SUCCESS The memory is correct set. + +**/ +EFI_STATUS +AhciCheckMemSet ( + IN UINTN Address, + IN UINT32 MaskValue, + IN UINT32 TestValue + ) +{ + UINT32 Value; + + Value = *(volatile UINT32 *) Address; + Value &= MaskValue; + + if (Value == TestValue) { + return EFI_SUCCESS; + } else { + return EFI_NOT_READY; + } +} + +/** + Wait for the value of the specified system memory set to the test value. + + @param[in] Address The system memory address to test. + @param[in] MaskValue The mask value of memory. + @param[in] TestValue The test value of memory. + @param[in] Timeout The timeout, in 100ns units, for wait memory set. + + @retval EFI_TIMEOUT The system memory setting is time out. + @retval EFI_SUCCESS The system memory is correct set. + +**/ +EFI_STATUS +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; +} + +/** + + Clear the port interrupt and error status. It will also clear HBA interrupt + status. + + @param[in] AhciBar AHCI bar address. + @param[in] Port The number of port. + +**/ +VOID +AhciClearPortStatus ( + IN UINTN AhciBar, + IN UINT8 Port + ) +{ + UINT32 Offset; + + // + // Clear any error status + // + Offset = AHCI_PORT_START + Port * AHCI_PORT_REG_WIDTH + AHCI_PORT_SERR; + AhciWriteReg (AhciBar, Offset, AhciReadReg (AhciBar, Offset)); + + // + // Clear any port interrupt status + // + Offset = AHCI_PORT_START + Port * AHCI_PORT_REG_WIDTH + AHCI_PORT_IS; + AhciWriteReg (AhciBar, Offset, AhciReadReg (AhciBar, Offset)); + + // + // Clear any HBA interrupt status + // + AhciWriteReg (AhciBar, AHCI_IS_OFFSET, AhciReadReg (AhciBar, AHCI_IS_OFFSET)); +} + +/** + Enable the FIS running for giving port. + + @param[in] AhciBar AHCI bar address. + @param[in] Port The number of port. + @param[in] Timeout The timeout, in 100ns units, to 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 +AhciEnableFisReceive ( + IN UINTN AhciBar, + IN UINT8 Port, + IN UINT64 Timeout + ) +{ + UINT32 Offset; + + Offset = AHCI_PORT_START + Port * AHCI_PORT_REG_WIDTH + AHCI_PORT_CMD; + AhciOrReg (AhciBar, Offset, AHCI_PORT_CMD_FRE); + + return EFI_SUCCESS; +} + +/** + Disable the FIS running for giving port. + + @param[in] AhciBar AHCI bar address. + @param[in] Port The number of port. + @param[in] 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 +AhciDisableFisReceive ( + IN UINTN AhciBar, + IN UINT8 Port, + IN UINT64 Timeout + ) +{ + UINT32 Offset; + UINT32 Data; + + Offset = AHCI_PORT_START + Port * AHCI_PORT_REG_WIDTH + AHCI_PORT_CMD; + Data = AhciReadReg (AhciBar, Offset); + + // + // Before disabling Fis receive, the DMA engine of the port should NOT be in + // running status. + // + if ((Data & (AHCI_PORT_CMD_ST | AHCI_PORT_CMD_CR)) != 0) { + return EFI_UNSUPPORTED; + } + + // + // Check if the Fis receive DMA engine for the port is running. + // + if ((Data & AHCI_PORT_CMD_FR) != AHCI_PORT_CMD_FR) { + return EFI_SUCCESS; + } + + AhciAndReg (AhciBar, Offset, (UINT32)~(AHCI_PORT_CMD_FRE)); + + return AhciWaitMmioSet ( + AhciBar, + Offset, + AHCI_PORT_CMD_FR, + 0, + Timeout + ); +} + +/** + Build the command list, command table and prepare the fis receiver. + + @param[in] Private The pointer to the PEI_AHCI_CONTROLLER_PRIVATE_DATA. + @param[in] Port The number of port. + @param[in] PortMultiplier The number of port multiplier. + @param[in] FisIndex The offset index of the FIS base address. + @param[in] CommandFis The control fis will be used for the transfer. + @param[in] CommandList The command list will be used for the transfer. + @param[in] CommandSlotNumber The command slot will be used for the transfer. + @param[in,out] DataPhysicalAddr The pointer to the data buffer pci bus master + address. + @param[in] DataLength The data count to be transferred. + +**/ +VOID +AhciBuildCommand ( + IN PEI_AHCI_CONTROLLER_PRIVATE_DATA *Private, + IN UINT8 Port, + IN UINT8 PortMultiplier, + IN UINT8 FisIndex, + IN EFI_AHCI_COMMAND_FIS *CommandFis, + IN EFI_AHCI_COMMAND_LIST *CommandList, + IN UINT8 CommandSlotNumber, + IN OUT VOID *DataPhysicalAddr, + IN UINT32 DataLength + ) +{ + EFI_AHCI_REGISTERS *AhciRegisters; + UINTN AhciBar; + UINT64 BaseAddr; + UINT32 PrdtNumber; + UINT32 PrdtIndex; + UINTN RemainedData; + UINTN MemAddr; + DATA_64 Data64; + UINT32 Offset; + + AhciRegisters = &Private->AhciRegisters; + AhciBar = Private->MmioBase; + + // + // Filling the PRDT + // + PrdtNumber = (UINT32)DivU64x32 ( + (UINT64)DataLength + AHCI_MAX_DATA_PER_PRDT - 1, + 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. + // Current driver implementation supports up to a maximum of AHCI_MAX_PRDT_NUMBER + // PRDT entries. + // + ASSERT (PrdtNumber <= AHCI_MAX_PRDT_NUMBER); + if (PrdtNumber > AHCI_MAX_PRDT_NUMBER) { + return; + } + + Data64.Uint64 = (UINTN) (AhciRegisters->AhciRFis) + sizeof (EFI_AHCI_RECEIVED_FIS) * FisIndex; + + BaseAddr = Data64.Uint64; + + ZeroMem ((VOID *)((UINTN) BaseAddr), sizeof (EFI_AHCI_RECEIVED_FIS)); + + ZeroMem (AhciRegisters->AhciCmdTable, sizeof (EFI_AHCI_COMMAND_TABLE)); + + CommandFis->AhciCFisPmNum = PortMultiplier; + + CopyMem (&AhciRegisters->AhciCmdTable->CommandFis, CommandFis, sizeof (EFI_AHCI_COMMAND_FIS)); + + Offset = AHCI_PORT_START + Port * AHCI_PORT_REG_WIDTH + AHCI_PORT_CMD; + AhciAndReg (AhciBar, Offset, (UINT32)~(AHCI_PORT_CMD_DLAE | AHCI_PORT_CMD_ATAPI)); + + RemainedData = (UINTN) DataLength; + MemAddr = (UINTN) DataPhysicalAddr; + CommandList->AhciCmdPrdtl = PrdtNumber; + + for (PrdtIndex = 0; PrdtIndex < PrdtNumber; PrdtIndex++) { + if (RemainedData < AHCI_MAX_DATA_PER_PRDT) { + AhciRegisters->AhciCmdTable->PrdtTable[PrdtIndex].AhciPrdtDbc = (UINT32)RemainedData - 1; + } else { + AhciRegisters->AhciCmdTable->PrdtTable[PrdtIndex].AhciPrdtDbc = AHCI_MAX_DATA_PER_PRDT - 1; + } + + Data64.Uint64 = (UINT64)MemAddr; + AhciRegisters->AhciCmdTable->PrdtTable[PrdtIndex].AhciPrdtDba = Data64.Uint32.Lower32; + AhciRegisters->AhciCmdTable->PrdtTable[PrdtIndex].AhciPrdtDbau = Data64.Uint32.Upper32; + RemainedData -= AHCI_MAX_DATA_PER_PRDT; + MemAddr += AHCI_MAX_DATA_PER_PRDT; + } + + // + // Set the last PRDT to Interrupt On Complete + // + if (PrdtNumber > 0) { + AhciRegisters->AhciCmdTable->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->AhciCmdTable; + AhciRegisters->AhciCmdList[CommandSlotNumber].AhciCmdCtba = Data64.Uint32.Lower32; + AhciRegisters->AhciCmdList[CommandSlotNumber].AhciCmdCtbau = Data64.Uint32.Upper32; + AhciRegisters->AhciCmdList[CommandSlotNumber].AhciCmdPmp = PortMultiplier; +} + +/** + Buid a command FIS. + + @param[in,out] CmdFis A pointer to the EFI_AHCI_COMMAND_FIS data + structure. + @param[in] AtaCommandBlock A pointer to the EFI_ATA_COMMAND_BLOCK data + structure. + +**/ +VOID +AhciBuildCommandFis ( + IN OUT EFI_AHCI_COMMAND_FIS *CmdFis, + IN EFI_ATA_COMMAND_BLOCK *AtaCommandBlock + ) +{ + ZeroMem (CmdFis, sizeof (EFI_AHCI_COMMAND_FIS)); + + CmdFis->AhciCFisType = 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); +} + +/** + Stop command running for giving port + + @param[in] AhciBar AHCI bar address. + @param[in] Port The number of port. + @param[in] Timeout The timeout value, in 100ns units, to 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 +AhciStopCommand ( + IN UINTN AhciBar, + IN UINT8 Port, + IN UINT64 Timeout + ) +{ + UINT32 Offset; + UINT32 Data; + + Offset = AHCI_PORT_START + Port * AHCI_PORT_REG_WIDTH + AHCI_PORT_CMD; + Data = AhciReadReg (AhciBar, Offset); + + if ((Data & (AHCI_PORT_CMD_ST | AHCI_PORT_CMD_CR)) == 0) { + return EFI_SUCCESS; + } + + if ((Data & AHCI_PORT_CMD_ST) != 0) { + AhciAndReg (AhciBar, Offset, (UINT32)~(AHCI_PORT_CMD_ST)); + } + + return AhciWaitMmioSet ( + AhciBar, + Offset, + AHCI_PORT_CMD_CR, + 0, + Timeout + ); +} + +/** + Start command for give slot on specific port. + + @param[in] AhciBar AHCI bar address. + @param[in] Port The number of port. + @param[in] CommandSlot The number of Command Slot. + @param[in] Timeout The timeout value, in 100ns units, to 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 +AhciStartCommand ( + IN UINTN AhciBar, + 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 (AhciBar, AHCI_CAPABILITY_OFFSET); + + CmdSlotBit = (UINT32) (1 << CommandSlot); + + AhciClearPortStatus ( + AhciBar, + Port + ); + + Status = AhciEnableFisReceive ( + AhciBar, + Port, + Timeout + ); + if (EFI_ERROR (Status)) { + return Status; + } + + Offset = AHCI_PORT_START + Port * AHCI_PORT_REG_WIDTH + AHCI_PORT_CMD; + PortStatus = AhciReadReg (AhciBar, Offset); + + StartCmd = 0; + if ((PortStatus & AHCI_PORT_CMD_ALPE) != 0) { + StartCmd = AhciReadReg (AhciBar, Offset); + StartCmd &= ~AHCI_PORT_CMD_ICC_MASK; + StartCmd |= AHCI_PORT_CMD_ACTIVE; + } + + Offset = AHCI_PORT_START + Port * AHCI_PORT_REG_WIDTH + AHCI_PORT_TFD; + PortTfd = AhciReadReg (AhciBar, Offset); + + if ((PortTfd & (AHCI_PORT_TFD_BSY | AHCI_PORT_TFD_DRQ)) != 0) { + if ((Capability & BIT24) != 0) { + Offset = AHCI_PORT_START + Port * AHCI_PORT_REG_WIDTH + AHCI_PORT_CMD; + AhciOrReg (AhciBar, Offset, AHCI_PORT_CMD_CLO); + + AhciWaitMmioSet ( + AhciBar, + Offset, + AHCI_PORT_CMD_CLO, + 0, + Timeout + ); + } + } + + Offset = AHCI_PORT_START + Port * AHCI_PORT_REG_WIDTH + AHCI_PORT_CMD; + AhciOrReg (AhciBar, Offset, AHCI_PORT_CMD_ST | StartCmd); + + // + // Setting the command + // + Offset = AHCI_PORT_START + Port * AHCI_PORT_REG_WIDTH + AHCI_PORT_CI; + AhciAndReg (AhciBar, Offset, 0); + AhciOrReg (AhciBar, Offset, CmdSlotBit); + + return EFI_SUCCESS; +} + +/** + Start a PIO Data transfer on specific port. + + @param[in] Private The pointer to the PEI_AHCI_CONTROLLER_PRIVATE_DATA. + @param[in] Port The number of port. + @param[in] PortMultiplier The number of port multiplier. + @param[in] FisIndex The offset index of the FIS base address. + @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 PIO data transfer, uses + 100ns as a unit. + + @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_OUT_OF_RESOURCES The operation fails due to lack of resources. + @retval EFI_SUCCESS The PIO data transfer executes successfully. + +**/ +EFI_STATUS +AhciPioTransfer ( + IN PEI_AHCI_CONTROLLER_PRIVATE_DATA *Private, + IN UINT8 Port, + IN UINT8 PortMultiplier, + IN UINT8 FisIndex, + 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; + EDKII_IOMMU_OPERATION MapOp; + UINTN MapLength; + EFI_PHYSICAL_ADDRESS PhyAddr; + VOID *MapData; + EFI_AHCI_REGISTERS *AhciRegisters; + UINTN AhciBar; + BOOLEAN InfiniteWait; + UINT32 Offset; + UINT32 OldRfisLo; + UINT32 OldRfisHi; + UINT32 OldCmdListLo; + UINT32 OldCmdListHi; + DATA_64 Data64; + UINT32 FisBaseAddr; + UINT32 Delay; + EFI_AHCI_COMMAND_FIS CFis; + EFI_AHCI_COMMAND_LIST CmdList; + UINT32 PortTfd; + UINT32 PrdCount; + BOOLEAN PioFisReceived; + BOOLEAN D2hFisReceived; + + // + // Current driver implementation supports up to a maximum of AHCI_MAX_PRDT_NUMBER + // PRDT entries. + // + if (DataCount / (UINT32)AHCI_MAX_PRDT_NUMBER > AHCI_MAX_DATA_PER_PRDT) { + DEBUG (( + DEBUG_ERROR, + "%a: Driver only support a maximum of 0x%x PRDT entries, " + "current number of data byte 0x%x is too large, maximum allowed is 0x%x.\n", + __FUNCTION__, AHCI_MAX_PRDT_NUMBER, DataCount, + AHCI_MAX_PRDT_NUMBER * AHCI_MAX_DATA_PER_PRDT + )); + return EFI_UNSUPPORTED; + } + + MapOp = Read ? EdkiiIoMmuOperationBusMasterWrite : + EdkiiIoMmuOperationBusMasterRead; + MapLength = DataCount; + Status = IoMmuMap ( + MapOp, + MemoryAddr, + &MapLength, + &PhyAddr, + &MapData + ); + if (EFI_ERROR (Status) || (MapLength != DataCount)) { + DEBUG ((DEBUG_ERROR, "%a: Fail to map data buffer.\n", __FUNCTION__)); + return EFI_OUT_OF_RESOURCES; + } + + AhciRegisters = &Private->AhciRegisters; + AhciBar = Private->MmioBase; + InfiniteWait = (Timeout == 0) ? TRUE : FALSE; + + // + // Fill FIS base address register + // + Offset = AHCI_PORT_START + Port * AHCI_PORT_REG_WIDTH + AHCI_PORT_FB; + OldRfisLo = AhciReadReg (AhciBar, Offset); + Offset = AHCI_PORT_START + Port * AHCI_PORT_REG_WIDTH + AHCI_PORT_FBU; + OldRfisHi = AhciReadReg (AhciBar, Offset); + Data64.Uint64 = (UINTN) (AhciRegisters->AhciRFis) + sizeof (EFI_AHCI_RECEIVED_FIS) * FisIndex; + Offset = AHCI_PORT_START + Port * AHCI_PORT_REG_WIDTH + AHCI_PORT_FB; + AhciWriteReg (AhciBar, Offset, Data64.Uint32.Lower32); + Offset = AHCI_PORT_START + Port * AHCI_PORT_REG_WIDTH + AHCI_PORT_FBU; + AhciWriteReg (AhciBar, Offset, Data64.Uint32.Upper32); + + // + // Single task envrionment, we only use one command table for all port + // + Offset = AHCI_PORT_START + Port * AHCI_PORT_REG_WIDTH + AHCI_PORT_CLB; + OldCmdListLo = AhciReadReg (AhciBar, Offset); + Offset = AHCI_PORT_START + Port * AHCI_PORT_REG_WIDTH + AHCI_PORT_CLBU; + OldCmdListHi = AhciReadReg (AhciBar, Offset); + Data64.Uint64 = (UINTN) (AhciRegisters->AhciCmdList); + Offset = AHCI_PORT_START + Port * AHCI_PORT_REG_WIDTH + AHCI_PORT_CLB; + AhciWriteReg (AhciBar, Offset, Data64.Uint32.Lower32); + Offset = AHCI_PORT_START + Port * AHCI_PORT_REG_WIDTH + AHCI_PORT_CLBU; + AhciWriteReg (AhciBar, Offset, Data64.Uint32.Upper32); + + // + // Package read needed + // + AhciBuildCommandFis (&CFis, AtaCommandBlock); + + ZeroMem (&CmdList, sizeof (EFI_AHCI_COMMAND_LIST)); + + CmdList.AhciCmdCfl = AHCI_FIS_REGISTER_H2D_LENGTH / 4; + CmdList.AhciCmdW = Read ? 0 : 1; + + AhciBuildCommand ( + Private, + Port, + PortMultiplier, + FisIndex, + &CFis, + &CmdList, + 0, + (VOID *)(UINTN)PhyAddr, + DataCount + ); + + Status = AhciStartCommand ( + AhciBar, + Port, + 0, + Timeout + ); + if (EFI_ERROR (Status)) { + goto Exit; + } + + // + // Checking the status and wait the driver sending Data + // + FisBaseAddr = (UINT32)(UINTN)AhciRegisters->AhciRFis + sizeof (EFI_AHCI_RECEIVED_FIS) * FisIndex; + if (Read) { + // + // Wait device sends the PIO setup fis before data transfer + // + Status = EFI_TIMEOUT; + Delay = (UINT32) DivU64x32 (Timeout, 1000) + 1; + do { + PioFisReceived = FALSE; + D2hFisReceived = FALSE; + Offset = FisBaseAddr + AHCI_PIO_FIS_OFFSET; + Status = AhciCheckMemSet (Offset, AHCI_FIS_TYPE_MASK, AHCI_FIS_PIO_SETUP); + if (!EFI_ERROR (Status)) { + DEBUG ((DEBUG_INFO, "%a: PioFisReceived.\n", __FUNCTION__)); + 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 + AHCI_D2H_FIS_OFFSET; + Status = AhciCheckMemSet (Offset, AHCI_FIS_TYPE_MASK, AHCI_FIS_REGISTER_D2H); + if (!EFI_ERROR (Status)) { + DEBUG ((DEBUG_INFO, "%a: D2hFisReceived.\n", __FUNCTION__)); + D2hFisReceived = TRUE; + } + + if (PioFisReceived || D2hFisReceived) { + Offset = AHCI_PORT_START + Port * AHCI_PORT_REG_WIDTH + AHCI_PORT_TFD; + PortTfd = AhciReadReg (AhciBar, (UINT32) Offset); + // + // PxTFD will be updated if there is a D2H or SetupFIS received. + // + if ((PortTfd & 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 + AHCI_D2H_FIS_OFFSET; + Status = AhciWaitMemSet ( + Offset, + AHCI_FIS_TYPE_MASK, + AHCI_FIS_REGISTER_D2H, + Timeout + ); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "%a: AhciWaitMemSet (%r)\n", __FUNCTION__, Status)); + goto Exit; + } + + Offset = AHCI_PORT_START + Port * AHCI_PORT_REG_WIDTH + AHCI_PORT_TFD; + PortTfd = AhciReadReg (AhciBar, (UINT32) Offset); + if ((PortTfd & AHCI_PORT_TFD_ERR) != 0) { + Status = EFI_DEVICE_ERROR; + } + } + +Exit: + AhciStopCommand ( + AhciBar, + Port, + Timeout + ); + + AhciDisableFisReceive ( + AhciBar, + Port, + Timeout + ); + + if (MapData != NULL) { + IoMmuUnmap (MapData); + } + + Offset = AHCI_PORT_START + Port * AHCI_PORT_REG_WIDTH + AHCI_PORT_FB; + AhciWriteReg (AhciBar, Offset, OldRfisLo); + Offset = AHCI_PORT_START + Port * AHCI_PORT_REG_WIDTH + AHCI_PORT_FBU; + AhciWriteReg (AhciBar, Offset, OldRfisHi); + + Offset = AHCI_PORT_START + Port * AHCI_PORT_REG_WIDTH + AHCI_PORT_CLB; + AhciWriteReg (AhciBar, Offset, OldCmdListLo); + Offset = AHCI_PORT_START + Port * AHCI_PORT_REG_WIDTH + AHCI_PORT_CLBU; + AhciWriteReg (AhciBar, Offset, OldCmdListHi); + + return Status; +} + +/** + Start a non data transfer on specific port. + + @param[in] Private The pointer to the PEI_AHCI_CONTROLLER_PRIVATE_DATA. + @param[in] Port The number of port. + @param[in] PortMultiplier The number of port multiplier. + @param[in] FisIndex The offset index of the FIS base address. + @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. + + @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 +AhciNonDataTransfer ( + IN PEI_AHCI_CONTROLLER_PRIVATE_DATA *Private, + IN UINT8 Port, + IN UINT8 PortMultiplier, + IN UINT8 FisIndex, + IN EFI_ATA_COMMAND_BLOCK *AtaCommandBlock, + IN OUT EFI_ATA_STATUS_BLOCK *AtaStatusBlock, + IN UINT64 Timeout + ) +{ + EFI_STATUS Status; + UINTN AhciBar; + EFI_AHCI_REGISTERS *AhciRegisters; + UINTN FisBaseAddr; + UINTN Offset; + UINT32 PortTfd; + EFI_AHCI_COMMAND_FIS CFis; + EFI_AHCI_COMMAND_LIST CmdList; + + AhciBar = Private->MmioBase; + AhciRegisters = &Private->AhciRegisters; + + // + // Package read needed + // + AhciBuildCommandFis (&CFis, AtaCommandBlock); + + ZeroMem (&CmdList, sizeof (EFI_AHCI_COMMAND_LIST)); + + CmdList.AhciCmdCfl = AHCI_FIS_REGISTER_H2D_LENGTH / 4; + + AhciBuildCommand ( + Private, + Port, + PortMultiplier, + FisIndex, + &CFis, + &CmdList, + 0, + NULL, + 0 + ); + + Status = AhciStartCommand ( + AhciBar, + Port, + 0, + Timeout + ); + if (EFI_ERROR (Status)) { + goto Exit; + } + + // + // Wait device sends the Response Fis + // + FisBaseAddr = (UINTN)AhciRegisters->AhciRFis + sizeof (EFI_AHCI_RECEIVED_FIS) * FisIndex; + Offset = FisBaseAddr + AHCI_D2H_FIS_OFFSET; + Status = AhciWaitMemSet ( + Offset, + AHCI_FIS_TYPE_MASK, + AHCI_FIS_REGISTER_D2H, + Timeout + ); + + if (EFI_ERROR (Status)) { + goto Exit; + } + + Offset = AHCI_PORT_START + Port * AHCI_PORT_REG_WIDTH + AHCI_PORT_TFD; + PortTfd = AhciReadReg (AhciBar, (UINT32) Offset); + if ((PortTfd & AHCI_PORT_TFD_ERR) != 0) { + Status = EFI_DEVICE_ERROR; + } + +Exit: + AhciStopCommand ( + AhciBar, + Port, + Timeout + ); + + AhciDisableFisReceive ( + AhciBar, + Port, + Timeout + ); + + return Status; +} + +/** + Do AHCI HBA reset. + + @param[in] AhciBar AHCI bar address. + @param[in] Timeout The timeout, in 100ns units, to 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 +AhciReset ( + IN UINTN AhciBar, + IN UINT64 Timeout + ) +{ + UINT32 Delay; + UINT32 Value; + UINT32 Capability; + + // + // Collect AHCI controller information + // + Capability = AhciReadReg (AhciBar, AHCI_CAPABILITY_OFFSET); + + // + // Enable AE before accessing any AHCI registers if Supports AHCI Mode Only is not set + // + if ((Capability & AHCI_CAP_SAM) == 0) { + AhciOrReg (AhciBar, AHCI_GHC_OFFSET, AHCI_GHC_ENABLE); + } + + AhciOrReg (AhciBar, AHCI_GHC_OFFSET, AHCI_GHC_RESET); + + Delay = (UINT32) (DivU64x32(Timeout, 1000) + 1); + + do { + Value = AhciReadReg(AhciBar, AHCI_GHC_OFFSET); + if ((Value & AHCI_GHC_RESET) == 0) { + return EFI_SUCCESS; + } + + // + // Stall for 100 microseconds. + // + MicroSecondDelay(100); + + Delay--; + } while (Delay > 0); + + return EFI_TIMEOUT; +} + +/** + Send Identify Drive command to a specific device. + + @param[in] Private The pointer to the PEI_AHCI_CONTROLLER_PRIVATE_DATA. + @param[in] Port The number of port. + @param[in] PortMultiplier The port multiplier port number. + @param[in] FisIndex The offset index of the FIS base address. + @param[in] Buffer The data buffer to store IDENTIFY PACKET data. + + @retval EFI_SUCCESS The cmd executes successfully. + @retval EFI_INVALID_PARAMETER Buffer is NULL. + @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. + +**/ +EFI_STATUS +AhciIdentify ( + IN PEI_AHCI_CONTROLLER_PRIVATE_DATA *Private, + IN UINT8 Port, + IN UINT8 PortMultiplier, + IN UINT8 FisIndex, + IN ATA_IDENTIFY_DATA *Buffer + ) +{ + EFI_STATUS Status; + EFI_ATA_COMMAND_BLOCK Acb; + EFI_ATA_STATUS_BLOCK Asb; + + if (Buffer == NULL) { + return EFI_INVALID_PARAMETER; + } + + ZeroMem (&Acb, sizeof (EFI_ATA_COMMAND_BLOCK)); + ZeroMem (&Asb, sizeof (EFI_ATA_STATUS_BLOCK)); + + Acb.AtaCommand = ATA_CMD_IDENTIFY_DRIVE; + Acb.AtaSectorCount = 1; + + Status = AhciPioTransfer ( + Private, + Port, + PortMultiplier, + FisIndex, + TRUE, + &Acb, + &Asb, + Buffer, + sizeof (ATA_IDENTIFY_DATA), + ATA_TIMEOUT + ); + + return Status; +} + + +/** + Collect the number of bits set within a port bitmap. + + @param[in] PortBitMap A 32-bit wide bit map of ATA AHCI ports. + + @retval The number of bits set in the bitmap. + +**/ +UINT8 +AhciGetNumberOfPortsFromMap ( + IN UINT32 PortBitMap + ) +{ + UINT8 NumberOfPorts; + + NumberOfPorts = 0; + + while (PortBitMap != 0) { + if ((PortBitMap & ((UINT32)BIT0)) != 0) { + NumberOfPorts++; + } + PortBitMap = PortBitMap >> 1; + } + + return NumberOfPorts; +} + +/** + Get the specified port number from a port bitmap. + + @param[in] PortBitMap A 32-bit wide bit map of ATA AHCI ports. + @param[in] PortIndex The specified port index. + @param[out] Port The port number of the port specified by PortIndex. + + @retval EFI_SUCCESS The specified port is found and its port number is + in Port. + @retval EFI_NOT_FOUND Cannot find the specified port within the port bitmap. + +**/ +EFI_STATUS +AhciGetPortFromMap ( + IN UINT32 PortBitMap, + IN UINT8 PortIndex, + OUT UINT8 *Port + ) +{ + if (PortIndex == 0) { + return EFI_NOT_FOUND; + } + + *Port = 0; + + while (PortBitMap != 0) { + if ((PortBitMap & ((UINT32)BIT0)) != 0) { + PortIndex--; + + // + // Found the port specified by PortIndex. + // + if (PortIndex == 0) { + return EFI_SUCCESS; + } + } + PortBitMap = PortBitMap >> 1; + *Port = *Port + 1; + } + + return EFI_NOT_FOUND; +} + +/** + Allocate transfer-related data struct which is used at AHCI mode. + + @param[in,out] Private A pointer to the PEI_AHCI_CONTROLLER_PRIVATE_DATA instance. + + @retval EFI_SUCCESS Data structures are allocated successfully. + @retval Others Data structures are not allocated successfully. + +**/ +EFI_STATUS +AhciCreateTransferDescriptor ( + IN OUT PEI_AHCI_CONTROLLER_PRIVATE_DATA *Private + ) +{ + EFI_STATUS Status; + UINTN AhciBar; + EFI_AHCI_REGISTERS *AhciRegisters; + EFI_PHYSICAL_ADDRESS DeviceAddress; + VOID *Base; + VOID *Mapping; + UINT32 Capability; + UINT32 PortImplementBitMap; + UINT8 MaxPortNumber; + UINT8 MaxCommandSlotNumber; + UINTN MaxRFisSize; + UINTN MaxCmdListSize; + UINTN MaxCmdTableSize; + + AhciBar = Private->MmioBase; + AhciRegisters = &Private->AhciRegisters; + + // + // Collect AHCI controller information + // + Capability = AhciReadReg (AhciBar, AHCI_CAPABILITY_OFFSET); + + // + // Get the number of command slots per port supported by this HBA. + // + MaxCommandSlotNumber = (UINT8) (((Capability & 0x1F00) >> 8) + 1); + ASSERT (MaxCommandSlotNumber > 0); + if (MaxCommandSlotNumber == 0) { + return EFI_DEVICE_ERROR; + } + + // + // Get the highest bit of implemented ports which decides how many bytes are + // allocated for recived FIS. + // + PortImplementBitMap = AhciReadReg (AhciBar, AHCI_PI_OFFSET); + MaxPortNumber = (UINT8)(UINTN)(HighBitSet32(PortImplementBitMap) + 1); + if (MaxPortNumber == 0) { + return EFI_DEVICE_ERROR; + } + // + // Get the number of ports that actually needed to be initialized. + // + MaxPortNumber = MIN (MaxPortNumber, AhciGetNumberOfPortsFromMap (Private->PortBitMap)); + + // + // Allocate memory for received FIS. + // + MaxRFisSize = MaxPortNumber * sizeof (EFI_AHCI_RECEIVED_FIS); + Status = IoMmuAllocateBuffer ( + EFI_SIZE_TO_PAGES (MaxRFisSize), + &Base, + &DeviceAddress, + &Mapping + ); + if (EFI_ERROR (Status)) { + return EFI_OUT_OF_RESOURCES; + } + ASSERT (DeviceAddress == ((EFI_PHYSICAL_ADDRESS) (UINTN) Base)); + AhciRegisters->AhciRFis = Base; + AhciRegisters->AhciRFisMap = Mapping; + AhciRegisters->MaxRFisSize = MaxRFisSize; + ZeroMem (AhciRegisters->AhciRFis, EFI_PAGE_SIZE * EFI_SIZE_TO_PAGES (MaxRFisSize)); + + // + // Allocate memory for command list. + // Note that the implemenation is a single task model which only use a command + // list for each port. + // + MaxCmdListSize = 1 * sizeof (EFI_AHCI_COMMAND_LIST); + Status = IoMmuAllocateBuffer ( + EFI_SIZE_TO_PAGES (MaxCmdListSize), + &Base, + &DeviceAddress, + &Mapping + ); + if (EFI_ERROR (Status)) { + Status = EFI_OUT_OF_RESOURCES; + goto ErrorExit; + } + ASSERT (DeviceAddress == ((EFI_PHYSICAL_ADDRESS) (UINTN) Base)); + AhciRegisters->AhciCmdList = Base; + AhciRegisters->AhciCmdListMap = Mapping; + AhciRegisters->MaxCmdListSize = MaxCmdListSize; + ZeroMem (AhciRegisters->AhciCmdList, EFI_PAGE_SIZE * EFI_SIZE_TO_PAGES (MaxCmdListSize)); + + // + // Allocate memory for command table + // According to AHCI 1.3 spec, a PRD table can contain maximum 65535 entries. + // + MaxCmdTableSize = sizeof (EFI_AHCI_COMMAND_TABLE); + Status = IoMmuAllocateBuffer ( + EFI_SIZE_TO_PAGES (MaxCmdTableSize), + &Base, + &DeviceAddress, + &Mapping + ); + if (EFI_ERROR (Status)) { + Status = EFI_OUT_OF_RESOURCES; + goto ErrorExit; + } + ASSERT (DeviceAddress == ((EFI_PHYSICAL_ADDRESS) (UINTN) Base)); + AhciRegisters->AhciCmdTable = Base; + AhciRegisters->AhciCmdTableMap = Mapping; + AhciRegisters->MaxCmdTableSize = MaxCmdTableSize; + ZeroMem (AhciRegisters->AhciCmdTable, EFI_PAGE_SIZE * EFI_SIZE_TO_PAGES (MaxCmdTableSize)); + + return EFI_SUCCESS; + +ErrorExit: + if (AhciRegisters->AhciRFisMap != NULL) { + IoMmuFreeBuffer ( + EFI_SIZE_TO_PAGES (AhciRegisters->MaxRFisSize), + AhciRegisters->AhciRFis, + AhciRegisters->AhciRFisMap + ); + AhciRegisters->AhciRFis = NULL; + } + + if (AhciRegisters->AhciCmdListMap != NULL) { + IoMmuFreeBuffer ( + EFI_SIZE_TO_PAGES (AhciRegisters->MaxCmdListSize), + AhciRegisters->AhciCmdList, + AhciRegisters->AhciCmdListMap + ); + AhciRegisters->AhciCmdList = NULL; + } + + return Status; +} + +/** + Gets ATA device Capacity according to ATA 6. + + This function returns the capacity of the ATA device if it follows + ATA 6 to support 48 bit addressing. + + @param[in] IdentifyData A pointer to ATA_IDENTIFY_DATA structure. + + @return The capacity of the ATA device or 0 if the device does not support + 48-bit addressing defined in ATA 6. + +**/ +EFI_LBA +GetAtapi6Capacity ( + IN ATA_IDENTIFY_DATA *IdentifyData + ) +{ + EFI_LBA Capacity; + EFI_LBA TmpLba; + UINTN Index; + + if ((IdentifyData->command_set_supported_83 & BIT10) == 0) { + // + // The device doesn't support 48 bit addressing + // + return 0; + } + + // + // 48 bit address feature set is supported, get maximum capacity + // + Capacity = 0; + for (Index = 0; Index < 4; Index++) { + // + // Lower byte goes first: word[100] is the lowest word, word[103] is highest + // + TmpLba = IdentifyData->maximum_lba_for_48bit_addressing[Index]; + Capacity |= LShiftU64 (TmpLba, 16 * Index); + } + + return Capacity; +} + +/** + Identifies ATA device via the Identify data. + + This function identifies the ATA device and initializes the media information. + + @attention This is boundary function that may receive untrusted input. + @attention The input is from peripheral hardware device. + + The Identify Drive command response data from an ATA device is the peripheral + hardware input, so this routine will do basic validation for the Identify Drive + command response data. + + @param[in,out] DeviceData A pointer to PEI_AHCI_ATA_DEVICE_DATA structure. + + @retval EFI_SUCCESS The device is successfully identified and media + information is correctly initialized. + @retval EFI_UNSUPPORTED The device is not a valid ATA device (hard disk). + +**/ +EFI_STATUS +IdentifyAtaDevice ( + IN OUT PEI_AHCI_ATA_DEVICE_DATA *DeviceData + ) +{ + ATA_IDENTIFY_DATA *IdentifyData; + EFI_PEI_BLOCK_IO2_MEDIA *Media; + EFI_LBA Capacity; + UINT32 MaxSectorCount; + UINT16 PhyLogicSectorSupport; + + IdentifyData = DeviceData->IdentifyData; + Media = &DeviceData->Media; + + if ((IdentifyData->config & BIT15) != 0) { + DEBUG (( + DEBUG_ERROR, "%a: Not a hard disk device on Port 0x%x PortMultiplierPort 0x%x\n", + __FUNCTION__, DeviceData->Port, DeviceData->PortMultiplier + )); + return EFI_UNSUPPORTED; + } + + DEBUG (( + DEBUG_INFO, "%a: Identify Device: Port 0x%x PortMultiplierPort 0x%x\n", + __FUNCTION__, DeviceData->Port, DeviceData->PortMultiplier + )); + + // + // Skip checking whether the WORD 88 (supported UltraDMA by drive), since the + // driver only support PIO data transfer for now. + // + + // + // Get the capacity information of the device. + // + Capacity = GetAtapi6Capacity (IdentifyData); + if (Capacity > MAX_28BIT_ADDRESSING_CAPACITY) { + // + // Capacity exceeds 120GB. 48-bit addressing is really needed + // + DeviceData->Lba48Bit = TRUE; + } else { + // + // This is a hard disk <= 120GB capacity, treat it as normal hard disk + // + Capacity = ((UINT32)IdentifyData->user_addressable_sectors_hi << 16) | + IdentifyData->user_addressable_sectors_lo; + DeviceData->Lba48Bit = FALSE; + } + + if (Capacity == 0) { + DEBUG ((DEBUG_ERROR, "%a: Invalid Capacity (0) for ATA device.\n", __FUNCTION__)); + return EFI_UNSUPPORTED; + } + Media->LastBlock = (EFI_PEI_LBA) (Capacity - 1); + + Media->BlockSize = 0x200; + // + // Check whether Long Physical Sector Feature is supported + // + PhyLogicSectorSupport = IdentifyData->phy_logic_sector_support; + DEBUG (( + DEBUG_INFO, "%a: PhyLogicSectorSupport = 0x%x\n", + __FUNCTION__, PhyLogicSectorSupport + )); + if ((PhyLogicSectorSupport & (BIT14 | BIT15)) == BIT14) { + // + // Check logical block size + // + if ((PhyLogicSectorSupport & BIT12) != 0) { + Media->BlockSize = (UINT32) (((IdentifyData->logic_sector_size_hi << 16) | + IdentifyData->logic_sector_size_lo) * sizeof (UINT16)); + } + } + + // + // Check BlockSize validity + // + MaxSectorCount = mMaxTransferBlockNumber[DeviceData->Lba48Bit]; + if ((Media->BlockSize == 0) || (Media->BlockSize > MAX_UINT32 / MaxSectorCount)) { + DEBUG ((DEBUG_ERROR, "%a: Invalid BlockSize (0x%x).\n", __FUNCTION__, Media->BlockSize)); + return EFI_UNSUPPORTED; + } + + DEBUG (( + DEBUG_INFO, "%a: BlockSize = 0x%x, LastBlock = 0x%lx\n", + __FUNCTION__, Media->BlockSize, Media->LastBlock + )); + + if ((IdentifyData->trusted_computing_support & BIT0) != 0) { + DEBUG ((DEBUG_INFO, "%a: Found Trust Computing feature support.\n", __FUNCTION__)); + DeviceData->TrustComputing = TRUE; + } + + Media->InterfaceType = MSG_SATA_DP; + Media->RemovableMedia = FALSE; + Media->MediaPresent = TRUE; + Media->ReadOnly = FALSE; + + return EFI_SUCCESS; +} + +/** + Allocate device information data structure to contain device information. + And insert the data structure to the tail of device list for tracing. + + @param[in,out] Private A pointer to the PEI_AHCI_CONTROLLER_PRIVATE_DATA + instance. + @param[in] DeviceIndex The device index. + @param[in] Port The port number of the ATA device to send + the command. + @param[in] PortMultiplierPort The port multiplier port number of the ATA + device to send the command. + If there is no port multiplier, then specify + 0xFFFF. + @param[in] FisIndex The index of the FIS of the ATA device to + send the command. + @param[in] IdentifyData The data buffer to store the output of the + IDENTIFY command. + + @retval EFI_SUCCESS Successfully insert the ATA device to the + tail of device list. + @retval EFI_OUT_OF_RESOURCES Not enough resource. + +**/ +EFI_STATUS +CreateNewDevice ( + IN OUT PEI_AHCI_CONTROLLER_PRIVATE_DATA *Private, + IN UINTN DeviceIndex, + IN UINT16 Port, + IN UINT16 PortMultiplier, + IN UINT8 FisIndex, + IN ATA_IDENTIFY_DATA *IdentifyData + ) +{ + PEI_AHCI_ATA_DEVICE_DATA *DeviceData; + EFI_STATUS Status; + + DeviceData = AllocateZeroPool (sizeof (PEI_AHCI_ATA_DEVICE_DATA)); + if (DeviceData == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + if (IdentifyData != NULL) { + DeviceData->IdentifyData = AllocateCopyPool (sizeof (ATA_IDENTIFY_DATA), IdentifyData); + if (DeviceData->IdentifyData == NULL) { + return EFI_OUT_OF_RESOURCES; + } + } + + DeviceData->Signature = AHCI_PEI_ATA_DEVICE_DATA_SIGNATURE; + DeviceData->Port = Port; + DeviceData->PortMultiplier = PortMultiplier; + DeviceData->FisIndex = FisIndex; + DeviceData->DeviceIndex = DeviceIndex; + DeviceData->Private = Private; + + Status = IdentifyAtaDevice (DeviceData); + if (EFI_ERROR (Status)) { + return Status; + } + + if (DeviceData->TrustComputing) { + Private->TrustComputingDevices++; + DeviceData->TrustComputingDeviceIndex = Private->TrustComputingDevices; + } + Private->ActiveDevices++; + InsertTailList (&Private->DeviceList, &DeviceData->Link); + + return EFI_SUCCESS; +} + +/** + Initialize ATA host controller at AHCI mode. + + The function is designed to initialize ATA host controller. + + @param[in,out] Private A pointer to the PEI_AHCI_CONTROLLER_PRIVATE_DATA instance. + + @retval EFI_SUCCESS The ATA AHCI controller is initialized successfully. + @retval EFI_OUT_OF_RESOURCES Not enough resource to complete while initializing + the controller. + @retval Others A device error occurred while initializing the + controller. + +**/ +EFI_STATUS +AhciModeInitialization ( + IN OUT PEI_AHCI_CONTROLLER_PRIVATE_DATA *Private + ) +{ + EFI_STATUS Status; + UINTN AhciBar; + UINT32 Capability; + UINT32 Value; + UINT8 MaxPortNumber; + UINT32 PortImplementBitMap; + UINT32 PortInitializeBitMap; + EFI_AHCI_REGISTERS *AhciRegisters; + UINT8 PortIndex; + UINT8 Port; + DATA_64 Data64; + UINT32 Data; + UINT32 Offset; + UINT32 PhyDetectDelay; + UINTN DeviceIndex; + ATA_IDENTIFY_DATA IdentifyData; + + AhciBar = Private->MmioBase; + + Status = AhciReset (AhciBar, AHCI_PEI_RESET_TIMEOUT); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "%a: AHCI HBA reset failed with %r.\n", __FUNCTION__, Status)); + return EFI_DEVICE_ERROR; + } + + // + // Collect AHCI controller information + // + Capability = AhciReadReg (AhciBar, AHCI_CAPABILITY_OFFSET); + + // + // Make sure that GHC.AE bit is set before accessing any AHCI registers. + // + Value = AhciReadReg (AhciBar, AHCI_GHC_OFFSET); + if ((Value & AHCI_GHC_ENABLE) == 0) { + AhciOrReg (AhciBar, AHCI_GHC_OFFSET, AHCI_GHC_ENABLE); + } + + Status = AhciCreateTransferDescriptor (Private); + if (EFI_ERROR (Status)) { + DEBUG (( + DEBUG_ERROR, + "%a: Transfer-related data allocation failed with %r.\n", + __FUNCTION__, Status + )); + return EFI_OUT_OF_RESOURCES; + } + + // + // 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 (AhciBar, AHCI_PI_OFFSET); + + // + // Get the number of ports that actually needed to be initialized. + // + MaxPortNumber = MIN (MaxPortNumber, (UINT8)(UINTN)(HighBitSet32(PortImplementBitMap) + 1)); + MaxPortNumber = MIN (MaxPortNumber, AhciGetNumberOfPortsFromMap (Private->PortBitMap)); + + PortInitializeBitMap = Private->PortBitMap; + AhciRegisters = &Private->AhciRegisters; + DeviceIndex = 0; + // + // Enumerate ATA ports + // + for (PortIndex = 1; PortIndex <= MaxPortNumber; PortIndex ++) { + Status = AhciGetPortFromMap (PortInitializeBitMap, PortIndex, &Port); + if ((PortImplementBitMap & (BIT0 << Port)) != 0) { + // + // Initialize FIS Base Address Register and Command List Base Address + // Register for use. + // + Data64.Uint64 = (UINTN) (AhciRegisters->AhciRFis) + + sizeof (EFI_AHCI_RECEIVED_FIS) * (PortIndex - 1); + Offset = AHCI_PORT_START + Port * AHCI_PORT_REG_WIDTH + AHCI_PORT_FB; + AhciWriteReg (AhciBar, Offset, Data64.Uint32.Lower32); + Offset = AHCI_PORT_START + Port * AHCI_PORT_REG_WIDTH + AHCI_PORT_FBU; + AhciWriteReg (AhciBar, Offset, Data64.Uint32.Upper32); + + Data64.Uint64 = (UINTN) (AhciRegisters->AhciCmdList); + Offset = AHCI_PORT_START + Port * AHCI_PORT_REG_WIDTH + AHCI_PORT_CLB; + AhciWriteReg (AhciBar, Offset, Data64.Uint32.Lower32); + Offset = AHCI_PORT_START + Port * AHCI_PORT_REG_WIDTH + AHCI_PORT_CLBU; + AhciWriteReg (AhciBar, Offset, Data64.Uint32.Upper32); + + Offset = AHCI_PORT_START + Port * AHCI_PORT_REG_WIDTH + AHCI_PORT_CMD; + Data = AhciReadReg (AhciBar, Offset); + if ((Data & AHCI_PORT_CMD_CPD) != 0) { + AhciOrReg (AhciBar, Offset, AHCI_PORT_CMD_POD); + } + + if ((Capability & AHCI_CAP_SSS) != 0) { + AhciOrReg (AhciBar, Offset, AHCI_PORT_CMD_SUD); + } + + // + // Disable aggressive power management. + // + Offset = AHCI_PORT_START + Port * AHCI_PORT_REG_WIDTH + AHCI_PORT_SCTL; + AhciOrReg (AhciBar, Offset, AHCI_PORT_SCTL_IPM_INIT); + // + // Disable the reporting of the corresponding interrupt to system software. + // + Offset = AHCI_PORT_START + Port * AHCI_PORT_REG_WIDTH + AHCI_PORT_IE; + AhciAndReg (AhciBar, Offset, 0); + + // + // Enable FIS Receive DMA engine for the first D2H FIS. + // + Offset = AHCI_PORT_START + Port * AHCI_PORT_REG_WIDTH + AHCI_PORT_CMD; + AhciOrReg (AhciBar, Offset, AHCI_PORT_CMD_FRE); + + // + // Wait no longer than 15 ms to wait the Phy to detect the presence of a device. + // + PhyDetectDelay = AHCI_BUS_PHY_DETECT_TIMEOUT; + Offset = AHCI_PORT_START + Port * AHCI_PORT_REG_WIDTH + AHCI_PORT_SSTS; + do { + Data = AhciReadReg (AhciBar, Offset) & AHCI_PORT_SSTS_DET_MASK; + if ((Data == AHCI_PORT_SSTS_DET_PCE) || (Data == 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 = AHCI_PORT_START + Port * AHCI_PORT_REG_WIDTH + AHCI_PORT_CMD; + AhciAndReg (AhciBar, Offset, (UINT32) ~(AHCI_PORT_CMD_SUD)); + DEBUG ((DEBUG_ERROR, "%a: No device detected at Port %d.\n", __FUNCTION__, Port)); + 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 = AHCI_PORT_START + Port * AHCI_PORT_REG_WIDTH + AHCI_PORT_SERR; + if (AhciReadReg(AhciBar, Offset) != 0) { + AhciWriteReg (AhciBar, Offset, AhciReadReg (AhciBar, Offset)); + } + Offset = AHCI_PORT_START + Port * AHCI_PORT_REG_WIDTH + AHCI_PORT_TFD; + + Data = AhciReadReg (AhciBar, Offset) & AHCI_PORT_TFD_MASK; + if (Data == 0) { + break; + } + + MicroSecondDelay (1000); + PhyDetectDelay--; + } while (PhyDetectDelay > 0); + + if (PhyDetectDelay == 0) { + DEBUG (( + DEBUG_ERROR, + "%a: Port %d device presence detected but phy not ready (TFD=0x%x).\n", + __FUNCTION__, Port, Data + )); + continue; + } + + // + // When the first D2H register FIS is received, the content of PxSIG register is updated. + // + Offset = AHCI_PORT_START + Port * AHCI_PORT_REG_WIDTH + AHCI_PORT_SIG; + Status = AhciWaitMmioSet ( + AhciBar, + Offset, + 0x0000FFFF, + 0x00000101, + 160000000 + ); + if (EFI_ERROR (Status)) { + DEBUG (( + DEBUG_ERROR, + "%a: Error occured when waiting for the first D2H register FIS - %r\n", + __FUNCTION__, Status + )); + continue; + } + + Data = AhciReadReg (AhciBar, Offset); + if ((Data & AHCI_ATAPI_SIG_MASK) == AHCI_ATA_DEVICE_SIG) { + Status = AhciIdentify (Private, Port, 0, PortIndex - 1, &IdentifyData); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "%a: AhciIdentify() failed with %r\n", __FUNCTION__, Status)); + continue; + } + DEBUG ((DEBUG_INFO, "%a: ATA hard disk found on Port %d.\n", __FUNCTION__, Port)); + } else { + continue; + } + + // + // Found an ATA hard disk device, add it into the device list. + // + DeviceIndex++; + CreateNewDevice ( + Private, + DeviceIndex, + Port, + 0xFFFF, + PortIndex - 1, + &IdentifyData + ); + } + } + + return EFI_SUCCESS; +} + +/** + Trust transfer data from/to ATA device. + + This function performs one ATA pass through transaction to do a trust transfer + from/to ATA device. It chooses the appropriate ATA command and protocol to invoke + PassThru interface of ATA pass through. + + @param[in] DeviceData Pointer to PEI_AHCI_ATA_DEVICE_DATA structure. + @param[in,out] Buffer The pointer to the current transaction buffer. + @param[in] SecurityProtocolId + The value of the "Security Protocol" parameter + of the security protocol command to be sent. + @param[in] SecurityProtocolSpecificData + The value of the "Security Protocol Specific" + parameter of the security protocol command to + be sent. + @param[in] TransferLength The block number or sector count of the transfer. + @param[in] IsTrustSend Indicates whether it is a trust send operation + or not. + @param[in] Timeout The timeout, in 100ns units, to use for the execution + of the security protocol command. A Timeout value + of 0 means that this function will wait indefinitely + for the security protocol command to execute. If + Timeout is greater than zero, then this function + will return EFI_TIMEOUT if the time required to + execute the receive data command is greater than + Timeout. + @param[out] TransferLengthOut + A pointer to a buffer to store the size in bytes + of the data written to the buffer. Ignore it when + IsTrustSend is TRUE. + + @retval EFI_SUCCESS The data transfer is complete successfully. + @return others Some error occurs when transferring data. + +**/ +EFI_STATUS +TrustTransferAtaDevice ( + IN PEI_AHCI_ATA_DEVICE_DATA *DeviceData, + IN OUT VOID *Buffer, + IN UINT8 SecurityProtocolId, + IN UINT16 SecurityProtocolSpecificData, + IN UINTN TransferLength, + IN BOOLEAN IsTrustSend, + IN UINT64 Timeout, + OUT UINTN *TransferLengthOut + ) +{ + PEI_AHCI_CONTROLLER_PRIVATE_DATA *Private; + EDKII_PEI_ATA_PASS_THRU_PPI *AtaPassThru; + EFI_ATA_COMMAND_BLOCK Acb; + EFI_ATA_PASS_THRU_COMMAND_PACKET Packet; + EFI_STATUS Status; + VOID *NewBuffer; + + Private = DeviceData->Private; + AtaPassThru = &Private->AtaPassThruPpi; + + // + // Ensure IsTrustSend are valid boolean values + // + ASSERT ((UINTN) IsTrustSend < 2); + if ((UINTN) IsTrustSend >= 2) { + return EFI_INVALID_PARAMETER; + } + + // + // Prepare for ATA command block. + // + ZeroMem (&Acb, sizeof (EFI_ATA_COMMAND_BLOCK)); + if (TransferLength == 0) { + Acb.AtaCommand = ATA_CMD_TRUST_NON_DATA; + } else { + Acb.AtaCommand = mAtaTrustCommands[IsTrustSend]; + } + Acb.AtaFeatures = SecurityProtocolId; + Acb.AtaSectorCount = (UINT8) (TransferLength / 512); + Acb.AtaSectorNumber = (UINT8) ((TransferLength / 512) >> 8); + // + // NOTE: ATA Spec has no explicitly definition for Security Protocol Specific layout. + // Here use big endian for Cylinder register. + // + Acb.AtaCylinderHigh = (UINT8) SecurityProtocolSpecificData; + Acb.AtaCylinderLow = (UINT8) (SecurityProtocolSpecificData >> 8); + Acb.AtaDeviceHead = (UINT8) (BIT7 | BIT6 | BIT5 | + (DeviceData->PortMultiplier == 0xFFFF ? + 0 : (DeviceData->PortMultiplier << 4))); + + // + // Prepare for ATA pass through packet. + // + ZeroMem (&Packet, sizeof (EFI_ATA_PASS_THRU_COMMAND_PACKET)); + if (TransferLength == 0) { + Packet.InTransferLength = 0; + Packet.OutTransferLength = 0; + Packet.Protocol = EFI_ATA_PASS_THRU_PROTOCOL_ATA_NON_DATA; + } else if (IsTrustSend) { + // + // Check the alignment of the incoming buffer prior to invoking underlying + // ATA PassThru PPI. + // + if ((AtaPassThru->Mode->IoAlign > 1) && + !IS_ALIGNED (Buffer, AtaPassThru->Mode->IoAlign)) { + NewBuffer = AllocateAlignedPages ( + EFI_SIZE_TO_PAGES (TransferLength), + AtaPassThru->Mode->IoAlign + ); + if (NewBuffer == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + CopyMem (NewBuffer, Buffer, TransferLength); + Buffer = NewBuffer; + } + Packet.OutDataBuffer = Buffer; + Packet.OutTransferLength = (UINT32) TransferLength; + Packet.Protocol = mAtaPassThruCmdProtocols[IsTrustSend]; + } else { + Packet.InDataBuffer = Buffer; + Packet.InTransferLength = (UINT32) TransferLength; + Packet.Protocol = mAtaPassThruCmdProtocols[IsTrustSend]; + } + Packet.Asb = NULL; + Packet.Acb = &Acb; + Packet.Timeout = Timeout; + Packet.Length = EFI_ATA_PASS_THRU_LENGTH_BYTES; + + Status = AtaPassThru->PassThru ( + AtaPassThru, + DeviceData->Port, + DeviceData->PortMultiplier, + &Packet + ); + if (TransferLengthOut != NULL) { + if (!IsTrustSend) { + *TransferLengthOut = Packet.InTransferLength; + } + } + return Status; +}