| /** @file | |
| Unaccepted memory is a special type of private memory. In Td guest | |
| TDCALL [TDG.MEM.PAGE.ACCEPT] is invoked to accept the unaccepted | |
| memory before use it. | |
| Copyright (c) 2020 - 2021, Intel Corporation. All rights reserved.<BR> | |
| SPDX-License-Identifier: BSD-2-Clause-Patent | |
| **/ | |
| #include <Library/BaseLib.h> | |
| #include <Library/DebugLib.h> | |
| #include <IndustryStandard/Tdx.h> | |
| #include <Uefi/UefiBaseType.h> | |
| #include <Library/TdxLib.h> | |
| #include <Library/BaseMemoryLib.h> | |
| UINT64 mNumberOfDuplicatedAcceptedPages; | |
| #define TDX_ACCEPTPAGE_MAX_RETRIED 3 | |
| // PageSize is mapped to PageLevel like below: | |
| // 4KB - 0, 2MB - 1 | |
| UINT32 mTdxAcceptPageLevelMap[2] = { | |
| SIZE_4KB, | |
| SIZE_2MB | |
| }; | |
| #define INVALID_ACCEPT_PAGELEVEL ARRAY_SIZE(mTdxAcceptPageLevelMap) | |
| /** | |
| This function gets the PageLevel according to the input page size. | |
| @param[in] PageSize Page size | |
| @return UINT32 The mapped page level | |
| **/ | |
| UINT32 | |
| GetGpaPageLevel ( | |
| UINT32 PageSize | |
| ) | |
| { | |
| UINT32 Index; | |
| for (Index = 0; Index < ARRAY_SIZE (mTdxAcceptPageLevelMap); Index++) { | |
| if (mTdxAcceptPageLevelMap[Index] == PageSize) { | |
| break; | |
| } | |
| } | |
| return Index; | |
| } | |
| /** | |
| This function accept a pending private page, and initialize the page to | |
| all-0 using the TD ephemeral private key. | |
| Sometimes TDCALL [TDG.MEM.PAGE.ACCEPT] may return | |
| TDX_EXIT_REASON_PAGE_SIZE_MISMATCH. It indicates the input PageLevel is | |
| not workable. In this case we need to try to fallback to a smaller | |
| PageLevel if possible. | |
| @param[in] StartAddress Guest physical address of the private | |
| page to accept. [63:52] and [11:0] must be 0. | |
| @param[in] NumberOfPages Number of the pages to be accepted. | |
| @param[in] PageSize GPA page size. Only accept 2M/4K size. | |
| @return EFI_SUCCESS Accept successfully | |
| @return others Indicate other errors | |
| **/ | |
| EFI_STATUS | |
| EFIAPI | |
| TdAcceptPages ( | |
| IN UINT64 StartAddress, | |
| IN UINT64 NumberOfPages, | |
| IN UINT32 PageSize | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| UINT64 Address; | |
| UINT64 TdxStatus; | |
| UINT64 Index; | |
| UINT32 GpaPageLevel; | |
| UINT32 PageSize2; | |
| UINTN Retried; | |
| Retried = 0; | |
| if ((StartAddress & ~0xFFFFFFFFFF000ULL) != 0) { | |
| ASSERT (FALSE); | |
| DEBUG ((DEBUG_ERROR, "Accept page address(0x%llx) is not valid. [63:52] and [11:0] must be 0\n", StartAddress)); | |
| return EFI_INVALID_PARAMETER; | |
| } | |
| Address = StartAddress; | |
| GpaPageLevel = GetGpaPageLevel (PageSize); | |
| if (GpaPageLevel == INVALID_ACCEPT_PAGELEVEL) { | |
| ASSERT (FALSE); | |
| DEBUG ((DEBUG_ERROR, "Accept page size must be 4K/2M. Invalid page size - 0x%llx\n", PageSize)); | |
| return EFI_INVALID_PARAMETER; | |
| } | |
| Status = EFI_SUCCESS; | |
| for (Index = 0; Index < NumberOfPages; Index++) { | |
| Retried = 0; | |
| DoAcceptPage: | |
| TdxStatus = TdCall (TDCALL_TDACCEPTPAGE, Address | GpaPageLevel, 0, 0, 0); | |
| if (TdxStatus != TDX_EXIT_REASON_SUCCESS) { | |
| if ((TdxStatus & ~0xFFFFULL) == TDX_EXIT_REASON_PAGE_ALREADY_ACCEPTED) { | |
| // | |
| // Already accepted | |
| // | |
| mNumberOfDuplicatedAcceptedPages++; | |
| DEBUG ((DEBUG_WARN, "Page at Address (0x%llx) has already been accepted. - %d\n", Address, mNumberOfDuplicatedAcceptedPages)); | |
| } else if ((TdxStatus & ~0xFFFFULL) == TDX_EXIT_REASON_PAGE_SIZE_MISMATCH) { | |
| // | |
| // GpaPageLevel is mismatch, fall back to a smaller GpaPageLevel if possible | |
| // | |
| DEBUG ((DEBUG_VERBOSE, "Address %llx cannot be accepted in PageLevel of %d\n", Address, GpaPageLevel)); | |
| if (GpaPageLevel == 0) { | |
| // | |
| // Cannot fall back to smaller page level | |
| // | |
| DEBUG ((DEBUG_ERROR, "AcceptPage cannot fallback from PageLevel %d\n", GpaPageLevel)); | |
| Status = EFI_INVALID_PARAMETER; | |
| break; | |
| } else { | |
| // | |
| // Fall back to a smaller page size | |
| // | |
| PageSize2 = mTdxAcceptPageLevelMap[GpaPageLevel - 1]; | |
| Status = TdAcceptPages (Address, 512, PageSize2); | |
| if (EFI_ERROR (Status)) { | |
| break; | |
| } | |
| } | |
| } else if ((TdxStatus & ~0xFFFFULL) == TDX_EXIT_REASON_OPERAND_BUSY) { | |
| // | |
| // Concurrent TDG.MEM.PAGE.ACCEPT is using the same Secure EPT entry | |
| // So try it again. There is a max retried count. If Retried exceeds the max count, | |
| // report the error and quit. | |
| // | |
| Retried += 1; | |
| if (Retried > TDX_ACCEPTPAGE_MAX_RETRIED) { | |
| DEBUG (( | |
| DEBUG_ERROR, | |
| "Address %llx (%d) failed to be accepted because of OPERAND_BUSY. Retried %d time.\n", | |
| Address, | |
| Index, | |
| Retried | |
| )); | |
| Status = EFI_INVALID_PARAMETER; | |
| break; | |
| } else { | |
| goto DoAcceptPage; | |
| } | |
| } else { | |
| // | |
| // Other errors | |
| // | |
| DEBUG (( | |
| DEBUG_ERROR, | |
| "Address %llx (%d) failed to be accepted. Error = 0x%llx\n", | |
| Address, | |
| Index, | |
| TdxStatus | |
| )); | |
| Status = EFI_INVALID_PARAMETER; | |
| break; | |
| } | |
| } | |
| Address += PageSize; | |
| } | |
| return Status; | |
| } |