| /** @file | |
| HddPassword PEI module which is used to unlock HDD password for S3. | |
| Copyright (c) 2019, Intel Corporation. All rights reserved.<BR> | |
| SPDX-License-Identifier: BSD-2-Clause-Patent | |
| **/ | |
| #include "HddPasswordPei.h" | |
| EFI_GUID mHddPasswordDeviceInfoGuid = HDD_PASSWORD_DEVICE_INFO_GUID; | |
| /** | |
| Send unlock hdd password cmd through ATA PassThru PPI. | |
| @param[in] AtaPassThru The pointer to the ATA PassThru PPI. | |
| @param[in] Port The port number of the ATA device. | |
| @param[in] PortMultiplierPort The port multiplier port number of the ATA device. | |
| @param[in] Identifier The identifier to set user or master password. | |
| @param[in] Password The hdd password of attached ATA device. | |
| @retval EFI_SUCCESS Successful to send unlock hdd password cmd. | |
| @retval EFI_INVALID_PARAMETER The parameter passed-in is invalid. | |
| @retval EFI_OUT_OF_RESOURCES Not enough memory to send unlock hdd password cmd. | |
| @retval EFI_DEVICE_ERROR Can not send unlock hdd password cmd. | |
| **/ | |
| EFI_STATUS | |
| UnlockDevice ( | |
| IN EDKII_PEI_ATA_PASS_THRU_PPI *AtaPassThru, | |
| IN UINT16 Port, | |
| IN UINT16 PortMultiplierPort, | |
| IN CHAR8 Identifier, | |
| IN CHAR8 *Password | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| EFI_ATA_COMMAND_BLOCK Acb; | |
| EFI_ATA_STATUS_BLOCK *Asb; | |
| EFI_ATA_PASS_THRU_COMMAND_PACKET Packet; | |
| UINT8 Buffer[HDD_PAYLOAD]; | |
| if ((AtaPassThru == NULL) || (Password == NULL)) { | |
| return EFI_INVALID_PARAMETER; | |
| } | |
| // | |
| // The 'Asb' field (a pointer to the EFI_ATA_STATUS_BLOCK structure) in | |
| // EFI_ATA_PASS_THRU_COMMAND_PACKET is required to be aligned specified by | |
| // the 'IoAlign' field in the EFI_ATA_PASS_THRU_MODE structure. Meanwhile, | |
| // the structure EFI_ATA_STATUS_BLOCK is composed of only UINT8 fields, so it | |
| // may not be aligned when allocated on stack for some compilers. Hence, we | |
| // use the API AllocateAlignedPages to ensure this structure is properly | |
| // aligned. | |
| // | |
| Asb = AllocateAlignedPages ( | |
| EFI_SIZE_TO_PAGES (sizeof (EFI_ATA_STATUS_BLOCK)), | |
| AtaPassThru->Mode->IoAlign | |
| ); | |
| if (Asb == NULL) { | |
| return EFI_OUT_OF_RESOURCES; | |
| } | |
| // | |
| // Prepare for ATA command block. | |
| // | |
| ZeroMem (&Acb, sizeof (Acb)); | |
| ZeroMem (Asb, sizeof (EFI_ATA_STATUS_BLOCK)); | |
| Acb.AtaCommand = ATA_SECURITY_UNLOCK_CMD; | |
| Acb.AtaDeviceHead = (UINT8)(PortMultiplierPort == 0xFFFF ? 0 : (PortMultiplierPort << 4)); | |
| // | |
| // Prepare for ATA pass through packet. | |
| // | |
| ZeroMem (&Packet, sizeof (Packet)); | |
| Packet.Protocol = EFI_ATA_PASS_THRU_PROTOCOL_PIO_DATA_OUT; | |
| Packet.Length = EFI_ATA_PASS_THRU_LENGTH_BYTES; | |
| Packet.Asb = Asb; | |
| Packet.Acb = &Acb; | |
| ((CHAR16 *)Buffer)[0] = Identifier & BIT0; | |
| CopyMem (&((CHAR16 *)Buffer)[1], Password, HDD_PASSWORD_MAX_LENGTH); | |
| Packet.OutDataBuffer = Buffer; | |
| Packet.OutTransferLength = sizeof (Buffer); | |
| Packet.Timeout = ATA_TIMEOUT; | |
| Status = AtaPassThru->PassThru ( | |
| AtaPassThru, | |
| Port, | |
| PortMultiplierPort, | |
| &Packet | |
| ); | |
| if (!EFI_ERROR (Status) && | |
| ((Asb->AtaStatus & ATA_STSREG_ERR) != 0) && | |
| ((Asb->AtaError & ATA_ERRREG_ABRT) != 0)) | |
| { | |
| Status = EFI_DEVICE_ERROR; | |
| } | |
| FreeAlignedPages (Asb, EFI_SIZE_TO_PAGES (sizeof (EFI_ATA_STATUS_BLOCK))); | |
| ZeroMem (Buffer, sizeof (Buffer)); | |
| DEBUG ((DEBUG_INFO, "%a() - %r\n", __func__, Status)); | |
| return Status; | |
| } | |
| /** | |
| Send security freeze lock cmd through ATA PassThru PPI. | |
| @param[in] AtaPassThru The pointer to the ATA PassThru PPI. | |
| @param[in] Port The port number of the ATA device. | |
| @param[in] PortMultiplierPort The port multiplier port number of the ATA device. | |
| @retval EFI_SUCCESS Successful to send security freeze lock cmd. | |
| @retval EFI_INVALID_PARAMETER The parameter passed-in is invalid. | |
| @retval EFI_OUT_OF_RESOURCES Not enough memory to send unlock hdd password cmd. | |
| @retval EFI_DEVICE_ERROR Can not send security freeze lock cmd. | |
| **/ | |
| EFI_STATUS | |
| FreezeLockDevice ( | |
| IN EDKII_PEI_ATA_PASS_THRU_PPI *AtaPassThru, | |
| IN UINT16 Port, | |
| IN UINT16 PortMultiplierPort | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| EFI_ATA_COMMAND_BLOCK Acb; | |
| EFI_ATA_STATUS_BLOCK *Asb; | |
| EFI_ATA_PASS_THRU_COMMAND_PACKET Packet; | |
| if (AtaPassThru == NULL) { | |
| return EFI_INVALID_PARAMETER; | |
| } | |
| // | |
| // The 'Asb' field (a pointer to the EFI_ATA_STATUS_BLOCK structure) in | |
| // EFI_ATA_PASS_THRU_COMMAND_PACKET is required to be aligned specified by | |
| // the 'IoAlign' field in the EFI_ATA_PASS_THRU_MODE structure. Meanwhile, | |
| // the structure EFI_ATA_STATUS_BLOCK is composed of only UINT8 fields, so it | |
| // may not be aligned when allocated on stack for some compilers. Hence, we | |
| // use the API AllocateAlignedPages to ensure this structure is properly | |
| // aligned. | |
| // | |
| Asb = AllocateAlignedPages ( | |
| EFI_SIZE_TO_PAGES (sizeof (EFI_ATA_STATUS_BLOCK)), | |
| AtaPassThru->Mode->IoAlign | |
| ); | |
| if (Asb == NULL) { | |
| return EFI_OUT_OF_RESOURCES; | |
| } | |
| // | |
| // Prepare for ATA command block. | |
| // | |
| ZeroMem (&Acb, sizeof (Acb)); | |
| ZeroMem (Asb, sizeof (EFI_ATA_STATUS_BLOCK)); | |
| Acb.AtaCommand = ATA_SECURITY_FREEZE_LOCK_CMD; | |
| Acb.AtaDeviceHead = (UINT8)(PortMultiplierPort == 0xFFFF ? 0 : (PortMultiplierPort << 4)); | |
| // | |
| // Prepare for ATA pass through packet. | |
| // | |
| ZeroMem (&Packet, sizeof (Packet)); | |
| Packet.Protocol = EFI_ATA_PASS_THRU_PROTOCOL_ATA_NON_DATA; | |
| Packet.Length = EFI_ATA_PASS_THRU_LENGTH_NO_DATA_TRANSFER; | |
| Packet.Asb = Asb; | |
| Packet.Acb = &Acb; | |
| Packet.Timeout = ATA_TIMEOUT; | |
| Status = AtaPassThru->PassThru ( | |
| AtaPassThru, | |
| Port, | |
| PortMultiplierPort, | |
| &Packet | |
| ); | |
| if (!EFI_ERROR (Status) && | |
| ((Asb->AtaStatus & ATA_STSREG_ERR) != 0) && | |
| ((Asb->AtaError & ATA_ERRREG_ABRT) != 0)) | |
| { | |
| Status = EFI_DEVICE_ERROR; | |
| } | |
| FreeAlignedPages (Asb, EFI_SIZE_TO_PAGES (sizeof (EFI_ATA_STATUS_BLOCK))); | |
| DEBUG ((DEBUG_INFO, "%a() - %r\n", __func__, Status)); | |
| return Status; | |
| } | |
| /** | |
| Unlock HDD password for S3. | |
| @param[in] AtaPassThruPpi Pointer to the EDKII_PEI_ATA_PASS_THRU_PPI instance. | |
| **/ | |
| VOID | |
| UnlockHddPassword ( | |
| IN EDKII_PEI_ATA_PASS_THRU_PPI *AtaPassThruPpi | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| VOID *Buffer; | |
| UINTN Length; | |
| UINT8 DummyData; | |
| HDD_PASSWORD_DEVICE_INFO *DevInfo; | |
| UINT16 Port; | |
| UINT16 PortMultiplierPort; | |
| EFI_DEVICE_PATH_PROTOCOL *DevicePath; | |
| UINTN DevicePathLength; | |
| // | |
| // Get HDD password device info from LockBox. | |
| // | |
| Buffer = (VOID *)&DummyData; | |
| Length = sizeof (DummyData); | |
| Status = RestoreLockBox (&mHddPasswordDeviceInfoGuid, Buffer, &Length); | |
| if (Status == EFI_BUFFER_TOO_SMALL) { | |
| Buffer = AllocatePages (EFI_SIZE_TO_PAGES (Length)); | |
| if (Buffer != NULL) { | |
| Status = RestoreLockBox (&mHddPasswordDeviceInfoGuid, Buffer, &Length); | |
| } | |
| } | |
| if ((Buffer == NULL) || (Buffer == (VOID *)&DummyData)) { | |
| return; | |
| } else if (EFI_ERROR (Status)) { | |
| FreePages (Buffer, EFI_SIZE_TO_PAGES (Length)); | |
| return; | |
| } | |
| Status = AtaPassThruPpi->GetDevicePath (AtaPassThruPpi, &DevicePathLength, &DevicePath); | |
| if (EFI_ERROR (Status) || (DevicePathLength <= sizeof (EFI_DEVICE_PATH_PROTOCOL))) { | |
| goto Exit; | |
| } | |
| // | |
| // Go through all the devices managed by the AtaPassThru PPI instance. | |
| // | |
| Port = 0xFFFF; | |
| while (TRUE) { | |
| Status = AtaPassThruPpi->GetNextPort (AtaPassThruPpi, &Port); | |
| if (EFI_ERROR (Status)) { | |
| // | |
| // We cannot find more legal port then we are done. | |
| // | |
| break; | |
| } | |
| PortMultiplierPort = 0xFFFF; | |
| while (TRUE) { | |
| Status = AtaPassThruPpi->GetNextDevice (AtaPassThruPpi, Port, &PortMultiplierPort); | |
| if (EFI_ERROR (Status)) { | |
| // | |
| // We cannot find more legal port multiplier port number for ATA device | |
| // on the port, then we are done. | |
| // | |
| break; | |
| } | |
| // | |
| // Search the device in the restored LockBox. | |
| // | |
| DevInfo = (HDD_PASSWORD_DEVICE_INFO *)Buffer; | |
| while ((UINTN)DevInfo < ((UINTN)Buffer + Length)) { | |
| // | |
| // Find the matching device. | |
| // | |
| if ((DevInfo->Device.Port == Port) && | |
| (DevInfo->Device.PortMultiplierPort == PortMultiplierPort) && | |
| (DevInfo->DevicePathLength >= DevicePathLength) && | |
| (CompareMem ( | |
| DevInfo->DevicePath, | |
| DevicePath, | |
| DevicePathLength - sizeof (EFI_DEVICE_PATH_PROTOCOL) | |
| ) == 0)) | |
| { | |
| // | |
| // If device locked, unlock first. | |
| // | |
| if (!IsZeroBuffer (DevInfo->Password, HDD_PASSWORD_MAX_LENGTH)) { | |
| UnlockDevice (AtaPassThruPpi, Port, PortMultiplierPort, 0, DevInfo->Password); | |
| } | |
| // | |
| // Freeze lock the device. | |
| // | |
| FreezeLockDevice (AtaPassThruPpi, Port, PortMultiplierPort); | |
| break; | |
| } | |
| DevInfo = (HDD_PASSWORD_DEVICE_INFO *) | |
| ((UINTN)DevInfo + sizeof (HDD_PASSWORD_DEVICE_INFO) + DevInfo->DevicePathLength); | |
| } | |
| } | |
| } | |
| Exit: | |
| ZeroMem (Buffer, Length); | |
| FreePages (Buffer, EFI_SIZE_TO_PAGES (Length)); | |
| } | |
| /** | |
| Entry point of the notification callback function itself within the PEIM. | |
| It is to unlock HDD password for S3. | |
| @param PeiServices Indirect reference to the PEI Services Table. | |
| @param NotifyDescriptor Address of the notification descriptor data structure. | |
| @param Ppi Address of the PPI that was installed. | |
| @return Status of the notification. | |
| The status code returned from this function is ignored. | |
| **/ | |
| EFI_STATUS | |
| EFIAPI | |
| HddPasswordAtaPassThruNotify ( | |
| IN EFI_PEI_SERVICES **PeiServices, | |
| IN EFI_PEI_NOTIFY_DESCRIPTOR *NotifyDesc, | |
| IN VOID *Ppi | |
| ) | |
| { | |
| DEBUG ((DEBUG_INFO, "%a() - enter at S3 resume\n", __func__)); | |
| UnlockHddPassword ((EDKII_PEI_ATA_PASS_THRU_PPI *)Ppi); | |
| DEBUG ((DEBUG_INFO, "%a() - exit at S3 resume\n", __func__)); | |
| return EFI_SUCCESS; | |
| } | |
| EFI_PEI_NOTIFY_DESCRIPTOR mHddPasswordAtaPassThruPpiNotifyDesc = { | |
| (EFI_PEI_PPI_DESCRIPTOR_NOTIFY_CALLBACK | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST), | |
| &gEdkiiPeiAtaPassThruPpiGuid, | |
| HddPasswordAtaPassThruNotify | |
| }; | |
| /** | |
| Main entry for this module. | |
| @param FileHandle Handle of the file being invoked. | |
| @param PeiServices Pointer to PEI Services table. | |
| @return Status from PeiServicesNotifyPpi. | |
| **/ | |
| EFI_STATUS | |
| EFIAPI | |
| HddPasswordPeiInit ( | |
| IN EFI_PEI_FILE_HANDLE FileHandle, | |
| IN CONST EFI_PEI_SERVICES **PeiServices | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| EFI_BOOT_MODE BootMode; | |
| Status = PeiServicesGetBootMode (&BootMode); | |
| if ((EFI_ERROR (Status)) || (BootMode != BOOT_ON_S3_RESUME)) { | |
| return EFI_UNSUPPORTED; | |
| } | |
| DEBUG ((DEBUG_INFO, "%a: Enters in S3 path.\n", __func__)); | |
| Status = PeiServicesNotifyPpi (&mHddPasswordAtaPassThruPpiNotifyDesc); | |
| ASSERT_EFI_ERROR (Status); | |
| return Status; | |
| } |