/** @file HddPassword PEI module which is used to unlock HDD password for S3. Copyright (c) 2019, Intel Corporation. All rights reserved.
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", __FUNCTION__, 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", __FUNCTION__, 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", __FUNCTION__)); UnlockHddPassword ((EDKII_PEI_ATA_PASS_THRU_PPI *) Ppi); DEBUG ((DEBUG_INFO, "%a() - exit at S3 resume\n", __FUNCTION__)); 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", __FUNCTION__)); Status = PeiServicesNotifyPpi (&mHddPasswordAtaPassThruPpiNotifyDesc); ASSERT_EFI_ERROR (Status); return Status; }