diff options
Diffstat (limited to 'ReferenceCode/RapidStart/Pei/RapidStartAhci.c')
-rw-r--r-- | ReferenceCode/RapidStart/Pei/RapidStartAhci.c | 1211 |
1 files changed, 1211 insertions, 0 deletions
diff --git a/ReferenceCode/RapidStart/Pei/RapidStartAhci.c b/ReferenceCode/RapidStart/Pei/RapidStartAhci.c new file mode 100644 index 0000000..7a37eb6 --- /dev/null +++ b/ReferenceCode/RapidStart/Pei/RapidStartAhci.c @@ -0,0 +1,1211 @@ +/** @file + Provide functions to initialize SATA controller and perform ACHI commands + +@copyright + Copyright (c) 1999 - 2012 Intel Corporation. All rights reserved + This software and associated documentation (if any) is furnished + under a license and may only be used or copied in accordance + with the terms of the license. Except as permitted by such + license, no part of this software or documentation may be + reproduced, stored in a retrieval system, or transmitted in any + form or by any means without the express written consent of + Intel Corporation. + + This file contains an 'Intel Peripheral Driver' and uniquely + identified as "Intel Reference Module" and is + licensed for Intel CPUs and chipsets under the terms of your + license agreement with Intel or your vendor. This file may + be modified by the user, subject to additional terms of the + license agreement + +**/ +#if !defined(EDK_RELEASE_VERSION) || (EDK_RELEASE_VERSION < 0x00020000) +#include "EdkIIGluePeim.h" +#include "RapidStartConfig.h" +#include <SaAccess.h> +#include <PchAccess.h> +#include <PchPlatformLib.h> +#include "RapidStartPeiLib.h" +#include "RapidStartData.h" +#include "RapidStartAhci.h" +#endif + +typedef struct { + UINT64 DataByteAddr; + UINT32 Reserved; + UINT32 ByteCountI; +} AHCI_PRDT; + +#ifndef EFI_DEBUG +#define DumpPortStatus(a) +#else +/** + Send serial out debug message for AHCI port status + + @param[in] Ahci - AHCI controller information structure +**/ +STATIC +VOID +DumpPortStatus ( + IN AHCI_CONTEXT *Ahci + ) +{ + UINTN PxBase; + ASSERT (Ahci->Port <= 31); + PxBase = AHCI_PORT_BASE (Ahci); + + DEBUG ( + (EFI_D_ERROR, + " CLB:%08x FB:%08x\n CMD:%08x TFD:%08x IS:%08x\nSSTS:%08x SERR:%08x\n CI:%08x SACT:%08x SIG:%08x\n", + MmioRead32 (PxBase + R_PCH_SATA_AHCI_PXCLB), + MmioRead32 (PxBase + R_PCH_SATA_AHCI_PXFB), + MmioRead32 (PxBase + R_PCH_SATA_AHCI_PXCMD), + MmioRead32 (PxBase + R_PCH_SATA_AHCI_PXTFD), + MmioRead32 (PxBase + R_PCH_SATA_AHCI_PXIS), + MmioRead32 (PxBase + R_PCH_SATA_AHCI_PXSSTS), + MmioRead32 (PxBase + R_PCH_SATA_AHCI_PXSERR), + MmioRead32 (PxBase + R_PCH_SATA_AHCI_PXCI), + MmioRead32 (PxBase + R_PCH_SATA_AHCI_PXSACT), + MmioRead32 (PxBase + R_PCH_SATA_AHCI_PXSIG)) + ); +} +#endif + +/** + Returns SATA Port Control and Status register value. + + @retval PCS value +**/ +STATIC +UINT16 +AhciGetPortControlStatus ( + VOID + ) +{ + UINTN PciD31F2RegBase; + PciD31F2RegBase = MmPciAddress ( + 0, + DEFAULT_PCI_BUS_NUMBER_PCH, + PCI_DEVICE_NUMBER_PCH_SATA, + PCI_FUNCTION_NUMBER_PCH_SATA, + 0 + ); + return MmioRead16 (PciD31F2RegBase + R_PCH_SATA_PCS); +} + +/** + Returns SATA enabled port bitmap basing on PCS value. + + @retval Enabled ports map. +**/ +UINT32 +AhciGetEnabledPorts ( + VOID + ) +{ + return AhciGetPortControlStatus () & 0x7F; +} + +/** + Returns SATA present port bitmap basing on PCS value. + + @retval Present ports map. +**/ +UINT32 +AhciGetPresentPorts ( + VOID + ) +{ + return (AhciGetPortControlStatus () >> 8) & 0x7F; +} + +/** + This is a work-around to make sure Port Implemented bits (RWO) are set. + PI is set according to PCS value restored from previous boot + + @param[in] Ahci - SATA controller information structure +**/ +STATIC +VOID +AhciConfigurePortsImplemented ( + IN AHCI_CONTEXT *Ahci + ) +{ + ASSERT (Ahci->Port <= 31); + if ((MmioRead32 (Ahci->Abar + R_PCH_SATA_AHCI_PI) & (1 << Ahci->Port)) == 0) { + MmioWrite32 (Ahci->Abar + R_PCH_SATA_AHCI_PI, AhciGetEnabledPorts ()); + // + // Two reads required (C-spec ch. 25 - 1.9.14.1) + // + MmioRead32 (Ahci->Abar + R_PCH_SATA_AHCI_PI); + MmioRead32 (Ahci->Abar + R_PCH_SATA_AHCI_PI); + ASSERT (MmioRead32 (Ahci->Abar + R_PCH_SATA_AHCI_PI) & (1 << Ahci->Port)); + } +} + +/** + Initialize SATA controller and enable decode + + @param[in] Ahci - SATA controller information structure + + @retval EFI_SUCCESS - SATA controller has been initialized successfully +**/ +EFI_STATUS +AhciInit ( + IN AHCI_CONTEXT *Ahci + ) +{ + UINTN PciD31F2RegBase; + + DEBUG ((EFI_D_INFO, "AhciInit()\n")); + + Ahci->State = AHCI_STATE_INIT; + Ahci->Identify = NULL; + + ASSERT ((Ahci->Abar &~(~0 << N_PCH_SATA_AHCI_BAR_ALIGNMENT)) == 0); + + PciD31F2RegBase = MmPciAddress ( + 0, + DEFAULT_PCI_BUS_NUMBER_PCH, + PCI_DEVICE_NUMBER_PCH_SATA, + PCI_FUNCTION_NUMBER_PCH_SATA, + 0 + ); + + MmioWrite32 (PciD31F2RegBase + R_PCH_SATA_AHCI_BAR, Ahci->Abar); + /// + /// Enable decode + /// + MmioWrite8 (PciD31F2RegBase + R_PCH_SATA_COMMAND, 0x6); + + DEBUG ((EFI_D_INFO, "FFS SATA BAR: %x\n", MmioRead32 (PciD31F2RegBase + R_PCH_SATA_AHCI_BAR))); + DEBUG ((EFI_D_INFO, "FFS SATA ID: %x\n", MmioRead32 (PciD31F2RegBase + 0))); + DEBUG ((EFI_D_INFO, "FFS SATA CMD: %x\n", MmioRead16 (PciD31F2RegBase + R_PCH_SATA_COMMAND))); + DEBUG ((EFI_D_INFO, "FFS SATA STS: %x\n", MmioRead16 (PciD31F2RegBase + R_PCH_SATA_PCISTS))); + DEBUG ((EFI_D_INFO, "FFS SATA PI: %x\n", MmioRead8 (PciD31F2RegBase + R_PCH_SATA_PI_REGISTER))); + DEBUG ((EFI_D_INFO, "FFS SATA CC: %x\n", MmioRead8 (PciD31F2RegBase + R_PCH_SATA_SUB_CLASS_CODE))); + + /// + /// Enable SATA ports if not enabled already. + /// + AhciConfigurePortsImplemented (Ahci); + + /// + /// Enable AHCI by setting AE bit + /// + MmioWrite32 (Ahci->Abar + R_PCH_SATA_AHCI_GHC, B_PCH_SATA_AHCI_GHC_AE); + + DEBUG ((EFI_D_INFO, "FFS AHCI VS: %x\n", MmioRead32 (Ahci->Abar + R_PCH_SATA_AHCI_VS))); + DEBUG ((EFI_D_INFO, "FFS AHCI GHC: %x\n", MmioRead32 (Ahci->Abar + R_PCH_SATA_AHCI_GHC))); + DEBUG ((EFI_D_INFO, "FFS AHCI CAP: %x\n", MmioRead32 (Ahci->Abar + R_PCH_SATA_AHCI_CAP))); + DEBUG ((EFI_D_INFO, "FFS AHCI PI: %x\n", MmioRead32 (Ahci->Abar + R_PCH_SATA_AHCI_PI))); + + return EFI_SUCCESS; +} + +/** + Disable SATA controller after all AHCI commands have completed + + @param[in] Ahci - SATA controller information structure +**/ +VOID +AhciDone ( + IN AHCI_CONTEXT *Ahci + ) +{ + UINTN PciD31F2RegBase; + + DEBUG ((EFI_D_INFO, "AhciDone()\n")); + ASSERT (Ahci->State == AHCI_STATE_INIT); + Ahci->State = AHCI_STATE_UNKNOWN; + + PciD31F2RegBase = MmPciAddress ( + 0, + DEFAULT_PCI_BUS_NUMBER_PCH, + PCI_DEVICE_NUMBER_PCH_SATA, + PCI_FUNCTION_NUMBER_PCH_SATA, + 0 + ); + + /// + /// Disable AHCI + /// + MmioWrite32 (Ahci->Abar + R_PCH_SATA_AHCI_GHC, 0); + + /// + /// Unconfigure SATA + /// + MmioWrite32 (PciD31F2RegBase + R_PCH_SATA_AHCI_BAR, 0); + MmioWrite8 (PciD31F2RegBase + R_PCH_SATA_COMMAND, 0); +} + +/** + Spin up Ahci port + + @param[in] Ahci - SATA controller information structure + @param[in] Port - SATA port number + + @retval EFI_SUCCESS - AHCI Port has been spun up successfully +**/ +EFI_STATUS +AhciSpinUpPort ( + IN AHCI_CONTEXT *Ahci, + IN UINTN Port + ) +{ + UINTN PxBase; + + DEBUG ((EFI_D_INFO, "AhciPortSpinUpPort(%d)\n", Port)); + ASSERT (Ahci->State == AHCI_STATE_INIT); + ASSERT (Port <= 31); + ASSERT (MmioRead32 (Ahci->Abar + R_PCH_SATA_AHCI_GHC) & B_PCH_SATA_AHCI_GHC_AE); + + PxBase = AHCI_PORT_BASE_X (Ahci, Port); + + ASSERT ((MmioRead32 (PxBase + R_PCH_SATA_AHCI_PXCMD) & B_PCH_SATA_AHCI_PxCMD_ST) == 0); + + /// + /// Clear errors + /// + MmioWrite32 (PxBase + R_PCH_SATA_AHCI_PXSERR, (UINT32)~0u); + + /// + /// Spin Up + /// + MmioOr32 (PxBase + R_PCH_SATA_AHCI_PXCMD, B_PCH_SATA_AHCI_PxCMD_SUD); + + return EFI_SUCCESS; +} + +/** + Initialize AHCI port for reading/writing RapidStart Store + + @param[in] Ahci - SATA controller information structure + + @retval EFI_DEVICE_ERROR - AHCI port initialization failed + @retval EFI_SUCCESS - AHCI port initialized successfully and RapidStart Store is ready for reading/writing + @retval EFI_SECURITY_VIOLATION - Drive is already in SECURITY FREEZE state and will not accept UNLOCK command + @retval EFI_ACCESS_DENIED - Drive is in LOCKED state and need to execute UNLOCK command before accessing +**/ +EFI_STATUS +AhciPortInit ( + IN AHCI_CONTEXT *Ahci + ) +{ + UINTN PxBase; + UINT32 Timer; + UINT32 Count; + + DEBUG ((EFI_D_INFO, "AhciPortInit()\n")); + ASSERT (Ahci->State == AHCI_STATE_INIT); + ASSERT (Ahci->Port <= 31); + ASSERT (MmioRead32 (Ahci->Abar + R_PCH_SATA_AHCI_GHC) & B_PCH_SATA_AHCI_GHC_AE); + DEBUG ((EFI_D_INFO, "Mem used: %d\n", AHCI_MEM_MAX (Ahci) - Ahci->PortBase)); + DEBUG ((EFI_D_INFO, "AHCI_MEM_MAX_SIZE: %d\n", AHCI_MEM_MAX_SIZE)); + + ASSERT (AHCI_MEM_MAX (Ahci) <= (Ahci->PortBase + Ahci->PortSize)); + ASSERT ((AHCI_MEM_MAX (Ahci) - Ahci->PortBase) == AHCI_MEM_MAX_SIZE); + + PxBase = AHCI_PORT_BASE (Ahci); + + ASSERT ((MmioRead32 (PxBase + R_PCH_SATA_AHCI_PXCMD) & B_PCH_SATA_AHCI_PxCMD_ST) == 0); + + ZeroMem ((VOID *) Ahci->PortBase, Ahci->PortSize); + + DEBUG ((EFI_D_INFO, "AHCI_CMD_LIST_BASE: %08lx\n", (UINT64) AHCI_CMD_LIST_BASE (Ahci))); + DEBUG ((EFI_D_INFO, "AHCI_RXFIS_BASE: %08lx\n", (UINT64) AHCI_RXFIS_BASE (Ahci))); + DEBUG ((EFI_D_INFO, "AHCI_CMD_TABLE_BASE: %08lx\n", (UINT64) AHCI_CMD_TABLE_BASE (Ahci))); + DEBUG ((EFI_D_INFO, "AHCI_MEM_MAX: %08lx\n", (UINT64) AHCI_MEM_MAX (Ahci))); + + ASSERT ((AHCI_CMD_LIST_BASE (Ahci) & 0x3FF) == 0); + ASSERT ((AHCI_RXFIS_BASE (Ahci) & 0xFF) == 0); + + MmioWrite32 (PxBase + R_PCH_SATA_AHCI_PXCLB, (UINT32) AHCI_CMD_LIST_BASE (Ahci)); + MmioWrite32 (PxBase + R_PCH_SATA_AHCI_PXCLBU, (UINT32) RShiftU64 (AHCI_CMD_LIST_BASE (Ahci), 32)); + + MmioWrite32 (PxBase + R_PCH_SATA_AHCI_PXFB, (UINT32) AHCI_RXFIS_BASE (Ahci)); + MmioWrite32 (PxBase + R_PCH_SATA_AHCI_PXFBU, (UINT32) RShiftU64 (AHCI_RXFIS_BASE (Ahci), 32)); + + MmioOr32 (PxBase + R_PCH_SATA_AHCI_PXCMD, B_PCH_SATA_AHCI_PxCMD_FRE); + + ASSERT (MmioRead32 (PxBase + R_PCH_SATA_AHCI_PXCMD) & B_PCH_SATA_AHCI_PxCMD_SUD); + + /// + /// Clear errors + /// + MmioWrite32 (PxBase + R_PCH_SATA_AHCI_PXSERR, (UINT32)~0u); + + for (Count = 0; Count < AHCI_INIT_RETRY_COUNT; ++Count) { + /// + /// Reset port if in error state + /// + if (Count > 0 || MmioRead32 (PxBase + R_PCH_SATA_AHCI_PXSERR) != 0) { + DEBUG ((EFI_D_INFO, "Resetting AHCI port.\n")); + MmioWrite32 (PxBase + R_PCH_SATA_AHCI_PXSCTL, V_PCH_SATA_AHCI_PXSCTL_DET_1); + PchPmTimerStall (1000); + MmioWrite32 (PxBase + R_PCH_SATA_AHCI_PXSCTL, 0); + } + + Timer = 0; + + /// + /// Wait till device detected + /// + while + ( + Timer < AHCI_INIT_TIMEOUT && + ((MmioRead32 (PxBase + R_PCH_SATA_AHCI_PXSSTS) & B_PCH_SATA_AHCI_PXSSTS_DET) != B_PCH_SATA_AHCI_PXSSTS_DET_3) + ) { + PchPmTimerStall (AHCI_INIT_WAIT); + Timer += AHCI_INIT_WAIT; + } + /// + /// Clear errors + /// + MmioWrite32 (PxBase + R_PCH_SATA_AHCI_PXSERR, (UINT32)~0u); + MmioWrite32 (PxBase + R_PCH_SATA_AHCI_PXIS, (UINT32)~0u); + + /// + /// Wait for device ready + /// + while (Timer < AHCI_INIT_TIMEOUT) { + if (( + MmioRead32 (PxBase + R_PCH_SATA_AHCI_PXTFD) & (B_PCH_SATA_AHCI_PXTFD_STS_DRQ | B_PCH_SATA_AHCI_PXTFD_STS_BSY) + ) == 0 + ) { + goto complete; + } + + PchPmTimerStall (AHCI_INIT_WAIT); + Timer += AHCI_INIT_WAIT; + } + } + +complete: + + if (Count == AHCI_INIT_RETRY_COUNT) { + + DEBUG ((EFI_D_ERROR, "AHCI port initialization timeout.\n")); + DumpPortStatus (Ahci); + + /// + /// Clear FRE + /// + MmioAnd32 (PxBase + R_PCH_SATA_AHCI_PXCMD, (UINT32)~B_PCH_SATA_AHCI_PxCMD_FRE); + + /// + /// Wait for FR to be cleared + /// + while (MmioRead32 (PxBase + R_PCH_SATA_AHCI_PXCMD) & B_PCH_SATA_AHCI_PxCMD_FR) { + } + + return EFI_DEVICE_ERROR; + } + + ASSERT ((MmioRead32 (PxBase + R_PCH_SATA_AHCI_PXCMD) & (B_PCH_SATA_AHCI_PxCMD_CLO | B_PCH_SATA_AHCI_PxCMD_CR)) == 0); + + /// + /// Set Start bit + /// + MmioOr32 (PxBase + R_PCH_SATA_AHCI_PXCMD, B_PCH_SATA_AHCI_PxCMD_ST); + + Ahci->State = AHCI_STATE_INUSE; + return EFI_SUCCESS; +} + +/** + Check whether device is password locked. + + @param[in] Ahci - SATA controller information structure + + @retval EFI_SUCCESS - Device not locked + @retval EFI_ACCESS_DENIED - Drive is in LOCKED state and need to execute UNLOCK command before accessing + @retval EFI_SECURITY_VIOLATION - Drive is already in SECURITY FREEZE state and will not accept UNLOCK command + @retval EFI_TIMEOUT - Timeout occured + @retval EFI_DEVICE_ERROR - Error occurred on device side. +**/ +EFI_STATUS +AhciGetLockStatus ( + IN AHCI_CONTEXT *Ahci +) +{ + UINT16 SecurityStatus; + EFI_STATUS Status; + VOID *Buffer; + + DEBUG ((EFI_D_INFO, "AhciGetLockStatus()\n")); + ASSERT (Ahci->State == AHCI_STATE_INUSE); + + if (Ahci->Identify == 0) { + Buffer = (VOID*) AHCI_ID_BLOCK (Ahci); + Status = AhciIdentifyDevice (Ahci, Buffer); + if (Status == EFI_SUCCESS) { + Ahci->Identify = (UINT16 *) Buffer; + } else { + DEBUG ((EFI_D_ERROR, "Error: Identifying device failed!\n")); + return Status; + } + } + SecurityStatus = Ahci->Identify[ATA_ID_DEV_SECURITY_STATUS]; + if ((SecurityStatus & (B_ATA_ID_DEV_SEC_SUPPORTED | B_ATA_ID_DEV_SEC_ENABLED | B_ATA_ID_DEV_SEC_LOCKED) + ) == (B_ATA_ID_DEV_SEC_SUPPORTED | B_ATA_ID_DEV_SEC_ENABLED | B_ATA_ID_DEV_SEC_LOCKED) + ) { + if (SecurityStatus & (B_ATA_ID_DEV_SEC_FROZEN | B_ATA_ID_DEV_SEC_COUNT_EXP)) { + DEBUG ((EFI_D_ERROR, "AHCI device lock freeze!\n")); + return EFI_SECURITY_VIOLATION; + } + DEBUG ((EFI_D_WARN, "AHCI device locked!\n")); + return EFI_ACCESS_DENIED; + } + return EFI_SUCCESS; +} + +/** + Stops AHCI port. + + @param[in] Ahci - SATA controller information structure +**/ +VOID +AhciPortDone ( + IN AHCI_CONTEXT *Ahci + ) +{ + UINTN PxBase; + + DEBUG ((EFI_D_INFO, "AhciPortDone()\n")); + Ahci->State = AHCI_STATE_INIT; + + PxBase = AHCI_PORT_BASE (Ahci); + ASSERT (MmioRead32 (PxBase + R_PCH_SATA_AHCI_PXCI) == 0); + + MmioAnd32 (PxBase + R_PCH_SATA_AHCI_PXCMD, (UINT32)~B_PCH_SATA_AHCI_PxCMD_ST); + + /// + /// Wait for CR to be cleared + /// + while (MmioRead32 (PxBase + R_PCH_SATA_AHCI_PXCMD) & B_PCH_SATA_AHCI_PxCMD_CR) { + } + + MmioAnd32 (PxBase + R_PCH_SATA_AHCI_PXCMD, (UINT32)~B_PCH_SATA_AHCI_PxCMD_FRE); + + /// + /// Wait for FR to be cleared + /// + while (MmioRead32 (PxBase + R_PCH_SATA_AHCI_PXCMD) & B_PCH_SATA_AHCI_PxCMD_FR) { + } +} + +/** + Check whether there are available command slots. + + @param[in] Ahci - SATA controller information structure + + @retval TRUE if there is an available slot, FALSE otherwise. +**/ +BOOLEAN +AhciHasFreeCmdSlot ( + IN AHCI_CONTEXT *Ahci + ) +{ + UINTN PxBase; + UINT32 ci; + + ASSERT (AHCI_MAX_CMD <= 32); + + PxBase = AHCI_PORT_BASE (Ahci); + ci = MmioRead32 (PxBase + R_PCH_SATA_AHCI_PXCI); + + return ci != ((UINT32)~0u >> (32 - AHCI_MAX_CMD)); +} + +/** + Finds or waits for available command slot + + @param[in] Ahci - SATA controller information structure + @param[out] CmdIndex - Available command slot index + + @retval EFI_SUCCESS - Available command slot index found + @retval EFI_TIMEOUT - Timeout occured + @retval EFI_DEVICE_ERROR - Error occurred on device side. + @retval EFI_NOT_STARTED - The RapidStart Entry flow should be canceled and do S3 resume back to OS +**/ +STATIC +EFI_STATUS +AhciFindCmdSlot ( + IN AHCI_CONTEXT *Ahci, + OUT UINTN *CmdIndex + ) +{ + UINTN PxBase; + UINT32 ci; + UINT32 Timer; + UINT32 Index; + + PxBase = AHCI_PORT_BASE (Ahci); + + Timer = 0; + while (Timer < AHCI_CMD_TIMEOUT) { + ci = MmioRead32 (PxBase + R_PCH_SATA_AHCI_PXCI); + + for (Index = 0; Index < AHCI_MAX_CMD; ++Index) { + if ((ci & (1u << Index)) == 0) { + *CmdIndex = Index; + return EFI_SUCCESS; + } + } + + if (Ahci->PollCancellation && RapidStartShouldCancelEntry ()) { + return EFI_NOT_STARTED; + } + + if (AHCI_ERROR (PxBase)) { + DEBUG ((EFI_D_ERROR, "AHCI error!\n")); + DumpPortStatus (Ahci); + return EFI_DEVICE_ERROR; + } + + PchPmTimerStall (AHCI_CMD_WAIT); + Timer += AHCI_CMD_WAIT; + } + + DEBUG ((EFI_D_ERROR, "AHCI command timeout!\n")); + DumpPortStatus (Ahci); + return EFI_TIMEOUT; +} + +/** + Waits until given command(s) complete. + + @param[in] Ahci - SATA controller information structure + @param[in] CmdMask - Command(s) mask + + @retval EFI_SUCCESS - Command complete + @retval EFI_TIMEOUT - Timeout occured + @retval EFI_DEVICE_ERROR - Error occurred on device side. + @retval EFI_NOT_STARTED - The RapidStart Entry flow should be canceled and do S3 resume back to OS +**/ +EFI_STATUS +AhciWaitComplete ( + IN AHCI_CONTEXT *Ahci, + IN UINT32 CmdMask + ) +{ + UINTN PxBase; + UINT32 ci; + UINT32 Timer; + + DEBUG ((EFI_D_INFO, "AhciWaitComplete(%d)\n", CmdMask)); + ASSERT (Ahci->State == AHCI_STATE_INUSE); + + PxBase = AHCI_PORT_BASE (Ahci); + + Timer = 0; + while (Timer < AHCI_CMD_TIMEOUT) { + ci = MmioRead32 (PxBase + R_PCH_SATA_AHCI_PXCI); + + if ((ci & CmdMask) == 0) { + return EFI_SUCCESS; + } + + if (Ahci->PollCancellation && RapidStartShouldCancelEntry ()) { + return EFI_NOT_STARTED; + } + + if (AHCI_ERROR (PxBase)) { + DEBUG ((EFI_D_ERROR, "AHCI error!\n")); + DumpPortStatus (Ahci); + return EFI_DEVICE_ERROR; + } + + PchPmTimerStall (AHCI_CMD_WAIT); + Timer += AHCI_CMD_WAIT; + } + + DEBUG ((EFI_D_ERROR, "AHCI command timeout!\n")); + DumpPortStatus (Ahci); + return EFI_TIMEOUT; +} + +/** + Waits until all AHCI commands completed. + + @param[in] Ahci - SATA controller information structure + + @retval EFI_SUCCESS - All AHCI commands have completed + @retval EFI_TIMEOUT - Timeout occured + @retval EFI_DEVICE_ERROR - Error occurred on device side. + @retval EFI_NOT_STARTED - The RapidStart Entry flow should be canceled and do S3 resume back to OS +**/ +EFI_STATUS +AhciWaitAllComplete ( + IN AHCI_CONTEXT *Ahci + ) +{ + UINTN PxBase; + UINT32 Ci; + UINT32 PrevCi; + EFI_STATUS Status; + UINT32 Timer; + + DEBUG ((EFI_D_INFO, "AhciWaitAllComplete() ")); + ASSERT (Ahci->State == AHCI_STATE_INUSE); + + PxBase = AHCI_PORT_BASE (Ahci); + Timer = 0; + Status = EFI_TIMEOUT; + + Ci = MmioRead32 (PxBase + R_PCH_SATA_AHCI_PXCI); + PrevCi = Ci; + + while (Timer < AHCI_CMD_TIMEOUT) { + if (Ci == 0) { + Status = EFI_SUCCESS; + break; + } + + if (Ahci->PollCancellation && RapidStartShouldCancelEntry ()) { + return EFI_NOT_STARTED; + } + + if (AHCI_ERROR (PxBase)) { + Status = EFI_DEVICE_ERROR; + break; + } + + if (Ci != PrevCi) { + DEBUG ((EFI_D_INFO, ".")); + Timer = 0; + } + + PchPmTimerStall (AHCI_CMD_WAIT); + Timer += AHCI_CMD_WAIT; + PrevCi = Ci; + Ci = MmioRead32 (PxBase + R_PCH_SATA_AHCI_PXCI); + } + + DEBUG ((EFI_D_INFO, "\n")); + if (Status == EFI_TIMEOUT) { + DEBUG ((EFI_D_ERROR, "AHCI command timeout!\n")); + } else if (EFI_ERROR (Status)) { + DEBUG ((EFI_D_ERROR, "AHCI error!\n")); + DumpPortStatus (Ahci); + } + + return Status; +} + +/** + Enqueues ahci command for read/write and optionally with Zero filter + + @param[in] Ahci - SATA controller information structure + @param[in,out] AtaCmd - ATA command structure + @param[out] CmdMask - If given a bit corresponding to allocated command slot is set + @param[in] ZeroPageBitMap - A pointer for ZeroPageTable + @param[out] SmRamBufferLba - If given the drive LBA storing original SMRAM buffer content will be passed by it. + + @retval EFI_SUCCESS - AHCI command successfully posted + @retval EFI_TIMEOUT - Timeout occured + @retval EFI_DEVICE_ERROR - Command failed +**/ +EFI_STATUS +AhciPostCommandWithZeroFilter ( + IN AHCI_CONTEXT *Ahci, + IN OUT ATA_COMMAND *AtaCmd, + OUT OPTIONAL UINT32 *CmdMask, + IN OPTIONAL UINT32 *ZeroPageBitMap, + OUT OPTIONAL UINT64 *SmRamBufferLba + ) +{ + UINTN PxBase; + UINTN CmdIndex; + UINTN CmdTableAddr; + UINT32 *CmdHeader; + UINT32 *CmdFis; + AHCI_PRDT *Prdt; + UINT16 PrdCount; + UINT16 Index; + UINT32 MemLeft; + UINT32 MemChunk; + UINT64 MemAddr; + EFI_STATUS Status; + UINT32 TempBitMap; + UINT32 *ZeroPagePointer; + UINT8 count; + UINT32 AhciMaxRegion; + UINT32 NumberOfPages; + UINT32 SectorCount; + + count = 0; + ZeroPagePointer = 0; + TempBitMap = 0; + CmdIndex = 0; + + DEBUG ( + (EFI_D_INFO, + "AhciPostCommand port=%d cmd=%02x lba=%08x mem=%08lx ", + Ahci->Port, + AtaCmd->Command, + (UINT32) AtaCmd->Lba, + (UINT64) AtaCmd->MemAddr) + ); + ASSERT (Ahci->State == AHCI_STATE_INUSE); + + MemAddr = AtaCmd->MemAddr; + MemLeft = AtaCmd->SectorCount << SECTOR_SHIFT; + PxBase = AHCI_PORT_BASE (Ahci); + + ASSERT ((MemAddr & (ZeroPageBitMap ? 0xFFF : 1)) == 0); + ASSERT (AtaCmd->SectorCount <= AHCI_MAX_SECTORS); + + Status = AhciFindCmdSlot (Ahci, &CmdIndex); + if (EFI_ERROR (Status)) { + return Status; + } + + ASSERT (CmdIndex < AHCI_MAX_CMD); + + CmdHeader = (UINT32 *) AHCI_CMD_HEADER (Ahci, CmdIndex); + CmdTableAddr = AHCI_CMD_TABLE (Ahci, CmdIndex); + CmdFis = (UINT32 *) (UINTN) CmdTableAddr; + Prdt = (AHCI_PRDT *) (UINTN) (CmdTableAddr + AHCI_CMD_TABLE_HEADER_SIZE); + + ASSERT ((UINT32) CmdHeader >= AHCI_CMD_LIST_BASE (Ahci)); + ASSERT ((UINT32) CmdHeader + AHCI_CMD_HEADER_SIZE <= AHCI_CMD_LIST_BASE (Ahci) + AHCI_CMD_LIST_SIZE); + ASSERT (CmdTableAddr >= Ahci->PortBase); + ASSERT (CmdTableAddr + AHCI_CMD_TABLE_SIZE <= Ahci->PortBase + Ahci->PortSize); + ASSERT (RShiftU64 (AtaCmd->Lba, 48) == 0); + + /// + /// Create PRD table + /// + PrdCount = 0; + AhciMaxRegion = AHCI_REGION_MAX; + SectorCount = 0; + /// + /// Initialize ZeroPage Filter Parameters + /// + if (ZeroPageBitMap != NULL) { + NumberOfPages = (UINT32) NUMBER_OF_PAGES_IN_DWORD (MemAddr); + ZeroPagePointer = ZeroPageBitMap + NumberOfPages; + count = (UINT8) (NUMBER_OF_PAGES (MemAddr) - (NumberOfPages * ZERO_BITMAP_UNIT)); + TempBitMap = (UINT32) *ZeroPagePointer; + AhciMaxRegion = EFI_PAGE_SIZE; + } + + while (MemLeft > 0) { + ASSERT (PrdCount < AHCI_MAX_PRDT); + ASSERT ((MemAddr & 1) == 0); + + if (MemLeft >= AhciMaxRegion) { + MemChunk = AhciMaxRegion; + } else { + MemChunk = MemLeft; + } + + ASSERT (((MemChunk - 1) &~(AhciMaxRegion - 1)) == 0); + ASSERT ((MemChunk & 0x1FF) == 0); + + if (ZeroPageBitMap != NULL) { + /// + /// Get LBA which storing SMRAM buffer and later restore this buffer after SMRAM handled. + /// + if (SmRamBufferLba != NULL) { + if (MemAddr == LEGACY_SMRAM_BUFFER) { + *SmRamBufferLba = AtaCmd->Lba + SectorCount; + } + } + + if (((UINT32) (TempBitMap >> count) & 1) == 0) { + Prdt[PrdCount].DataByteAddr = MemAddr; + Prdt[PrdCount].Reserved = 0; + Prdt[PrdCount].ByteCountI = MemChunk - 1; + PrdCount++; + SectorCount += 8; + } + /// + /// Shift ZeroPage filter bitmap to next bit, if it is end of current DWORD, will shift to next DWORD + /// + if (count < (ZERO_BITMAP_UNIT - 1)) { + count++; + } else { + count = 0; + ZeroPagePointer++; + TempBitMap = (UINT32) *ZeroPagePointer; + } + } else { + Prdt[PrdCount].DataByteAddr = MemAddr; + Prdt[PrdCount].Reserved = 0; + Prdt[PrdCount].ByteCountI = MemChunk - 1; + PrdCount++; + SectorCount += MemChunk >> SECTOR_SHIFT; + } + + MemAddr += MemChunk; + MemLeft -= MemChunk; + } + + AtaCmd->MemAddr += AtaCmd->SectorCount << SECTOR_SHIFT; + AtaCmd->SectorCount = SectorCount; + + if (ZeroPageBitMap != NULL && PrdCount == 0) { + DEBUG ((EFI_D_INFO, "skipped\n")); + return EFI_SUCCESS; + } + /// + /// Fill command header + /// + ASSERT ((CmdTableAddr & 0x7F) == 0); + CmdHeader[0] = AHCI_CMD_PRDTL (PrdCount) | AHCI_CMD_CFL (5); + if (AtaCmd->Direction == AHCI_DIR_HOST2DEV) { + CmdHeader[0] |= AHCI_CMD_WRITE | AHCI_CMD_PREFETCHABLE; + } + + CmdHeader[1] = 0; + CmdHeader[2] = (UINT32) CmdTableAddr; + CmdHeader[3] = (UINT32) RShiftU64 (CmdTableAddr, 32); + for (Index = 4; Index < 8; ++Index) { + CmdHeader[Index] = 0; + } + /// + /// Create FIS + /// + CmdFis[0] = SATA_FIS_FEAT (AtaCmd->Feature) | SATA_FIS_CMD (AtaCmd->Command) | SATA_FIS_C | SATA_FIS_HOST2DEVICE; + CmdFis[1] = SATA_FIS_DEV_LBA | SATA_FIS_LBA (AtaCmd->Lba); + CmdFis[2] = SATA_FIS_FEAT_EXP (AtaCmd->Feature) | SATA_FIS_LBA_EXP (AtaCmd->Lba); + CmdFis[3] = SATA_FIS_SECT_COUNT (SectorCount); + CmdFis[4] = AtaCmd->Auxiliary.Data; + DEBUG ((EFI_D_INFO, "FIS[4] = %x\n", AtaCmd->Auxiliary.Data)); + + DEBUG ((EFI_D_INFO, "actualcount=%d ", SectorCount)); + + /// + /// Issue the command to the controller + /// + MmioWrite32 (PxBase + R_PCH_SATA_AHCI_PXCI, (1u << CmdIndex)); + + if (CmdMask != NULL) { + *CmdMask |= (1u << CmdIndex); + } + + AtaCmd->Lba += SectorCount; + DEBUG ((EFI_D_INFO, "idx=%d\n", CmdIndex)); + + return EFI_SUCCESS; +} + +/** + Executes AHCI command and waits until command completes. + + @param[in] Ahci - SATA controller information structure + @param[in,out] AtaCmd - ATA command structure + + @retval EFI_SUCCESS - AHCI command successfully posted + @retval EFI_TIMEOUT - Timeout occured + @retval EFI_DEVICE_ERROR - Command failed +**/ +EFI_STATUS +AhciExecCommand ( + IN AHCI_CONTEXT *Ahci, + IN OUT ATA_COMMAND *AtaCmd + ) +{ + EFI_STATUS Status; + UINTN CmdMask; + + CmdMask = 0; + Status = AhciPostCommand (Ahci, AtaCmd, &CmdMask); + if (Status == EFI_SUCCESS) { + Status = AhciWaitComplete (Ahci, CmdMask); + } + + return Status; +} + +/** + Issues ATA command with no data transfer. + + @param[in] Ahci - SATA controller information structure + @param[in] Command - Command to be issued + + @retval EFI_SUCCESS - fucntion executed successfully + @retval EFI_TIMEOUT - Timeout occured + @retval EFI_DEVICE_ERROR - Command failed +**/ +EFI_STATUS +AhciSimpleCommand ( + IN AHCI_CONTEXT *Ahci, + IN UINT8 Command + ) +{ + ATA_COMMAND AtaCmd; + + ZeroMem (&AtaCmd, sizeof (AtaCmd)); + AtaCmd.Command = Command; + + return AhciExecCommand (Ahci, &AtaCmd); +} + +/** + Sends Identify Device ATA command to port + + @param[in] Ahci - SATA controller information structure + @param[out] Buffer - Buffer to store device information + + @retval EFI_SUCCESS - function executed successfully + @retval EFI_TIMEOUT - Timeout occured + @retval EFI_DEVICE_ERROR - Command failed +**/ +EFI_STATUS +AhciIdentifyDevice ( + IN AHCI_CONTEXT *Ahci, + OUT VOID *Buffer + ) +{ + ATA_COMMAND AtaCmd; + + DEBUG ((EFI_D_INFO, "AhciIdentifyDevice()\n")); + ASSERT (Buffer != NULL); + + AtaCmd.Command = ATA_CMD_IDENTIFY_DEVICE; + AtaCmd.Direction = AHCI_DIR_DEV2HOST; + AtaCmd.Lba = 0; + AtaCmd.SectorCount = 1; + AtaCmd.MemAddr = (UINTN) Buffer; + AtaCmd.Feature = 0; + + return AhciExecCommand (Ahci, &AtaCmd); +} + +/** + Performs TRIM command on given LBA range. + + @param[in] Ahci - SATA controller information structure + @param[in] Lba - First block to be trimmed + @param[in] Count - Number of blocks to trim + + @retval EFI_SUCCESS - fucntion executed successfully + @retval EFI_TIMEOUT - Timeout occured + @exception EFI_UNSUPPORTED - TRIM is not supported by the device + @retval EFI_DEVICE_ERROR - Command failed +**/ +EFI_STATUS +AhciTrimRange ( + IN AHCI_CONTEXT *Ahci, + IN UINT64 Lba, + IN UINT32 Count + ) +{ + UINT64 *RangeEntries; + UINTN Index; + UINT16 Blocks; + ATA_COMMAND AtaCmd; + EFI_STATUS Status; + + DEBUG ((EFI_D_INFO, "AhciTrimRange(lba=%lx count=%x)\n", Lba, Count)); + ASSERT (Ahci->Identify != NULL); + + if (!((Ahci->Identify[ATA_ID_DEV_DATA_SET_MGMNT_SUPPORT] & B_ATA_ID_DEV_DATA_SET_TRIM) && + (Ahci->Identify[ATA_ID_DEV_DATA_SET_MGMNT_BLOCKS] > 0)) + ) { + DEBUG ((EFI_D_WARN, "TRIM is not supported!\n")); + return EFI_UNSUPPORTED; + } + + RangeEntries = (UINT64 *) AHCI_TMP_BLOCK (Ahci); + + AtaCmd.Command = ATA_CMD_DATA_SET_MANAGEMENT; + AtaCmd.Direction = AHCI_DIR_HOST2DEV; + AtaCmd.Feature = V_ATA_TRIM_FEATURE; + AtaCmd.SectorCount = 1; + + while (Count > 0) { + for (Index = 0; Index < (AHCI_TMP_BLOCK_SIZE / sizeof (UINT64)); ++Index) { + if (Count <= 0xFFFF) { + Blocks = (UINT16) Count; + } else { + Blocks = 0xFFFF; + } + + if (Blocks != 0) { + RangeEntries[Index] = LShiftU64 (Blocks, 48) | Lba; + } else { + RangeEntries[Index] = 0; + } + + Count -= Blocks; + Lba += Blocks; + } + + AtaCmd.Lba = 0; + AtaCmd.MemAddr = (UINTN) RangeEntries; + + Status = AhciExecCommand (Ahci, &AtaCmd); + if (EFI_ERROR (Status)) { + return Status; + } + } + + return EFI_SUCCESS; +} + +/** + Unlocks ATA device. + + @param[in] Ahci - SATA controller information structure + @param[in] Password - security credential + + @retval EFI_SUCCESS - Command executed successfully + @retval EFI_SECURITY_VIOLATION - Device security lock freeze + @exception EFI_UNSUPPORTED - Security is not supported by the device + @retval EFI_TIMEOUT - Timeout occured + @retval EFI_DEVICE_ERROR - Command failed +**/ +EFI_STATUS +AhciSecurityUnlock ( + IN AHCI_CONTEXT *Ahci, + IN CHAR8 *Password + ) +{ + UINT16 *Block; + ATA_COMMAND AtaCmd; + EFI_STATUS Status; + + DEBUG ((EFI_D_INFO, "AhciSecurityUnlock()\n")); + ASSERT (Ahci->Identify != NULL); + + if ((Ahci->Identify[ATA_ID_DEV_SECURITY_STATUS] & B_ATA_ID_DEV_SEC_SUPPORTED) == 0) { + DEBUG ((EFI_D_ERROR, "AHCI security not supported!\n")); + return EFI_UNSUPPORTED; + } + + if ((Ahci->Identify[ATA_ID_DEV_SECURITY_STATUS] & (B_ATA_ID_DEV_SEC_ENABLED | B_ATA_ID_DEV_SEC_LOCKED)) != + (B_ATA_ID_DEV_SEC_ENABLED | B_ATA_ID_DEV_SEC_LOCKED) + ) { + DEBUG ((EFI_D_WARN, "AHCI device already unlocked!\n")); + return EFI_SUCCESS; + } + + if (Ahci->Identify[ATA_ID_DEV_SECURITY_STATUS] & (B_ATA_ID_DEV_SEC_FROZEN | B_ATA_ID_DEV_SEC_COUNT_EXP)) { + DEBUG ((EFI_D_ERROR, "AHCI device lock freeze!\n")); + return EFI_SECURITY_VIOLATION; + } + + Block = (UINT16 *) AHCI_TMP_BLOCK (Ahci); + Block[0] = 0; + CopyMem (&Block[1], Password, ATA_PASSWORD_LEN); + + AtaCmd.Command = ATA_CMD_SECURITY_UNLOCK; + AtaCmd.Direction = AHCI_DIR_HOST2DEV; + AtaCmd.Feature = 0; + AtaCmd.Lba = 0; + AtaCmd.MemAddr = (UINTN) Block; + AtaCmd.SectorCount = 1; + + Status = AhciExecCommand (Ahci, &AtaCmd); + if (EFI_ERROR (Status)) { + DEBUG ((EFI_D_ERROR, "AHCI unlock failed!\n")); + } + + return Status; +} + +/** + Hybrid Hard Disk Support. + + @param[in] Ahci - SATA controller information structure + + @retval EFI_SUCCESS - fucntion executed successfully + @retval EFI_TIMEOUT - Timeout occured + @exception EFI_UNSUPPORTED - HHD is not supported by the device + @retval EFI_DEVICE_ERROR - Command failed +**/ +EFI_STATUS +AhciHybridHardDiskSupport ( + IN AHCI_CONTEXT *Ahci + ) +{ + EFI_STATUS Status; + ATA_COMMAND AtaCmd; + UINT8 *HybridLogRangeEntries; + UINT8 *HybridInfoHeader; + UINT8 *HybridInfoDescriptor; + UINT16 Index, HybridInfoDescriptorNumber; + UINT64 X, Y; + UINT64 NvmSize; + + DEBUG ((EFI_D_INFO, "Ahci Hybrid Hard Device Support Start\n")); + ASSERT (Ahci->Identify != NULL); + + if (!((Ahci->Identify[ATA_ID_DEV_HYBRID_FEATURE_SUPPORT] & B_ATA_ID_DEV_HYBRID_FEATURE_SUPPORT) & + (Ahci->Identify[ATA_ID_DEV_HYBRID_FEATURE_ENABLE] & B_ATA_ID_DEV_HYBRID_FEATURE_ENABLE)) + ) { + DEBUG ((EFI_D_ERROR, "Hybrid is not supported!\n")); + DEBUG ((EFI_D_ERROR, "Hybrid Feature support = %x\n", Ahci->Identify[ATA_ID_DEV_HYBRID_FEATURE_SUPPORT])); + DEBUG ((EFI_D_ERROR, "Hybrid Feature Enable = %x\n", Ahci->Identify[ATA_ID_DEV_HYBRID_FEATURE_ENABLE])); + + return EFI_UNSUPPORTED; + } + + HybridLogRangeEntries = (UINT8 *) AHCI_TMP_BLOCK (Ahci); + + AtaCmd.Command = ATA_CMD_READ_LOG_EXT; + AtaCmd.Direction = AHCI_DIR_DEV2HOST; + AtaCmd.Lba = 0x14; + AtaCmd.SectorCount = 1; + AtaCmd.MemAddr = (UINTN) HybridLogRangeEntries; + AtaCmd.Feature = 0; + + Status = AhciExecCommand (Ahci, &AtaCmd); + if (EFI_ERROR (Status)) { + DEBUG ((EFI_D_ERROR, "Hybrid can't be supported, READ_LOG_EXT fail and Status is %r\n", Status)); + return Status; + } + + HybridInfoHeader = HybridLogRangeEntries; + + if (HybridInfoHeader[2] != 0xFF) { + DEBUG ((EFI_D_ERROR, "Hybrid Information is Disabled\n")); + return EFI_DEVICE_ERROR; + } + + DEBUG ((EFI_D_INFO, "Maximum Hybrid Priority is %x\n", HybridInfoHeader[7])); + if (HybridInfoHeader[7] == 0 || HybridInfoHeader[7] > 16) { + DEBUG ((EFI_D_ERROR, "Maximum Hybrid Priority Level is invalid !!\n")); + return EFI_DEVICE_ERROR; + } + + Ahci->HybridInfo.HybridInfoValid = HybridInfoHeader[2]; + Ahci->HybridInfo.HybridPriority = HybridInfoHeader[7] - 1; + DEBUG ((EFI_D_INFO, "RapidStart Hybrid Priority is %x\n", Ahci->HybridInfo.HybridPriority)); + + X = 0; + Y = 0; + HybridInfoDescriptorNumber = *(UINT16 *)HybridInfoHeader; + DEBUG ((EFI_D_INFO, "HybridInfoDescriptorNumber is %x\n", HybridInfoDescriptorNumber)); + for (Index = 0; Index < HybridInfoDescriptorNumber; Index++) { + HybridInfoDescriptor = HybridLogRangeEntries + 64; + HybridInfoDescriptor += (16 * Index); + DEBUG ((EFI_D_INFO, "%x - Hybrid Priority is %x\n", Index, HybridInfoDescriptor[0])); + DEBUG ((EFI_D_INFO, " - Consumed NVM Size Fraction is %x\n", HybridInfoDescriptor[1])); + DEBUG ((EFI_D_INFO, " - Consumed Mapping Resources Fraction is %x\n", HybridInfoDescriptor[2])); + DEBUG ((EFI_D_INFO, " - Consumed NVM Size for Dirty Data Fraction is %x\n", HybridInfoDescriptor[3])); + DEBUG ((EFI_D_INFO, " - Consumed Mapping Resources for Dirty Data Fraction is %x\n", HybridInfoDescriptor[4])); + + X+= HybridInfoDescriptor[3]; + Y+= HybridInfoDescriptor[4]; + } + + DEBUG ((EFI_D_INFO, "Total Consumed Capacity For Dirty Data Fraction is %x\n", X)); + + NvmSize = *(UINT64 *)(HybridInfoHeader + 16); + DEBUG ((EFI_D_INFO, "NVM Size is %lx\n", NvmSize)); + X = MultU64x64 (X, NvmSize); + X = DivU64x32 (X, 0xFF); + + Ahci->TotalRemainingCacheCapacityInSector = (UINT32)(NvmSize - X); + DEBUG ((EFI_D_INFO, "TotalRemainingCacheCapacityInSector = %x\n", Ahci->TotalRemainingCacheCapacityInSector)); + + DEBUG ((EFI_D_INFO, "Ahci Hybrid Hard Device Support Successfully\n")); + + return EFI_SUCCESS; +} |