X-Git-Url: https://git.proxmox.com/?p=mirror_edk2.git;a=blobdiff_plain;f=MdeModulePkg%2FBus%2FAta%2FAtaAtapiPassThru%2FAhciMode.c;h=4d01c1dd7fca2c70d97250e9bace7840135e4d09;hp=06b280a001af3f48506388228f29c4f557629822;hb=6052a15f4a4297b430cf03f2456e51f8d3bb4598;hpb=5dec0c688ea92257e721eecdaf4fe4bcb15274dc diff --git a/MdeModulePkg/Bus/Ata/AtaAtapiPassThru/AhciMode.c b/MdeModulePkg/Bus/Ata/AtaAtapiPassThru/AhciMode.c index 06b280a001..4d01c1dd7f 100644 --- a/MdeModulePkg/Bus/Ata/AtaAtapiPassThru/AhciMode.c +++ b/MdeModulePkg/Bus/Ata/AtaAtapiPassThru/AhciMode.c @@ -1,14 +1,15 @@ /** @file The file for AHCI mode of ATA host controller. - - Copyright (c) 2010, 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. + Copyright (c) 2010 - 2016, Intel Corporation. All rights reserved.
+ (C) Copyright 2015 Hewlett Packard Enterprise Development LP
+ 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. **/ @@ -33,7 +34,7 @@ AhciReadReg ( UINT32 Data; ASSERT (PciIo != NULL); - + Data = 0; PciIo->Mem.Read ( @@ -95,7 +96,7 @@ AhciAndReg ( ) { UINT32 Data; - + ASSERT (PciIo != NULL); Data = AhciReadReg (PciIo, Offset); @@ -133,36 +134,45 @@ AhciOrReg ( } /** - Wait for memory set to the test value. - + Wait for the value of the specified MMIO register set to the test value. + @param PciIo The PCI IO protocol instance. - @param Offset The memory address to test. + @param Offset The MMIO address to test. @param MaskValue The mask value of memory. @param TestValue The test value of memory. - @param Timeout The time out value for wait memory set. + @param Timeout The time out value for wait memory set, uses 100ns as a unit. - @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. + @retval EFI_TIMEOUT The MMIO setting is time out. + @retval EFI_SUCCESS The MMIO is correct set. **/ EFI_STATUS EFIAPI -AhciWaitMemSet ( +AhciWaitMmioSet ( IN EFI_PCI_IO_PROTOCOL *PciIo, - IN UINT32 Offset, + IN UINTN Offset, IN UINT32 MaskValue, IN UINT32 TestValue, IN UINT64 Timeout ) { - UINT32 Value; - UINT32 Delay; + UINT32 Value; + UINT64 Delay; + BOOLEAN InfiniteWait; + + if (Timeout == 0) { + InfiniteWait = TRUE; + } else { + InfiniteWait = FALSE; + } - Delay = (UINT32) (DivU64x32(Timeout, 1000) + 1); + Delay = DivU64x32 (Timeout, 1000) + 1; do { - Value = AhciReadReg (PciIo, Offset) & MaskValue; + // + // Access PCI MMIO space to see if the value is the tested one. + // + Value = AhciReadReg (PciIo, (UINT32) Offset) & MaskValue; if (Value == TestValue) { return EFI_SUCCESS; @@ -175,23 +185,123 @@ AhciWaitMemSet ( Delay--; - } while (Delay > 0); + } while (InfiniteWait || (Delay > 0)); - if (Delay == 0) { - return EFI_TIMEOUT; + return EFI_TIMEOUT; +} + +/** + Wait for the value of the specified system memory set to the test value. + + @param Address The system memory address to test. + @param MaskValue The mask value of memory. + @param TestValue The test value of memory. + @param Timeout The time out value for wait memory set, uses 100ns as a unit. + + @retval EFI_TIMEOUT The system memory setting is time out. + @retval EFI_SUCCESS The system memory is correct set. + +**/ +EFI_STATUS +EFIAPI +AhciWaitMemSet ( + IN EFI_PHYSICAL_ADDRESS Address, + IN UINT32 MaskValue, + IN UINT32 TestValue, + IN UINT64 Timeout + ) +{ + UINT32 Value; + 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; +} + +/** + Check the memory status to the test value. + + @param[in] Address The memory address to test. + @param[in] MaskValue The mask value of memory. + @param[in] TestValue The test value of memory. + @param[in, out] Task Optional. Pointer to the ATA_NONBLOCK_TASK used by + non-blocking mode. If NULL, then just try once. + + @retval EFI_NOTREADY The memory is not set. + @retval EFI_TIMEOUT The memory setting retry times out. + @retval EFI_SUCCESS The memory is correct set. + +**/ +EFI_STATUS +EFIAPI +AhciCheckMemSet ( + IN UINTN Address, + IN UINT32 MaskValue, + IN UINT32 TestValue, + IN OUT ATA_NONBLOCK_TASK *Task + ) +{ + UINT32 Value; + + if (Task != NULL) { + Task->RetryTimes--; + } + + Value = *(volatile UINT32 *) Address; + Value &= MaskValue; + + if (Value == TestValue) { + return EFI_SUCCESS; } - return EFI_DEVICE_ERROR; + if ((Task != NULL) && !Task->InfiniteWait && (Task->RetryTimes == 0)) { + return EFI_TIMEOUT; + } else { + return EFI_NOT_READY; + } } /** - Check if the device is still on port. It also checks if the AHCI controller - supports the address and data count will be transfered. + Check if the device is still on port. It also checks if the AHCI controller + supports the address and data count will be transferred. @param PciIo The PCI IO protocol instance. @param Port The number of port. - @retval EFI_SUCCESS The device is attached to port and the transfer data is + @retval EFI_SUCCESS The device is attached to port and the transfer data is supported by AHCI controller. @retval EFI_UNSUPPORTED The transfer address and count is not supported by AHCI controller. @@ -206,7 +316,7 @@ AhciCheckDeviceStatus ( IN UINT8 Port ) { - UINT32 Data; + UINT32 Data; UINT32 Offset; Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_SSTS; @@ -214,7 +324,7 @@ AhciCheckDeviceStatus ( Data = AhciReadReg (PciIo, Offset) & EFI_AHCI_PORT_SSTS_DET_MASK; if (Data == EFI_AHCI_PORT_SSTS_DET_PCE) { - return EFI_SUCCESS; + return EFI_SUCCESS; } return EFI_NOT_READY; @@ -224,23 +334,23 @@ AhciCheckDeviceStatus ( Clear the port interrupt and error status. It will also clear HBA interrupt status. - + @param PciIo The PCI IO protocol instance. @param Port The number of port. - -**/ + +**/ VOID EFIAPI AhciClearPortStatus ( IN EFI_PCI_IO_PROTOCOL *PciIo, IN UINT8 Port - ) + ) { UINT32 Offset; // // Clear any error status - // + // Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_SERR; AhciWriteReg (PciIo, Offset, AhciReadReg (PciIo, Offset)); @@ -256,12 +366,67 @@ AhciClearPortStatus ( AhciWriteReg (PciIo, EFI_AHCI_IS_OFFSET, AhciReadReg (PciIo, EFI_AHCI_IS_OFFSET)); } +/** + This function is used to dump the Status Registers and if there is ERR bit set + in the Status Register, the Error Register's value is also be dumped. + + @param PciIo The PCI IO protocol instance. + @param AhciRegisters The pointer to the EFI_AHCI_REGISTERS. + @param Port The number of port. + @param AtaStatusBlock A pointer to EFI_ATA_STATUS_BLOCK data structure. + +**/ +VOID +EFIAPI +AhciDumpPortStatus ( + IN EFI_PCI_IO_PROTOCOL *PciIo, + IN EFI_AHCI_REGISTERS *AhciRegisters, + IN UINT8 Port, + IN OUT EFI_ATA_STATUS_BLOCK *AtaStatusBlock + ) +{ + UINTN Offset; + UINT32 Data; + UINTN FisBaseAddr; + EFI_STATUS Status; + + ASSERT (PciIo != NULL); + + if (AtaStatusBlock != NULL) { + ZeroMem (AtaStatusBlock, sizeof (EFI_ATA_STATUS_BLOCK)); + + FisBaseAddr = (UINTN)AhciRegisters->AhciRFis + Port * sizeof (EFI_AHCI_RECEIVED_FIS); + Offset = FisBaseAddr + EFI_AHCI_D2H_FIS_OFFSET; + + Status = AhciCheckMemSet (Offset, EFI_AHCI_FIS_TYPE_MASK, EFI_AHCI_FIS_REGISTER_D2H, NULL); + if (!EFI_ERROR (Status)) { + // + // If D2H FIS is received, update StatusBlock with its content. + // + CopyMem (AtaStatusBlock, (UINT8 *)Offset, sizeof (EFI_ATA_STATUS_BLOCK)); + } else { + // + // If D2H FIS is not received, only update Status & Error field through PxTFD + // as there is no other way to get the content of the Shadow Register Block. + // + Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_TFD; + Data = AhciReadReg (PciIo, (UINT32)Offset); + + AtaStatusBlock->AtaStatus = (UINT8)Data; + if ((AtaStatusBlock->AtaStatus & BIT0) != 0) { + AtaStatusBlock->AtaError = (UINT8)(Data >> 8); + } + } + } +} + + /** Enable the FIS running for giving port. - + @param PciIo The PCI IO protocol instance. @param Port The number of port. - @param Timeout The timeout value of enabling FIS. + @param Timeout The timeout value of enabling FIS, uses 100ns as a unit. @retval EFI_DEVICE_ERROR The FIS enable setting fails. @retval EFI_TIMEOUT The FIS enable setting is time out. @@ -274,20 +439,14 @@ AhciEnableFisReceive ( IN EFI_PCI_IO_PROTOCOL *PciIo, IN UINT8 Port, IN UINT64 Timeout - ) -{ + ) +{ UINT32 Offset; Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_CMD; AhciOrReg (PciIo, Offset, EFI_AHCI_PORT_CMD_FRE); - return AhciWaitMemSet ( - PciIo, - Offset, - EFI_AHCI_PORT_CMD_FR, - EFI_AHCI_PORT_CMD_FR, - Timeout - ); + return EFI_SUCCESS; } /** @@ -295,7 +454,7 @@ AhciEnableFisReceive ( @param PciIo The PCI IO protocol instance. @param Port The number of port. - @param Timeout The timeout value of disabling FIS. + @param 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. @@ -309,7 +468,7 @@ AhciDisableFisReceive ( IN EFI_PCI_IO_PROTOCOL *PciIo, IN UINT8 Port, IN UINT64 Timeout - ) + ) { UINT32 Offset; UINT32 Data; @@ -323,7 +482,7 @@ AhciDisableFisReceive ( if ((Data & (EFI_AHCI_PORT_CMD_ST | EFI_AHCI_PORT_CMD_CR)) != 0) { return EFI_UNSUPPORTED; } - + // // Check if the Fis receive DMA engine for the port is running. // @@ -333,20 +492,20 @@ AhciDisableFisReceive ( AhciAndReg (PciIo, Offset, (UINT32)~(EFI_AHCI_PORT_CMD_FRE)); - return AhciWaitMemSet ( - PciIo, + return AhciWaitMmioSet ( + PciIo, Offset, EFI_AHCI_PORT_CMD_FR, 0, Timeout - ); + ); } /** Build the command list, command table and prepare the fis receiver. - + @param PciIo The PCI IO protocol instance. @param AhciRegisters The pointer to the EFI_AHCI_REGISTERS. @param Port The number of port. @@ -359,7 +518,7 @@ AhciDisableFisReceive ( @param DataPhysicalAddr The pointer to the data buffer pci bus master address. @param DataLength The data count to be transferred. -**/ +**/ VOID EFIAPI AhciBuildCommand ( @@ -373,12 +532,12 @@ AhciBuildCommand ( IN UINT8 AtapiCommandLength, IN UINT8 CommandSlotNumber, IN OUT VOID *DataPhysicalAddr, - IN UINT64 DataLength - ) + IN UINT32 DataLength + ) { - UINT64 BaseAddr; - UINT64 PrdtNumber; - UINT64 PrdtIndex; + UINT64 BaseAddr; + UINT32 PrdtNumber; + UINT32 PrdtIndex; UINTN RemainedData; UINTN MemAddr; DATA_64 Data64; @@ -386,8 +545,8 @@ AhciBuildCommand ( // // Filling the PRDT - // - PrdtNumber = (DataLength + EFI_AHCI_MAX_DATA_PER_PRDT - 1) / EFI_AHCI_MAX_DATA_PER_PRDT; + // + PrdtNumber = (UINT32)DivU64x32 (((UINT64)DataLength + EFI_AHCI_MAX_DATA_PER_PRDT - 1), EFI_AHCI_MAX_DATA_PER_PRDT); // // According to AHCI 1.3 spec, a PRDT entry can point to a maximum 4MB data block. @@ -399,13 +558,13 @@ AhciBuildCommand ( Data64.Uint64 = (UINTN) (AhciRegisters->AhciRFis) + sizeof (EFI_AHCI_RECEIVED_FIS) * Port; BaseAddr = Data64.Uint64; - - ZeroMem ((VOID *)((UINTN) BaseAddr), sizeof (EFI_AHCI_RECEIVED_FIS)); - + + ZeroMem ((VOID *)((UINTN) BaseAddr), sizeof (EFI_AHCI_RECEIVED_FIS)); + ZeroMem (AhciRegisters->AhciCommandTable, sizeof (EFI_AHCI_COMMAND_TABLE)); CommandFis->AhciCFisPmNum = PortMultiplier; - + CopyMem (&AhciRegisters->AhciCommandTable->CommandFis, CommandFis, sizeof (EFI_AHCI_COMMAND_FIS)); Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_CMD; @@ -418,19 +577,18 @@ AhciBuildCommand ( CommandList->AhciCmdA = 1; CommandList->AhciCmdP = 1; - CommandList->AhciCmdC = (DataLength == 0) ? 1 : 0; AhciOrReg (PciIo, Offset, (EFI_AHCI_PORT_CMD_DLAE | EFI_AHCI_PORT_CMD_ATAPI)); } else { AhciAndReg (PciIo, Offset, (UINT32)~(EFI_AHCI_PORT_CMD_DLAE | EFI_AHCI_PORT_CMD_ATAPI)); } - + RemainedData = (UINTN) DataLength; MemAddr = (UINTN) DataPhysicalAddr; - CommandList->AhciCmdPrdtl = (UINT32)PrdtNumber; - + CommandList->AhciCmdPrdtl = PrdtNumber; + for (PrdtIndex = 0; PrdtIndex < PrdtNumber; PrdtIndex++) { - if (RemainedData < EFI_AHCI_MAX_DATA_PER_PRDT) { + if (RemainedData < EFI_AHCI_MAX_DATA_PER_PRDT) { AhciRegisters->AhciCommandTable->PrdtTable[PrdtIndex].AhciPrdtDbc = (UINT32)RemainedData - 1; } else { AhciRegisters->AhciCommandTable->PrdtTable[PrdtIndex].AhciPrdtDbc = EFI_AHCI_MAX_DATA_PER_PRDT - 1; @@ -439,7 +597,7 @@ AhciBuildCommand ( Data64.Uint64 = (UINT64)MemAddr; AhciRegisters->AhciCommandTable->PrdtTable[PrdtIndex].AhciPrdtDba = Data64.Uint32.Lower32; AhciRegisters->AhciCommandTable->PrdtTable[PrdtIndex].AhciPrdtDbau = Data64.Uint32.Upper32; - RemainedData -= EFI_AHCI_MAX_DATA_PER_PRDT; + RemainedData -= EFI_AHCI_MAX_DATA_PER_PRDT; MemAddr += EFI_AHCI_MAX_DATA_PER_PRDT; } @@ -454,7 +612,7 @@ AhciBuildCommand ( (VOID *) ((UINTN) AhciRegisters->AhciCmdList + (UINTN) CommandSlotNumber * sizeof (EFI_AHCI_COMMAND_LIST)), CommandList, sizeof (EFI_AHCI_COMMAND_LIST) - ); + ); Data64.Uint64 = (UINT64)(UINTN) AhciRegisters->AhciCommandTablePciAddr; AhciRegisters->AhciCmdList[CommandSlotNumber].AhciCmdCtba = Data64.Uint32.Lower32; @@ -465,7 +623,7 @@ AhciBuildCommand ( /** Buid a command FIS. - + @param CmdFis A pointer to the EFI_AHCI_COMMAND_FIS data structure. @param AtaCommandBlock A pointer to the AhciBuildCommandFis data structure. @@ -483,7 +641,7 @@ AhciBuildCommandFis ( // // Indicator it's a command // - CmdFis->AhciCFisCmdInd = 0x1; + CmdFis->AhciCFisCmdInd = 0x1; CmdFis->AhciCFisCmd = AtaCommandBlock->AtaCommand; CmdFis->AhciCFisFeature = AtaCommandBlock->AtaFeatures; @@ -506,19 +664,22 @@ AhciBuildCommandFis ( /** Start a PIO data transfer on specific port. - - @param PciIo The PCI IO protocol instance. - @param AhciRegisters The pointer to the EFI_AHCI_REGISTERS. - @param Port The number of port. - @param PortMultiplier The timeout value of stop. - @param AtapiCommand The atapi command will be used for the transfer. - @param AtapiCommandLength The length of the atapi command. - @param Read The transfer direction. - @param AtaCommandBlock The EFI_ATA_COMMAND_BLOCK data. - @param AtaStatusBlock The EFI_ATA_STATUS_BLOCK data. - @param MemoryAddr The pointer to the data buffer. - @param DataCount The data count to be transferred. - @param Timeout The timeout value of non data transfer. + + @param[in] PciIo The PCI IO protocol instance. + @param[in] AhciRegisters The pointer to the EFI_AHCI_REGISTERS. + @param[in] Port The number of port. + @param[in] PortMultiplier The timeout value of stop. + @param[in] AtapiCommand The atapi command will be used for the + transfer. + @param[in] AtapiCommandLength The length of the atapi command. + @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 non data transfer, uses 100ns as a unit. + @param[in] Task Optional. Pointer to the ATA_NONBLOCK_TASK + used by non-blocking mode. @retval EFI_DEVICE_ERROR The PIO data transfer abort with error occurs. @retval EFI_TIMEOUT The operation is time out. @@ -534,26 +695,37 @@ AhciPioTransfer ( IN UINT8 Port, IN UINT8 PortMultiplier, IN EFI_AHCI_ATAPI_COMMAND *AtapiCommand OPTIONAL, - IN UINT8 AtapiCommandLength, - IN BOOLEAN Read, + IN UINT8 AtapiCommandLength, + IN BOOLEAN Read, IN EFI_ATA_COMMAND_BLOCK *AtaCommandBlock, IN OUT EFI_ATA_STATUS_BLOCK *AtaStatusBlock, IN OUT VOID *MemoryAddr, IN UINT32 DataCount, - IN UINT64 Timeout + IN UINT64 Timeout, + IN ATA_NONBLOCK_TASK *Task ) { EFI_STATUS Status; UINTN FisBaseAddr; - UINT32 Offset; - UINT32 Value; + UINTN Offset; EFI_PHYSICAL_ADDRESS PhyAddr; VOID *Map; UINTN MapLength; EFI_PCI_IO_PROTOCOL_OPERATION Flag; - UINT32 Delay; + UINT64 Delay; EFI_AHCI_COMMAND_FIS CFis; EFI_AHCI_COMMAND_LIST CmdList; + UINT32 PortTfd; + UINT32 PrdCount; + BOOLEAN InfiniteWait; + BOOLEAN PioFisReceived; + BOOLEAN D2hFisReceived; + + if (Timeout == 0) { + InfiniteWait = TRUE; + } else { + InfiniteWait = FALSE; + } if (Read) { Flag = EfiPciIoOperationBusMasterWrite; @@ -575,9 +747,9 @@ AhciPioTransfer ( ); if (EFI_ERROR (Status) || (DataCount != MapLength)) { - return EFI_OUT_OF_RESOURCES; + return EFI_BAD_BUFFER_SIZE; } - + // // Package read needed // @@ -600,83 +772,110 @@ AhciPioTransfer ( 0, (VOID *)(UINTN)PhyAddr, DataCount - ); - + ); + Status = AhciStartCommand ( - PciIo, - Port, + PciIo, + Port, 0, Timeout ); if (EFI_ERROR (Status)) { goto Exit; } - + // - // Checking the status and wait the driver sending data + // Check the status and wait the driver sending data // FisBaseAddr = (UINTN)AhciRegisters->AhciRFis + Port * sizeof (EFI_AHCI_RECEIVED_FIS); - // - // Wait device sends the PIO setup fis before data transfer - // - Delay = (UINT32) (DivU64x32(Timeout, 1000) + 1); - do { - Value = *(UINT32 *) (FisBaseAddr + EFI_AHCI_PIO_FIS_OFFSET); - - if ((Value & EFI_AHCI_FIS_TYPE_MASK) == EFI_AHCI_FIS_PIO_SETUP) { - break; - } + if (Read && (AtapiCommand == 0)) { // - // Stall for 100 microseconds. + // Wait device sends the PIO setup fis before data transfer // - MicroSecondDelay(100); + Status = EFI_TIMEOUT; + Delay = DivU64x32 (Timeout, 1000) + 1; + do { + PioFisReceived = FALSE; + D2hFisReceived = FALSE; + Offset = FisBaseAddr + EFI_AHCI_PIO_FIS_OFFSET; + Status = AhciCheckMemSet (Offset, EFI_AHCI_FIS_TYPE_MASK, EFI_AHCI_FIS_PIO_SETUP, NULL); + if (!EFI_ERROR (Status)) { + 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 + EFI_AHCI_D2H_FIS_OFFSET; + Status = AhciCheckMemSet (Offset, EFI_AHCI_FIS_TYPE_MASK, EFI_AHCI_FIS_REGISTER_D2H, NULL); + if (!EFI_ERROR (Status)) { + D2hFisReceived = TRUE; + } - Delay--; - } while (Delay > 0); + if (PioFisReceived || D2hFisReceived) { + Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_TFD; + PortTfd = AhciReadReg (PciIo, (UINT32) Offset); + // + // PxTFD will be updated if there is a D2H or SetupFIS received. + // + if ((PortTfd & EFI_AHCI_PORT_TFD_ERR) != 0) { + Status = EFI_DEVICE_ERROR; + break; + } - if (Delay == 0) { - Status = EFI_TIMEOUT; - goto Exit; - } + PrdCount = *(volatile UINT32 *) (&(AhciRegisters->AhciCmdList[0].AhciCmdPrdbc)); + if (PrdCount == DataCount) { + Status = EFI_SUCCESS; + break; + } + } - // - // Wait for command compelte - // - Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_CI; - Status = AhciWaitMemSet ( - PciIo, - Offset, - 0xFFFFFFFF, - 0, - Timeout - ); + // + // Stall for 100 microseconds. + // + MicroSecondDelay(100); - if (EFI_ERROR (Status)) { - goto Exit; - } + Delay--; + if (Delay == 0) { + Status = EFI_TIMEOUT; + } + } while (InfiniteWait || (Delay > 0)); + } else { + // + // Wait for D2H Fis is received + // + Offset = FisBaseAddr + EFI_AHCI_D2H_FIS_OFFSET; + Status = AhciWaitMemSet ( + Offset, + EFI_AHCI_FIS_TYPE_MASK, + EFI_AHCI_FIS_REGISTER_D2H, + Timeout + ); - Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_IS; - Status = AhciWaitMemSet ( - PciIo, - Offset, - EFI_AHCI_PORT_IS_PSS, - EFI_AHCI_PORT_IS_PSS, - Timeout - ); - if (EFI_ERROR (Status)) { - goto Exit; + if (EFI_ERROR (Status)) { + goto Exit; + } + + Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_TFD; + PortTfd = AhciReadReg (PciIo, (UINT32) Offset); + if ((PortTfd & EFI_AHCI_PORT_TFD_ERR) != 0) { + Status = EFI_DEVICE_ERROR; + } } -Exit: +Exit: AhciStopCommand ( - PciIo, + PciIo, Port, Timeout ); - + AhciDisableFisReceive ( - PciIo, + PciIo, Port, Timeout ); @@ -686,197 +885,251 @@ Exit: Map ); + AhciDumpPortStatus (PciIo, AhciRegisters, Port, AtaStatusBlock); + return Status; } /** Start a DMA data transfer on specific port - @param PciIo The PCI IO protocol instance. - @param AhciRegisters The pointer to the EFI_AHCI_REGISTERS. - @param Port The number of port. - @param PortMultiplier The timeout value of stop. - @param AtapiCommand The atapi command will be used for the transfer. - @param AtapiCommandLength The length of the atapi command. - @param Read The transfer direction. - @param AtaCommandBlock The EFI_ATA_COMMAND_BLOCK data. - @param AtaStatusBlock The EFI_ATA_STATUS_BLOCK data. - @param MemoryAddr The pointer to the data buffer. - @param DataCount The data count to be transferred. - @param Timeout The timeout value of non data transfer. + @param[in] Instance The ATA_ATAPI_PASS_THRU_INSTANCE protocol instance. + @param[in] AhciRegisters The pointer to the EFI_AHCI_REGISTERS. + @param[in] Port The number of port. + @param[in] PortMultiplier The timeout value of stop. + @param[in] AtapiCommand The atapi command will be used for the + transfer. + @param[in] AtapiCommandLength The length of the atapi command. + @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 non data transfer, uses 100ns as a unit. + @param[in] Task Optional. Pointer to the ATA_NONBLOCK_TASK + used by non-blocking mode. @retval EFI_DEVICE_ERROR The DMA 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 DMA data transfer executes successfully. - + **/ EFI_STATUS EFIAPI AhciDmaTransfer ( - IN EFI_PCI_IO_PROTOCOL *PciIo, + IN ATA_ATAPI_PASS_THRU_INSTANCE *Instance, IN EFI_AHCI_REGISTERS *AhciRegisters, IN UINT8 Port, IN UINT8 PortMultiplier, IN EFI_AHCI_ATAPI_COMMAND *AtapiCommand OPTIONAL, IN UINT8 AtapiCommandLength, - IN BOOLEAN Read, + IN BOOLEAN Read, IN EFI_ATA_COMMAND_BLOCK *AtaCommandBlock, IN OUT EFI_ATA_STATUS_BLOCK *AtaStatusBlock, IN OUT VOID *MemoryAddr, - IN UINTN DataCount, - IN UINT64 Timeout + IN UINT32 DataCount, + IN UINT64 Timeout, + IN ATA_NONBLOCK_TASK *Task ) { EFI_STATUS Status; - UINT32 Offset; + UINTN Offset; EFI_PHYSICAL_ADDRESS PhyAddr; VOID *Map; UINTN MapLength; EFI_PCI_IO_PROTOCOL_OPERATION Flag; EFI_AHCI_COMMAND_FIS CFis; EFI_AHCI_COMMAND_LIST CmdList; + UINTN FisBaseAddr; + UINT32 PortTfd; - if (Read) { - Flag = EfiPciIoOperationBusMasterWrite; - } else { - Flag = EfiPciIoOperationBusMasterRead; - } + EFI_PCI_IO_PROTOCOL *PciIo; + EFI_TPL OldTpl; - // - // construct command list and command table with pci bus address - // - MapLength = DataCount; - Status = PciIo->Map ( - PciIo, - Flag, - MemoryAddr, - &MapLength, - &PhyAddr, - &Map - ); + Map = NULL; + PciIo = Instance->PciIo; - if (EFI_ERROR (Status) || (DataCount != MapLength)) { - return EFI_OUT_OF_RESOURCES; + if (PciIo == NULL) { + return EFI_INVALID_PARAMETER; } // - // Package read needed + // Before starting the Blocking BlockIO operation, push to finish all non-blocking + // BlockIO tasks. + // Delay 100us to simulate the blocking time out checking. // - AhciBuildCommandFis (&CFis, AtaCommandBlock); - - ZeroMem (&CmdList, sizeof (EFI_AHCI_COMMAND_LIST)); + OldTpl = gBS->RaiseTPL (TPL_NOTIFY); + while ((Task == NULL) && (!IsListEmpty (&Instance->NonBlockingTaskList))) { + AsyncNonBlockingTransferRoutine (NULL, Instance); + // + // Stall for 100us. + // + MicroSecondDelay (100); + } + gBS->RestoreTPL (OldTpl); - CmdList.AhciCmdCfl = EFI_AHCI_FIS_REGISTER_H2D_LENGTH / 4; - CmdList.AhciCmdW = Read ? 0 : 1; + if ((Task == NULL) || ((Task != NULL) && (!Task->IsStart))) { + // + // Mark the Task to indicate that it has been started. + // + if (Task != NULL) { + Task->IsStart = TRUE; + } + if (Read) { + Flag = EfiPciIoOperationBusMasterWrite; + } else { + Flag = EfiPciIoOperationBusMasterRead; + } - AhciBuildCommand ( - PciIo, - AhciRegisters, - Port, - PortMultiplier, - &CFis, - &CmdList, - AtapiCommand, - AtapiCommandLength, - 0, - (VOID *)(UINTN)PhyAddr, - DataCount - ); - - Status = AhciStartCommand ( - PciIo, - Port, - 0, - Timeout - ); - if (EFI_ERROR (Status)) { - goto Exit; - } - - // - // Wait device PRD processed - // - Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_IS; - Status = AhciWaitMemSet ( - PciIo, - Offset, - EFI_AHCI_PORT_IS_DPS, - EFI_AHCI_PORT_IS_DPS, - Timeout - ); - - if (EFI_ERROR (Status)) { - goto Exit; + // + // Construct command list and command table with pci bus address. + // + MapLength = DataCount; + Status = PciIo->Map ( + PciIo, + Flag, + MemoryAddr, + &MapLength, + &PhyAddr, + &Map + ); + + if (EFI_ERROR (Status) || (DataCount != MapLength)) { + return EFI_BAD_BUFFER_SIZE; + } + + if (Task != NULL) { + Task->Map = Map; + } + // + // Package read needed + // + AhciBuildCommandFis (&CFis, AtaCommandBlock); + + ZeroMem (&CmdList, sizeof (EFI_AHCI_COMMAND_LIST)); + + CmdList.AhciCmdCfl = EFI_AHCI_FIS_REGISTER_H2D_LENGTH / 4; + CmdList.AhciCmdW = Read ? 0 : 1; + + AhciBuildCommand ( + PciIo, + AhciRegisters, + Port, + PortMultiplier, + &CFis, + &CmdList, + AtapiCommand, + AtapiCommandLength, + 0, + (VOID *)(UINTN)PhyAddr, + DataCount + ); + + Status = AhciStartCommand ( + PciIo, + Port, + 0, + Timeout + ); + if (EFI_ERROR (Status)) { + goto Exit; + } } // // Wait for command compelte // - Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_CI; - Status = AhciWaitMemSet ( - PciIo, - Offset, - 0xFFFFFFFF, - 0, - Timeout - ); - if (EFI_ERROR (Status)) { - goto Exit; + FisBaseAddr = (UINTN)AhciRegisters->AhciRFis + Port * sizeof (EFI_AHCI_RECEIVED_FIS); + Offset = FisBaseAddr + EFI_AHCI_D2H_FIS_OFFSET; + if (Task != NULL) { + // + // For Non-blocking + // + Status = AhciCheckMemSet ( + Offset, + EFI_AHCI_FIS_TYPE_MASK, + EFI_AHCI_FIS_REGISTER_D2H, + Task + ); + } else { + Status = AhciWaitMemSet ( + Offset, + EFI_AHCI_FIS_TYPE_MASK, + EFI_AHCI_FIS_REGISTER_D2H, + Timeout + ); } - Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_IS; - Status = AhciWaitMemSet ( - PciIo, - Offset, - EFI_AHCI_PORT_IS_DHRS, - EFI_AHCI_PORT_IS_DHRS, - Timeout - ); if (EFI_ERROR (Status)) { goto Exit; } -Exit: - AhciStopCommand ( - PciIo, - Port, - Timeout - ); + Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_TFD; + PortTfd = AhciReadReg (PciIo, (UINT32) Offset); + if ((PortTfd & EFI_AHCI_PORT_TFD_ERR) != 0) { + Status = EFI_DEVICE_ERROR; + } - AhciDisableFisReceive ( - PciIo, - Port, - Timeout - ); +Exit: + // + // For Blocking mode, the command should be stopped, the Fis should be disabled + // and the PciIo should be unmapped. + // For non-blocking mode, only when a error is happened (if the return status is + // EFI_NOT_READY that means the command doesn't finished, try again.), first do the + // context cleanup, then set the packet's Asb status. + // + if (Task == NULL || + ((Task != NULL) && (Status != EFI_NOT_READY)) + ) { + AhciStopCommand ( + PciIo, + Port, + Timeout + ); - PciIo->Unmap ( - PciIo, - Map - ); - + AhciDisableFisReceive ( + PciIo, + Port, + Timeout + ); + + PciIo->Unmap ( + PciIo, + (Task != NULL) ? Task->Map : Map + ); + + if (Task != NULL) { + Task->Packet->Asb->AtaStatus = 0x01; + } + } + + AhciDumpPortStatus (PciIo, AhciRegisters, Port, AtaStatusBlock); return Status; } /** Start a non data transfer on specific port. - - @param PciIo The PCI IO protocol instance. - @param AhciRegisters The pointer to the EFI_AHCI_REGISTERS. - @param Port The number of port. - @param PortMultiplier The timeout value of stop. - @param AtapiCommand The atapi command will be used for the transfer. - @param AtapiCommandLength The length of the atapi command. - @param AtaCommandBlock The EFI_ATA_COMMAND_BLOCK data. - @param AtaStatusBlock The EFI_ATA_STATUS_BLOCK data. - @param Timeout The timeout value of non data transfer. + + @param[in] PciIo The PCI IO protocol instance. + @param[in] AhciRegisters The pointer to the EFI_AHCI_REGISTERS. + @param[in] Port The number of port. + @param[in] PortMultiplier The timeout value of stop. + @param[in] AtapiCommand The atapi command will be used for the + transfer. + @param[in] AtapiCommandLength The length of the atapi command. + @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. + @param[in] Task Optional. Pointer to the ATA_NONBLOCK_TASK + used by non-blocking mode. @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 EFIAPI AhciNonDataTransfer ( @@ -888,15 +1141,14 @@ AhciNonDataTransfer ( IN UINT8 AtapiCommandLength, IN EFI_ATA_COMMAND_BLOCK *AtaCommandBlock, IN OUT EFI_ATA_STATUS_BLOCK *AtaStatusBlock, - IN UINT64 Timeout - ) + IN UINT64 Timeout, + IN ATA_NONBLOCK_TASK *Task + ) { - EFI_STATUS Status; + EFI_STATUS Status; UINTN FisBaseAddr; - UINT32 Offset; - UINT32 Value; - UINT32 Delay; - + UINTN Offset; + UINT32 PortTfd; EFI_AHCI_COMMAND_FIS CFis; EFI_AHCI_COMMAND_LIST CmdList; @@ -921,79 +1173,65 @@ AhciNonDataTransfer ( 0, NULL, 0 - ); - + ); + Status = AhciStartCommand ( - PciIo, - Port, + PciIo, + Port, 0, Timeout ); if (EFI_ERROR (Status)) { goto Exit; } - + // // Wait device sends the Response Fis // FisBaseAddr = (UINTN)AhciRegisters->AhciRFis + Port * sizeof (EFI_AHCI_RECEIVED_FIS); - // - // Wait device sends the PIO setup fis before data transfer - // - Delay = (UINT32) (DivU64x32(Timeout, 1000) + 1); - do { - Value = *(UINT32 *) (FisBaseAddr + EFI_AHCI_D2H_FIS_OFFSET); - - if ((Value & EFI_AHCI_FIS_TYPE_MASK) == EFI_AHCI_FIS_REGISTER_D2H) { - break; - } - - // - // Stall for 100 microseconds. - // - MicroSecondDelay(100); + Offset = FisBaseAddr + EFI_AHCI_D2H_FIS_OFFSET; + Status = AhciWaitMemSet ( + Offset, + EFI_AHCI_FIS_TYPE_MASK, + EFI_AHCI_FIS_REGISTER_D2H, + Timeout + ); - Delay --; - } while (Delay > 0); - - if (Delay == 0) { - Status = EFI_TIMEOUT; + if (EFI_ERROR (Status)) { goto Exit; } - Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_CI; + Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_TFD; + PortTfd = AhciReadReg (PciIo, (UINT32) Offset); + if ((PortTfd & EFI_AHCI_PORT_TFD_ERR) != 0) { + Status = EFI_DEVICE_ERROR; + } - Status = AhciWaitMemSet ( - PciIo, - Offset, - 0xFFFFFFFF, - 0, - Timeout - ); - -Exit: +Exit: AhciStopCommand ( - PciIo, + PciIo, Port, Timeout ); AhciDisableFisReceive ( - PciIo, + PciIo, Port, Timeout ); + AhciDumpPortStatus (PciIo, AhciRegisters, Port, AtaStatusBlock); + return Status; } /** Stop command running for giving port - + @param PciIo The PCI IO protocol instance. @param Port The number of port. - @param Timeout The timeout value of stop. - + @param Timeout The timeout value of stop, uses 100ns as a unit. + @retval EFI_DEVICE_ERROR The command stop unsuccessfully. @retval EFI_TIMEOUT The operation is time out. @retval EFI_SUCCESS The command stop successfully. @@ -1014,30 +1252,30 @@ AhciStopCommand ( Data = AhciReadReg (PciIo, Offset); if ((Data & (EFI_AHCI_PORT_CMD_ST | EFI_AHCI_PORT_CMD_CR)) == 0) { - return EFI_SUCCESS; + return EFI_SUCCESS; } if ((Data & EFI_AHCI_PORT_CMD_ST) != 0) { AhciAndReg (PciIo, Offset, (UINT32)~(EFI_AHCI_PORT_CMD_ST)); } - return AhciWaitMemSet ( - PciIo, + return AhciWaitMmioSet ( + PciIo, Offset, EFI_AHCI_PORT_CMD_CR, 0, Timeout - ); + ); } /** Start command for give slot on specific port. - + @param PciIo The PCI IO protocol instance. @param Port The number of port. - @param CommandSlot The number of CommandSlot. - @param Timeout The timeout value of start. - + @param CommandSlot The number of Command Slot. + @param Timeout The timeout value of start, uses 100ns as a unit. + @retval EFI_DEVICE_ERROR The command start unsuccessfully. @retval EFI_TIMEOUT The operation is time out. @retval EFI_SUCCESS The command start successfully. @@ -1073,29 +1311,18 @@ AhciStartCommand ( ); Status = AhciEnableFisReceive ( - PciIo, + PciIo, Port, Timeout ); - + if (EFI_ERROR (Status)) { return Status; } - // - // Setting the command - // - Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_SACT; - AhciAndReg (PciIo, Offset, 0); - AhciOrReg (PciIo, Offset, CmdSlotBit); - - Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_CI; - AhciAndReg (PciIo, Offset, 0); - AhciOrReg (PciIo, Offset, CmdSlotBit); - Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_CMD; PortStatus = AhciReadReg (PciIo, Offset); - + StartCmd = 0; if ((PortStatus & EFI_AHCI_PORT_CMD_ALPE) != 0) { StartCmd = AhciReadReg (PciIo, Offset); @@ -1109,21 +1336,28 @@ AhciStartCommand ( if ((PortTfd & (EFI_AHCI_PORT_TFD_BSY | EFI_AHCI_PORT_TFD_DRQ)) != 0) { if ((Capability & BIT24) != 0) { Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_CMD; - AhciOrReg (PciIo, Offset, EFI_AHCI_PORT_CMD_COL); + AhciOrReg (PciIo, Offset, EFI_AHCI_PORT_CMD_CLO); - AhciWaitMemSet ( - PciIo, + AhciWaitMmioSet ( + PciIo, Offset, - EFI_AHCI_PORT_CMD_COL, + EFI_AHCI_PORT_CMD_CLO, 0, Timeout - ); + ); } } Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_CMD; AhciOrReg (PciIo, Offset, EFI_AHCI_PORT_CMD_ST | StartCmd); + // + // Setting the command + // + Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_CI; + AhciAndReg (PciIo, Offset, 0); + AhciOrReg (PciIo, Offset, CmdSlotBit); + return EFI_SUCCESS; } @@ -1132,8 +1366,8 @@ AhciStartCommand ( @param PciIo The PCI IO protocol instance. @param Port The number of port. - @param Timeout The timeout value of reset. - + @param Timeout The timeout value of reset, uses 100ns as a unit. + @retval EFI_DEVICE_ERROR The port reset unsuccessfully @retval EFI_TIMEOUT The reset operation is time out. @retval EFI_SUCCESS The port reset successfully. @@ -1148,8 +1382,8 @@ AhciPortReset ( ) { EFI_STATUS Status; - UINT32 Offset; - + UINT32 Offset; + AhciClearPortStatus (PciIo, Port); AhciStopCommand (PciIo, Port, Timeout); @@ -1163,14 +1397,14 @@ AhciPortReset ( AhciOrReg (PciIo, Offset, EFI_AHCI_PORT_SCTL_DET_INIT); // - // wait 5 milliseceond before de-assert DET + // wait 5 millisecond before de-assert DET // MicroSecondDelay (5000); AhciAndReg (PciIo, Offset, (UINT32)EFI_AHCI_PORT_SCTL_MASK); // - // wait 5 milliseceond before de-assert DET + // wait 5 millisecond before de-assert DET // MicroSecondDelay (5000); @@ -1178,15 +1412,16 @@ AhciPortReset ( // Wait for communication to be re-established // Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_SSTS; - Status = AhciWaitMemSet ( + Status = AhciWaitMmioSet ( PciIo, Offset, EFI_AHCI_PORT_SSTS_DET_MASK, EFI_AHCI_PORT_SSTS_DET_PCE, Timeout - ); + ); if (EFI_ERROR (Status)) { + DEBUG ((EFI_D_ERROR, "Port %d COMRESET failed: %r\n", Port, Status)); return Status; } @@ -1198,11 +1433,10 @@ AhciPortReset ( /** Do AHCI HBA reset. - + @param PciIo The PCI IO protocol instance. - @param Timeout The timeout value of reset. - - + @param Timeout The timeout value of reset, uses 100ns as a unit. + @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. @@ -1213,19 +1447,23 @@ EFIAPI AhciReset ( IN EFI_PCI_IO_PROTOCOL *PciIo, IN UINT64 Timeout - ) + ) { - EFI_STATUS Status; - UINT32 Delay; + UINT64 Delay; UINT32 Value; - AhciOrReg (PciIo, EFI_AHCI_GHC_OFFSET, EFI_AHCI_GHC_ENABLE); + // + // Make sure that GHC.AE bit is set before accessing any AHCI registers. + // + Value = AhciReadReg(PciIo, EFI_AHCI_GHC_OFFSET); - AhciOrReg (PciIo, EFI_AHCI_GHC_OFFSET, EFI_AHCI_GHC_RESET); + if ((Value & EFI_AHCI_GHC_ENABLE) == 0) { + AhciOrReg (PciIo, EFI_AHCI_GHC_OFFSET, EFI_AHCI_GHC_ENABLE); + } - Status = EFI_TIMEOUT; + AhciOrReg (PciIo, EFI_AHCI_GHC_OFFSET, EFI_AHCI_GHC_RESET); - Delay = (UINT32) (DivU64x32(Timeout, 1000) + 1); + Delay = DivU64x32(Timeout, 1000) + 1; do { Value = AhciReadReg(PciIo, EFI_AHCI_GHC_OFFSET); @@ -1249,13 +1487,227 @@ AhciReset ( return EFI_SUCCESS; } +/** + Send SMART Return Status command to check if the execution of SMART cmd is successful or not. + + @param PciIo The PCI IO protocol instance. + @param AhciRegisters The pointer to the EFI_AHCI_REGISTERS. + @param Port The number of port. + @param PortMultiplier The port multiplier port number. + @param AtaStatusBlock A pointer to EFI_ATA_STATUS_BLOCK data structure. + + @retval EFI_SUCCESS Successfully get the return status of S.M.A.R.T command execution. + @retval Others Fail to get return status data. + +**/ +EFI_STATUS +EFIAPI +AhciAtaSmartReturnStatusCheck ( + IN EFI_PCI_IO_PROTOCOL *PciIo, + IN EFI_AHCI_REGISTERS *AhciRegisters, + IN UINT8 Port, + IN UINT8 PortMultiplier, + IN OUT EFI_ATA_STATUS_BLOCK *AtaStatusBlock + ) +{ + EFI_STATUS Status; + EFI_ATA_COMMAND_BLOCK AtaCommandBlock; + UINT8 LBAMid; + UINT8 LBAHigh; + UINTN FisBaseAddr; + UINT32 Value; + + ZeroMem (&AtaCommandBlock, sizeof (EFI_ATA_COMMAND_BLOCK)); + + AtaCommandBlock.AtaCommand = ATA_CMD_SMART; + AtaCommandBlock.AtaFeatures = ATA_SMART_RETURN_STATUS; + AtaCommandBlock.AtaCylinderLow = ATA_CONSTANT_4F; + AtaCommandBlock.AtaCylinderHigh = ATA_CONSTANT_C2; + + // + // Send S.M.A.R.T Read Return Status command to device + // + Status = AhciNonDataTransfer ( + PciIo, + AhciRegisters, + (UINT8)Port, + (UINT8)PortMultiplier, + NULL, + 0, + &AtaCommandBlock, + AtaStatusBlock, + ATA_ATAPI_TIMEOUT, + NULL + ); + + if (EFI_ERROR (Status)) { + REPORT_STATUS_CODE ( + EFI_ERROR_CODE | EFI_ERROR_MINOR, + (EFI_IO_BUS_ATA_ATAPI | EFI_IOB_ATA_BUS_SMART_DISABLED) + ); + return EFI_DEVICE_ERROR; + } + + REPORT_STATUS_CODE ( + EFI_PROGRESS_CODE, + (EFI_IO_BUS_ATA_ATAPI | EFI_IOB_ATA_BUS_SMART_ENABLE) + ); + + FisBaseAddr = (UINTN)AhciRegisters->AhciRFis + Port * sizeof (EFI_AHCI_RECEIVED_FIS); + + Value = *(UINT32 *) (FisBaseAddr + EFI_AHCI_D2H_FIS_OFFSET); + + if ((Value & EFI_AHCI_FIS_TYPE_MASK) == EFI_AHCI_FIS_REGISTER_D2H) { + LBAMid = ((UINT8 *)(UINTN)(FisBaseAddr + EFI_AHCI_D2H_FIS_OFFSET))[5]; + LBAHigh = ((UINT8 *)(UINTN)(FisBaseAddr + EFI_AHCI_D2H_FIS_OFFSET))[6]; + + if ((LBAMid == 0x4f) && (LBAHigh == 0xc2)) { + // + // The threshold exceeded condition is not detected by the device + // + DEBUG ((EFI_D_INFO, "The S.M.A.R.T threshold exceeded condition is not detected\n")); + REPORT_STATUS_CODE ( + EFI_PROGRESS_CODE, + (EFI_IO_BUS_ATA_ATAPI | EFI_IOB_ATA_BUS_SMART_UNDERTHRESHOLD) + ); + } else if ((LBAMid == 0xf4) && (LBAHigh == 0x2c)) { + // + // The threshold exceeded condition is detected by the device + // + DEBUG ((EFI_D_INFO, "The S.M.A.R.T threshold exceeded condition is detected\n")); + REPORT_STATUS_CODE ( + EFI_PROGRESS_CODE, + (EFI_IO_BUS_ATA_ATAPI | EFI_IOB_ATA_BUS_SMART_OVERTHRESHOLD) + ); + } + } + + return EFI_SUCCESS; +} + +/** + Enable SMART command of the disk if supported. + + @param PciIo The PCI IO protocol instance. + @param AhciRegisters The pointer to the EFI_AHCI_REGISTERS. + @param Port The number of port. + @param PortMultiplier The port multiplier port number. + @param IdentifyData A pointer to data buffer which is used to contain IDENTIFY data. + @param AtaStatusBlock A pointer to EFI_ATA_STATUS_BLOCK data structure. + +**/ +VOID +EFIAPI +AhciAtaSmartSupport ( + IN EFI_PCI_IO_PROTOCOL *PciIo, + IN EFI_AHCI_REGISTERS *AhciRegisters, + IN UINT8 Port, + IN UINT8 PortMultiplier, + IN EFI_IDENTIFY_DATA *IdentifyData, + IN OUT EFI_ATA_STATUS_BLOCK *AtaStatusBlock + ) +{ + EFI_STATUS Status; + EFI_ATA_COMMAND_BLOCK AtaCommandBlock; + + // + // Detect if the device supports S.M.A.R.T. + // + if ((IdentifyData->AtaData.command_set_supported_82 & 0x0001) != 0x0001) { + // + // S.M.A.R.T is not supported by the device + // + DEBUG ((EFI_D_INFO, "S.M.A.R.T feature is not supported at port [%d] PortMultiplier [%d]!\n", + Port, PortMultiplier)); + REPORT_STATUS_CODE ( + EFI_ERROR_CODE | EFI_ERROR_MINOR, + (EFI_IO_BUS_ATA_ATAPI | EFI_IOB_ATA_BUS_SMART_NOTSUPPORTED) + ); + } else { + // + // Check if the feature is enabled. If not, then enable S.M.A.R.T. + // + if ((IdentifyData->AtaData.command_set_feature_enb_85 & 0x0001) != 0x0001) { + + REPORT_STATUS_CODE ( + EFI_PROGRESS_CODE, + (EFI_IO_BUS_ATA_ATAPI | EFI_IOB_ATA_BUS_SMART_DISABLE) + ); + + ZeroMem (&AtaCommandBlock, sizeof (EFI_ATA_COMMAND_BLOCK)); + + AtaCommandBlock.AtaCommand = ATA_CMD_SMART; + AtaCommandBlock.AtaFeatures = ATA_SMART_ENABLE_OPERATION; + AtaCommandBlock.AtaCylinderLow = ATA_CONSTANT_4F; + AtaCommandBlock.AtaCylinderHigh = ATA_CONSTANT_C2; + + // + // Send S.M.A.R.T Enable command to device + // + Status = AhciNonDataTransfer ( + PciIo, + AhciRegisters, + (UINT8)Port, + (UINT8)PortMultiplier, + NULL, + 0, + &AtaCommandBlock, + AtaStatusBlock, + ATA_ATAPI_TIMEOUT, + NULL + ); + + + if (!EFI_ERROR (Status)) { + // + // Send S.M.A.R.T AutoSave command to device + // + ZeroMem (&AtaCommandBlock, sizeof (EFI_ATA_COMMAND_BLOCK)); + + AtaCommandBlock.AtaCommand = ATA_CMD_SMART; + AtaCommandBlock.AtaFeatures = 0xD2; + AtaCommandBlock.AtaSectorCount = 0xF1; + AtaCommandBlock.AtaCylinderLow = ATA_CONSTANT_4F; + AtaCommandBlock.AtaCylinderHigh = ATA_CONSTANT_C2; + + Status = AhciNonDataTransfer ( + PciIo, + AhciRegisters, + (UINT8)Port, + (UINT8)PortMultiplier, + NULL, + 0, + &AtaCommandBlock, + AtaStatusBlock, + ATA_ATAPI_TIMEOUT, + NULL + ); + + if (!EFI_ERROR (Status)) { + Status = AhciAtaSmartReturnStatusCheck ( + PciIo, + AhciRegisters, + (UINT8)Port, + (UINT8)PortMultiplier, + AtaStatusBlock + ); + } + } + } + DEBUG ((EFI_D_INFO, "Enabled S.M.A.R.T feature at port [%d] PortMultiplier [%d]!\n", + Port, PortMultiplier)); + } + + return ; +} + /** Send Buffer cmd to specific device. - + @param PciIo The PCI IO protocol instance. @param AhciRegisters The pointer to the EFI_AHCI_REGISTERS. @param Port The number of port. - @param PortMultiplier The timeout value of stop. + @param PortMultiplier The port multiplier port number. @param Buffer The data buffer to store IDENTIFY PACKET data. @retval EFI_DEVICE_ERROR The cmd abort with error occurs. @@ -1271,7 +1723,7 @@ AhciIdentify ( IN EFI_AHCI_REGISTERS *AhciRegisters, IN UINT8 Port, IN UINT8 PortMultiplier, - IN OUT EFI_IDENTIFY_DATA *Buffer + IN OUT EFI_IDENTIFY_DATA *Buffer ) { EFI_STATUS Status; @@ -1284,7 +1736,7 @@ AhciIdentify ( ZeroMem (&AtaCommandBlock, sizeof (EFI_ATA_COMMAND_BLOCK)); ZeroMem (&AtaStatusBlock, sizeof (EFI_ATA_STATUS_BLOCK)); - + AtaCommandBlock.AtaCommand = ATA_CMD_IDENTIFY_DRIVE; AtaCommandBlock.AtaSectorCount = 1; @@ -1300,7 +1752,8 @@ AhciIdentify ( &AtaStatusBlock, Buffer, sizeof (EFI_IDENTIFY_DATA), - ATA_ATAPI_TIMEOUT + ATA_ATAPI_TIMEOUT, + NULL ); return Status; @@ -1308,11 +1761,11 @@ AhciIdentify ( /** Send Buffer cmd to specific device. - + @param PciIo The PCI IO protocol instance. @param AhciRegisters The pointer to the EFI_AHCI_REGISTERS. @param Port The number of port. - @param PortMultiplier The timeout value of stop. + @param PortMultiplier The port multiplier port number. @param Buffer The data buffer to store IDENTIFY PACKET data. @retval EFI_DEVICE_ERROR The cmd abort with error occurs. @@ -1328,7 +1781,7 @@ AhciIdentifyPacket ( IN EFI_AHCI_REGISTERS *AhciRegisters, IN UINT8 Port, IN UINT8 PortMultiplier, - IN OUT EFI_IDENTIFY_DATA *Buffer + IN OUT EFI_IDENTIFY_DATA *Buffer ) { EFI_STATUS Status; @@ -1338,7 +1791,7 @@ AhciIdentifyPacket ( if (PciIo == NULL || AhciRegisters == NULL) { return EFI_INVALID_PARAMETER; } - + ZeroMem (&AtaCommandBlock, sizeof (EFI_ATA_COMMAND_BLOCK)); ZeroMem (&AtaStatusBlock, sizeof (EFI_ATA_STATUS_BLOCK)); @@ -1357,7 +1810,8 @@ AhciIdentifyPacket ( &AtaStatusBlock, Buffer, sizeof (EFI_IDENTIFY_DATA), - ATA_ATAPI_TIMEOUT + ATA_ATAPI_TIMEOUT, + NULL ); return Status; @@ -1365,11 +1819,11 @@ AhciIdentifyPacket ( /** Send SET FEATURE cmd on specific device. - + @param PciIo The PCI IO protocol instance. @param AhciRegisters The pointer to the EFI_AHCI_REGISTERS. @param Port The number of port. - @param PortMultiplier The timeout value of stop. + @param PortMultiplier The port multiplier port number. @param Feature The data to send Feature register. @param FeatureSpecificData The specific data for SET FEATURE cmd. @@ -1396,7 +1850,7 @@ AhciDeviceSetFeature ( ZeroMem (&AtaCommandBlock, sizeof (EFI_ATA_COMMAND_BLOCK)); ZeroMem (&AtaStatusBlock, sizeof (EFI_ATA_STATUS_BLOCK)); - + AtaCommandBlock.AtaCommand = ATA_CMD_SET_FEATURES; AtaCommandBlock.AtaFeatures = (UINT8) Feature; AtaCommandBlock.AtaFeaturesExp = (UINT8) (Feature >> 8); @@ -1414,19 +1868,20 @@ AhciDeviceSetFeature ( 0, &AtaCommandBlock, &AtaStatusBlock, - ATA_ATAPI_TIMEOUT + ATA_ATAPI_TIMEOUT, + NULL ); return Status; } /** - This function is used to send out ATAPI commands conforms to the Packet Command + This function is used to send out ATAPI commands conforms to the Packet Command with PIO Protocol. @param PciIo The PCI IO protocol instance. @param AhciRegisters The pointer to the EFI_AHCI_REGISTERS. - @param Port The number of port. + @param Port The number of port. @param PortMultiplier The number of port multiplier. @param Packet A pointer to EFI_EXT_SCSI_PASS_THRU_SCSI_REQUEST_PACKET structure. @@ -1451,7 +1906,6 @@ AhciPacketCommandExecute ( EFI_ATA_COMMAND_BLOCK AtaCommandBlock; EFI_ATA_STATUS_BLOCK AtaStatusBlock; BOOLEAN Read; - UINT8 Retry; if (Packet == NULL || Packet->Cdb == NULL) { return EFI_INVALID_PARAMETER; @@ -1481,7 +1935,7 @@ AhciPacketCommandExecute ( Read = FALSE; } - if (Length == 0) { + if (Length == 0) { Status = AhciNonDataTransfer ( PciIo, AhciRegisters, @@ -1491,44 +1945,32 @@ AhciPacketCommandExecute ( Packet->CdbLength, &AtaCommandBlock, &AtaStatusBlock, - Packet->Timeout + Packet->Timeout, + NULL ); } else { - // - // READ_CAPACITY cmd may execute failure. Retry 5 times - // - if (((UINT8 *)Packet->Cdb)[0] == ATA_CMD_READ_CAPACITY) { - Retry = 5; - } else { - Retry = 1; - } - do { - Status = AhciPioTransfer ( - PciIo, - AhciRegisters, - Port, - PortMultiplier, - Packet->Cdb, - Packet->CdbLength, - Read, - &AtaCommandBlock, - &AtaStatusBlock, - Buffer, - Length, - Packet->Timeout - ); - if (!EFI_ERROR (Status)) { - break; - } - Retry--; - } while (Retry != 0); + Status = AhciPioTransfer ( + PciIo, + AhciRegisters, + Port, + PortMultiplier, + Packet->Cdb, + Packet->CdbLength, + Read, + &AtaCommandBlock, + &AtaStatusBlock, + Buffer, + Length, + Packet->Timeout, + NULL + ); } return Status; } /** Allocate transfer-related data struct which is used at AHCI mode. - + @param PciIo The PCI IO protocol instance. @param AhciRegisters The pointer to the EFI_AHCI_REGISTERS. @@ -1545,31 +1987,43 @@ AhciCreateTransferDescriptor ( VOID *Buffer; UINT32 Capability; + UINT32 PortImplementBitMap; UINT8 MaxPortNumber; UINT8 MaxCommandSlotNumber; BOOLEAN Support64Bit; UINT64 MaxReceiveFisSize; UINT64 MaxCommandListSize; UINT64 MaxCommandTableSize; + EFI_PHYSICAL_ADDRESS AhciRFisPciAddr; + EFI_PHYSICAL_ADDRESS AhciCmdListPciAddr; + EFI_PHYSICAL_ADDRESS AhciCommandTablePciAddr; Buffer = NULL; // // Collect AHCI controller information // Capability = AhciReadReg(PciIo, EFI_AHCI_CAPABILITY_OFFSET); - MaxPortNumber = (UINT8) ((Capability & 0x1F) + 1); // // Get the number of command slots per port supported by this HBA. // MaxCommandSlotNumber = (UINT8) (((Capability & 0x1F00) >> 8) + 1); Support64Bit = (BOOLEAN) (((Capability & BIT31) != 0) ? TRUE : FALSE); + + PortImplementBitMap = AhciReadReg(PciIo, EFI_AHCI_PI_OFFSET); + // + // Get the highest bit of implemented ports which decides how many bytes are allocated for recived FIS. + // + MaxPortNumber = (UINT8)(UINTN)(HighBitSet32(PortImplementBitMap) + 1); + if (MaxPortNumber == 0) { + return EFI_DEVICE_ERROR; + } MaxReceiveFisSize = MaxPortNumber * sizeof (EFI_AHCI_RECEIVED_FIS); Status = PciIo->AllocateBuffer ( PciIo, AllocateAnyPages, EfiBootServicesData, - (UINTN)EFI_SIZE_TO_PAGES (MaxReceiveFisSize), + EFI_SIZE_TO_PAGES ((UINTN) MaxReceiveFisSize), &Buffer, 0 ); @@ -1589,25 +2043,26 @@ AhciCreateTransferDescriptor ( EfiPciIoOperationBusMasterCommonBuffer, Buffer, &Bytes, - (EFI_PHYSICAL_ADDRESS *) &AhciRegisters->AhciRFisPciAddr, + &AhciRFisPciAddr, &AhciRegisters->MapRFis ); if (EFI_ERROR (Status) || (Bytes != MaxReceiveFisSize)) { // - // Map error or unable to map the whole RFis buffer into a contiguous region. + // Map error or unable to map the whole RFis buffer into a contiguous region. // Status = EFI_OUT_OF_RESOURCES; goto Error6; } - if ((!Support64Bit) && ((EFI_PHYSICAL_ADDRESS)(UINTN)AhciRegisters->AhciRFisPciAddr > 0x100000000ULL)) { + if ((!Support64Bit) && (AhciRFisPciAddr > 0x100000000ULL)) { // // The AHCI HBA doesn't support 64bit addressing, so should not get a >4G pci bus master address. // Status = EFI_DEVICE_ERROR; goto Error5; } + AhciRegisters->AhciRFisPciAddr = (EFI_AHCI_RECEIVED_FIS *)(UINTN)AhciRFisPciAddr; // // Allocate memory for command list @@ -1619,14 +2074,14 @@ AhciCreateTransferDescriptor ( PciIo, AllocateAnyPages, EfiBootServicesData, - (UINTN)EFI_SIZE_TO_PAGES (MaxCommandListSize), + EFI_SIZE_TO_PAGES ((UINTN) MaxCommandListSize), &Buffer, 0 ); if (EFI_ERROR (Status)) { // - // Free mapped resource. + // Free mapped resource. // Status = EFI_OUT_OF_RESOURCES; goto Error5; @@ -1643,7 +2098,7 @@ AhciCreateTransferDescriptor ( EfiPciIoOperationBusMasterCommonBuffer, Buffer, &Bytes, - (EFI_PHYSICAL_ADDRESS *)&AhciRegisters->AhciCmdListPciAddr, + &AhciCmdListPciAddr, &AhciRegisters->MapCmdList ); @@ -1655,13 +2110,14 @@ AhciCreateTransferDescriptor ( goto Error4; } - if ((!Support64Bit) && ((EFI_PHYSICAL_ADDRESS)(UINTN)AhciRegisters->AhciCmdListPciAddr > 0x100000000ULL)) { + if ((!Support64Bit) && (AhciCmdListPciAddr > 0x100000000ULL)) { // // The AHCI HBA doesn't support 64bit addressing, so should not get a >4G pci bus master address. // Status = EFI_DEVICE_ERROR; goto Error3; } + AhciRegisters->AhciCmdListPciAddr = (EFI_AHCI_COMMAND_LIST *)(UINTN)AhciCmdListPciAddr; // // Allocate memory for command table @@ -1674,14 +2130,14 @@ AhciCreateTransferDescriptor ( PciIo, AllocateAnyPages, EfiBootServicesData, - (UINTN)EFI_SIZE_TO_PAGES (MaxCommandTableSize), + EFI_SIZE_TO_PAGES ((UINTN) MaxCommandTableSize), &Buffer, 0 ); if (EFI_ERROR (Status)) { // - // Free mapped resource. + // Free mapped resource. // Status = EFI_OUT_OF_RESOURCES; goto Error3; @@ -1698,7 +2154,7 @@ AhciCreateTransferDescriptor ( EfiPciIoOperationBusMasterCommonBuffer, Buffer, &Bytes, - (EFI_PHYSICAL_ADDRESS *)&AhciRegisters->AhciCommandTablePciAddr, + &AhciCommandTablePciAddr, &AhciRegisters->MapCommandTable ); @@ -1710,17 +2166,18 @@ AhciCreateTransferDescriptor ( goto Error2; } - if ((!Support64Bit) && ((EFI_PHYSICAL_ADDRESS)(UINTN)AhciRegisters->AhciCommandTablePciAddr > 0x100000000ULL)) { + if ((!Support64Bit) && (AhciCommandTablePciAddr > 0x100000000ULL)) { // // The AHCI HBA doesn't support 64bit addressing, so should not get a >4G pci bus master address. // Status = EFI_DEVICE_ERROR; goto Error1; } + AhciRegisters->AhciCommandTablePciAddr = (EFI_AHCI_COMMAND_TABLE *)(UINTN)AhciCommandTablePciAddr; return EFI_SUCCESS; // - // Map error or unable to map the whole CmdList buffer into a contiguous region. + // Map error or unable to map the whole CmdList buffer into a contiguous region. // Error1: PciIo->Unmap ( @@ -1730,7 +2187,7 @@ Error1: Error2: PciIo->FreeBuffer ( PciIo, - (UINTN)EFI_SIZE_TO_PAGES (MaxCommandTableSize), + EFI_SIZE_TO_PAGES ((UINTN) MaxCommandTableSize), AhciRegisters->AhciCommandTable ); Error3: @@ -1741,7 +2198,7 @@ Error3: Error4: PciIo->FreeBuffer ( PciIo, - (UINTN)EFI_SIZE_TO_PAGES (MaxCommandListSize), + EFI_SIZE_TO_PAGES ((UINTN) MaxCommandListSize), AhciRegisters->AhciCmdList ); Error5: @@ -1752,7 +2209,7 @@ Error5: Error6: PciIo->FreeBuffer ( PciIo, - (UINTN)EFI_SIZE_TO_PAGES (MaxReceiveFisSize), + EFI_SIZE_TO_PAGES ((UINTN) MaxReceiveFisSize), AhciRegisters->AhciRFis ); @@ -1762,8 +2219,8 @@ Error6: /** Initialize ATA host controller at AHCI mode. - The function is designed to initialize ATA host controller. - + The function is designed to initialize ATA host controller. + @param[in] Instance A pointer to the ATA_ATAPI_PASS_THRU_INSTANCE instance. **/ @@ -1779,8 +2236,6 @@ AhciModeInitialization ( UINT32 Capability; UINT8 MaxPortNumber; UINT32 PortImplementBitMap; - UINT8 MaxCommandSlotNumber; - BOOLEAN Support64Bit; EFI_AHCI_REGISTERS *AhciRegisters; @@ -1792,7 +2247,9 @@ AhciModeInitialization ( EFI_ATA_DEVICE_TYPE DeviceType; EFI_ATA_COLLECTIVE_MODE *SupportedModes; EFI_ATA_TRANSFER_MODE TransferMode; - + UINT32 PhyDetectDelay; + UINT32 Value; + if (Instance == NULL) { return EFI_INVALID_PARAMETER; } @@ -1800,35 +2257,55 @@ AhciModeInitialization ( PciIo = Instance->PciIo; IdeInit = Instance->IdeControllerInit; - Status = AhciReset (PciIo, ATA_ATAPI_TIMEOUT); + Status = AhciReset (PciIo, EFI_AHCI_BUS_RESET_TIMEOUT); if (EFI_ERROR (Status)) { return EFI_DEVICE_ERROR; } // - // Enable AE before accessing any AHCI registers + // Collect AHCI controller information // - AhciOrReg (PciIo, EFI_AHCI_GHC_OFFSET, EFI_AHCI_GHC_ENABLE); + Capability = AhciReadReg (PciIo, EFI_AHCI_CAPABILITY_OFFSET); // - // Collect AHCI controller information + // Make sure that GHC.AE bit is set before accessing any AHCI registers. // - Capability = AhciReadReg(PciIo, EFI_AHCI_CAPABILITY_OFFSET); + Value = AhciReadReg(PciIo, EFI_AHCI_GHC_OFFSET); + + if ((Value & EFI_AHCI_GHC_ENABLE) == 0) { + AhciOrReg (PciIo, EFI_AHCI_GHC_OFFSET, EFI_AHCI_GHC_ENABLE); + } + + // + // Enable 64-bit DMA support in the PCI layer if this controller + // supports it. + // + if ((Capability & EFI_AHCI_CAP_S64A) != 0) { + Status = PciIo->Attributes ( + PciIo, + EfiPciIoAttributeOperationEnable, + EFI_PCI_IO_ATTRIBUTE_DUAL_ADDRESS_CYCLE, + NULL + ); + if (EFI_ERROR (Status)) { + DEBUG ((EFI_D_WARN, + "AhciModeInitialization: failed to enable 64-bit DMA on 64-bit capable controller (%r)\n", + Status)); + } + } // // Get the number of command slots per port supported by this HBA. // - MaxCommandSlotNumber = (UINT8) (((Capability & 0x1F00) >> 8) + 1); - Support64Bit = (BOOLEAN) (((Capability & BIT31) != 0) ? TRUE : FALSE); + 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. + // It indicates which ports that the HBA supports are available for software to use. // PortImplementBitMap = AhciReadReg(PciIo, EFI_AHCI_PI_OFFSET); - MaxPortNumber = (UINT8) ((Capability & 0x1F) + 1); - + AhciRegisters = &Instance->AhciRegisters; Status = AhciCreateTransferDescriptor (PciIo, AhciRegisters); @@ -1836,174 +2313,233 @@ AhciModeInitialization ( return EFI_OUT_OF_RESOURCES; } - for (Port = 0; Port < MaxPortNumber; Port ++) { - Data64.Uint64 = (UINTN) (AhciRegisters->AhciRFisPciAddr) + sizeof (EFI_AHCI_RECEIVED_FIS) * Port; - - Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_FB; - AhciWriteReg (PciIo, Offset, Data64.Uint32.Lower32); - Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_FBU; - AhciWriteReg (PciIo, Offset, Data64.Uint32.Upper32); - - // - // Single task envrionment, we only use one command table for all port - // - Data64.Uint64 = (UINTN) (AhciRegisters->AhciCmdListPciAddr); - - Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_CLB; - AhciWriteReg (PciIo, Offset, Data64.Uint32.Lower32); - Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_CLBU; - AhciWriteReg (PciIo, Offset, Data64.Uint32.Upper32); - + for (Port = 0; Port < EFI_AHCI_MAX_PORTS; Port ++) { if ((PortImplementBitMap & (BIT0 << Port)) != 0) { - Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_CMD; - - if ((Capability & EFI_AHCI_PORT_CMD_ASP) != 0) { - AhciOrReg (PciIo, Offset, EFI_AHCI_PORT_CMD_SUD); + // + // According to AHCI spec, MaxPortNumber should be equal or greater than the number of implemented ports. + // + if ((MaxPortNumber--) == 0) { + // + // Should never be here. + // + ASSERT (FALSE); + return EFI_SUCCESS; } + + IdeInit->NotifyPhase (IdeInit, EfiIdeBeforeChannelEnumeration, Port); + + // + // Initialize FIS Base Address Register and Command List Base Address Register for use. + // + Data64.Uint64 = (UINTN) (AhciRegisters->AhciRFisPciAddr) + sizeof (EFI_AHCI_RECEIVED_FIS) * Port; + Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_FB; + AhciWriteReg (PciIo, Offset, Data64.Uint32.Lower32); + Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_FBU; + AhciWriteReg (PciIo, Offset, Data64.Uint32.Upper32); + + Data64.Uint64 = (UINTN) (AhciRegisters->AhciCmdListPciAddr); + Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_CLB; + AhciWriteReg (PciIo, Offset, Data64.Uint32.Lower32); + Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_CLBU; + AhciWriteReg (PciIo, Offset, Data64.Uint32.Upper32); + + Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_CMD; Data = AhciReadReg (PciIo, Offset); if ((Data & EFI_AHCI_PORT_CMD_CPD) != 0) { AhciOrReg (PciIo, Offset, EFI_AHCI_PORT_CMD_POD); } - - AhciAndReg (PciIo, Offset, (UINT32)~(EFI_AHCI_PORT_CMD_FRE|EFI_AHCI_PORT_CMD_COL|EFI_AHCI_PORT_CMD_ST)); - } - - Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_SCTL; - AhciAndReg (PciIo, Offset, (UINT32)~(EFI_AHCI_PORT_SCTL_IPM_MASK)); - - AhciAndReg (PciIo, Offset,(UINT32) ~(EFI_AHCI_PORT_SCTL_IPM_PSD)); - AhciOrReg (PciIo, Offset, EFI_AHCI_PORT_SCTL_IPM_PSD); - - AhciAndReg (PciIo, Offset, (UINT32)~(EFI_AHCI_PORT_SCTL_IPM_SSD)); - AhciOrReg (PciIo, Offset, EFI_AHCI_PORT_SCTL_IPM_SSD); - - Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_IE; - AhciAndReg (PciIo, Offset, 0); - - Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_SERR; - AhciWriteReg (PciIo, Offset, AhciReadReg (PciIo, Offset)); - } - // - // Stall for 100 milliseconds. - // - MicroSecondDelay(100000); - - IdeInit->NotifyPhase (IdeInit, EfiIdeBeforeChannelEnumeration, Port); - - for (Port = 0; Port < MaxPortNumber; Port ++) { - if ((PortImplementBitMap & (BIT0 << Port)) != 0) { - - Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_SSTS; - Data = AhciReadReg (PciIo, Offset) & EFI_AHCI_PORT_SSTS_DET_MASK; - - if (Data == 0) { - continue; + if ((Capability & EFI_AHCI_CAP_SSS) != 0) { + AhciOrReg (PciIo, Offset, EFI_AHCI_PORT_CMD_SUD); } + // - // Found device in the port + // Disable aggressive power management. // - if (Data == EFI_AHCI_PORT_SSTS_DET_PCE) { - Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_SIG; - - Status = AhciWaitMemSet ( - PciIo, - Offset, - 0x0000FFFF, - 0x00000101, - ATA_ATAPI_TIMEOUT - ); - if (EFI_ERROR (Status)) { - continue; + Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_SCTL; + AhciOrReg (PciIo, Offset, EFI_AHCI_PORT_SCTL_IPM_INIT); + // + // Disable the reporting of the corresponding interrupt to system software. + // + Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_IE; + AhciAndReg (PciIo, Offset, 0); + + // + // Now inform the IDE Controller Init Module. + // + IdeInit->NotifyPhase (IdeInit, EfiIdeBusBeforeDevicePresenceDetection, Port); + + // + // Enable FIS Receive DMA engine for the first D2H FIS. + // + Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_CMD; + AhciOrReg (PciIo, Offset, EFI_AHCI_PORT_CMD_FRE); + + // + // Wait no longer than 10 ms to wait the Phy to detect the presence of a device. + // It's the requirment from SATA1.0a spec section 5.2. + // + PhyDetectDelay = EFI_AHCI_BUS_PHY_DETECT_TIMEOUT; + Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_SSTS; + do { + Data = AhciReadReg (PciIo, Offset) & EFI_AHCI_PORT_SSTS_DET_MASK; + if ((Data == EFI_AHCI_PORT_SSTS_DET_PCE) || (Data == EFI_AHCI_PORT_SSTS_DET)) { + break; } + MicroSecondDelay (1000); + PhyDetectDelay--; + } while (PhyDetectDelay > 0); + + if (PhyDetectDelay == 0) { // - // Now inform the IDE Controller Init Module. + // No device detected at this port. + // Clear PxCMD.SUD for those ports at which there are no device present. // - IdeInit->NotifyPhase (IdeInit, EfiIdeBusBeforeDevicePresenceDetection, Port); + Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_CMD; + AhciAndReg (PciIo, Offset, (UINT32) ~(EFI_AHCI_PORT_CMD_SUD)); + continue; + } - Data = AhciReadReg (PciIo, Offset); + // + // According to SATA1.0a spec section 5.2, we need to wait for PxTFD.BSY and PxTFD.DRQ + // and PxTFD.ERR to be zero. The maximum wait time is 16s which is defined at ATA spec. + // + PhyDetectDelay = 16 * 1000; + do { + Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_SERR; + if (AhciReadReg(PciIo, Offset) != 0) { + AhciWriteReg (PciIo, Offset, AhciReadReg(PciIo, Offset)); + } + Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_TFD; - if ((Data & EFI_AHCI_ATAPI_SIG_MASK) == EFI_AHCI_ATAPI_DEVICE_SIG) { - Status = AhciIdentifyPacket (PciIo, AhciRegisters, Port, 0, &Buffer); + Data = AhciReadReg (PciIo, Offset) & EFI_AHCI_PORT_TFD_MASK; + if (Data == 0) { + break; + } - if (EFI_ERROR (Status)) { - continue; - } + MicroSecondDelay (1000); + PhyDetectDelay--; + } while (PhyDetectDelay > 0); - DeviceType = EfiIdeCdrom; - } else if ((Data & EFI_AHCI_ATAPI_SIG_MASK) == EFI_AHCI_ATA_DEVICE_SIG) { - Status = AhciIdentify (PciIo, AhciRegisters, Port, 0, &Buffer); + if (PhyDetectDelay == 0) { + DEBUG ((EFI_D_ERROR, "Port %d Device presence detected but phy not ready (TFD=0x%X)\n", Port, Data)); + continue; + } + + // + // When the first D2H register FIS is received, the content of PxSIG register is updated. + // + Offset = EFI_AHCI_PORT_START + Port * EFI_AHCI_PORT_REG_WIDTH + EFI_AHCI_PORT_SIG; + Status = AhciWaitMmioSet ( + PciIo, + Offset, + 0x0000FFFF, + 0x00000101, + EFI_TIMER_PERIOD_SECONDS(16) + ); + if (EFI_ERROR (Status)) { + continue; + } - if (EFI_ERROR (Status)) { - continue; - } + Data = AhciReadReg (PciIo, Offset); + if ((Data & EFI_AHCI_ATAPI_SIG_MASK) == EFI_AHCI_ATAPI_DEVICE_SIG) { + Status = AhciIdentifyPacket (PciIo, AhciRegisters, Port, 0, &Buffer); - DeviceType = EfiIdeHarddisk; - } else { + if (EFI_ERROR (Status)) { continue; } - DEBUG ((EFI_D_INFO, "port [%d] port mulitplier [%d] has a [%a]\n", - Port, 0, DeviceType == EfiIdeCdrom ? "cdrom" : "harddisk")); - - // - // Submit identify data to IDE controller init driver - // - IdeInit->SubmitData (IdeInit, Port, 0, &Buffer); + DeviceType = EfiIdeCdrom; + } else if ((Data & EFI_AHCI_ATAPI_SIG_MASK) == EFI_AHCI_ATA_DEVICE_SIG) { + Status = AhciIdentify (PciIo, AhciRegisters, Port, 0, &Buffer); - // - // Now start to config ide device parameter and transfer mode. - // - Status = IdeInit->CalculateMode ( - IdeInit, - Port, - 0, - &SupportedModes - ); if (EFI_ERROR (Status)) { - DEBUG ((EFI_D_ERROR, "Calculate Mode Fail, Status = %r\n", Status)); + REPORT_STATUS_CODE (EFI_PROGRESS_CODE, (EFI_PERIPHERAL_FIXED_MEDIA | EFI_P_EC_NOT_DETECTED)); continue; } - // - // Set best supported PIO mode on this IDE device - // - if (SupportedModes->PioMode.Mode <= EfiAtaPioMode2) { - TransferMode.ModeCategory = EFI_ATA_MODE_DEFAULT_PIO; - } else { - TransferMode.ModeCategory = EFI_ATA_MODE_FLOW_PIO; - } + DeviceType = EfiIdeHarddisk; + } else { + continue; + } + DEBUG ((EFI_D_INFO, "port [%d] port mulitplier [%d] has a [%a]\n", + Port, 0, DeviceType == EfiIdeCdrom ? "cdrom" : "harddisk")); - TransferMode.ModeNumber = (UINT8) (SupportedModes->PioMode.Mode); - - // - // Set supported DMA mode on this IDE device. Note that UDMA & MDMA cann't - // be set together. Only one DMA mode can be set to a device. If setting - // DMA mode operation fails, we can continue moving on because we only use - // PIO mode at boot time. DMA modes are used by certain kind of OS booting - // - if (SupportedModes->UdmaMode.Valid) { - TransferMode.ModeCategory = EFI_ATA_MODE_UDMA; - TransferMode.ModeNumber = (UINT8) (SupportedModes->UdmaMode.Mode); - } else if (SupportedModes->MultiWordDmaMode.Valid) { - TransferMode.ModeCategory = EFI_ATA_MODE_MDMA; - TransferMode.ModeNumber = (UINT8) SupportedModes->MultiWordDmaMode.Mode; - } + // + // If the device is a hard disk, then try to enable S.M.A.R.T feature + // + if ((DeviceType == EfiIdeHarddisk) && PcdGetBool (PcdAtaSmartEnable)) { + AhciAtaSmartSupport ( + PciIo, + AhciRegisters, + Port, + 0, + &Buffer, + NULL + ); + } - Status = AhciDeviceSetFeature (PciIo, AhciRegisters, Port, 0, 0x03, (UINT32)(*(UINT8 *)&TransferMode)); + // + // Submit identify data to IDE controller init driver + // + IdeInit->SubmitData (IdeInit, Port, 0, &Buffer); - if (EFI_ERROR (Status)) { - DEBUG ((EFI_D_ERROR, "Set transfer Mode Fail, Status = %r\n", Status)); - continue; - } - // - // Found a ATA or ATAPI device, add it into the device list. - // - CreateNewDeviceInfo (Instance, Port, 0, DeviceType, &Buffer); + // + // Now start to config ide device parameter and transfer mode. + // + Status = IdeInit->CalculateMode ( + IdeInit, + Port, + 0, + &SupportedModes + ); + if (EFI_ERROR (Status)) { + DEBUG ((EFI_D_ERROR, "Calculate Mode Fail, Status = %r\n", Status)); + continue; + } + + // + // Set best supported PIO mode on this IDE device + // + if (SupportedModes->PioMode.Mode <= EfiAtaPioMode2) { + TransferMode.ModeCategory = EFI_ATA_MODE_DEFAULT_PIO; + } else { + TransferMode.ModeCategory = EFI_ATA_MODE_FLOW_PIO; + } + + TransferMode.ModeNumber = (UINT8) (SupportedModes->PioMode.Mode); + + // + // Set supported DMA mode on this IDE device. Note that UDMA & MDMA cann't + // be set together. Only one DMA mode can be set to a device. If setting + // DMA mode operation fails, we can continue moving on because we only use + // PIO mode at boot time. DMA modes are used by certain kind of OS booting + // + if (SupportedModes->UdmaMode.Valid) { + TransferMode.ModeCategory = EFI_ATA_MODE_UDMA; + TransferMode.ModeNumber = (UINT8) (SupportedModes->UdmaMode.Mode); + } else if (SupportedModes->MultiWordDmaMode.Valid) { + TransferMode.ModeCategory = EFI_ATA_MODE_MDMA; + TransferMode.ModeNumber = (UINT8) SupportedModes->MultiWordDmaMode.Mode; + } + + Status = AhciDeviceSetFeature (PciIo, AhciRegisters, Port, 0, 0x03, (UINT32)(*(UINT8 *)&TransferMode)); + if (EFI_ERROR (Status)) { + DEBUG ((EFI_D_ERROR, "Set transfer Mode Fail, Status = %r\n", Status)); + continue; + } + + // + // Found a ATA or ATAPI device, add it into the device list. + // + CreateNewDeviceInfo (Instance, Port, 0xFFFF, DeviceType, &Buffer); + if (DeviceType == EfiIdeHarddisk) { + REPORT_STATUS_CODE (EFI_PROGRESS_CODE, (EFI_PERIPHERAL_FIXED_MEDIA | EFI_P_PC_ENABLE)); } } } + return EFI_SUCCESS; }