| /** @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.<BR> | |
| SPDX-License-Identifier: BSD-2-Clause-Patent | |
| **/ | |
| #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 | |
| // | |
| // Due to limited resource for VTd PEI DMA buffer on platforms, the driver | |
| // limits the maximum transfer block number for 48-bit addressing. | |
| // Here, setting to 0x800 means that for device with 512-byte block size, the | |
| // maximum buffer for DMA mapping will be 1M bytes in size. | |
| // | |
| #define MAX_48BIT_TRANSFER_BLOCK_NUM 0x800 | |
| 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 system 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; | |
| } | |
| /** | |
| Build 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", | |
| __func__, | |
| 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", __func__)); | |
| 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 environment, 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", __func__)); | |
| 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", __func__)); | |
| 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", __func__, 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", | |
| __func__, | |
| DeviceData->Port, | |
| DeviceData->PortMultiplier | |
| )); | |
| return EFI_UNSUPPORTED; | |
| } | |
| DEBUG (( | |
| DEBUG_INFO, | |
| "%a: Identify Device: Port 0x%x PortMultiplierPort 0x%x\n", | |
| __func__, | |
| 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", __func__)); | |
| 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", | |
| __func__, | |
| 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", __func__, Media->BlockSize)); | |
| return EFI_UNSUPPORTED; | |
| } | |
| DEBUG (( | |
| DEBUG_INFO, | |
| "%a: BlockSize = 0x%x, LastBlock = 0x%lx\n", | |
| __func__, | |
| Media->BlockSize, | |
| Media->LastBlock | |
| )); | |
| if ((IdentifyData->trusted_computing_support & BIT0) != 0) { | |
| DEBUG ((DEBUG_INFO, "%a: Found Trust Computing feature support.\n", __func__)); | |
| 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", __func__, 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", | |
| __func__, | |
| 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 & PortImplementBitMap; | |
| AhciRegisters = &Private->AhciRegisters; | |
| DeviceIndex = 0; | |
| // | |
| // Enumerate ATA ports | |
| // | |
| for (PortIndex = 1; PortIndex <= MaxPortNumber; PortIndex++) { | |
| Status = AhciGetPortFromMap (PortInitializeBitMap, PortIndex, &Port); | |
| if (EFI_ERROR (Status)) { | |
| // | |
| // No more available port, just break out of the loop. | |
| // | |
| break; | |
| } | |
| 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", __func__, 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", | |
| __func__, | |
| 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 occurred when waiting for the first D2H register FIS - %r\n", | |
| __func__, | |
| 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", __func__, Status)); | |
| continue; | |
| } | |
| DEBUG ((DEBUG_INFO, "%a: ATA hard disk found on Port %d.\n", __func__, 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; | |
| } | |
| /** | |
| Transfer data from ATA device. | |
| This function performs one ATA pass through transaction to transfer data from/to | |
| ATA device. It chooses the appropriate ATA command and protocol to invoke PassThru | |
| interface of ATA pass through. | |
| @param[in] DeviceData A pointer to PEI_AHCI_ATA_DEVICE_DATA structure. | |
| @param[in,out] Buffer The pointer to the current transaction buffer. | |
| @param[in] StartLba The starting logical block address to be accessed. | |
| @param[in] TransferLength The block number or sector count of the transfer. | |
| @param[in] IsWrite Indicates whether it is a write operation. | |
| @retval EFI_SUCCESS The data transfer is complete successfully. | |
| @return others Some error occurs when transferring data. | |
| **/ | |
| EFI_STATUS | |
| TransferAtaDevice ( | |
| IN PEI_AHCI_ATA_DEVICE_DATA *DeviceData, | |
| IN OUT VOID *Buffer, | |
| IN EFI_LBA StartLba, | |
| IN UINT32 TransferLength, | |
| IN BOOLEAN IsWrite | |
| ) | |
| { | |
| 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; | |
| Private = DeviceData->Private; | |
| AtaPassThru = &Private->AtaPassThruPpi; | |
| // | |
| // Ensure Lba48Bit and IsWrite are valid boolean values | |
| // | |
| ASSERT ((UINTN)DeviceData->Lba48Bit < 2); | |
| ASSERT ((UINTN)IsWrite < 2); | |
| if (((UINTN)DeviceData->Lba48Bit >= 2) || | |
| ((UINTN)IsWrite >= 2)) | |
| { | |
| return EFI_INVALID_PARAMETER; | |
| } | |
| // | |
| // Prepare for ATA command block. | |
| // | |
| ZeroMem (&Acb, sizeof (EFI_ATA_COMMAND_BLOCK)); | |
| Acb.AtaCommand = mAtaCommands[DeviceData->Lba48Bit][IsWrite]; | |
| Acb.AtaSectorNumber = (UINT8)StartLba; | |
| Acb.AtaCylinderLow = (UINT8)RShiftU64 (StartLba, 8); | |
| Acb.AtaCylinderHigh = (UINT8)RShiftU64 (StartLba, 16); | |
| Acb.AtaDeviceHead = (UINT8)(BIT7 | BIT6 | BIT5 | | |
| (DeviceData->PortMultiplier == 0xFFFF ? | |
| 0 : (DeviceData->PortMultiplier << 4))); | |
| Acb.AtaSectorCount = (UINT8)TransferLength; | |
| if (DeviceData->Lba48Bit) { | |
| Acb.AtaSectorNumberExp = (UINT8)RShiftU64 (StartLba, 24); | |
| Acb.AtaCylinderLowExp = (UINT8)RShiftU64 (StartLba, 32); | |
| Acb.AtaCylinderHighExp = (UINT8)RShiftU64 (StartLba, 40); | |
| Acb.AtaSectorCountExp = (UINT8)(TransferLength >> 8); | |
| } else { | |
| Acb.AtaDeviceHead = (UINT8)(Acb.AtaDeviceHead | RShiftU64 (StartLba, 24)); | |
| } | |
| // | |
| // Prepare for ATA pass through packet. | |
| // | |
| ZeroMem (&Packet, sizeof (EFI_ATA_PASS_THRU_COMMAND_PACKET)); | |
| if (IsWrite) { | |
| Packet.OutDataBuffer = Buffer; | |
| Packet.OutTransferLength = TransferLength; | |
| } else { | |
| Packet.InDataBuffer = Buffer; | |
| Packet.InTransferLength = TransferLength; | |
| } | |
| Packet.Asb = NULL; | |
| Packet.Acb = &Acb; | |
| Packet.Protocol = mAtaPassThruCmdProtocols[IsWrite]; | |
| Packet.Length = EFI_ATA_PASS_THRU_LENGTH_SECTOR_COUNT; | |
| // | |
| // |------------------------|-----------------| | |
| // | ATA PIO Transfer Mode | Transfer Rate | | |
| // |------------------------|-----------------| | |
| // | PIO Mode 0 | 3.3Mbytes/sec | | |
| // |------------------------|-----------------| | |
| // | PIO Mode 1 | 5.2Mbytes/sec | | |
| // |------------------------|-----------------| | |
| // | PIO Mode 2 | 8.3Mbytes/sec | | |
| // |------------------------|-----------------| | |
| // | PIO Mode 3 | 11.1Mbytes/sec | | |
| // |------------------------|-----------------| | |
| // | PIO Mode 4 | 16.6Mbytes/sec | | |
| // |------------------------|-----------------| | |
| // | |
| // As AtaBus is used to manage ATA devices, we have to use the lowest transfer | |
| // rate to calculate the possible maximum timeout value for each read/write | |
| // operation. The timout value is rounded up to nearest integar and here an | |
| // additional 30s is added to follow ATA spec in which it mentioned that the | |
| // device may take up to 30s to respond commands in the Standby/Idle mode. | |
| // | |
| // Calculate the maximum timeout value for PIO read/write operation. | |
| // | |
| Packet.Timeout = TIMER_PERIOD_SECONDS ( | |
| DivU64x32 ( | |
| MultU64x32 (TransferLength, DeviceData->Media.BlockSize), | |
| 3300000 | |
| ) + 31 | |
| ); | |
| return AtaPassThru->PassThru ( | |
| AtaPassThru, | |
| DeviceData->Port, | |
| DeviceData->PortMultiplier, | |
| &Packet | |
| ); | |
| } | |
| /** | |
| 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) && | |
| !ADDRESS_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; | |
| } |