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