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