]> git.proxmox.com Git - mirror_edk2.git/blob - OvmfPkg/Library/BaseMemEncryptSevLib/X64/SnpPageStateChangeInternal.c
OvmfPkg/UefiCpuPkg: Add CcExit prefix to the APIs of CcExitLib
[mirror_edk2.git] / OvmfPkg / Library / BaseMemEncryptSevLib / X64 / SnpPageStateChangeInternal.c
1 /** @file
2
3 SEV-SNP Page Validation functions.
4
5 Copyright (c) 2021 AMD Incorporated. All rights reserved.<BR>
6
7 SPDX-License-Identifier: BSD-2-Clause-Patent
8
9 **/
10
11 #include <Uefi/UefiBaseType.h>
12 #include <Library/BaseLib.h>
13 #include <Library/BaseMemoryLib.h>
14 #include <Library/MemEncryptSevLib.h>
15 #include <Library/DebugLib.h>
16 #include <Library/CcExitLib.h>
17
18 #include <Register/Amd/Ghcb.h>
19 #include <Register/Amd/Msr.h>
20
21 #include "SnpPageStateChange.h"
22
23 #define IS_ALIGNED(x, y) ((((x) & (y - 1)) == 0))
24 #define PAGES_PER_LARGE_ENTRY 512
25
26 STATIC
27 UINTN
28 MemoryStateToGhcbOp (
29 IN SEV_SNP_PAGE_STATE State
30 )
31 {
32 UINTN Cmd;
33
34 switch (State) {
35 case SevSnpPageShared: Cmd = SNP_PAGE_STATE_SHARED;
36 break;
37 case SevSnpPagePrivate: Cmd = SNP_PAGE_STATE_PRIVATE;
38 break;
39 default: ASSERT (0);
40 }
41
42 return Cmd;
43 }
44
45 VOID
46 SnpPageStateFailureTerminate (
47 VOID
48 )
49 {
50 MSR_SEV_ES_GHCB_REGISTER Msr;
51
52 //
53 // Use the GHCB MSR Protocol to request termination by the hypervisor
54 //
55 Msr.GhcbPhysicalAddress = 0;
56 Msr.GhcbTerminate.Function = GHCB_INFO_TERMINATE_REQUEST;
57 Msr.GhcbTerminate.ReasonCodeSet = GHCB_TERMINATE_GHCB;
58 Msr.GhcbTerminate.ReasonCode = GHCB_TERMINATE_GHCB_GENERAL;
59 AsmWriteMsr64 (MSR_SEV_ES_GHCB, Msr.GhcbPhysicalAddress);
60
61 AsmVmgExit ();
62
63 ASSERT (FALSE);
64 CpuDeadLoop ();
65 }
66
67 /**
68 This function issues the PVALIDATE instruction to validate or invalidate the memory
69 range specified. If PVALIDATE returns size mismatch then it retry validating with
70 smaller page size.
71
72 */
73 STATIC
74 VOID
75 PvalidateRange (
76 IN SNP_PAGE_STATE_CHANGE_INFO *Info,
77 IN UINTN StartIndex,
78 IN UINTN EndIndex,
79 IN BOOLEAN Validate
80 )
81 {
82 UINTN Address, RmpPageSize, Ret, i;
83
84 for ( ; StartIndex <= EndIndex; StartIndex++) {
85 //
86 // Get the address and the page size from the Info.
87 //
88 Address = Info->Entry[StartIndex].GuestFrameNumber << EFI_PAGE_SHIFT;
89 RmpPageSize = Info->Entry[StartIndex].PageSize;
90
91 Ret = AsmPvalidate (RmpPageSize, Validate, Address);
92
93 //
94 // If we fail to validate due to size mismatch then try with the
95 // smaller page size. This senario will occur if the backing page in
96 // the RMP entry is 4K and we are validating it as a 2MB.
97 //
98 if ((Ret == PVALIDATE_RET_SIZE_MISMATCH) && (RmpPageSize == PvalidatePageSize2MB)) {
99 for (i = 0; i < PAGES_PER_LARGE_ENTRY; i++) {
100 Ret = AsmPvalidate (PvalidatePageSize4K, Validate, Address);
101 if (Ret) {
102 break;
103 }
104
105 Address = Address + EFI_PAGE_SIZE;
106 }
107 }
108
109 //
110 // If validation failed then do not continue.
111 //
112 if (Ret) {
113 DEBUG ((
114 DEBUG_ERROR,
115 "%a:%a: Failed to %a address 0x%Lx Error code %d\n",
116 gEfiCallerBaseName,
117 __FUNCTION__,
118 Validate ? "Validate" : "Invalidate",
119 Address,
120 Ret
121 ));
122 SnpPageStateFailureTerminate ();
123 }
124 }
125 }
126
127 STATIC
128 EFI_PHYSICAL_ADDRESS
129 BuildPageStateBuffer (
130 IN EFI_PHYSICAL_ADDRESS BaseAddress,
131 IN EFI_PHYSICAL_ADDRESS EndAddress,
132 IN SEV_SNP_PAGE_STATE State,
133 IN BOOLEAN UseLargeEntry,
134 IN SNP_PAGE_STATE_CHANGE_INFO *Info
135 )
136 {
137 EFI_PHYSICAL_ADDRESS NextAddress;
138 UINTN i, RmpPageSize;
139
140 // Clear the page state structure
141 SetMem (Info, sizeof (*Info), 0);
142
143 i = 0;
144 NextAddress = EndAddress;
145
146 //
147 // Populate the page state entry structure
148 //
149 while ((BaseAddress < EndAddress) && (i < SNP_PAGE_STATE_MAX_ENTRY)) {
150 //
151 // Is this a 2MB aligned page? Check if we can use the Large RMP entry.
152 //
153 if (UseLargeEntry && IS_ALIGNED (BaseAddress, SIZE_2MB) &&
154 ((EndAddress - BaseAddress) >= SIZE_2MB))
155 {
156 RmpPageSize = PvalidatePageSize2MB;
157 NextAddress = BaseAddress + SIZE_2MB;
158 } else {
159 RmpPageSize = PvalidatePageSize4K;
160 NextAddress = BaseAddress + EFI_PAGE_SIZE;
161 }
162
163 Info->Entry[i].GuestFrameNumber = BaseAddress >> EFI_PAGE_SHIFT;
164 Info->Entry[i].PageSize = RmpPageSize;
165 Info->Entry[i].Operation = MemoryStateToGhcbOp (State);
166 Info->Entry[i].CurrentPage = 0;
167 Info->Header.EndEntry = (UINT16)i;
168
169 BaseAddress = NextAddress;
170 i++;
171 }
172
173 return NextAddress;
174 }
175
176 STATIC
177 VOID
178 PageStateChangeVmgExit (
179 IN GHCB *Ghcb,
180 IN SNP_PAGE_STATE_CHANGE_INFO *Info
181 )
182 {
183 EFI_STATUS Status;
184
185 //
186 // As per the GHCB specification, the hypervisor can resume the guest before
187 // processing all the entries. Checks whether all the entries are processed.
188 //
189 // The stragtegy here is to wait for the hypervisor to change the page
190 // state in the RMP table before guest access the memory pages. If the
191 // page state was not successful, then later memory access will result
192 // in the crash.
193 //
194 while (Info->Header.CurrentEntry <= Info->Header.EndEntry) {
195 Ghcb->SaveArea.SwScratch = (UINT64)Ghcb->SharedBuffer;
196 CcExitVmgSetOffsetValid (Ghcb, GhcbSwScratch);
197
198 Status = CcExitVmgExit (Ghcb, SVM_EXIT_SNP_PAGE_STATE_CHANGE, 0, 0);
199
200 //
201 // The Page State Change VMGEXIT can pass the failure through the
202 // ExitInfo2. Lets check both the return value as well as ExitInfo2.
203 //
204 if ((Status != 0) || (Ghcb->SaveArea.SwExitInfo2)) {
205 SnpPageStateFailureTerminate ();
206 }
207 }
208 }
209
210 /**
211 The function is used to set the page state when SEV-SNP is active. The page state
212 transition consist of changing the page ownership in the RMP table, and using the
213 PVALIDATE instruction to update the Validated bit in RMP table.
214
215 When the UseLargeEntry is set to TRUE, then function will try to use the large RMP
216 entry (whevever possible).
217 */
218 VOID
219 InternalSetPageState (
220 IN EFI_PHYSICAL_ADDRESS BaseAddress,
221 IN UINTN NumPages,
222 IN SEV_SNP_PAGE_STATE State,
223 IN BOOLEAN UseLargeEntry
224 )
225 {
226 GHCB *Ghcb;
227 EFI_PHYSICAL_ADDRESS NextAddress, EndAddress;
228 MSR_SEV_ES_GHCB_REGISTER Msr;
229 BOOLEAN InterruptState;
230 SNP_PAGE_STATE_CHANGE_INFO *Info;
231
232 Msr.GhcbPhysicalAddress = AsmReadMsr64 (MSR_SEV_ES_GHCB);
233 Ghcb = Msr.Ghcb;
234
235 EndAddress = BaseAddress + EFI_PAGES_TO_SIZE (NumPages);
236
237 DEBUG ((
238 DEBUG_VERBOSE,
239 "%a:%a Address 0x%Lx - 0x%Lx State = %a LargeEntry = %d\n",
240 gEfiCallerBaseName,
241 __FUNCTION__,
242 BaseAddress,
243 EndAddress,
244 State == SevSnpPageShared ? "Shared" : "Private",
245 UseLargeEntry
246 ));
247
248 while (BaseAddress < EndAddress) {
249 UINTN CurrentEntry, EndEntry;
250
251 //
252 // Initialize the GHCB
253 //
254 CcExitVmgInit (Ghcb, &InterruptState);
255
256 //
257 // Build the page state structure
258 //
259 Info = (SNP_PAGE_STATE_CHANGE_INFO *)Ghcb->SharedBuffer;
260 NextAddress = BuildPageStateBuffer (
261 BaseAddress,
262 EndAddress,
263 State,
264 UseLargeEntry,
265 Info
266 );
267
268 //
269 // Save the current and end entry from the page state structure. We need
270 // it later.
271 //
272 CurrentEntry = Info->Header.CurrentEntry;
273 EndEntry = Info->Header.EndEntry;
274
275 //
276 // If the caller requested to change the page state to shared then
277 // invalidate the pages before making the page shared in the RMP table.
278 //
279 if (State == SevSnpPageShared) {
280 PvalidateRange (Info, CurrentEntry, EndEntry, FALSE);
281 }
282
283 //
284 // Invoke the page state change VMGEXIT.
285 //
286 PageStateChangeVmgExit (Ghcb, Info);
287
288 //
289 // If the caller requested to change the page state to private then
290 // validate the pages after it has been added in the RMP table.
291 //
292 if (State == SevSnpPagePrivate) {
293 PvalidateRange (Info, CurrentEntry, EndEntry, TRUE);
294 }
295
296 CcExitVmgDone (Ghcb, InterruptState);
297
298 BaseAddress = NextAddress;
299 }
300 }