From 87bc3f192e17cd8f7caf0f3b7a87e039ceab323f Mon Sep 17 00:00:00 2001 From: Hao Wu Date: Fri, 22 Jun 2018 16:53:28 +0800 Subject: [PATCH] MdeModulePkg/AhciPei: Add AHCI mode ATA device support in PEI REF:https://bugzilla.tianocore.org/show_bug.cgi?id=1409 This commit will add the AHCI mode ATA device support in the PEI phase. More specifically, the newly add AhciPei driver will consume the ATA AHCI host controller PPI for ATA controllers working under AHCI code within the system. And then produces the below PPIs for each controller: EDKII PEI ATA PassThru PPI Storage Security Command PPI Also, the driver will consume the S3StorageDeviceInitList LockBox in S3 phase. The purpose is to perform an on-demand (partial) ATA device enumeration/initialization on each controller to benefit the S3 resume performance. The implementation of this driver is currently based on the below specifications: Serial ATA Revision 2.6 Serial ATA Advanced Host Controller Interface (AHCI) 1.3.1 AT Attachment with Packet Interface - 6 (ATA/ATAPI-6) Cc: Jian J Wang Contributed-under: TianoCore Contribution Agreement 1.1 Signed-off-by: Hao Wu Reviewed-by: Eric Dong Reviewed-by: Ray Ni --- MdeModulePkg/Bus/Ata/AhciPei/AhciMode.c | 2015 +++++++++++++++++ MdeModulePkg/Bus/Ata/AhciPei/AhciPei.c | 310 +++ MdeModulePkg/Bus/Ata/AhciPei/AhciPei.h | 708 ++++++ MdeModulePkg/Bus/Ata/AhciPei/AhciPei.inf | 74 + MdeModulePkg/Bus/Ata/AhciPei/AhciPei.uni | 21 + MdeModulePkg/Bus/Ata/AhciPei/AhciPeiExtra.uni | 19 + .../Bus/Ata/AhciPei/AhciPeiPassThru.c | 521 +++++ .../Bus/Ata/AhciPei/AhciPeiPassThru.h | 184 ++ MdeModulePkg/Bus/Ata/AhciPei/AhciPeiS3.c | 139 ++ .../Bus/Ata/AhciPei/AhciPeiStorageSecurity.c | 391 ++++ .../Bus/Ata/AhciPei/AhciPeiStorageSecurity.h | 247 ++ MdeModulePkg/Bus/Ata/AhciPei/DevicePath.c | 284 +++ MdeModulePkg/Bus/Ata/AhciPei/DmaMem.c | 270 +++ MdeModulePkg/MdeModulePkg.dsc | 1 + 14 files changed, 5184 insertions(+) create mode 100644 MdeModulePkg/Bus/Ata/AhciPei/AhciMode.c create mode 100644 MdeModulePkg/Bus/Ata/AhciPei/AhciPei.c create mode 100644 MdeModulePkg/Bus/Ata/AhciPei/AhciPei.h create mode 100644 MdeModulePkg/Bus/Ata/AhciPei/AhciPei.inf create mode 100644 MdeModulePkg/Bus/Ata/AhciPei/AhciPei.uni create mode 100644 MdeModulePkg/Bus/Ata/AhciPei/AhciPeiExtra.uni create mode 100644 MdeModulePkg/Bus/Ata/AhciPei/AhciPeiPassThru.c create mode 100644 MdeModulePkg/Bus/Ata/AhciPei/AhciPeiPassThru.h create mode 100644 MdeModulePkg/Bus/Ata/AhciPei/AhciPeiS3.c create mode 100644 MdeModulePkg/Bus/Ata/AhciPei/AhciPeiStorageSecurity.c create mode 100644 MdeModulePkg/Bus/Ata/AhciPei/AhciPeiStorageSecurity.h create mode 100644 MdeModulePkg/Bus/Ata/AhciPei/DevicePath.c create mode 100644 MdeModulePkg/Bus/Ata/AhciPei/DmaMem.c 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; +} diff --git a/MdeModulePkg/Bus/Ata/AhciPei/AhciPei.c b/MdeModulePkg/Bus/Ata/AhciPei/AhciPei.c new file mode 100644 index 0000000000..6df3d2c8d1 --- /dev/null +++ b/MdeModulePkg/Bus/Ata/AhciPei/AhciPei.c @@ -0,0 +1,310 @@ +/** @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" + +EFI_PEI_PPI_DESCRIPTOR mAhciAtaPassThruPpiListTemplate = { + (EFI_PEI_PPI_DESCRIPTOR_PPI | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST), + &gEdkiiPeiAtaPassThruPpiGuid, + NULL +}; + +EFI_PEI_PPI_DESCRIPTOR mAhciStorageSecurityPpiListTemplate = { + (EFI_PEI_PPI_DESCRIPTOR_PPI | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST), + &gEdkiiPeiStorageSecurityCommandPpiGuid, + NULL +}; + +EFI_PEI_NOTIFY_DESCRIPTOR mAhciEndOfPeiNotifyListTemplate = { + (EFI_PEI_PPI_DESCRIPTOR_NOTIFY_CALLBACK | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST), + &gEfiEndOfPeiSignalPpiGuid, + AhciPeimEndOfPei +}; + + +/** + Free the DMA resources allocated by an ATA AHCI controller. + + @param[in] Private A pointer to the PEI_AHCI_CONTROLLER_PRIVATE_DATA data + structure. + +**/ +VOID +AhciFreeDmaResource ( + IN PEI_AHCI_CONTROLLER_PRIVATE_DATA *Private + ) +{ + EFI_AHCI_REGISTERS *AhciRegisters; + + ASSERT (Private != NULL); + + AhciRegisters = &Private->AhciRegisters; + + if (AhciRegisters->AhciRFisMap != NULL) { + IoMmuFreeBuffer ( + EFI_SIZE_TO_PAGES (AhciRegisters->MaxRFisSize), + AhciRegisters->AhciRFis, + AhciRegisters->AhciRFisMap + ); + } + + if (AhciRegisters->AhciCmdListMap != NULL) { + IoMmuFreeBuffer ( + EFI_SIZE_TO_PAGES (AhciRegisters->MaxCmdListSize), + AhciRegisters->AhciCmdList, + AhciRegisters->AhciCmdListMap + ); + } + + if (AhciRegisters->AhciCmdTableMap != NULL) { + IoMmuFreeBuffer ( + EFI_SIZE_TO_PAGES (AhciRegisters->MaxCmdTableSize), + AhciRegisters->AhciCmdTable, + AhciRegisters->AhciCmdTableMap + ); + } + +} + +/** + One notified function to cleanup the allocated DMA buffers at EndOfPei. + + @param[in] PeiServices Pointer to PEI Services Table. + @param[in] NotifyDescriptor Pointer to the descriptor for the Notification + event that caused this function to execute. + @param[in] Ppi Pointer to the PPI data associated with this function. + + @retval EFI_SUCCESS The function completes successfully + +**/ +EFI_STATUS +EFIAPI +AhciPeimEndOfPei ( + IN EFI_PEI_SERVICES **PeiServices, + IN EFI_PEI_NOTIFY_DESCRIPTOR *NotifyDescriptor, + IN VOID *Ppi + ) +{ + PEI_AHCI_CONTROLLER_PRIVATE_DATA *Private; + + Private = GET_AHCI_PEIM_HC_PRIVATE_DATA_FROM_THIS_NOTIFY (NotifyDescriptor); + AhciFreeDmaResource (Private); + + return EFI_SUCCESS; +} + +/** + Entry point of the PEIM. + + @param[in] FileHandle Handle of the file being invoked. + @param[in] PeiServices Describes the list of possible PEI Services. + + @retval EFI_SUCCESS PPI successfully installed. + +**/ +EFI_STATUS +EFIAPI +AtaAhciPeimEntry ( + IN EFI_PEI_FILE_HANDLE FileHandle, + IN CONST EFI_PEI_SERVICES **PeiServices + ) +{ + EFI_STATUS Status; + EFI_BOOT_MODE BootMode; + EDKII_ATA_AHCI_HOST_CONTROLLER_PPI *AhciHcPpi; + UINT8 Controller; + UINTN MmioBase; + UINTN DevicePathLength; + EFI_DEVICE_PATH_PROTOCOL *DevicePath; + UINT32 PortBitMap; + PEI_AHCI_CONTROLLER_PRIVATE_DATA *Private; + UINT8 NumberOfPorts; + + DEBUG ((DEBUG_INFO, "%a: Enters.\n", __FUNCTION__)); + + // + // Get the current boot mode. + // + Status = PeiServicesGetBootMode (&BootMode); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "%a: Fail to get the current boot mode.\n", __FUNCTION__)); + return Status; + } + + // + // Locate the ATA AHCI host controller PPI. + // + Status = PeiServicesLocatePpi ( + &gEdkiiPeiAtaAhciHostControllerPpiGuid, + 0, + NULL, + (VOID **) &AhciHcPpi + ); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "%a: Failed to locate AtaAhciHostControllerPpi.\n", __FUNCTION__)); + return EFI_UNSUPPORTED; + } + + Controller = 0; + MmioBase = 0; + while (TRUE) { + Status = AhciHcPpi->GetAhciHcMmioBar ( + AhciHcPpi, + Controller, + &MmioBase + ); + // + // When status is error, meant no controller is found. + // + if (EFI_ERROR (Status)) { + break; + } + + Status = AhciHcPpi->GetAhciHcDevicePath ( + AhciHcPpi, + Controller, + &DevicePathLength, + &DevicePath + ); + if (EFI_ERROR (Status)) { + DEBUG (( + DEBUG_ERROR, "%a: Fail to allocate get the device path for Controller %d.\n", + __FUNCTION__, Controller + )); + return Status; + } + + // + // Check validity of the device path of the ATA AHCI controller. + // + Status = AhciIsHcDevicePathValid (DevicePath, DevicePathLength); + if (EFI_ERROR (Status)) { + DEBUG (( + DEBUG_ERROR, "%a: The device path is invalid for Controller %d.\n", + __FUNCTION__, Controller + )); + Controller++; + continue; + } + + // + // For S3 resume performance consideration, not all ports on an ATA AHCI + // controller will be enumerated/initialized. The driver consumes the + // content within S3StorageDeviceInitList LockBox to get the ports that + // will be enumerated/initialized during S3 resume. + // + if (BootMode == BOOT_ON_S3_RESUME) { + NumberOfPorts = AhciS3GetEumeratePorts (DevicePath, DevicePathLength, &PortBitMap); + if (NumberOfPorts == 0) { + // + // No ports need to be enumerated for this controller. + // + Controller++; + continue; + } + } else { + PortBitMap = MAX_UINT32; + } + + // + // Memory allocation for controller private data. + // + Private = AllocateZeroPool (sizeof (PEI_AHCI_CONTROLLER_PRIVATE_DATA)); + if (Private == NULL) { + DEBUG (( + DEBUG_ERROR, "%a: Fail to allocate private data for Controller %d.\n", + __FUNCTION__, Controller + )); + return EFI_OUT_OF_RESOURCES; + } + + // + // Initialize controller private data. + // + Private->Signature = AHCI_PEI_CONTROLLER_PRIVATE_DATA_SIGNATURE; + Private->MmioBase = MmioBase; + Private->DevicePathLength = DevicePathLength; + Private->DevicePath = DevicePath; + Private->PortBitMap = PortBitMap; + InitializeListHead (&Private->DeviceList); + + Status = AhciModeInitialization (Private); + if (EFI_ERROR (Status)) { + DEBUG (( + DEBUG_ERROR, + "%a: Controller initialization fail for Controller %d with Status - %r.\n", + __FUNCTION__, + Controller, + Status + )); + Controller++; + continue; + } + + Private->AtaPassThruMode.Attributes = EFI_ATA_PASS_THRU_ATTRIBUTES_PHYSICAL | + EFI_ATA_PASS_THRU_ATTRIBUTES_LOGICAL; + Private->AtaPassThruMode.IoAlign = sizeof (UINTN); + Private->AtaPassThruPpi.Revision = EDKII_PEI_ATA_PASS_THRU_PPI_REVISION; + Private->AtaPassThruPpi.Mode = &Private->AtaPassThruMode; + Private->AtaPassThruPpi.PassThru = AhciAtaPassThruPassThru; + Private->AtaPassThruPpi.GetNextPort = AhciAtaPassThruGetNextPort; + Private->AtaPassThruPpi.GetNextDevice = AhciAtaPassThruGetNextDevice; + Private->AtaPassThruPpi.GetDevicePath = AhciAtaPassThruGetDevicePath; + CopyMem ( + &Private->AtaPassThruPpiList, + &mAhciAtaPassThruPpiListTemplate, + sizeof (EFI_PEI_PPI_DESCRIPTOR) + ); + Private->AtaPassThruPpiList.Ppi = &Private->AtaPassThruPpi; + PeiServicesInstallPpi (&Private->AtaPassThruPpiList); + + if (Private->TrustComputingDevices != 0) { + DEBUG (( + DEBUG_INFO, + "%a: Security Security Command PPI will be produced for Controller %d.\n", + __FUNCTION__, Controller + )); + Private->StorageSecurityPpi.Revision = EDKII_STORAGE_SECURITY_PPI_REVISION; + Private->StorageSecurityPpi.GetNumberofDevices = AhciStorageSecurityGetDeviceNo; + Private->StorageSecurityPpi.GetDevicePath = AhciStorageSecurityGetDevicePath; + Private->StorageSecurityPpi.ReceiveData = AhciStorageSecurityReceiveData; + Private->StorageSecurityPpi.SendData = AhciStorageSecuritySendData; + CopyMem ( + &Private->StorageSecurityPpiList, + &mAhciStorageSecurityPpiListTemplate, + sizeof (EFI_PEI_PPI_DESCRIPTOR) + ); + Private->StorageSecurityPpiList.Ppi = &Private->StorageSecurityPpi; + PeiServicesInstallPpi (&Private->StorageSecurityPpiList); + } + + CopyMem ( + &Private->EndOfPeiNotifyList, + &mAhciEndOfPeiNotifyListTemplate, + sizeof (EFI_PEI_NOTIFY_DESCRIPTOR) + ); + PeiServicesNotifyPpi (&Private->EndOfPeiNotifyList); + + DEBUG (( + DEBUG_INFO, "%a: Controller %d has been successfully initialized.\n", + __FUNCTION__, Controller + )); + Controller++; + } + + return EFI_SUCCESS; +} diff --git a/MdeModulePkg/Bus/Ata/AhciPei/AhciPei.h b/MdeModulePkg/Bus/Ata/AhciPei/AhciPei.h new file mode 100644 index 0000000000..b2c586de54 --- /dev/null +++ b/MdeModulePkg/Bus/Ata/AhciPei/AhciPei.h @@ -0,0 +1,708 @@ +/** @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. + +**/ + +#ifndef _AHCI_PEI_H_ +#define _AHCI_PEI_H_ + +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +// +// Structure forward declarations +// +typedef struct _PEI_AHCI_CONTROLLER_PRIVATE_DATA PEI_AHCI_CONTROLLER_PRIVATE_DATA; + +#include "AhciPeiPassThru.h" +#include "AhciPeiStorageSecurity.h" + +// +// ATA AHCI driver implementation related definitions +// +// +// Refer SATA1.0a spec section 5.2, the Phy detection time should be less than 10ms. +// The value is in millisecond units. Add a bit of margin for robustness. +// +#define AHCI_BUS_PHY_DETECT_TIMEOUT 15 +// +// Refer SATA1.0a spec, the bus reset time should be less than 1s. +// The value is in 100ns units. +// +#define AHCI_PEI_RESET_TIMEOUT 10000000 +// +// Time out Value for ATA pass through protocol, in 100ns units. +// +#define ATA_TIMEOUT 30000000 +// +// Maximal number of Physical Region Descriptor Table entries supported. +// +#define AHCI_MAX_PRDT_NUMBER 8 + +#define AHCI_CAPABILITY_OFFSET 0x0000 +#define AHCI_CAP_SAM BIT18 +#define AHCI_CAP_SSS BIT27 + +#define AHCI_GHC_OFFSET 0x0004 +#define AHCI_GHC_RESET BIT0 +#define AHCI_GHC_ENABLE BIT31 + +#define AHCI_IS_OFFSET 0x0008 +#define AHCI_PI_OFFSET 0x000C + +#define AHCI_MAX_PORTS 32 + +typedef struct { + UINT32 Lower32; + UINT32 Upper32; +} DATA_32; + +typedef union { + DATA_32 Uint32; + UINT64 Uint64; +} DATA_64; + +#define AHCI_ATAPI_SIG_MASK 0xFFFF0000 +#define AHCI_ATA_DEVICE_SIG 0x00000000 + +// +// Each PRDT entry can point to a memory block up to 4M byte +// +#define AHCI_MAX_DATA_PER_PRDT 0x400000 + +#define AHCI_FIS_REGISTER_H2D 0x27 //Register FIS - Host to Device +#define AHCI_FIS_REGISTER_H2D_LENGTH 20 +#define AHCI_FIS_REGISTER_D2H 0x34 //Register FIS - Device to Host +#define AHCI_FIS_PIO_SETUP 0x5F //PIO Setup FIS - Device to Host + +#define AHCI_D2H_FIS_OFFSET 0x40 +#define AHCI_PIO_FIS_OFFSET 0x20 +#define AHCI_FIS_TYPE_MASK 0xFF + +// +// Port register +// +#define AHCI_PORT_START 0x0100 +#define AHCI_PORT_REG_WIDTH 0x0080 +#define AHCI_PORT_CLB 0x0000 +#define AHCI_PORT_CLBU 0x0004 +#define AHCI_PORT_FB 0x0008 +#define AHCI_PORT_FBU 0x000C +#define AHCI_PORT_IS 0x0010 +#define AHCI_PORT_IE 0x0014 +#define AHCI_PORT_CMD 0x0018 +#define AHCI_PORT_CMD_ST BIT0 +#define AHCI_PORT_CMD_SUD BIT1 +#define AHCI_PORT_CMD_POD BIT2 +#define AHCI_PORT_CMD_CLO BIT3 +#define AHCI_PORT_CMD_FRE BIT4 +#define AHCI_PORT_CMD_FR BIT14 +#define AHCI_PORT_CMD_CR BIT15 +#define AHCI_PORT_CMD_CPD BIT20 +#define AHCI_PORT_CMD_ATAPI BIT24 +#define AHCI_PORT_CMD_DLAE BIT25 +#define AHCI_PORT_CMD_ALPE BIT26 +#define AHCI_PORT_CMD_ACTIVE (1 << 28) +#define AHCI_PORT_CMD_ICC_MASK (BIT28 | BIT29 | BIT30 | BIT31) + +#define AHCI_PORT_TFD 0x0020 +#define AHCI_PORT_TFD_ERR BIT0 +#define AHCI_PORT_TFD_DRQ BIT3 +#define AHCI_PORT_TFD_BSY BIT7 +#define AHCI_PORT_TFD_MASK (BIT7 | BIT3 | BIT0) + +#define AHCI_PORT_SIG 0x0024 +#define AHCI_PORT_SSTS 0x0028 +#define AHCI_PORT_SSTS_DET_MASK 0x000F +#define AHCI_PORT_SSTS_DET 0x0001 +#define AHCI_PORT_SSTS_DET_PCE 0x0003 + +#define AHCI_PORT_SCTL 0x002C +#define AHCI_PORT_SCTL_IPM_INIT 0x0300 + +#define AHCI_PORT_SERR 0x0030 +#define AHCI_PORT_CI 0x0038 + +#define IS_ALIGNED(addr, size) (((UINTN) (addr) & (size - 1)) == 0) +#define TIMER_PERIOD_SECONDS(Seconds) MultU64x32((UINT64)(Seconds), 10000000) + +#pragma pack(1) + +// +// Received FIS structure +// +typedef struct { + UINT8 AhciDmaSetupFis[0x1C]; // Dma Setup Fis: offset 0x00 + UINT8 AhciDmaSetupFisRsvd[0x04]; + UINT8 AhciPioSetupFis[0x14]; // Pio Setup Fis: offset 0x20 + UINT8 AhciPioSetupFisRsvd[0x0C]; + UINT8 AhciD2HRegisterFis[0x14]; // D2H Register Fis: offset 0x40 + UINT8 AhciD2HRegisterFisRsvd[0x04]; + UINT64 AhciSetDeviceBitsFis; // Set Device Bits Fix: offset 0x58 + UINT8 AhciUnknownFis[0x40]; // Unkonwn Fis: offset 0x60 + UINT8 AhciUnknownFisRsvd[0x60]; +} EFI_AHCI_RECEIVED_FIS; + +// +// Command List structure includes total 32 entries. +// The entry Data structure is listed at the following. +// +typedef struct { + UINT32 AhciCmdCfl:5; //Command FIS Length + UINT32 AhciCmdA:1; //ATAPI + UINT32 AhciCmdW:1; //Write + UINT32 AhciCmdP:1; //Prefetchable + UINT32 AhciCmdR:1; //Reset + UINT32 AhciCmdB:1; //BIST + UINT32 AhciCmdC:1; //Clear Busy upon R_OK + UINT32 AhciCmdRsvd:1; + UINT32 AhciCmdPmp:4; //Port Multiplier Port + UINT32 AhciCmdPrdtl:16; //Physical Region Descriptor Table Length + UINT32 AhciCmdPrdbc; //Physical Region Descriptor Byte Count + UINT32 AhciCmdCtba; //Command Table Descriptor Base Address + UINT32 AhciCmdCtbau; //Command Table Descriptor Base Address Upper 32-BITs + UINT32 AhciCmdRsvd1[4]; +} EFI_AHCI_COMMAND_LIST; + +// +// This is a software constructed FIS. +// For Data transfer operations, this is the H2D Register FIS format as +// specified in the Serial ATA Revision 2.6 specification. +// +typedef struct { + UINT8 AhciCFisType; + UINT8 AhciCFisPmNum:4; + UINT8 AhciCFisRsvd:1; + UINT8 AhciCFisRsvd1:1; + UINT8 AhciCFisRsvd2:1; + UINT8 AhciCFisCmdInd:1; + UINT8 AhciCFisCmd; + UINT8 AhciCFisFeature; + UINT8 AhciCFisSecNum; + UINT8 AhciCFisClyLow; + UINT8 AhciCFisClyHigh; + UINT8 AhciCFisDevHead; + UINT8 AhciCFisSecNumExp; + UINT8 AhciCFisClyLowExp; + UINT8 AhciCFisClyHighExp; + UINT8 AhciCFisFeatureExp; + UINT8 AhciCFisSecCount; + UINT8 AhciCFisSecCountExp; + UINT8 AhciCFisRsvd3; + UINT8 AhciCFisControl; + UINT8 AhciCFisRsvd4[4]; + UINT8 AhciCFisRsvd5[44]; +} EFI_AHCI_COMMAND_FIS; + +// +// ACMD: ATAPI command (12 or 16 bytes) +// +typedef struct { + UINT8 AtapiCmd[0x10]; +} EFI_AHCI_ATAPI_COMMAND; + +// +// Physical Region Descriptor Table includes up to 65535 entries +// The entry data structure is listed at the following. +// the actual entry number comes from the PRDTL field in the command +// list entry for this command slot. +// +typedef struct { + UINT32 AhciPrdtDba; //Data Base Address + UINT32 AhciPrdtDbau; //Data Base Address Upper 32-BITs + UINT32 AhciPrdtRsvd; + UINT32 AhciPrdtDbc:22; //Data Byte Count + UINT32 AhciPrdtRsvd1:9; + UINT32 AhciPrdtIoc:1; //Interrupt on Completion +} EFI_AHCI_COMMAND_PRDT; + +// +// Command table Data strucute which is pointed to by the entry in the command list +// +typedef struct { + EFI_AHCI_COMMAND_FIS CommandFis; // A software constructed FIS. + EFI_AHCI_ATAPI_COMMAND AtapiCmd; // 12 or 16 bytes ATAPI cmd. + UINT8 Reserved[0x30]; + // + // The scatter/gather list for Data transfer. + // + EFI_AHCI_COMMAND_PRDT PrdtTable[AHCI_MAX_PRDT_NUMBER]; +} EFI_AHCI_COMMAND_TABLE; + +#pragma pack() + +typedef struct { + EFI_AHCI_RECEIVED_FIS *AhciRFis; + EFI_AHCI_COMMAND_LIST *AhciCmdList; + EFI_AHCI_COMMAND_TABLE *AhciCmdTable; + UINTN MaxRFisSize; + UINTN MaxCmdListSize; + UINTN MaxCmdTableSize; + VOID *AhciRFisMap; + VOID *AhciCmdListMap; + VOID *AhciCmdTableMap; +} EFI_AHCI_REGISTERS; + +// +// Unique signature for AHCI ATA device information structure. +// +#define AHCI_PEI_ATA_DEVICE_DATA_SIGNATURE SIGNATURE_32 ('A', 'P', 'A', 'D') + +// +// AHCI mode device information structure. +// +typedef struct { + UINT32 Signature; + LIST_ENTRY Link; + + UINT16 Port; + UINT16 PortMultiplier; + UINT8 FisIndex; + UINTN DeviceIndex; + ATA_IDENTIFY_DATA *IdentifyData; + + BOOLEAN Lba48Bit; + BOOLEAN TrustComputing; + UINTN TrustComputingDeviceIndex; + EFI_PEI_BLOCK_IO2_MEDIA Media; + + PEI_AHCI_CONTROLLER_PRIVATE_DATA *Private; +} PEI_AHCI_ATA_DEVICE_DATA; + +#define AHCI_PEI_ATA_DEVICE_INFO_FROM_THIS(a) \ + CR (a, \ + PEI_AHCI_ATA_DEVICE_DATA, \ + Link, \ + AHCI_PEI_ATA_DEVICE_DATA_SIGNATURE \ + ); + +// +// Unique signature for private data structure. +// +#define AHCI_PEI_CONTROLLER_PRIVATE_DATA_SIGNATURE SIGNATURE_32 ('A','P','C','P') + +// +// ATA AHCI controller private data structure. +// +struct _PEI_AHCI_CONTROLLER_PRIVATE_DATA { + UINT32 Signature; + UINTN MmioBase; + UINTN DevicePathLength; + EFI_DEVICE_PATH_PROTOCOL *DevicePath; + + EFI_ATA_PASS_THRU_MODE AtaPassThruMode; + EDKII_PEI_ATA_PASS_THRU_PPI AtaPassThruPpi; + EDKII_PEI_STORAGE_SECURITY_CMD_PPI StorageSecurityPpi; + EFI_PEI_PPI_DESCRIPTOR AtaPassThruPpiList; + EFI_PEI_PPI_DESCRIPTOR BlkIoPpiList; + EFI_PEI_PPI_DESCRIPTOR BlkIo2PpiList; + EFI_PEI_PPI_DESCRIPTOR StorageSecurityPpiList; + EFI_PEI_NOTIFY_DESCRIPTOR EndOfPeiNotifyList; + + EFI_AHCI_REGISTERS AhciRegisters; + + UINT32 PortBitMap; + UINT32 ActiveDevices; + UINT32 TrustComputingDevices; + LIST_ENTRY DeviceList; + + UINT16 PreviousPort; + UINT16 PreviousPortMultiplier; +}; + +#define GET_AHCI_PEIM_HC_PRIVATE_DATA_FROM_THIS_PASS_THRU(a) \ + CR (a, PEI_AHCI_CONTROLLER_PRIVATE_DATA, AtaPassThruPpi, AHCI_PEI_CONTROLLER_PRIVATE_DATA_SIGNATURE) +#define GET_AHCI_PEIM_HC_PRIVATE_DATA_FROM_THIS_BLKIO(a) \ + CR (a, PEI_AHCI_CONTROLLER_PRIVATE_DATA, BlkIoPpi, AHCI_PEI_CONTROLLER_PRIVATE_DATA_SIGNATURE) +#define GET_AHCI_PEIM_HC_PRIVATE_DATA_FROM_THIS_BLKIO2(a) \ + CR (a, PEI_AHCI_CONTROLLER_PRIVATE_DATA, BlkIo2Ppi, AHCI_PEI_CONTROLLER_PRIVATE_DATA_SIGNATURE) +#define GET_AHCI_PEIM_HC_PRIVATE_DATA_FROM_THIS_STROAGE_SECURITY(a) \ + CR (a, PEI_AHCI_CONTROLLER_PRIVATE_DATA, StorageSecurityPpi, AHCI_PEI_CONTROLLER_PRIVATE_DATA_SIGNATURE) +#define GET_AHCI_PEIM_HC_PRIVATE_DATA_FROM_THIS_NOTIFY(a) \ + CR (a, PEI_AHCI_CONTROLLER_PRIVATE_DATA, EndOfPeiNotifyList, AHCI_PEI_CONTROLLER_PRIVATE_DATA_SIGNATURE) + +// +// Global variables +// +extern UINT32 mMaxTransferBlockNumber[2]; + +// +// Internal functions +// + +/** + Allocates pages that are suitable for an OperationBusMasterCommonBuffer or + OperationBusMasterCommonBuffer64 mapping. + + @param Pages The number of pages to allocate. + @param HostAddress A pointer to store the base system memory address of the + allocated range. + @param DeviceAddress The resulting map address for the bus master PCI controller to use to + access the hosts HostAddress. + @param Mapping A resulting value to pass to Unmap(). + + @retval EFI_SUCCESS The requested memory pages were allocated. + @retval EFI_UNSUPPORTED Attributes is unsupported. The only legal attribute bits are + MEMORY_WRITE_COMBINE and MEMORY_CACHED. + @retval EFI_INVALID_PARAMETER One or more parameters are invalid. + @retval EFI_OUT_OF_RESOURCES The memory pages could not be allocated. + +**/ +EFI_STATUS +IoMmuAllocateBuffer ( + IN UINTN Pages, + OUT VOID **HostAddress, + OUT EFI_PHYSICAL_ADDRESS *DeviceAddress, + OUT VOID **Mapping + ); + +/** + Frees memory that was allocated with AllocateBuffer(). + + @param Pages The number of pages to free. + @param HostAddress The base system memory address of the allocated range. + @param Mapping The mapping value returned from Map(). + + @retval EFI_SUCCESS The requested memory pages were freed. + @retval EFI_INVALID_PARAMETER The memory range specified by HostAddress and Pages + was not allocated with AllocateBuffer(). + +**/ +EFI_STATUS +IoMmuFreeBuffer ( + IN UINTN Pages, + IN VOID *HostAddress, + IN VOID *Mapping + ); + +/** + Provides the controller-specific addresses required to access system memory from a + DMA bus master. + + @param Operation Indicates if the bus master is going to read or write to system memory. + @param HostAddress The system memory address to map to the PCI controller. + @param NumberOfBytes On input the number of bytes to map. On output the number of bytes + that were mapped. + @param DeviceAddress The resulting map address for the bus master PCI controller to use to + access the hosts HostAddress. + @param Mapping A resulting value to pass to Unmap(). + + @retval EFI_SUCCESS The range was mapped for the returned NumberOfBytes. + @retval EFI_UNSUPPORTED The HostAddress cannot be mapped as a common buffer. + @retval EFI_INVALID_PARAMETER One or more parameters are invalid. + @retval EFI_OUT_OF_RESOURCES The request could not be completed due to a lack of resources. + @retval EFI_DEVICE_ERROR The system hardware could not map the requested address. + +**/ +EFI_STATUS +IoMmuMap ( + IN EDKII_IOMMU_OPERATION Operation, + IN VOID *HostAddress, + IN OUT UINTN *NumberOfBytes, + OUT EFI_PHYSICAL_ADDRESS *DeviceAddress, + OUT VOID **Mapping + ); + +/** + Completes the Map() operation and releases any corresponding resources. + + @param Mapping The mapping value returned from Map(). + + @retval EFI_SUCCESS The range was unmapped. + @retval EFI_INVALID_PARAMETER Mapping is not a value that was returned by Map(). + @retval EFI_DEVICE_ERROR The data was not committed to the target system memory. +**/ +EFI_STATUS +IoMmuUnmap ( + IN VOID *Mapping + ); + +/** + One notified function to cleanup the allocated DMA buffers at EndOfPei. + + @param[in] PeiServices Pointer to PEI Services Table. + @param[in] NotifyDescriptor Pointer to the descriptor for the Notification + event that caused this function to execute. + @param[in] Ppi Pointer to the PPI data associated with this function. + + @retval EFI_SUCCESS The function completes successfully + +**/ +EFI_STATUS +EFIAPI +AhciPeimEndOfPei ( + IN EFI_PEI_SERVICES **PeiServices, + IN EFI_PEI_NOTIFY_DESCRIPTOR *NotifyDescriptor, + IN VOID *Ppi + ); + +/** + 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 + ); + +/** + 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 + ); + +/** + 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 + ); + +/** + 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 + ); + +/** + 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 + ); + +/** + Returns a pointer to the next node in a device path. + + If Node is NULL, then ASSERT(). + + @param Node A pointer to a device path node data structure. + + @return a pointer to the device path node that follows the device path node + specified by Node. + +**/ +EFI_DEVICE_PATH_PROTOCOL * +NextDevicePathNode ( + IN CONST VOID *Node + ); + +/** + Get the size of the current device path instance. + + @param[in] DevicePath A pointer to the EFI_DEVICE_PATH_PROTOCOL + structure. + @param[out] InstanceSize The size of the current device path instance. + @param[out] EntireDevicePathEnd Indicate whether the instance is the last + one in the device path strucure. + + @retval EFI_SUCCESS The size of the current device path instance is fetched. + @retval Others Fails to get the size of the current device path instance. + +**/ +EFI_STATUS +GetDevicePathInstanceSize ( + IN EFI_DEVICE_PATH_PROTOCOL *DevicePath, + OUT UINTN *InstanceSize, + OUT BOOLEAN *EntireDevicePathEnd + ); + +/** + Check the validity of the device path of a ATA AHCI host controller. + + @param[in] DevicePath A pointer to the EFI_DEVICE_PATH_PROTOCOL + structure. + @param[in] DevicePathLength The length of the device path. + + @retval EFI_SUCCESS The device path is valid. + @retval EFI_INVALID_PARAMETER The device path is invalid. + +**/ +EFI_STATUS +AhciIsHcDevicePathValid ( + IN EFI_DEVICE_PATH_PROTOCOL *DevicePath, + IN UINTN DevicePathLength + ); + +/** + Build the device path for an ATA device with given port and port multiplier number. + + @param[in] Private A pointer to the PEI_AHCI_CONTROLLER_PRIVATE_DATA + data structure. + @param[in] Port The given port number. + @param[in] PortMultiplierPort The given port multiplier number. + @param[out] DevicePathLength The length of the device path in bytes specified + by DevicePath. + @param[out] DevicePath The device path of ATA device. + + @retval EFI_SUCCESS The operation succeeds. + @retval EFI_INVALID_PARAMETER The parameters are invalid. + @retval EFI_OUT_OF_RESOURCES The operation fails due to lack of resources. + +**/ +EFI_STATUS +AhciBuildDevicePath ( + IN PEI_AHCI_CONTROLLER_PRIVATE_DATA *Private, + IN UINT16 Port, + IN UINT16 PortMultiplierPort, + OUT UINTN *DevicePathLength, + OUT EFI_DEVICE_PATH_PROTOCOL **DevicePath + ); + +/** + Collect the ports that need to be enumerated on a controller for S3 phase. + + @param[in] HcDevicePath Device path of the controller. + @param[in] HcDevicePathLength Length of the device path specified by + HcDevicePath. + @param[out] PortBitMap Bitmap that indicates the ports that need + to be enumerated on the controller. + + @retval The number of ports that need to be enumerated. + +**/ +UINT8 +AhciS3GetEumeratePorts ( + IN EFI_DEVICE_PATH_PROTOCOL *HcDevicePath, + IN UINTN HcDevicePathLength, + OUT UINT32 *PortBitMap + ); + +#endif diff --git a/MdeModulePkg/Bus/Ata/AhciPei/AhciPei.inf b/MdeModulePkg/Bus/Ata/AhciPei/AhciPei.inf new file mode 100644 index 0000000000..e02d2272fb --- /dev/null +++ b/MdeModulePkg/Bus/Ata/AhciPei/AhciPei.inf @@ -0,0 +1,74 @@ +## @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. +# +## + +[Defines] + INF_VERSION = 0x00010005 + BASE_NAME = AhciPei + MODULE_UNI_FILE = AhciPei.uni + FILE_GUID = 79E5CA15-7A2D-4F37-A63B-D1C7BBCA47AD + MODULE_TYPE = PEIM + VERSION_STRING = 1.0 + ENTRY_POINT = AtaAhciPeimEntry + +# +# The following information is for reference only and not required by the build tools. +# +# VALID_ARCHITECTURES = IA32 X64 EBC +# + +[Sources] + AhciPei.c + AhciPei.h + AhciPeiPassThru.c + AhciPeiPassThru.h + AhciPeiS3.c + AhciPeiStorageSecurity.c + AhciPeiStorageSecurity.h + AhciMode.c + DevicePath.c + DmaMem.c + +[Packages] + MdePkg/MdePkg.dec + MdeModulePkg/MdeModulePkg.dec + +[LibraryClasses] + DebugLib + PeiServicesLib + MemoryAllocationLib + BaseMemoryLib + IoLib + TimerLib + LockBoxLib + PeimEntryPoint + +[Ppis] + gEdkiiPeiAtaAhciHostControllerPpiGuid ## CONSUMES + gEdkiiIoMmuPpiGuid ## CONSUMES + gEfiEndOfPeiSignalPpiGuid ## CONSUMES + gEdkiiPeiAtaPassThruPpiGuid ## SOMETIMES_PRODUCES + gEdkiiPeiStorageSecurityCommandPpiGuid ## SOMETIMES_PRODUCES + +[Guids] + gS3StorageDeviceInitListGuid ## SOMETIMES_CONSUMES ## UNDEFINED + +[Depex] + gEfiPeiMemoryDiscoveredPpiGuid AND + gEfiPeiMasterBootModePpiGuid AND + gEdkiiPeiAtaAhciHostControllerPpiGuid + +[UserExtensions.TianoCore."ExtraFiles"] + AhciPeiExtra.uni diff --git a/MdeModulePkg/Bus/Ata/AhciPei/AhciPei.uni b/MdeModulePkg/Bus/Ata/AhciPei/AhciPei.uni new file mode 100644 index 0000000000..6dd3a74cc5 --- /dev/null +++ b/MdeModulePkg/Bus/Ata/AhciPei/AhciPei.uni @@ -0,0 +1,21 @@ +// /** @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. +// +// **/ + + +#string STR_MODULE_ABSTRACT #language en-US "Manage AHCI mode ATA hard disk device at PEI phase" + +#string STR_MODULE_DESCRIPTION #language en-US "The AhciPei driver is used to manage ATA hard disk device working under AHCI mode at PEI phase." diff --git a/MdeModulePkg/Bus/Ata/AhciPei/AhciPeiExtra.uni b/MdeModulePkg/Bus/Ata/AhciPei/AhciPeiExtra.uni new file mode 100644 index 0000000000..295f426473 --- /dev/null +++ b/MdeModulePkg/Bus/Ata/AhciPei/AhciPeiExtra.uni @@ -0,0 +1,19 @@ +// /** @file +// AhciPei Localized Strings and Content +// +// 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. +// +// **/ + +#string STR_PROPERTIES_MODULE_NAME +#language en-US +"AHCI Bus Peim" diff --git a/MdeModulePkg/Bus/Ata/AhciPei/AhciPeiPassThru.c b/MdeModulePkg/Bus/Ata/AhciPei/AhciPeiPassThru.c new file mode 100644 index 0000000000..394d6d98ad --- /dev/null +++ b/MdeModulePkg/Bus/Ata/AhciPei/AhciPeiPassThru.c @@ -0,0 +1,521 @@ +/** @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" + +/** + Traverse the attached ATA devices list to find out the device with given Port + and PortMultiplierPort. + + @param[in] Private A pointer to the PEI_AHCI_CONTROLLER_PRIVATE_DATA + instance. + @param[in] Port The port number of the ATA device. + @param[in] PortMultiplierPort The port multiplier port number of the ATA device. + + @retval The pointer to the PEI_AHCI_ATA_DEVICE_DATA structure of the device + info to access. + +**/ +PEI_AHCI_ATA_DEVICE_DATA * +SearchDeviceByPort ( + IN PEI_AHCI_CONTROLLER_PRIVATE_DATA *Private, + IN UINT16 Port, + IN UINT16 PortMultiplierPort + ) +{ + PEI_AHCI_ATA_DEVICE_DATA *DeviceData; + LIST_ENTRY *Node; + + Node = GetFirstNode (&Private->DeviceList); + while (!IsNull (&Private->DeviceList, Node)) { + DeviceData = AHCI_PEI_ATA_DEVICE_INFO_FROM_THIS (Node); + + if ((DeviceData->Port == Port) && + (DeviceData->PortMultiplier == PortMultiplierPort)) { + return DeviceData; + } + + Node = GetNextNode (&Private->DeviceList, Node); + } + + return NULL; +} + +/** + Sends an ATA command to an ATA device that is attached to the ATA controller. + + @param[in] Private Pointer to the PEI_AHCI_CONTROLLER_PRIVATE_DATA. + @param[in] Port The port number of the ATA device. + @param[in] PortMultiplierPort The port multiplier port number of the ATA + device. + @param[in] FisIndex The index of the FIS. + @param[in,out] Packet A pointer to the ATA command to send to + the ATA device specified by Port and + PortMultiplierPort. + + @retval EFI_SUCCESS The ATA command was sent by the host. For + bi-directional commands, InTransferLength bytes + were transferred from InDataBuffer. For write + and bi-directional commands, OutTransferLength + bytes were transferred by OutDataBuffer. + @retval EFI_BAD_BUFFER_SIZE The ATA command was not executed. The number + of bytes that could be transferred is returned + in InTransferLength. For write and bi-directional + commands, OutTransferLength bytes were transferred + by OutDataBuffer. + @retval EFI_NOT_READY The ATA command could not be sent because there + are too many ATA commands already queued. The + caller may retry again later. + @retval EFI_DEVICE_ERROR A device error occurred while attempting to + send the ATA command. + @retval EFI_INVALID_PARAMETER Port, PortMultiplierPort, or the contents of + Acb are invalid. The ATA command was not sent, + so no additional status information is available. + +**/ +EFI_STATUS +AhciPassThruExecute ( + IN PEI_AHCI_CONTROLLER_PRIVATE_DATA *Private, + IN UINT16 Port, + IN UINT16 PortMultiplierPort, + IN UINT8 FisIndex, + IN OUT EFI_ATA_PASS_THRU_COMMAND_PACKET *Packet + ) +{ + EFI_STATUS Status; + + switch (Packet->Protocol) { + case EFI_ATA_PASS_THRU_PROTOCOL_ATA_NON_DATA: + Status = AhciNonDataTransfer ( + Private, + (UINT8) Port, + (UINT8) PortMultiplierPort, + FisIndex, + Packet->Acb, + Packet->Asb, + Packet->Timeout + ); + break; + case EFI_ATA_PASS_THRU_PROTOCOL_PIO_DATA_IN: + Status = AhciPioTransfer ( + Private, + (UINT8) Port, + (UINT8) PortMultiplierPort, + FisIndex, + TRUE, + Packet->Acb, + Packet->Asb, + Packet->InDataBuffer, + Packet->InTransferLength, + Packet->Timeout + ); + break; + case EFI_ATA_PASS_THRU_PROTOCOL_PIO_DATA_OUT: + Status = AhciPioTransfer ( + Private, + (UINT8) Port, + (UINT8) PortMultiplierPort, + FisIndex, + FALSE, + Packet->Acb, + Packet->Asb, + Packet->OutDataBuffer, + Packet->OutTransferLength, + Packet->Timeout + ); + break; + default: + return EFI_UNSUPPORTED; + } + + return Status; +} + +/** + Sends an ATA command to an ATA device that is attached to the ATA controller. + + @param[in] This The PPI instance pointer. + @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,out] Packet A pointer to the ATA command to send to + the ATA device specified by Port and + PortMultiplierPort. + + @retval EFI_SUCCESS The ATA command was sent by the host. For + bi-directional commands, InTransferLength bytes + were transferred from InDataBuffer. For write + and bi-directional commands, OutTransferLength + bytes were transferred by OutDataBuffer. + @retval EFI_NOT_FOUND The specified ATA device is not found. + @retval EFI_INVALID_PARAMETER The contents of Acb are invalid. The ATA command + was not sent, so no additional status information + is available. + @retval EFI_BAD_BUFFER_SIZE The ATA command was not executed. The number + of bytes that could be transferred is returned + in InTransferLength. For write and bi-directional + commands, OutTransferLength bytes were transferred + by OutDataBuffer. + @retval EFI_NOT_READY The ATA command could not be sent because there + are too many ATA commands already queued. The + caller may retry again later. + @retval EFI_DEVICE_ERROR A device error occurred while attempting to + send the ATA command. + +**/ +EFI_STATUS +EFIAPI +AhciAtaPassThruPassThru ( + IN EDKII_PEI_ATA_PASS_THRU_PPI *This, + IN UINT16 Port, + IN UINT16 PortMultiplierPort, + IN OUT EFI_ATA_PASS_THRU_COMMAND_PACKET *Packet + ) +{ + UINT32 IoAlign; + PEI_AHCI_CONTROLLER_PRIVATE_DATA *Private; + PEI_AHCI_ATA_DEVICE_DATA *DeviceData; + UINT32 MaxSectorCount; + UINT32 BlockSize; + + if (This == NULL || Packet == NULL) { + return EFI_INVALID_PARAMETER; + } + + IoAlign = This->Mode->IoAlign; + if ((IoAlign > 1) && !IS_ALIGNED (Packet->InDataBuffer, IoAlign)) { + return EFI_INVALID_PARAMETER; + } + + if ((IoAlign > 1) && !IS_ALIGNED (Packet->OutDataBuffer, IoAlign)) { + return EFI_INVALID_PARAMETER; + } + + if ((IoAlign > 1) && !IS_ALIGNED (Packet->Asb, IoAlign)) { + return EFI_INVALID_PARAMETER; + } + + Private = GET_AHCI_PEIM_HC_PRIVATE_DATA_FROM_THIS_PASS_THRU (This); + DeviceData = SearchDeviceByPort (Private, Port, PortMultiplierPort); + if (DeviceData == NULL) { + return EFI_NOT_FOUND; + } + + MaxSectorCount = mMaxTransferBlockNumber[DeviceData->Lba48Bit]; + BlockSize = DeviceData->Media.BlockSize; + + // + // Convert the transfer length from sector count to byte. + // + if (((Packet->Length & EFI_ATA_PASS_THRU_LENGTH_BYTES) == 0) && + (Packet->InTransferLength != 0)) { + Packet->InTransferLength = Packet->InTransferLength * BlockSize; + } + + // + // Convert the transfer length from sector count to byte. + // + if (((Packet->Length & EFI_ATA_PASS_THRU_LENGTH_BYTES) == 0) && + (Packet->OutTransferLength != 0)) { + Packet->OutTransferLength = Packet->OutTransferLength * BlockSize; + } + + // + // If the data buffer described by InDataBuffer/OutDataBuffer and + // InTransferLength/OutTransferLength is too big to be transferred in a single + // command, then no data is transferred and EFI_BAD_BUFFER_SIZE is returned. + // + if (((Packet->InTransferLength != 0) && (Packet->InTransferLength > MaxSectorCount * BlockSize)) || + ((Packet->OutTransferLength != 0) && (Packet->OutTransferLength > MaxSectorCount * BlockSize))) { + return EFI_BAD_BUFFER_SIZE; + } + + return AhciPassThruExecute ( + Private, + DeviceData->Port, + DeviceData->PortMultiplier, + DeviceData->FisIndex, + Packet + ); +} + +/** + Used to retrieve the list of legal port numbers for ATA devices on an ATA controller. + These can either be the list of ports where ATA devices are actually present or the + list of legal port numbers for the ATA controller. Regardless, the caller of this + function must probe the port number returned to see if an ATA device is actually + present at that location on the ATA controller. + + The GetNextPort() function retrieves the port number on an ATA controller. If on + input Port is 0xFFFF, then the port number of the first port on the ATA controller + is returned in Port and EFI_SUCCESS is returned. + + If Port is a port number that was returned on a previous call to GetNextPort(), + then the port number of the next port on the ATA controller is returned in Port, + and EFI_SUCCESS is returned. If Port is not 0xFFFF and Port was not returned on + a previous call to GetNextPort(), then EFI_INVALID_PARAMETER is returned. + + If Port is the port number of the last port on the ATA controller, then EFI_NOT_FOUND + is returned. + + @param[in] This The PPI instance pointer. + @param[in,out] Port On input, a pointer to the port number on the ATA controller. + On output, a pointer to the next port number on the ATA + controller. An input value of 0xFFFF retrieves the first + port number on the ATA controller. + + @retval EFI_SUCCESS The next port number on the ATA controller was + returned in Port. + @retval EFI_NOT_FOUND There are no more ports on this ATA controller. + @retval EFI_INVALID_PARAMETER Port is not 0xFFFF and Port was not returned + on a previous call to GetNextPort(). + +**/ +EFI_STATUS +EFIAPI +AhciAtaPassThruGetNextPort ( + IN EDKII_PEI_ATA_PASS_THRU_PPI *This, + IN OUT UINT16 *Port + ) +{ + PEI_AHCI_CONTROLLER_PRIVATE_DATA *Private; + PEI_AHCI_ATA_DEVICE_DATA *DeviceData; + LIST_ENTRY *Node; + + if (This == NULL || Port == NULL) { + return EFI_INVALID_PARAMETER; + } + + Private = GET_AHCI_PEIM_HC_PRIVATE_DATA_FROM_THIS_PASS_THRU (This); + + if (*Port == 0xFFFF) { + // + // If the Port is all 0xFF's, start to traverse the device list from the + // beginning. + // + Node = GetFirstNode (&Private->DeviceList); + if (!IsNull (&Private->DeviceList, Node)) { + DeviceData = AHCI_PEI_ATA_DEVICE_INFO_FROM_THIS (Node); + + *Port = DeviceData->Port; + goto Exit; + } + + return EFI_NOT_FOUND; + } else if (*Port == Private->PreviousPort) { + Node = GetFirstNode (&Private->DeviceList); + + while (!IsNull (&Private->DeviceList, Node)) { + DeviceData = AHCI_PEI_ATA_DEVICE_INFO_FROM_THIS (Node); + + if (DeviceData->Port > *Port){ + *Port = DeviceData->Port; + goto Exit; + } + + Node = GetNextNode (&Private->DeviceList, Node); + } + + return EFI_NOT_FOUND; + } else { + // + // Port is not equal to all 0xFF's and not equal to previous return value. + // + return EFI_INVALID_PARAMETER; + } + +Exit: + // + // Update the PreviousPort. + // + Private->PreviousPort = *Port; + + return EFI_SUCCESS; +} + +/** + Used to retrieve the list of legal port multiplier port numbers for ATA devices + on a port of an ATA controller. These can either be the list of port multiplier + ports where ATA devices are actually present on port or the list of legal port + multiplier ports on that port. Regardless, the caller of this function must probe + the port number and port multiplier port number returned to see if an ATA device + is actually present. + + The GetNextDevice() function retrieves the port multiplier port number of an ATA + device present on a port of an ATA controller. + + If PortMultiplierPort points to a port multiplier port number value that was + returned on a previous call to GetNextDevice(), then the port multiplier port + number of the next ATA device on the port of the ATA controller is returned in + PortMultiplierPort, and EFI_SUCCESS is returned. + + If PortMultiplierPort points to 0xFFFF, then the port multiplier port number + of the first ATA device on port of the ATA controller is returned in PortMultiplierPort + and EFI_SUCCESS is returned. + + If PortMultiplierPort is not 0xFFFF and the value pointed to by PortMultiplierPort + was not returned on a previous call to GetNextDevice(), then EFI_INVALID_PARAMETER + is returned. + + If PortMultiplierPort is the port multiplier port number of the last ATA device + on the port of the ATA controller, then EFI_NOT_FOUND is returned. + + @param[in] This The PPI instance pointer. + @param[in] Port The port number present on the ATA controller. + @param[in,out] PortMultiplierPort On input, a pointer to the port multiplier + port number of an ATA device present on the + ATA controller. If on input a PortMultiplierPort + of 0xFFFF is specified, then the port multiplier + port number of the first ATA device is returned. + On output, a pointer to the port multiplier port + number of the next ATA device present on an ATA + controller. + + @retval EFI_SUCCESS The port multiplier port number of the next ATA + device on the port of the ATA controller was + returned in PortMultiplierPort. + @retval EFI_NOT_FOUND There are no more ATA devices on this port of + the ATA controller. + @retval EFI_INVALID_PARAMETER PortMultiplierPort is not 0xFFFF, and PortMultiplierPort + was not returned on a previous call to GetNextDevice(). + +**/ +EFI_STATUS +EFIAPI +AhciAtaPassThruGetNextDevice ( + IN EDKII_PEI_ATA_PASS_THRU_PPI *This, + IN UINT16 Port, + IN OUT UINT16 *PortMultiplierPort + ) +{ + PEI_AHCI_CONTROLLER_PRIVATE_DATA *Private; + PEI_AHCI_ATA_DEVICE_DATA *DeviceData; + LIST_ENTRY *Node; + + if (This == NULL || PortMultiplierPort == NULL) { + return EFI_INVALID_PARAMETER; + } + + Private = GET_AHCI_PEIM_HC_PRIVATE_DATA_FROM_THIS_PASS_THRU (This); + + if (Private->PreviousPortMultiplier == 0xFFFF) { + // + // If a device is directly attached on a port, previous call to this + // function will return the value 0xFFFF for PortMultiplierPort. In + // this case, there should be no more device on the port multiplier. + // + Private->PreviousPortMultiplier = 0; + return EFI_NOT_FOUND; + } + + if (*PortMultiplierPort == Private->PreviousPortMultiplier) { + Node = GetFirstNode (&Private->DeviceList); + + while (!IsNull (&Private->DeviceList, Node)) { + DeviceData = AHCI_PEI_ATA_DEVICE_INFO_FROM_THIS (Node); + + if ((DeviceData->Port == Port) && + (DeviceData->PortMultiplier > *PortMultiplierPort)){ + *PortMultiplierPort = DeviceData->PortMultiplier; + goto Exit; + } + + Node = GetNextNode (&Private->DeviceList, Node); + } + + return EFI_NOT_FOUND; + } else if (*PortMultiplierPort == 0xFFFF) { + // + // If the PortMultiplierPort is all 0xFF's, start to traverse the device list + // from the beginning. + // + Node = GetFirstNode (&Private->DeviceList); + + while (!IsNull (&Private->DeviceList, Node)) { + DeviceData = AHCI_PEI_ATA_DEVICE_INFO_FROM_THIS (Node); + + if (DeviceData->Port == Port){ + *PortMultiplierPort = DeviceData->PortMultiplier; + goto Exit; + } + + Node = GetNextNode (&Private->DeviceList, Node); + } + + return EFI_NOT_FOUND; + } else { + // + // PortMultiplierPort is not equal to all 0xFF's and not equal to previous + // return value. + // + return EFI_INVALID_PARAMETER; + } + +Exit: + // + // Update the PreviousPortMultiplier. + // + Private->PreviousPortMultiplier = *PortMultiplierPort; + + return EFI_SUCCESS; +} + +/** + Gets the device path information of the underlying ATA host controller. + + @param[in] This The PPI instance pointer. + @param[out] DevicePathLength The length of the device path in bytes specified + by DevicePath. + @param[out] DevicePath The device path of the underlying ATA host controller. + This field re-uses EFI Device Path Protocol as + defined by Section 10.2 EFI Device Path Protocol + of UEFI 2.7 Specification. + + @retval EFI_SUCCESS The device path of the ATA host controller has + been successfully returned. + @retval EFI_INVALID_PARAMETER DevicePathLength or DevicePath is NULL. + @retval EFI_OUT_OF_RESOURCES Not enough resource to return the device path. + +**/ +EFI_STATUS +EFIAPI +AhciAtaPassThruGetDevicePath ( + IN EDKII_PEI_ATA_PASS_THRU_PPI *This, + OUT UINTN *DevicePathLength, + OUT EFI_DEVICE_PATH_PROTOCOL **DevicePath + ) +{ + PEI_AHCI_CONTROLLER_PRIVATE_DATA *Private; + + if (This == NULL || DevicePathLength == NULL || DevicePath == NULL) { + return EFI_INVALID_PARAMETER; + } + + Private = GET_AHCI_PEIM_HC_PRIVATE_DATA_FROM_THIS_PASS_THRU (This); + + *DevicePathLength = Private->DevicePathLength; + *DevicePath = AllocateCopyPool (Private->DevicePathLength, Private->DevicePath); + if (*DevicePath == NULL) { + *DevicePathLength = 0; + return EFI_OUT_OF_RESOURCES; + } + + return EFI_SUCCESS; +} diff --git a/MdeModulePkg/Bus/Ata/AhciPei/AhciPeiPassThru.h b/MdeModulePkg/Bus/Ata/AhciPei/AhciPeiPassThru.h new file mode 100644 index 0000000000..925ee27f93 --- /dev/null +++ b/MdeModulePkg/Bus/Ata/AhciPei/AhciPeiPassThru.h @@ -0,0 +1,184 @@ +/** @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. + +**/ + +#ifndef _AHCI_PEI_PASSTHRU_H_ +#define _AHCI_PEI_PASSTHRU_H_ + +/** + Sends an ATA command to an ATA device that is attached to the ATA controller. + + @param[in] This The PPI instance pointer. + @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,out] Packet A pointer to the ATA command to send to + the ATA device specified by Port and + PortMultiplierPort. + + @retval EFI_SUCCESS The ATA command was sent by the host. For + bi-directional commands, InTransferLength bytes + were transferred from InDataBuffer. For write + and bi-directional commands, OutTransferLength + bytes were transferred by OutDataBuffer. + @retval EFI_NOT_FOUND The specified ATA device is not found. + @retval EFI_INVALID_PARAMETER The contents of Acb are invalid. The ATA command + was not sent, so no additional status information + is available. + @retval EFI_BAD_BUFFER_SIZE The ATA command was not executed. The number + of bytes that could be transferred is returned + in InTransferLength. For write and bi-directional + commands, OutTransferLength bytes were transferred + by OutDataBuffer. + @retval EFI_NOT_READY The ATA command could not be sent because there + are too many ATA commands already queued. The + caller may retry again later. + @retval EFI_DEVICE_ERROR A device error occurred while attempting to + send the ATA command. + +**/ +EFI_STATUS +EFIAPI +AhciAtaPassThruPassThru ( + IN EDKII_PEI_ATA_PASS_THRU_PPI *This, + IN UINT16 Port, + IN UINT16 PortMultiplierPort, + IN OUT EFI_ATA_PASS_THRU_COMMAND_PACKET *Packet + ); + +/** + Used to retrieve the list of legal port numbers for ATA devices on an ATA controller. + These can either be the list of ports where ATA devices are actually present or the + list of legal port numbers for the ATA controller. Regardless, the caller of this + function must probe the port number returned to see if an ATA device is actually + present at that location on the ATA controller. + + The GetNextPort() function retrieves the port number on an ATA controller. If on + input Port is 0xFFFF, then the port number of the first port on the ATA controller + is returned in Port and EFI_SUCCESS is returned. + + If Port is a port number that was returned on a previous call to GetNextPort(), + then the port number of the next port on the ATA controller is returned in Port, + and EFI_SUCCESS is returned. If Port is not 0xFFFF and Port was not returned on + a previous call to GetNextPort(), then EFI_INVALID_PARAMETER is returned. + + If Port is the port number of the last port on the ATA controller, then EFI_NOT_FOUND + is returned. + + @param[in] This The PPI instance pointer. + @param[in,out] Port On input, a pointer to the port number on the ATA controller. + On output, a pointer to the next port number on the ATA + controller. An input value of 0xFFFF retrieves the first + port number on the ATA controller. + + @retval EFI_SUCCESS The next port number on the ATA controller was + returned in Port. + @retval EFI_NOT_FOUND There are no more ports on this ATA controller. + @retval EFI_INVALID_PARAMETER Port is not 0xFFFF and Port was not returned + on a previous call to GetNextPort(). + +**/ +EFI_STATUS +EFIAPI +AhciAtaPassThruGetNextPort ( + IN EDKII_PEI_ATA_PASS_THRU_PPI *This, + IN OUT UINT16 *Port + ); + +/** + Used to retrieve the list of legal port multiplier port numbers for ATA devices + on a port of an ATA controller. These can either be the list of port multiplier + ports where ATA devices are actually present on port or the list of legal port + multiplier ports on that port. Regardless, the caller of this function must probe + the port number and port multiplier port number returned to see if an ATA device + is actually present. + + The GetNextDevice() function retrieves the port multiplier port number of an ATA + device present on a port of an ATA controller. + + If PortMultiplierPort points to a port multiplier port number value that was + returned on a previous call to GetNextDevice(), then the port multiplier port + number of the next ATA device on the port of the ATA controller is returned in + PortMultiplierPort, and EFI_SUCCESS is returned. + + If PortMultiplierPort points to 0xFFFF, then the port multiplier port number + of the first ATA device on port of the ATA controller is returned in PortMultiplierPort + and EFI_SUCCESS is returned. + + If PortMultiplierPort is not 0xFFFF and the value pointed to by PortMultiplierPort + was not returned on a previous call to GetNextDevice(), then EFI_INVALID_PARAMETER + is returned. + + If PortMultiplierPort is the port multiplier port number of the last ATA device + on the port of the ATA controller, then EFI_NOT_FOUND is returned. + + @param[in] This The PPI instance pointer. + @param[in] Port The port number present on the ATA controller. + @param[in,out] PortMultiplierPort On input, a pointer to the port multiplier + port number of an ATA device present on the + ATA controller. If on input a PortMultiplierPort + of 0xFFFF is specified, then the port multiplier + port number of the first ATA device is returned. + On output, a pointer to the port multiplier port + number of the next ATA device present on an ATA + controller. + + @retval EFI_SUCCESS The port multiplier port number of the next ATA + device on the port of the ATA controller was + returned in PortMultiplierPort. + @retval EFI_NOT_FOUND There are no more ATA devices on this port of + the ATA controller. + @retval EFI_INVALID_PARAMETER PortMultiplierPort is not 0xFFFF, and PortMultiplierPort + was not returned on a previous call to GetNextDevice(). + +**/ +EFI_STATUS +EFIAPI +AhciAtaPassThruGetNextDevice ( + IN EDKII_PEI_ATA_PASS_THRU_PPI *This, + IN UINT16 Port, + IN OUT UINT16 *PortMultiplierPort + ); + +/** + Gets the device path information of the underlying ATA host controller. + + @param[in] This The PPI instance pointer. + @param[out] DevicePathLength The length of the device path in bytes specified + by DevicePath. + @param[out] DevicePath The device path of the underlying ATA host controller. + This field re-uses EFI Device Path Protocol as + defined by Section 10.2 EFI Device Path Protocol + of UEFI 2.7 Specification. + + @retval EFI_SUCCESS The device path of the ATA host controller has + been successfully returned. + @retval EFI_INVALID_PARAMETER DevicePathLength or DevicePath is NULL. + @retval EFI_OUT_OF_RESOURCES Not enough resource to return the device path. + +**/ +EFI_STATUS +EFIAPI +AhciAtaPassThruGetDevicePath ( + IN EDKII_PEI_ATA_PASS_THRU_PPI *This, + OUT UINTN *DevicePathLength, + OUT EFI_DEVICE_PATH_PROTOCOL **DevicePath + ); + +#endif diff --git a/MdeModulePkg/Bus/Ata/AhciPei/AhciPeiS3.c b/MdeModulePkg/Bus/Ata/AhciPei/AhciPeiS3.c new file mode 100644 index 0000000000..3f77b979c1 --- /dev/null +++ b/MdeModulePkg/Bus/Ata/AhciPei/AhciPeiS3.c @@ -0,0 +1,139 @@ +/** @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" + +#include + +#include + +/** + Collect the ports that need to be enumerated on a controller for S3 phase. + + @param[in] HcDevicePath Device path of the controller. + @param[in] HcDevicePathLength Length of the device path specified by + HcDevicePath. + @param[out] PortBitMap Bitmap that indicates the ports that need + to be enumerated on the controller. + + @retval The number of ports that need to be enumerated. + +**/ +UINT8 +AhciS3GetEumeratePorts ( + IN EFI_DEVICE_PATH_PROTOCOL *HcDevicePath, + IN UINTN HcDevicePathLength, + OUT UINT32 *PortBitMap + ) +{ + EFI_STATUS Status; + UINT8 DummyData; + UINTN S3InitDevicesLength; + EFI_DEVICE_PATH_PROTOCOL *S3InitDevices; + EFI_DEVICE_PATH_PROTOCOL *DevicePathInst; + UINTN DevicePathInstLength; + BOOLEAN EntireEnd; + SATA_DEVICE_PATH *SataDeviceNode; + + *PortBitMap = 0; + + // + // From the LockBox, get the list of device paths for devices need to be + // initialized in S3. + // + S3InitDevices = NULL; + S3InitDevicesLength = sizeof (DummyData); + EntireEnd = FALSE; + Status = RestoreLockBox (&gS3StorageDeviceInitListGuid, &DummyData, &S3InitDevicesLength); + if (Status != EFI_BUFFER_TOO_SMALL) { + return 0; + } else { + S3InitDevices = AllocatePool (S3InitDevicesLength); + if (S3InitDevices == NULL) { + return 0; + } + + Status = RestoreLockBox (&gS3StorageDeviceInitListGuid, S3InitDevices, &S3InitDevicesLength); + if (EFI_ERROR (Status)) { + return 0; + } + } + + if (S3InitDevices == NULL) { + return 0; + } + + // + // Only enumerate the ports that exist in the device list. + // + do { + // + // Fetch the size of current device path instance. + // + Status = GetDevicePathInstanceSize ( + S3InitDevices, + &DevicePathInstLength, + &EntireEnd + ); + if (EFI_ERROR (Status)) { + break; + } + + DevicePathInst = S3InitDevices; + S3InitDevices = (EFI_DEVICE_PATH_PROTOCOL *)((UINTN) S3InitDevices + DevicePathInstLength); + + if (HcDevicePathLength >= DevicePathInstLength) { + continue; + } + + // + // Compare the device paths to determine if the device is managed by this + // controller. + // + if (CompareMem ( + DevicePathInst, + HcDevicePath, + HcDevicePathLength - sizeof (EFI_DEVICE_PATH_PROTOCOL) + ) == 0) { + // + // Get the port number. + // + while (DevicePathInst->Type != END_DEVICE_PATH_TYPE) { + if ((DevicePathInst->Type == MESSAGING_DEVICE_PATH) && + (DevicePathInst->SubType == MSG_SATA_DP)) { + SataDeviceNode = (SATA_DEVICE_PATH *) DevicePathInst; + // + // For now, the driver only support upto AHCI_MAX_PORTS ports and + // devices directly connected to a HBA. + // + if ((SataDeviceNode->HBAPortNumber >= AHCI_MAX_PORTS) || + (SataDeviceNode->PortMultiplierPortNumber != 0xFFFF)) { + break; + } + *PortBitMap |= (UINT32)BIT0 << SataDeviceNode->HBAPortNumber; + break; + } + DevicePathInst = NextDevicePathNode (DevicePathInst); + } + } + } while (!EntireEnd); + + // + // Return the number of ports need to be enumerated on this controller. + // + return AhciGetNumberOfPortsFromMap (*PortBitMap); +} diff --git a/MdeModulePkg/Bus/Ata/AhciPei/AhciPeiStorageSecurity.c b/MdeModulePkg/Bus/Ata/AhciPei/AhciPeiStorageSecurity.c new file mode 100644 index 0000000000..49c384cadd --- /dev/null +++ b/MdeModulePkg/Bus/Ata/AhciPei/AhciPeiStorageSecurity.c @@ -0,0 +1,391 @@ +/** @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" + +/** + Traverse the attached ATA devices list to find out the device with given trust + computing device index. + + @param[in] Private A pointer to the PEI_AHCI_CONTROLLER_PRIVATE_DATA + instance. + @param[in] TrustComputingDeviceIndex The trust computing device index. + + @retval The pointer to the PEI_AHCI_ATA_DEVICE_DATA structure of the device + info to access. + +**/ +PEI_AHCI_ATA_DEVICE_DATA * +SearchTrustComputingDeviceByIndex ( + IN PEI_AHCI_CONTROLLER_PRIVATE_DATA *Private, + IN UINTN TrustComputingDeviceIndex + ) +{ + PEI_AHCI_ATA_DEVICE_DATA *DeviceData; + LIST_ENTRY *Node; + + Node = GetFirstNode (&Private->DeviceList); + while (!IsNull (&Private->DeviceList, Node)) { + DeviceData = AHCI_PEI_ATA_DEVICE_INFO_FROM_THIS (Node); + + if (DeviceData->TrustComputingDeviceIndex == TrustComputingDeviceIndex) { + return DeviceData; + } + + Node = GetNextNode (&Private->DeviceList, Node); + } + + return NULL; +} + +/** + Gets the count of storage security devices that one specific driver detects. + + @param[in] This The PPI instance pointer. + @param[out] NumberofDevices The number of storage security devices discovered. + + @retval EFI_SUCCESS The operation performed successfully. + @retval EFI_INVALID_PARAMETER The parameters are invalid. + +**/ +EFI_STATUS +EFIAPI +AhciStorageSecurityGetDeviceNo ( + IN EDKII_PEI_STORAGE_SECURITY_CMD_PPI *This, + OUT UINTN *NumberofDevices + ) +{ + PEI_AHCI_CONTROLLER_PRIVATE_DATA *Private; + + if (This == NULL || NumberofDevices == NULL) { + return EFI_INVALID_PARAMETER; + } + + Private = GET_AHCI_PEIM_HC_PRIVATE_DATA_FROM_THIS_STROAGE_SECURITY (This); + *NumberofDevices = Private->TrustComputingDevices; + + return EFI_SUCCESS; +} + +/** + Gets the device path of a specific storage security device. + + @param[in] This The PPI instance pointer. + @param[in] DeviceIndex Specifies the storage security device to which + the function wants to talk. Because the driver + that implements Storage Security Command PPIs + will manage multiple storage devices, the PPIs + that want to talk to a single device must specify + the device index that was assigned during the + enumeration process. This index is a number from + one to NumberofDevices. + @param[out] DevicePathLength The length of the device path in bytes specified + by DevicePath. + @param[out] DevicePath The device path of storage security device. + This field re-uses EFI Device Path Protocol as + defined by Section 10.2 EFI Device Path Protocol + of UEFI 2.7 Specification. + + @retval EFI_SUCCESS The operation succeeds. + @retval EFI_INVALID_PARAMETER DevicePathLength or DevicePath is NULL. + @retval EFI_NOT_FOUND The specified storage security device not found. + @retval EFI_OUT_OF_RESOURCES The operation fails due to lack of resources. + +**/ +EFI_STATUS +EFIAPI +AhciStorageSecurityGetDevicePath ( + IN EDKII_PEI_STORAGE_SECURITY_CMD_PPI *This, + IN UINTN DeviceIndex, + OUT UINTN *DevicePathLength, + OUT EFI_DEVICE_PATH_PROTOCOL **DevicePath + ) +{ + PEI_AHCI_CONTROLLER_PRIVATE_DATA *Private; + PEI_AHCI_ATA_DEVICE_DATA *DeviceData; + EFI_STATUS Status; + + if (This == NULL || DevicePathLength == NULL || DevicePath == NULL) { + return EFI_INVALID_PARAMETER; + } + + Private = GET_AHCI_PEIM_HC_PRIVATE_DATA_FROM_THIS_STROAGE_SECURITY (This); + if ((DeviceIndex == 0) || (DeviceIndex > Private->TrustComputingDevices)) { + return EFI_INVALID_PARAMETER; + } + + DeviceData = SearchTrustComputingDeviceByIndex (Private, DeviceIndex); + if (DeviceData == NULL) { + return EFI_NOT_FOUND; + } + + Status = AhciBuildDevicePath ( + Private, + DeviceData->Port, + DeviceData->PortMultiplier, + DevicePathLength, + DevicePath + ); + if (EFI_ERROR (Status)) { + return Status; + } + + return EFI_SUCCESS; +} + +/** + Send a security protocol command to a device that receives data and/or the result + of one or more commands sent by SendData. + + The ReceiveData function sends a security protocol command to the given DeviceIndex. + The security protocol command sent is defined by SecurityProtocolId and contains + the security protocol specific data SecurityProtocolSpecificData. The function + returns the data from the security protocol command in PayloadBuffer. + + For devices supporting the SCSI command set, the security protocol command is sent + using the SECURITY PROTOCOL IN command defined in SPC-4. + + For devices supporting the ATA command set, the security protocol command is sent + using one of the TRUSTED RECEIVE commands defined in ATA8-ACS if PayloadBufferSize + is non-zero. + + If the PayloadBufferSize is zero, the security protocol command is sent using the + Trusted Non-Data command defined in ATA8-ACS. + + If PayloadBufferSize is too small to store the available data from the security + protocol command, the function shall copy PayloadBufferSize bytes into the + PayloadBuffer and return EFI_WARN_BUFFER_TOO_SMALL. + + If PayloadBuffer or PayloadTransferSize is NULL and PayloadBufferSize is non-zero, + the function shall return EFI_INVALID_PARAMETER. + + If the given DeviceIndex does not support security protocol commands, the function + shall return EFI_UNSUPPORTED. + + If the security protocol fails to complete within the Timeout period, the function + shall return EFI_TIMEOUT. + + If the security protocol command completes without an error, the function shall + return EFI_SUCCESS. If the security protocol command completes with an error, the + function shall return EFI_DEVICE_ERROR. + + @param[in] This The PPI instance pointer. + @param[in] DeviceIndex Specifies the storage security device to which the + function wants to talk. Because the driver that + implements Storage Security Command PPIs will manage + multiple storage devices, the PPIs that want to talk + to a single device must specify the device index + that was assigned during the enumeration process. + This index is a number from one to NumberofDevices. + @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[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] PayloadBufferSize + Size in bytes of the payload data buffer. + @param[out] PayloadBuffer A pointer to a destination buffer to store the + security protocol command specific payload data + for the security protocol command. The caller is + responsible for having either implicit or explicit + ownership of the buffer. + @param[out] PayloadTransferSize + A pointer to a buffer to store the size in bytes + of the data written to the payload data buffer. + + @retval EFI_SUCCESS The security protocol command completed + successfully. + @retval EFI_WARN_BUFFER_TOO_SMALL The PayloadBufferSize was too small to + store the available data from the device. + The PayloadBuffer contains the truncated + data. + @retval EFI_UNSUPPORTED The given DeviceIndex does not support + security protocol commands. + @retval EFI_DEVICE_ERROR The security protocol command completed + with an error. + @retval EFI_INVALID_PARAMETER The PayloadBuffer or PayloadTransferSize + is NULL and PayloadBufferSize is non-zero. + @retval EFI_TIMEOUT A timeout occurred while waiting for the + security protocol command to execute. + +**/ +EFI_STATUS +EFIAPI +AhciStorageSecurityReceiveData ( + IN EDKII_PEI_STORAGE_SECURITY_CMD_PPI *This, + IN UINTN DeviceIndex, + IN UINT64 Timeout, + IN UINT8 SecurityProtocolId, + IN UINT16 SecurityProtocolSpecificData, + IN UINTN PayloadBufferSize, + OUT VOID *PayloadBuffer, + OUT UINTN *PayloadTransferSize + ) +{ + PEI_AHCI_CONTROLLER_PRIVATE_DATA *Private; + PEI_AHCI_ATA_DEVICE_DATA *DeviceData; + + if ((PayloadBuffer == NULL) || (PayloadTransferSize == NULL) || (PayloadBufferSize == 0)) { + return EFI_INVALID_PARAMETER; + } + + Private = GET_AHCI_PEIM_HC_PRIVATE_DATA_FROM_THIS_STROAGE_SECURITY (This); + if ((DeviceIndex == 0) || (DeviceIndex > Private->TrustComputingDevices)) { + return EFI_INVALID_PARAMETER; + } + + DeviceData = SearchTrustComputingDeviceByIndex (Private, DeviceIndex); + if (DeviceData == NULL) { + return EFI_NOT_FOUND; + } + + ASSERT ((DeviceData->IdentifyData->trusted_computing_support & BIT0) != 0); + if ((DeviceData->IdentifyData->trusted_computing_support & BIT0) == 0) { + return EFI_UNSUPPORTED; + } + + return TrustTransferAtaDevice ( + DeviceData, + PayloadBuffer, + SecurityProtocolId, + SecurityProtocolSpecificData, + PayloadBufferSize, + FALSE, + Timeout, + PayloadTransferSize + ); +} + +/** + Send a security protocol command to a device. + + The SendData function sends a security protocol command containing the payload + PayloadBuffer to the given DeviceIndex. The security protocol command sent is + defined by SecurityProtocolId and contains the security protocol specific data + SecurityProtocolSpecificData. If the underlying protocol command requires a + specific padding for the command payload, the SendData function shall add padding + bytes to the command payload to satisfy the padding requirements. + + For devices supporting the SCSI command set, the security protocol command is + sent using the SECURITY PROTOCOL OUT command defined in SPC-4. + + For devices supporting the ATA command set, the security protocol command is + sent using one of the TRUSTED SEND commands defined in ATA8-ACS if PayloadBufferSize + is non-zero. If the PayloadBufferSize is zero, the security protocol command + is sent using the Trusted Non-Data command defined in ATA8-ACS. + + If PayloadBuffer is NULL and PayloadBufferSize is non-zero, the function shall + return EFI_INVALID_PARAMETER. + + If the given DeviceIndex does not support security protocol commands, the function + shall return EFI_UNSUPPORTED. + + If the security protocol fails to complete within the Timeout period, the function + shall return EFI_TIMEOUT. + + If the security protocol command completes without an error, the function shall + return EFI_SUCCESS. If the security protocol command completes with an error, + the functio shall return EFI_DEVICE_ERROR. + + @param[in] This The PPI instance pointer. + @param[in] DeviceIndex The ID of the device. + @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[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] PayloadBufferSize Size in bytes of the payload data buffer. + @param[in] PayloadBuffer A pointer to a destination buffer to store the + security protocol command specific payload data + for the security protocol command. + + @retval EFI_SUCCESS The security protocol command completed successfully. + @retval EFI_UNSUPPORTED The given DeviceIndex does not support security + protocol commands. + @retval EFI_DEVICE_ERROR The security protocol command completed with + an error. + @retval EFI_INVALID_PARAMETER The PayloadBuffer is NULL and PayloadBufferSize + is non-zero. + @retval EFI_TIMEOUT A timeout occurred while waiting for the security + protocol command to execute. + +**/ +EFI_STATUS +EFIAPI +AhciStorageSecuritySendData ( + IN EDKII_PEI_STORAGE_SECURITY_CMD_PPI *This, + IN UINTN DeviceIndex, + IN UINT64 Timeout, + IN UINT8 SecurityProtocolId, + IN UINT16 SecurityProtocolSpecificData, + IN UINTN PayloadBufferSize, + IN VOID *PayloadBuffer + ) +{ + PEI_AHCI_CONTROLLER_PRIVATE_DATA *Private; + PEI_AHCI_ATA_DEVICE_DATA *DeviceData; + + if ((PayloadBuffer == NULL) && (PayloadBufferSize != 0)) { + return EFI_INVALID_PARAMETER; + } + + Private = GET_AHCI_PEIM_HC_PRIVATE_DATA_FROM_THIS_STROAGE_SECURITY (This); + if ((DeviceIndex == 0) || (DeviceIndex > Private->TrustComputingDevices)) { + return EFI_INVALID_PARAMETER; + } + + DeviceData = SearchTrustComputingDeviceByIndex (Private, DeviceIndex); + if (DeviceData == NULL) { + return EFI_NOT_FOUND; + } + + ASSERT ((DeviceData->IdentifyData->trusted_computing_support & BIT0) != 0); + if ((DeviceData->IdentifyData->trusted_computing_support & BIT0) == 0) { + return EFI_UNSUPPORTED; + } + + return TrustTransferAtaDevice ( + DeviceData, + PayloadBuffer, + SecurityProtocolId, + SecurityProtocolSpecificData, + PayloadBufferSize, + TRUE, + Timeout, + NULL + ); +} diff --git a/MdeModulePkg/Bus/Ata/AhciPei/AhciPeiStorageSecurity.h b/MdeModulePkg/Bus/Ata/AhciPei/AhciPeiStorageSecurity.h new file mode 100644 index 0000000000..0bdb964ec7 --- /dev/null +++ b/MdeModulePkg/Bus/Ata/AhciPei/AhciPeiStorageSecurity.h @@ -0,0 +1,247 @@ +/** @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. + +**/ + +#ifndef _AHCI_PEI_STORAGE_SECURITY_H_ +#define _AHCI_PEI_STORAGE_SECURITY_H_ + +/** + Gets the count of storage security devices that one specific driver detects. + + @param[in] This The PPI instance pointer. + @param[out] NumberofDevices The number of storage security devices discovered. + + @retval EFI_SUCCESS The operation performed successfully. + @retval EFI_INVALID_PARAMETER The parameters are invalid. + +**/ +EFI_STATUS +EFIAPI +AhciStorageSecurityGetDeviceNo ( + IN EDKII_PEI_STORAGE_SECURITY_CMD_PPI *This, + OUT UINTN *NumberofDevices + ); + +/** + Gets the device path of a specific storage security device. + + @param[in] This The PPI instance pointer. + @param[in] DeviceIndex Specifies the storage security device to which + the function wants to talk. Because the driver + that implements Storage Security Command PPIs + will manage multiple storage devices, the PPIs + that want to talk to a single device must specify + the device index that was assigned during the + enumeration process. This index is a number from + one to NumberofDevices. + @param[out] DevicePathLength The length of the device path in bytes specified + by DevicePath. + @param[out] DevicePath The device path of storage security device. + This field re-uses EFI Device Path Protocol as + defined by Section 10.2 EFI Device Path Protocol + of UEFI 2.7 Specification. + + @retval EFI_SUCCESS The operation succeeds. + @retval EFI_INVALID_PARAMETER DevicePathLength or DevicePath is NULL. + @retval EFI_NOT_FOUND The specified storage security device not found. + @retval EFI_OUT_OF_RESOURCES The operation fails due to lack of resources. + +**/ +EFI_STATUS +EFIAPI +AhciStorageSecurityGetDevicePath ( + IN EDKII_PEI_STORAGE_SECURITY_CMD_PPI *This, + IN UINTN DeviceIndex, + OUT UINTN *DevicePathLength, + OUT EFI_DEVICE_PATH_PROTOCOL **DevicePath + ); + +/** + Send a security protocol command to a device that receives data and/or the result + of one or more commands sent by SendData. + + The ReceiveData function sends a security protocol command to the given DeviceIndex. + The security protocol command sent is defined by SecurityProtocolId and contains + the security protocol specific data SecurityProtocolSpecificData. The function + returns the data from the security protocol command in PayloadBuffer. + + For devices supporting the SCSI command set, the security protocol command is sent + using the SECURITY PROTOCOL IN command defined in SPC-4. + + For devices supporting the ATA command set, the security protocol command is sent + using one of the TRUSTED RECEIVE commands defined in ATA8-ACS if PayloadBufferSize + is non-zero. + + If the PayloadBufferSize is zero, the security protocol command is sent using the + Trusted Non-Data command defined in ATA8-ACS. + + If PayloadBufferSize is too small to store the available data from the security + protocol command, the function shall copy PayloadBufferSize bytes into the + PayloadBuffer and return EFI_WARN_BUFFER_TOO_SMALL. + + If PayloadBuffer or PayloadTransferSize is NULL and PayloadBufferSize is non-zero, + the function shall return EFI_INVALID_PARAMETER. + + If the given DeviceIndex does not support security protocol commands, the function + shall return EFI_UNSUPPORTED. + + If the security protocol fails to complete within the Timeout period, the function + shall return EFI_TIMEOUT. + + If the security protocol command completes without an error, the function shall + return EFI_SUCCESS. If the security protocol command completes with an error, the + function shall return EFI_DEVICE_ERROR. + + @param[in] This The PPI instance pointer. + @param[in] DeviceIndex Specifies the storage security device to which the + function wants to talk. Because the driver that + implements Storage Security Command PPIs will manage + multiple storage devices, the PPIs that want to talk + to a single device must specify the device index + that was assigned during the enumeration process. + This index is a number from one to NumberofDevices. + @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[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] PayloadBufferSize + Size in bytes of the payload data buffer. + @param[out] PayloadBuffer A pointer to a destination buffer to store the + security protocol command specific payload data + for the security protocol command. The caller is + responsible for having either implicit or explicit + ownership of the buffer. + @param[out] PayloadTransferSize + A pointer to a buffer to store the size in bytes + of the data written to the payload data buffer. + + @retval EFI_SUCCESS The security protocol command completed + successfully. + @retval EFI_WARN_BUFFER_TOO_SMALL The PayloadBufferSize was too small to + store the available data from the device. + The PayloadBuffer contains the truncated + data. + @retval EFI_UNSUPPORTED The given DeviceIndex does not support + security protocol commands. + @retval EFI_DEVICE_ERROR The security protocol command completed + with an error. + @retval EFI_INVALID_PARAMETER The PayloadBuffer or PayloadTransferSize + is NULL and PayloadBufferSize is non-zero. + @retval EFI_TIMEOUT A timeout occurred while waiting for the + security protocol command to execute. + +**/ +EFI_STATUS +EFIAPI +AhciStorageSecurityReceiveData ( + IN EDKII_PEI_STORAGE_SECURITY_CMD_PPI *This, + IN UINTN DeviceIndex, + IN UINT64 Timeout, + IN UINT8 SecurityProtocolId, + IN UINT16 SecurityProtocolSpecificData, + IN UINTN PayloadBufferSize, + OUT VOID *PayloadBuffer, + OUT UINTN *PayloadTransferSize + ); + +/** + Send a security protocol command to a device. + + The SendData function sends a security protocol command containing the payload + PayloadBuffer to the given DeviceIndex. The security protocol command sent is + defined by SecurityProtocolId and contains the security protocol specific data + SecurityProtocolSpecificData. If the underlying protocol command requires a + specific padding for the command payload, the SendData function shall add padding + bytes to the command payload to satisfy the padding requirements. + + For devices supporting the SCSI command set, the security protocol command is + sent using the SECURITY PROTOCOL OUT command defined in SPC-4. + + For devices supporting the ATA command set, the security protocol command is + sent using one of the TRUSTED SEND commands defined in ATA8-ACS if PayloadBufferSize + is non-zero. If the PayloadBufferSize is zero, the security protocol command + is sent using the Trusted Non-Data command defined in ATA8-ACS. + + If PayloadBuffer is NULL and PayloadBufferSize is non-zero, the function shall + return EFI_INVALID_PARAMETER. + + If the given DeviceIndex does not support security protocol commands, the function + shall return EFI_UNSUPPORTED. + + If the security protocol fails to complete within the Timeout period, the function + shall return EFI_TIMEOUT. + + If the security protocol command completes without an error, the function shall + return EFI_SUCCESS. If the security protocol command completes with an error, + the functio shall return EFI_DEVICE_ERROR. + + @param[in] This The PPI instance pointer. + @param[in] DeviceIndex The ID of the device. + @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[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] PayloadBufferSize Size in bytes of the payload data buffer. + @param[in] PayloadBuffer A pointer to a destination buffer to store the + security protocol command specific payload data + for the security protocol command. + + @retval EFI_SUCCESS The security protocol command completed successfully. + @retval EFI_UNSUPPORTED The given DeviceIndex does not support security + protocol commands. + @retval EFI_DEVICE_ERROR The security protocol command completed with + an error. + @retval EFI_INVALID_PARAMETER The PayloadBuffer is NULL and PayloadBufferSize + is non-zero. + @retval EFI_TIMEOUT A timeout occurred while waiting for the security + protocol command to execute. + +**/ +EFI_STATUS +EFIAPI +AhciStorageSecuritySendData ( + IN EDKII_PEI_STORAGE_SECURITY_CMD_PPI *This, + IN UINTN DeviceIndex, + IN UINT64 Timeout, + IN UINT8 SecurityProtocolId, + IN UINT16 SecurityProtocolSpecificData, + IN UINTN PayloadBufferSize, + IN VOID *PayloadBuffer + ); + +#endif diff --git a/MdeModulePkg/Bus/Ata/AhciPei/DevicePath.c b/MdeModulePkg/Bus/Ata/AhciPei/DevicePath.c new file mode 100644 index 0000000000..02cf8d1827 --- /dev/null +++ b/MdeModulePkg/Bus/Ata/AhciPei/DevicePath.c @@ -0,0 +1,284 @@ +/** @file + The device path help function. + + 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" + +// +// Template for a SATA Device Path node +// +SATA_DEVICE_PATH mAhciSataDevicePathNodeTemplate = { + { // Header + MESSAGING_DEVICE_PATH, + MSG_SATA_DP, + { + (UINT8) (sizeof (SATA_DEVICE_PATH)), + (UINT8) ((sizeof (SATA_DEVICE_PATH)) >> 8) + } + }, + 0x0, // HBAPortNumber + 0xFFFF, // PortMultiplierPortNumber + 0x0 // Lun +}; + +// +// Template for an End of entire Device Path node +// +EFI_DEVICE_PATH_PROTOCOL mAhciEndDevicePathNodeTemplate = { + END_DEVICE_PATH_TYPE, + END_ENTIRE_DEVICE_PATH_SUBTYPE, + { + (UINT8) (sizeof (EFI_DEVICE_PATH_PROTOCOL)), + (UINT8) ((sizeof (EFI_DEVICE_PATH_PROTOCOL)) >> 8) + } +}; + +/** + Returns the 16-bit Length field of a device path node. + + Returns the 16-bit Length field of the device path node specified by Node. + Node is not required to be aligned on a 16-bit boundary, so it is recommended + that a function such as ReadUnaligned16() be used to extract the contents of + the Length field. + + If Node is NULL, then ASSERT(). + + @param Node A pointer to a device path node data structure. + + @return The 16-bit Length field of the device path node specified by Node. + +**/ +UINTN +DevicePathNodeLength ( + IN CONST VOID *Node + ) +{ + ASSERT (Node != NULL); + return ReadUnaligned16 ((UINT16 *)&((EFI_DEVICE_PATH_PROTOCOL *)(Node))->Length[0]); +} + +/** + Returns a pointer to the next node in a device path. + + If Node is NULL, then ASSERT(). + + @param Node A pointer to a device path node data structure. + + @return a pointer to the device path node that follows the device path node + specified by Node. + +**/ +EFI_DEVICE_PATH_PROTOCOL * +NextDevicePathNode ( + IN CONST VOID *Node + ) +{ + ASSERT (Node != NULL); + return (EFI_DEVICE_PATH_PROTOCOL *)((UINT8 *)(Node) + DevicePathNodeLength(Node)); +} + +/** + Get the size of the current device path instance. + + @param[in] DevicePath A pointer to the EFI_DEVICE_PATH_PROTOCOL + structure. + @param[out] InstanceSize The size of the current device path instance. + @param[out] EntireDevicePathEnd Indicate whether the instance is the last + one in the device path strucure. + + @retval EFI_SUCCESS The size of the current device path instance is fetched. + @retval Others Fails to get the size of the current device path instance. + +**/ +EFI_STATUS +GetDevicePathInstanceSize ( + IN EFI_DEVICE_PATH_PROTOCOL *DevicePath, + OUT UINTN *InstanceSize, + OUT BOOLEAN *EntireDevicePathEnd + ) +{ + EFI_DEVICE_PATH_PROTOCOL *Walker; + + if (DevicePath == NULL || InstanceSize == NULL || EntireDevicePathEnd == NULL) { + return EFI_INVALID_PARAMETER; + } + + // + // Find the end of the device path instance + // + Walker = DevicePath; + while (Walker->Type != END_DEVICE_PATH_TYPE) { + Walker = NextDevicePathNode (Walker); + } + + // + // Check if 'Walker' points to the end of an entire device path + // + if (Walker->SubType == END_ENTIRE_DEVICE_PATH_SUBTYPE) { + *EntireDevicePathEnd = TRUE; + } else if (Walker->SubType == END_INSTANCE_DEVICE_PATH_SUBTYPE) { + *EntireDevicePathEnd = FALSE; + } else { + return EFI_INVALID_PARAMETER; + } + + // + // Compute the size of the device path instance + // + *InstanceSize = ((UINTN) Walker - (UINTN) (DevicePath)) + sizeof (EFI_DEVICE_PATH_PROTOCOL); + + return EFI_SUCCESS; +} + +/** + Check the validity of the device path of a ATA AHCI host controller. + + @param[in] DevicePath A pointer to the EFI_DEVICE_PATH_PROTOCOL + structure. + @param[in] DevicePathLength The length of the device path. + + @retval EFI_SUCCESS The device path is valid. + @retval EFI_INVALID_PARAMETER The device path is invalid. + +**/ +EFI_STATUS +AhciIsHcDevicePathValid ( + IN EFI_DEVICE_PATH_PROTOCOL *DevicePath, + IN UINTN DevicePathLength + ) +{ + EFI_DEVICE_PATH_PROTOCOL *Start; + UINTN Size; + + if (DevicePath == NULL) { + return EFI_INVALID_PARAMETER; + } + + // + // Validate the DevicePathLength is big enough to touch the first node. + // + if (DevicePathLength < sizeof (EFI_DEVICE_PATH_PROTOCOL)) { + return EFI_INVALID_PARAMETER; + } + + Start = DevicePath; + while (!(DevicePath->Type == END_DEVICE_PATH_TYPE && + DevicePath->SubType == END_ENTIRE_DEVICE_PATH_SUBTYPE)) { + DevicePath = NextDevicePathNode (DevicePath); + + // + // Prevent overflow and invalid zero in the 'Length' field of a device path + // node. + // + if ((UINTN) DevicePath <= (UINTN) Start) { + return EFI_INVALID_PARAMETER; + } + + // + // Prevent touching memory beyond given DevicePathLength. + // + if ((UINTN) DevicePath - (UINTN) Start > + DevicePathLength - sizeof (EFI_DEVICE_PATH_PROTOCOL)) { + return EFI_INVALID_PARAMETER; + } + } + + // + // Check if the device path and its size match each other. + // + Size = ((UINTN) DevicePath - (UINTN) Start) + sizeof (EFI_DEVICE_PATH_PROTOCOL); + if (Size != DevicePathLength) { + return EFI_INVALID_PARAMETER; + } + + return EFI_SUCCESS; +} + +/** + Build the device path for an ATA device with given port and port multiplier number. + + @param[in] Private A pointer to the PEI_AHCI_CONTROLLER_PRIVATE_DATA + data structure. + @param[in] Port The given port number. + @param[in] PortMultiplierPort The given port multiplier number. + @param[out] DevicePathLength The length of the device path in bytes specified + by DevicePath. + @param[out] DevicePath The device path of ATA device. + + @retval EFI_SUCCESS The operation succeeds. + @retval EFI_INVALID_PARAMETER The parameters are invalid. + @retval EFI_OUT_OF_RESOURCES The operation fails due to lack of resources. + +**/ +EFI_STATUS +AhciBuildDevicePath ( + IN PEI_AHCI_CONTROLLER_PRIVATE_DATA *Private, + IN UINT16 Port, + IN UINT16 PortMultiplierPort, + OUT UINTN *DevicePathLength, + OUT EFI_DEVICE_PATH_PROTOCOL **DevicePath + ) +{ + EFI_DEVICE_PATH_PROTOCOL *DevicePathWalker; + SATA_DEVICE_PATH *SataDeviceNode; + + if (DevicePathLength == NULL || DevicePath == NULL) { + return EFI_INVALID_PARAMETER; + } + + *DevicePathLength = Private->DevicePathLength + sizeof (SATA_DEVICE_PATH); + *DevicePath = AllocatePool (*DevicePathLength); + if (*DevicePath == NULL) { + *DevicePathLength = 0; + return EFI_OUT_OF_RESOURCES; + } + + // + // Construct the host controller part device nodes + // + DevicePathWalker = *DevicePath; + CopyMem ( + DevicePathWalker, + Private->DevicePath, + Private->DevicePathLength - sizeof (EFI_DEVICE_PATH_PROTOCOL) + ); + + // + // Construct the SATA device node + // + DevicePathWalker = (EFI_DEVICE_PATH_PROTOCOL *) ((UINT8 *)DevicePathWalker + + (Private->DevicePathLength - sizeof (EFI_DEVICE_PATH_PROTOCOL))); + CopyMem ( + DevicePathWalker, + &mAhciSataDevicePathNodeTemplate, + sizeof (mAhciSataDevicePathNodeTemplate) + ); + SataDeviceNode = (SATA_DEVICE_PATH *)DevicePathWalker; + SataDeviceNode->HBAPortNumber = Port; + SataDeviceNode->PortMultiplierPortNumber = PortMultiplierPort; + + // + // Construct the end device node + // + DevicePathWalker = (EFI_DEVICE_PATH_PROTOCOL *) ((UINT8 *)DevicePathWalker + + sizeof (SATA_DEVICE_PATH)); + CopyMem ( + DevicePathWalker, + &mAhciEndDevicePathNodeTemplate, + sizeof (mAhciEndDevicePathNodeTemplate) + ); + + return EFI_SUCCESS; +} diff --git a/MdeModulePkg/Bus/Ata/AhciPei/DmaMem.c b/MdeModulePkg/Bus/Ata/AhciPei/DmaMem.c new file mode 100644 index 0000000000..098b02c1a1 --- /dev/null +++ b/MdeModulePkg/Bus/Ata/AhciPei/DmaMem.c @@ -0,0 +1,270 @@ +/** @file + The DMA memory help function. + + 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" + +/** + Get IOMMU PPI. + + @return Pointer to IOMMU PPI. + +**/ +EDKII_IOMMU_PPI * +GetIoMmu ( + VOID + ) +{ + EFI_STATUS Status; + EDKII_IOMMU_PPI *IoMmu; + + IoMmu = NULL; + Status = PeiServicesLocatePpi ( + &gEdkiiIoMmuPpiGuid, + 0, + NULL, + (VOID **) &IoMmu + ); + if (!EFI_ERROR (Status) && (IoMmu != NULL)) { + return IoMmu; + } + + return NULL; +} + +/** + Provides the controller-specific addresses required to access system memory from a + DMA bus master. + + @param Operation Indicates if the bus master is going to read or write to system memory. + @param HostAddress The system memory address to map to the PCI controller. + @param NumberOfBytes On input the number of bytes to map. On output the number of bytes + that were mapped. + @param DeviceAddress The resulting map address for the bus master PCI controller to use to + access the hosts HostAddress. + @param Mapping A resulting value to pass to Unmap(). + + @retval EFI_SUCCESS The range was mapped for the returned NumberOfBytes. + @retval EFI_UNSUPPORTED The HostAddress cannot be mapped as a common buffer. + @retval EFI_INVALID_PARAMETER One or more parameters are invalid. + @retval EFI_OUT_OF_RESOURCES The request could not be completed due to a lack of resources. + @retval EFI_DEVICE_ERROR The system hardware could not map the requested address. + +**/ +EFI_STATUS +IoMmuMap ( + IN EDKII_IOMMU_OPERATION Operation, + IN VOID *HostAddress, + IN OUT UINTN *NumberOfBytes, + OUT EFI_PHYSICAL_ADDRESS *DeviceAddress, + OUT VOID **Mapping + ) +{ + EFI_STATUS Status; + UINT64 Attribute; + EDKII_IOMMU_PPI *IoMmu; + + IoMmu = GetIoMmu (); + + if (IoMmu != NULL) { + Status = IoMmu->Map ( + IoMmu, + Operation, + HostAddress, + NumberOfBytes, + DeviceAddress, + Mapping + ); + if (EFI_ERROR (Status)) { + return EFI_OUT_OF_RESOURCES; + } + switch (Operation) { + case EdkiiIoMmuOperationBusMasterRead: + case EdkiiIoMmuOperationBusMasterRead64: + Attribute = EDKII_IOMMU_ACCESS_READ; + break; + case EdkiiIoMmuOperationBusMasterWrite: + case EdkiiIoMmuOperationBusMasterWrite64: + Attribute = EDKII_IOMMU_ACCESS_WRITE; + break; + case EdkiiIoMmuOperationBusMasterCommonBuffer: + case EdkiiIoMmuOperationBusMasterCommonBuffer64: + Attribute = EDKII_IOMMU_ACCESS_READ | EDKII_IOMMU_ACCESS_WRITE; + break; + default: + ASSERT(FALSE); + return EFI_INVALID_PARAMETER; + } + Status = IoMmu->SetAttribute ( + IoMmu, + *Mapping, + Attribute + ); + if (EFI_ERROR (Status)) { + return Status; + } + } else { + *DeviceAddress = (EFI_PHYSICAL_ADDRESS)(UINTN)HostAddress; + *Mapping = NULL; + Status = EFI_SUCCESS; + } + return Status; +} + +/** + Completes the Map() operation and releases any corresponding resources. + + @param Mapping The mapping value returned from Map(). + + @retval EFI_SUCCESS The range was unmapped. + @retval EFI_INVALID_PARAMETER Mapping is not a value that was returned by Map(). + @retval EFI_DEVICE_ERROR The data was not committed to the target system memory. +**/ +EFI_STATUS +IoMmuUnmap ( + IN VOID *Mapping + ) +{ + EFI_STATUS Status; + EDKII_IOMMU_PPI *IoMmu; + + IoMmu = GetIoMmu (); + + if (IoMmu != NULL) { + Status = IoMmu->SetAttribute (IoMmu, Mapping, 0); + Status = IoMmu->Unmap (IoMmu, Mapping); + } else { + Status = EFI_SUCCESS; + } + return Status; +} + +/** + Allocates pages that are suitable for an OperationBusMasterCommonBuffer or + OperationBusMasterCommonBuffer64 mapping. + + @param Pages The number of pages to allocate. + @param HostAddress A pointer to store the base system memory address of the + allocated range. + @param DeviceAddress The resulting map address for the bus master PCI controller to use to + access the hosts HostAddress. + @param Mapping A resulting value to pass to Unmap(). + + @retval EFI_SUCCESS The requested memory pages were allocated. + @retval EFI_UNSUPPORTED Attributes is unsupported. The only legal attribute bits are + MEMORY_WRITE_COMBINE and MEMORY_CACHED. + @retval EFI_INVALID_PARAMETER One or more parameters are invalid. + @retval EFI_OUT_OF_RESOURCES The memory pages could not be allocated. + +**/ +EFI_STATUS +IoMmuAllocateBuffer ( + IN UINTN Pages, + OUT VOID **HostAddress, + OUT EFI_PHYSICAL_ADDRESS *DeviceAddress, + OUT VOID **Mapping + ) +{ + EFI_STATUS Status; + UINTN NumberOfBytes; + EFI_PHYSICAL_ADDRESS HostPhyAddress; + EDKII_IOMMU_PPI *IoMmu; + + *HostAddress = NULL; + *DeviceAddress = 0; + + IoMmu = GetIoMmu (); + + if (IoMmu != NULL) { + Status = IoMmu->AllocateBuffer ( + IoMmu, + EfiBootServicesData, + Pages, + HostAddress, + 0 + ); + if (EFI_ERROR (Status)) { + return EFI_OUT_OF_RESOURCES; + } + + NumberOfBytes = EFI_PAGES_TO_SIZE(Pages); + Status = IoMmu->Map ( + IoMmu, + EdkiiIoMmuOperationBusMasterCommonBuffer, + *HostAddress, + &NumberOfBytes, + DeviceAddress, + Mapping + ); + if (EFI_ERROR (Status)) { + return EFI_OUT_OF_RESOURCES; + } + Status = IoMmu->SetAttribute ( + IoMmu, + *Mapping, + EDKII_IOMMU_ACCESS_READ | EDKII_IOMMU_ACCESS_WRITE + ); + if (EFI_ERROR (Status)) { + return Status; + } + } else { + Status = PeiServicesAllocatePages ( + EfiBootServicesData, + Pages, + &HostPhyAddress + ); + if (EFI_ERROR (Status)) { + return EFI_OUT_OF_RESOURCES; + } + *HostAddress = (VOID *)(UINTN)HostPhyAddress; + *DeviceAddress = HostPhyAddress; + *Mapping = NULL; + } + return Status; +} + +/** + Frees memory that was allocated with AllocateBuffer(). + + @param Pages The number of pages to free. + @param HostAddress The base system memory address of the allocated range. + @param Mapping The mapping value returned from Map(). + + @retval EFI_SUCCESS The requested memory pages were freed. + @retval EFI_INVALID_PARAMETER The memory range specified by HostAddress and Pages + was not allocated with AllocateBuffer(). + +**/ +EFI_STATUS +IoMmuFreeBuffer ( + IN UINTN Pages, + IN VOID *HostAddress, + IN VOID *Mapping + ) +{ + EFI_STATUS Status; + EDKII_IOMMU_PPI *IoMmu; + + IoMmu = GetIoMmu (); + + if (IoMmu != NULL) { + Status = IoMmu->SetAttribute (IoMmu, Mapping, 0); + Status = IoMmu->Unmap (IoMmu, Mapping); + Status = IoMmu->FreeBuffer (IoMmu, Pages, HostAddress); + } else { + Status = EFI_SUCCESS; + } + return Status; +} diff --git a/MdeModulePkg/MdeModulePkg.dsc b/MdeModulePkg/MdeModulePkg.dsc index 3b315fca5a..ed90683d5d 100644 --- a/MdeModulePkg/MdeModulePkg.dsc +++ b/MdeModulePkg/MdeModulePkg.dsc @@ -241,6 +241,7 @@ MdeModulePkg/Bus/Pci/SataControllerDxe/SataControllerDxe.inf MdeModulePkg/Bus/Ata/AtaBusDxe/AtaBusDxe.inf MdeModulePkg/Bus/Ata/AtaAtapiPassThru/AtaAtapiPassThru.inf + MdeModulePkg/Bus/Ata/AhciPei/AhciPei.inf MdeModulePkg/Bus/Scsi/ScsiBusDxe/ScsiBusDxe.inf MdeModulePkg/Bus/Scsi/ScsiDiskDxe/ScsiDiskDxe.inf MdeModulePkg/Bus/Usb/UsbBusDxe/UsbBusDxe.inf -- 2.39.2