/** @file The NvmExpressPei driver is used to manage non-volatile memory subsystem which follows NVM Express specification at PEI phase. Copyright (c) 2018 - 2019, Intel Corporation. All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent **/ #include "NvmExpressPei.h" EFI_PEI_PPI_DESCRIPTOR mNvmeBlkIoPpiListTemplate = { EFI_PEI_PPI_DESCRIPTOR_PPI, &gEfiPeiVirtualBlockIoPpiGuid, NULL }; EFI_PEI_PPI_DESCRIPTOR mNvmeBlkIo2PpiListTemplate = { (EFI_PEI_PPI_DESCRIPTOR_PPI | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST), &gEfiPeiVirtualBlockIo2PpiGuid, NULL }; EFI_PEI_PPI_DESCRIPTOR mNvmeStorageSecurityPpiListTemplate = { (EFI_PEI_PPI_DESCRIPTOR_PPI | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST), &gEdkiiPeiStorageSecurityCommandPpiGuid, NULL }; EFI_PEI_PPI_DESCRIPTOR mNvmePassThruPpiListTemplate = { (EFI_PEI_PPI_DESCRIPTOR_PPI | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST), &gEdkiiPeiNvmExpressPassThruPpiGuid, NULL }; EFI_PEI_NOTIFY_DESCRIPTOR mNvmeEndOfPeiNotifyListTemplate = { (EFI_PEI_PPI_DESCRIPTOR_NOTIFY_CALLBACK | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST), &gEfiEndOfPeiSignalPpiGuid, NvmePeimEndOfPei }; EFI_PEI_NOTIFY_DESCRIPTOR mNvmeHostControllerNotify = { (EFI_PEI_PPI_DESCRIPTOR_NOTIFY_CALLBACK | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST), &gEdkiiPeiNvmExpressHostControllerPpiGuid, NvmeHostControllerPpiInstallationCallback }; EFI_PEI_NOTIFY_DESCRIPTOR mPciDevicePpiNotify = { (EFI_PEI_PPI_DESCRIPTOR_NOTIFY_CALLBACK | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST), &gEdkiiPeiPciDevicePpiGuid, NvmePciDevicePpiInstallationCallback }; /** Check if the specified Nvm Express device namespace is active, and then get the Identify Namespace data. @param[in,out] Private The pointer to the PEI_NVME_CONTROLLER_PRIVATE_DATA data structure. @param[in] NamespaceId The specified namespace identifier. @retval EFI_SUCCESS The specified namespace in the device is successfully enumerated. @return Others Error occurs when enumerating the namespace. **/ EFI_STATUS EnumerateNvmeDevNamespace ( IN OUT PEI_NVME_CONTROLLER_PRIVATE_DATA *Private, IN UINT32 NamespaceId ) { EFI_STATUS Status; NVME_ADMIN_NAMESPACE_DATA *NamespaceData; PEI_NVME_NAMESPACE_INFO *NamespaceInfo; UINT32 DeviceIndex; UINT32 Lbads; UINT32 Flbas; UINT32 LbaFmtIdx; NamespaceData = (NVME_ADMIN_NAMESPACE_DATA *)AllocateZeroPool (sizeof (NVME_ADMIN_NAMESPACE_DATA)); if (NamespaceData == NULL) { return EFI_OUT_OF_RESOURCES; } // // Identify Namespace // Status = NvmeIdentifyNamespace ( Private, NamespaceId, NamespaceData ); if (EFI_ERROR (Status)) { DEBUG ((DEBUG_ERROR, "%a: NvmeIdentifyNamespace fail, Status - %r\n", __func__, Status)); goto Exit; } // // Validate Namespace // if (NamespaceData->Ncap == 0) { DEBUG ((DEBUG_INFO, "%a: Namespace ID %d is an inactive one.\n", __func__, NamespaceId)); Status = EFI_DEVICE_ERROR; goto Exit; } DeviceIndex = Private->ActiveNamespaceNum; NamespaceInfo = &Private->NamespaceInfo[DeviceIndex]; NamespaceInfo->NamespaceId = NamespaceId; NamespaceInfo->NamespaceUuid = NamespaceData->Eui64; NamespaceInfo->Controller = Private; Private->ActiveNamespaceNum++; // // Build BlockIo media structure // Flbas = NamespaceData->Flbas; LbaFmtIdx = Flbas & 0xF; // // Currently this NVME driver only suport Metadata Size == 0 // if (NamespaceData->LbaFormat[LbaFmtIdx].Ms != 0) { DEBUG (( DEBUG_ERROR, "NVME IDENTIFY NAMESPACE [%d] Ms(%d) is not supported.\n", NamespaceId, NamespaceData->LbaFormat[LbaFmtIdx].Ms )); Status = EFI_UNSUPPORTED; goto Exit; } Lbads = NamespaceData->LbaFormat[LbaFmtIdx].Lbads; NamespaceInfo->Media.InterfaceType = MSG_NVME_NAMESPACE_DP; NamespaceInfo->Media.RemovableMedia = FALSE; NamespaceInfo->Media.MediaPresent = TRUE; NamespaceInfo->Media.ReadOnly = FALSE; NamespaceInfo->Media.BlockSize = (UINT32)1 << Lbads; NamespaceInfo->Media.LastBlock = (EFI_PEI_LBA)NamespaceData->Nsze - 1; DEBUG (( DEBUG_INFO, "%a: Namespace ID %d - BlockSize = 0x%x, LastBlock = 0x%lx\n", __func__, NamespaceId, NamespaceInfo->Media.BlockSize, NamespaceInfo->Media.LastBlock )); Exit: if (NamespaceData != NULL) { FreePool (NamespaceData); } return Status; } /** Discover all Nvm Express device active namespaces. @param[in,out] Private The pointer to the PEI_NVME_CONTROLLER_PRIVATE_DATA data structure. @retval EFI_SUCCESS All the namespaces in the device are successfully enumerated. @return EFI_NOT_FOUND No active namespaces can be found. **/ EFI_STATUS NvmeDiscoverNamespaces ( IN OUT PEI_NVME_CONTROLLER_PRIVATE_DATA *Private ) { UINT32 NamespaceId; Private->ActiveNamespaceNum = 0; Private->NamespaceInfo = AllocateZeroPool (Private->ControllerData->Nn * sizeof (PEI_NVME_NAMESPACE_INFO)); // // According to Nvm Express 1.1 spec Figure 82, the field 'Nn' of the identify // controller data defines the number of valid namespaces present for the // controller. Namespaces shall be allocated in order (starting with 1) and // packed sequentially. // for (NamespaceId = 1; NamespaceId <= Private->ControllerData->Nn; NamespaceId++) { // // For now, we do not care the return status. Since if a valid namespace is inactive, // error status will be returned. But we continue to enumerate other valid namespaces. // EnumerateNvmeDevNamespace (Private, NamespaceId); } if (Private->ActiveNamespaceNum == 0) { return EFI_NOT_FOUND; } return EFI_SUCCESS; } /** One notified function to cleanup the allocated resources at the end of PEI. @param[in] PeiServices Pointer to PEI Services Table. @param[in] NotifyDescriptor Pointer to the descriptor for the Notification event that caused this function to execute. @param[in] Ppi Pointer to the PPI data associated with this function. @retval EFI_SUCCESS The function completes successfully **/ EFI_STATUS EFIAPI NvmePeimEndOfPei ( IN EFI_PEI_SERVICES **PeiServices, IN EFI_PEI_NOTIFY_DESCRIPTOR *NotifyDescriptor, IN VOID *Ppi ) { PEI_NVME_CONTROLLER_PRIVATE_DATA *Private; Private = GET_NVME_PEIM_HC_PRIVATE_DATA_FROM_THIS_NOTIFY (NotifyDescriptor); NvmeFreeDmaResource (Private); return EFI_SUCCESS; } /** Initialize and install PrivateData PPIs. @param[in] MmioBase MMIO base address of specific Nvme controller @param[in] DevicePath A pointer to the EFI_DEVICE_PATH_PROTOCOL structure. @param[in] DevicePathLength Length of the device path. @retval EFI_SUCCESS Nvme controller initialized and PPIs installed @retval others Failed to initialize Nvme controller **/ EFI_STATUS NvmeInitPrivateData ( IN UINTN MmioBase, IN EFI_DEVICE_PATH_PROTOCOL *DevicePath, IN UINTN DevicePathLength ) { EFI_STATUS Status; EFI_BOOT_MODE BootMode; PEI_NVME_CONTROLLER_PRIVATE_DATA *Private; EFI_PHYSICAL_ADDRESS DeviceAddress; DEBUG ((DEBUG_INFO, "%a: Enters.\n", __func__)); // // Get the current boot mode. // Status = PeiServicesGetBootMode (&BootMode); if (EFI_ERROR (Status)) { DEBUG ((DEBUG_ERROR, "%a: Fail to get the current boot mode.\n", __func__)); return Status; } // // Check validity of the device path of the NVM Express controller. // Status = NvmeIsHcDevicePathValid (DevicePath, DevicePathLength); if (EFI_ERROR (Status)) { DEBUG (( DEBUG_ERROR, "%a: The device path is invalid.\n", __func__ )); return Status; } // // For S3 resume performance consideration, not all NVM Express controllers // will be initialized. The driver consumes the content within // S3StorageDeviceInitList LockBox to see if a controller will be skipped // during S3 resume. // if ((BootMode == BOOT_ON_S3_RESUME) && (NvmeS3SkipThisController (DevicePath, DevicePathLength))) { DEBUG (( DEBUG_ERROR, "%a: skipped during S3.\n", __func__ )); return EFI_SUCCESS; } // // Memory allocation for controller private data // Private = AllocateZeroPool (sizeof (PEI_NVME_CONTROLLER_PRIVATE_DATA)); if (Private == NULL) { DEBUG (( DEBUG_ERROR, "%a: Fail to allocate private data.\n", __func__ )); return EFI_OUT_OF_RESOURCES; } // // Memory allocation for transfer-related data // Status = IoMmuAllocateBuffer ( NVME_MEM_MAX_PAGES, &Private->Buffer, &DeviceAddress, &Private->BufferMapping ); if (EFI_ERROR (Status)) { DEBUG (( DEBUG_ERROR, "%a: Fail to allocate DMA buffers.\n", __func__ )); return Status; } ASSERT (DeviceAddress == ((EFI_PHYSICAL_ADDRESS)(UINTN)Private->Buffer)); DEBUG ((DEBUG_INFO, "%a: DMA buffer base at 0x%x\n", __func__, Private->Buffer)); // // Initialize controller private data // Private->Signature = NVME_PEI_CONTROLLER_PRIVATE_DATA_SIGNATURE; Private->MmioBase = MmioBase; Private->DevicePathLength = DevicePathLength; Private->DevicePath = DevicePath; // // Initialize the NVME controller // Status = NvmeControllerInit (Private); if (EFI_ERROR (Status)) { DEBUG (( DEBUG_ERROR, "%a: Controller initialization fail with Status - %r.\n", __func__, Status )); NvmeFreeDmaResource (Private); return Status; } // // Enumerate the NVME namespaces on the controller // Status = NvmeDiscoverNamespaces (Private); if (EFI_ERROR (Status)) { // // No active namespace was found on the controller // DEBUG (( DEBUG_ERROR, "%a: Namespaces discovery fail with Status - %r.\n", __func__, Status )); NvmeFreeDmaResource (Private); return Status; } // // Nvm Express Pass Thru PPI // Private->PassThruMode.Attributes = EFI_NVM_EXPRESS_PASS_THRU_ATTRIBUTES_PHYSICAL | EFI_NVM_EXPRESS_PASS_THRU_ATTRIBUTES_LOGICAL | EFI_NVM_EXPRESS_PASS_THRU_ATTRIBUTES_CMD_SET_NVM; Private->PassThruMode.IoAlign = sizeof (UINTN); Private->PassThruMode.NvmeVersion = EDKII_PEI_NVM_EXPRESS_PASS_THRU_PPI_REVISION; Private->NvmePassThruPpi.Mode = &Private->PassThruMode; Private->NvmePassThruPpi.GetDevicePath = NvmePassThruGetDevicePath; Private->NvmePassThruPpi.GetNextNameSpace = NvmePassThruGetNextNameSpace; Private->NvmePassThruPpi.PassThru = NvmePassThru; CopyMem ( &Private->NvmePassThruPpiList, &mNvmePassThruPpiListTemplate, sizeof (EFI_PEI_PPI_DESCRIPTOR) ); Private->NvmePassThruPpiList.Ppi = &Private->NvmePassThruPpi; PeiServicesInstallPpi (&Private->NvmePassThruPpiList); // // Block Io PPI // Private->BlkIoPpi.GetNumberOfBlockDevices = NvmeBlockIoPeimGetDeviceNo; Private->BlkIoPpi.GetBlockDeviceMediaInfo = NvmeBlockIoPeimGetMediaInfo; Private->BlkIoPpi.ReadBlocks = NvmeBlockIoPeimReadBlocks; CopyMem ( &Private->BlkIoPpiList, &mNvmeBlkIoPpiListTemplate, sizeof (EFI_PEI_PPI_DESCRIPTOR) ); Private->BlkIoPpiList.Ppi = &Private->BlkIoPpi; Private->BlkIo2Ppi.Revision = EFI_PEI_RECOVERY_BLOCK_IO2_PPI_REVISION; Private->BlkIo2Ppi.GetNumberOfBlockDevices = NvmeBlockIoPeimGetDeviceNo2; Private->BlkIo2Ppi.GetBlockDeviceMediaInfo = NvmeBlockIoPeimGetMediaInfo2; Private->BlkIo2Ppi.ReadBlocks = NvmeBlockIoPeimReadBlocks2; CopyMem ( &Private->BlkIo2PpiList, &mNvmeBlkIo2PpiListTemplate, sizeof (EFI_PEI_PPI_DESCRIPTOR) ); Private->BlkIo2PpiList.Ppi = &Private->BlkIo2Ppi; PeiServicesInstallPpi (&Private->BlkIoPpiList); // // Check if the NVME controller supports the Security Receive/Send commands // if ((Private->ControllerData->Oacs & SECURITY_SEND_RECEIVE_SUPPORTED) != 0) { DEBUG (( DEBUG_INFO, "%a: Security Security Command PPI will be produced.\n", __func__ )); Private->StorageSecurityPpi.Revision = EDKII_STORAGE_SECURITY_PPI_REVISION; Private->StorageSecurityPpi.GetNumberofDevices = NvmeStorageSecurityGetDeviceNo; Private->StorageSecurityPpi.GetDevicePath = NvmeStorageSecurityGetDevicePath; Private->StorageSecurityPpi.ReceiveData = NvmeStorageSecurityReceiveData; Private->StorageSecurityPpi.SendData = NvmeStorageSecuritySendData; CopyMem ( &Private->StorageSecurityPpiList, &mNvmeStorageSecurityPpiListTemplate, sizeof (EFI_PEI_PPI_DESCRIPTOR) ); Private->StorageSecurityPpiList.Ppi = &Private->StorageSecurityPpi; PeiServicesInstallPpi (&Private->StorageSecurityPpiList); } CopyMem ( &Private->EndOfPeiNotifyList, &mNvmeEndOfPeiNotifyListTemplate, sizeof (EFI_PEI_NOTIFY_DESCRIPTOR) ); PeiServicesNotifyPpi (&Private->EndOfPeiNotifyList); return EFI_SUCCESS; } /** Initialize Nvme controller from fiven PCI_DEVICE_PPI. @param[in] PciDevice Pointer to the PCI Device PPI instance. @retval EFI_SUCCESS The function completes successfully @retval Others Cannot initialize Nvme controller for given device **/ EFI_STATUS NvmeInitControllerDataFromPciDevice ( EDKII_PCI_DEVICE_PPI *PciDevice ) { EFI_STATUS Status; PCI_TYPE00 PciData; UINTN MmioBase; EFI_DEVICE_PATH_PROTOCOL *DevicePath; UINTN DevicePathLength; UINT64 EnabledPciAttributes; UINT32 MmioBaseH; // // Now further check the PCI header: Base Class (offset 0x0B), Sub Class (offset 0x0A) and // Programming Interface (offset 0x09). This controller should be an Nvme controller // Status = PciDevice->PciIo.Pci.Read ( &PciDevice->PciIo, EfiPciIoWidthUint8, PCI_CLASSCODE_OFFSET, sizeof (PciData.Hdr.ClassCode), PciData.Hdr.ClassCode ); if (EFI_ERROR (Status)) { return EFI_UNSUPPORTED; } if (!IS_PCI_NVMHCI (&PciData)) { return EFI_UNSUPPORTED; } Status = PciDevice->PciIo.Attributes ( &PciDevice->PciIo, EfiPciIoAttributeOperationSupported, 0, &EnabledPciAttributes ); if (EFI_ERROR (Status)) { return EFI_UNSUPPORTED; } else { EnabledPciAttributes &= (UINT64)EFI_PCI_DEVICE_ENABLE; Status = PciDevice->PciIo.Attributes ( &PciDevice->PciIo, EfiPciIoAttributeOperationEnable, EnabledPciAttributes, NULL ); if (EFI_ERROR (Status)) { return EFI_UNSUPPORTED; } } Status = PciDevice->PciIo.Pci.Read ( &PciDevice->PciIo, EfiPciIoWidthUint32, PCI_BASE_ADDRESSREG_OFFSET, 1, &MmioBase ); if (EFI_ERROR (Status)) { return EFI_UNSUPPORTED; } switch (MmioBase & 0x07) { case 0x0: // // Memory space for 32 bit bar address // MmioBase = MmioBase & 0xFFFFFFF0; break; case 0x4: // // For 64 bit bar address, read the high 32bits of this 64 bit bar // Status = PciDevice->PciIo.Pci.Read ( &PciDevice->PciIo, EfiPciIoWidthUint32, PCI_BASE_ADDRESSREG_OFFSET + 4, 1, &MmioBaseH ); // // For 32 bit environment, high 32bits of the bar should be zero. // if ( EFI_ERROR (Status) || ((MmioBaseH != 0) && (sizeof (UINTN) == sizeof (UINT32)))) { return EFI_UNSUPPORTED; } MmioBase = MmioBase & 0xFFFFFFF0; MmioBase |= LShiftU64 ((UINT64)MmioBaseH, 32); break; default: // // Unknown bar type // return EFI_UNSUPPORTED; } DevicePathLength = GetDevicePathSize (PciDevice->DevicePath); DevicePath = PciDevice->DevicePath; Status = NvmeInitPrivateData (MmioBase, DevicePath, DevicePathLength); if (EFI_ERROR (Status)) { DEBUG (( DEBUG_INFO, "%a: Failed to init controller, with Status - %r\n", __func__, Status )); } return EFI_SUCCESS; } /** Callback for EDKII_PCI_DEVICE_PPI installation. @param[in] PeiServices Pointer to PEI Services Table. @param[in] NotifyDescriptor Pointer to the descriptor for the Notification event that caused this function to execute. @param[in] Ppi Pointer to the PPI data associated with this function. @retval EFI_SUCCESS The function completes successfully @retval Others Cannot initialize Nvme controller from given PCI_DEVICE_PPI **/ EFI_STATUS EFIAPI NvmePciDevicePpiInstallationCallback ( IN EFI_PEI_SERVICES **PeiServices, IN EFI_PEI_NOTIFY_DESCRIPTOR *NotifyDescriptor, IN VOID *Ppi ) { EDKII_PCI_DEVICE_PPI *PciDevice; PciDevice = (EDKII_PCI_DEVICE_PPI *)Ppi; return NvmeInitControllerDataFromPciDevice (PciDevice); } /** Initialize Nvme controller from EDKII_NVM_EXPRESS_HOST_CONTROLLER_PPI instance. @param[in] NvmeHcPpi Pointer to the Nvme Host Controller PPI instance. @retval EFI_SUCCESS PPI successfully installed. **/ EFI_STATUS NvmeInitControllerFromHostControllerPpi ( IN EDKII_NVM_EXPRESS_HOST_CONTROLLER_PPI *NvmeHcPpi ) { UINT8 Controller; UINTN MmioBase; UINTN DevicePathLength; EFI_DEVICE_PATH_PROTOCOL *DevicePath; EFI_STATUS Status; Controller = 0; MmioBase = 0; while (TRUE) { Status = NvmeHcPpi->GetNvmeHcMmioBar ( NvmeHcPpi, Controller, &MmioBase ); // // When status is error, meant no controller is found // if (EFI_ERROR (Status)) { break; } Status = NvmeHcPpi->GetNvmeHcDevicePath ( NvmeHcPpi, Controller, &DevicePathLength, &DevicePath ); if (EFI_ERROR (Status)) { DEBUG (( DEBUG_ERROR, "%a: Fail to allocate get the device path for Controller %d.\n", __func__, Controller )); return Status; } Status = NvmeInitPrivateData (MmioBase, DevicePath, DevicePathLength); if (EFI_ERROR (Status)) { DEBUG (( DEBUG_ERROR, "%a: Controller initialization fail for Controller %d with Status - %r.\n", __func__, Controller, Status )); } else { DEBUG (( DEBUG_INFO, "%a: Controller %d has been successfully initialized.\n", __func__, Controller )); } Controller++; } return EFI_SUCCESS; } /** Callback for EDKII_NVM_EXPRESS_HOST_CONTROLLER_PPI installation. @param[in] PeiServices Pointer to PEI Services Table. @param[in] NotifyDescriptor Pointer to the descriptor for the Notification event that caused this function to execute. @param[in] Ppi Pointer to the PPI data associated with this function. @retval EFI_SUCCESS The function completes successfully @retval Others Cannot initialize Nvme controller from given EDKII_NVM_EXPRESS_HOST_CONTROLLER_PPI **/ EFI_STATUS EFIAPI NvmeHostControllerPpiInstallationCallback ( IN EFI_PEI_SERVICES **PeiServices, IN EFI_PEI_NOTIFY_DESCRIPTOR *NotifyDescriptor, IN VOID *Ppi ) { EDKII_NVM_EXPRESS_HOST_CONTROLLER_PPI *NvmeHcPpi; if (Ppi == NULL) { return EFI_INVALID_PARAMETER; } NvmeHcPpi = (EDKII_NVM_EXPRESS_HOST_CONTROLLER_PPI *)Ppi; return NvmeInitControllerFromHostControllerPpi (NvmeHcPpi); } /** Entry point of the PEIM. @param[in] FileHandle Handle of the file being invoked. @param[in] PeiServices Describes the list of possible PEI Services. @retval EFI_SUCCESS PPI successfully installed. **/ EFI_STATUS EFIAPI NvmExpressPeimEntry ( IN EFI_PEI_FILE_HANDLE FileHandle, IN CONST EFI_PEI_SERVICES **PeiServices ) { DEBUG ((DEBUG_INFO, "%a: Enters.\n", __func__)); PeiServicesNotifyPpi (&mNvmeHostControllerNotify); PeiServicesNotifyPpi (&mPciDevicePpiNotify); return EFI_SUCCESS; }