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