]> git.proxmox.com Git - mirror_edk2.git/blame - OvmfPkg/AcpiPlatformDxe/BootScript.c
OvmfPkg/AcpiPlatformDxe: replay QEMU_LOADER_WRITE_POINTER commands at S3
[mirror_edk2.git] / OvmfPkg / AcpiPlatformDxe / BootScript.c
CommitLineData
df73df13
LE
1/** @file\r
2 Append an ACPI S3 Boot Script fragment from the QEMU_LOADER_WRITE_POINTER\r
3 commands of QEMU's fully processed table linker/loader script.\r
4\r
5 Copyright (C) 2017, Red Hat, Inc.\r
6\r
7 This program and the accompanying materials are licensed and made available\r
8 under the terms and conditions of the BSD License which accompanies this\r
9 distribution. The full text of the license may be found at\r
10 http://opensource.org/licenses/bsd-license.php\r
11\r
12 THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, WITHOUT\r
13 WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.\r
14**/\r
15\r
16#include <Library/MemoryAllocationLib.h>\r
17#include <Library/QemuFwCfgLib.h>\r
18#include <Protocol/S3SaveState.h>\r
19\r
20#include "AcpiPlatform.h"\r
21\r
22\r
23//\r
24// Condensed structure for capturing the fw_cfg operations -- select, skip,\r
25// write -- inherent in executing a QEMU_LOADER_WRITE_POINTER command.\r
26//\r
27typedef struct {\r
28 UINT16 PointerItem; // resolved from QEMU_LOADER_WRITE_POINTER.PointerFile\r
29 UINT8 PointerSize; // copied as-is from QEMU_LOADER_WRITE_POINTER\r
30 UINT32 PointerOffset; // copied as-is from QEMU_LOADER_WRITE_POINTER\r
31 UINT64 PointerValue; // resolved from QEMU_LOADER_WRITE_POINTER.PointeeFile\r
32} CONDENSED_WRITE_POINTER;\r
33\r
34\r
35//\r
36// Context structure to accumulate CONDENSED_WRITE_POINTER objects from\r
37// QEMU_LOADER_WRITE_POINTER commands.\r
38//\r
39// Any pointers in this structure own the pointed-to objects; that is, when the\r
40// context structure is released, all pointed-to objects must be released too.\r
41//\r
42struct S3_CONTEXT {\r
43 CONDENSED_WRITE_POINTER *WritePointers; // one array element per processed\r
44 // QEMU_LOADER_WRITE_POINTER\r
45 // command\r
46 UINTN Allocated; // number of elements allocated for\r
47 // WritePointers\r
48 UINTN Used; // number of elements populated in\r
49 // WritePointers\r
50};\r
51\r
52\r
53//\r
54// Scratch buffer, allocated in EfiReservedMemoryType type memory, for the ACPI\r
55// S3 Boot Script opcodes to work on. We use the buffer to compose and to\r
56// replay several fw_cfg select+skip and write operations, using the DMA access\r
57// method. The fw_cfg operations will implement the actions dictated by\r
58// CONDENSED_WRITE_POINTER objects.\r
59//\r
60#pragma pack (1)\r
61typedef struct {\r
62 FW_CFG_DMA_ACCESS Access; // filled in from\r
63 // CONDENSED_WRITE_POINTER.PointerItem,\r
64 // CONDENSED_WRITE_POINTER.PointerSize,\r
65 // CONDENSED_WRITE_POINTER.PointerOffset\r
66 UINT64 PointerValue; // filled in from\r
67 // CONDENSED_WRITE_POINTER.PointerValue\r
68} SCRATCH_BUFFER;\r
69#pragma pack ()\r
70\r
71\r
72/**\r
73 Allocate an S3_CONTEXT object.\r
74\r
75 @param[out] S3Context The allocated S3_CONTEXT object is returned\r
76 through this parameter.\r
77\r
78 @param[in] WritePointerCount Number of CONDENSED_WRITE_POINTER elements to\r
79 allocate room for. WritePointerCount must be\r
80 positive.\r
81\r
82 @retval EFI_SUCCESS Allocation successful.\r
83\r
84 @retval EFI_OUT_OF_RESOURCES Out of memory.\r
85\r
86 @retval EFI_INVALID_PARAMETER WritePointerCount is zero.\r
87**/\r
88EFI_STATUS\r
89AllocateS3Context (\r
90 OUT S3_CONTEXT **S3Context,\r
91 IN UINTN WritePointerCount\r
92 )\r
93{\r
94 EFI_STATUS Status;\r
95 S3_CONTEXT *Context;\r
96\r
97 if (WritePointerCount == 0) {\r
98 return EFI_INVALID_PARAMETER;\r
99 }\r
100\r
101 Context = AllocateZeroPool (sizeof *Context);\r
102 if (Context == NULL) {\r
103 return EFI_OUT_OF_RESOURCES;\r
104 }\r
105\r
106 Context->WritePointers = AllocatePool (WritePointerCount *\r
107 sizeof *Context->WritePointers);\r
108 if (Context->WritePointers == NULL) {\r
109 Status = EFI_OUT_OF_RESOURCES;\r
110 goto FreeContext;\r
111 }\r
112\r
113 Context->Allocated = WritePointerCount;\r
114 *S3Context = Context;\r
115 return EFI_SUCCESS;\r
116\r
117FreeContext:\r
118 FreePool (Context);\r
119\r
120 return Status;\r
121}\r
122\r
123\r
124/**\r
125 Release an S3_CONTEXT object.\r
126\r
127 @param[in] S3Context The object to release.\r
128**/\r
129VOID\r
130ReleaseS3Context (\r
131 IN S3_CONTEXT *S3Context\r
132 )\r
133{\r
134 FreePool (S3Context->WritePointers);\r
135 FreePool (S3Context);\r
136}\r
137\r
138\r
139/**\r
140 Save the information necessary to replicate a QEMU_LOADER_WRITE_POINTER\r
141 command during S3 resume, in condensed format.\r
142\r
143 This function is to be called from ProcessCmdWritePointer(), after all the\r
144 sanity checks have passed, and before the fw_cfg operations are performed.\r
145\r
146 @param[in,out] S3Context The S3_CONTEXT object into which the caller wants\r
147 to save the information that was derived from\r
148 QEMU_LOADER_WRITE_POINTER.\r
149\r
150 @param[in] PointerItem The FIRMWARE_CONFIG_ITEM that\r
151 QEMU_LOADER_WRITE_POINTER.PointerFile was resolved\r
152 to, expressed as a UINT16 value.\r
153\r
154 @param[in] PointerSize Copied directly from\r
155 QEMU_LOADER_WRITE_POINTER.PointerSize.\r
156\r
157 @param[in] PointerOffset Copied directly from\r
158 QEMU_LOADER_WRITE_POINTER.PointerOffset.\r
159\r
160 @param[in] PointerValue The base address of the allocated / downloaded\r
161 fw_cfg blob that is identified by\r
162 QEMU_LOADER_WRITE_POINTER.PointeeFile.\r
163\r
164 @retval EFI_SUCCESS The information derived from\r
165 QEMU_LOADER_WRITE_POINTER has been successfully\r
166 absorbed into S3Context.\r
167\r
168 @retval EFI_OUT_OF_RESOURCES No room available in S3Context.\r
169**/\r
170EFI_STATUS\r
171SaveCondensedWritePointerToS3Context (\r
172 IN OUT S3_CONTEXT *S3Context,\r
173 IN UINT16 PointerItem,\r
174 IN UINT8 PointerSize,\r
175 IN UINT32 PointerOffset,\r
176 IN UINT64 PointerValue\r
177 )\r
178{\r
179 CONDENSED_WRITE_POINTER *Condensed;\r
180\r
181 if (S3Context->Used == S3Context->Allocated) {\r
182 return EFI_OUT_OF_RESOURCES;\r
183 }\r
184 Condensed = S3Context->WritePointers + S3Context->Used;\r
185 Condensed->PointerItem = PointerItem;\r
186 Condensed->PointerSize = PointerSize;\r
187 Condensed->PointerOffset = PointerOffset;\r
188 Condensed->PointerValue = PointerValue;\r
189 DEBUG ((DEBUG_VERBOSE, "%a: 0x%04x/[0x%08x+%d] := 0x%Lx (%Lu)\n",\r
190 __FUNCTION__, PointerItem, PointerOffset, PointerSize, PointerValue,\r
191 (UINT64)S3Context->Used));\r
192 ++S3Context->Used;\r
193 return EFI_SUCCESS;\r
194}\r
195\r
196\r
197/**\r
198 Translate and append the information from an S3_CONTEXT object to the ACPI S3\r
199 Boot Script.\r
200\r
201 The effects of a successful call to this function cannot be undone.\r
202\r
203 @param[in] S3Context The S3_CONTEXT object to translate to ACPI S3 Boot\r
204 Script opcodes.\r
205\r
206 @retval EFI_OUT_OF_RESOURCES Out of memory.\r
207\r
208 @retval EFI_SUCCESS The translation of S3Context to ACPI S3 Boot\r
209 Script opcodes has been successful.\r
210\r
211 @return Error codes from underlying functions.\r
212**/\r
213EFI_STATUS\r
214TransferS3ContextToBootScript (\r
215 IN CONST S3_CONTEXT *S3Context\r
216 )\r
217{\r
218 EFI_STATUS Status;\r
219 EFI_S3_SAVE_STATE_PROTOCOL *S3SaveState;\r
220 SCRATCH_BUFFER *ScratchBuffer;\r
221 FW_CFG_DMA_ACCESS *Access;\r
222 UINT64 BigEndianAddressOfAccess;\r
223 UINT32 ControlPollData;\r
224 UINT32 ControlPollMask;\r
225 UINTN Index;\r
226\r
227 //\r
228 // If the following protocol lookup fails, it shall not happen due to an\r
229 // unexpected DXE driver dispatch order.\r
230 //\r
231 // Namely, this function is only invoked on QEMU. Therefore it is only\r
232 // reached after Platform BDS signals gRootBridgesConnectedEventGroupGuid\r
233 // (see OnRootBridgesConnected() in "EntryPoint.c"). Hence, because\r
234 // TransferS3ContextToBootScript() is invoked in BDS, all DXE drivers,\r
235 // including S3SaveStateDxe (producing EFI_S3_SAVE_STATE_PROTOCOL), have been\r
236 // dispatched by the time we get here. (S3SaveStateDxe is not expected to\r
237 // have any stricter-than-TRUE DEPEX -- not a DEPEX that gets unblocked only\r
238 // within BDS anyway.)\r
239 //\r
240 // Reaching this function also depends on QemuFwCfgS3Enabled(). That implies\r
241 // S3SaveStateDxe has not exited immediately due to S3 being disabled. Thus\r
242 // EFI_S3_SAVE_STATE_PROTOCOL can only be missing for genuinely unforeseeable\r
243 // reasons.\r
244 //\r
245 Status = gBS->LocateProtocol (&gEfiS3SaveStateProtocolGuid,\r
246 NULL /* Registration */, (VOID **)&S3SaveState);\r
247 if (EFI_ERROR (Status)) {\r
248 DEBUG ((DEBUG_ERROR, "%a: LocateProtocol(): %r\n", __FUNCTION__, Status));\r
249 return Status;\r
250 }\r
251\r
252 ScratchBuffer = AllocateReservedPool (sizeof *ScratchBuffer);\r
253 if (ScratchBuffer == NULL) {\r
254 return EFI_OUT_OF_RESOURCES;\r
255 }\r
256\r
257 //\r
258 // Set up helper variables that we'll use identically for all\r
259 // CONDENSED_WRITE_POINTER elements.\r
260 //\r
261 Access = &ScratchBuffer->Access;\r
262 BigEndianAddressOfAccess = SwapBytes64 ((UINTN)Access);\r
263 ControlPollData = 0;\r
264 ControlPollMask = MAX_UINT32;\r
265\r
266 //\r
267 // For each CONDENSED_WRITE_POINTER, we need six ACPI S3 Boot Script opcodes:\r
268 // (1) restore an FW_CFG_DMA_ACCESS object in reserved memory that selects\r
269 // the writeable fw_cfg file PointerFile (through PointerItem), and skips\r
270 // to PointerOffset in it,\r
271 // (2) call QEMU with the FW_CFG_DMA_ACCESS object,\r
272 // (3) wait for the select+skip to finish,\r
273 // (4) restore a SCRATCH_BUFFER object in reserved memory that writes\r
274 // PointerValue (base address of the allocated / downloaded PointeeFile),\r
275 // of size PointerSize, into the fw_cfg file selected in (1), at the\r
276 // offset sought to in (1),\r
277 // (5) call QEMU with the FW_CFG_DMA_ACCESS object,\r
278 // (6) wait for the write to finish.\r
279 //\r
280 // EFI_S3_SAVE_STATE_PROTOCOL does not allow rolling back opcode additions,\r
281 // therefore we treat any failure here as fatal.\r
282 //\r
283 for (Index = 0; Index < S3Context->Used; ++Index) {\r
284 CONST CONDENSED_WRITE_POINTER *Condensed;\r
285\r
286 Condensed = &S3Context->WritePointers[Index];\r
287\r
288 //\r
289 // (1) restore an FW_CFG_DMA_ACCESS object in reserved memory that selects\r
290 // the writeable fw_cfg file PointerFile (through PointerItem), and\r
291 // skips to PointerOffset in it,\r
292 //\r
293 Access->Control = SwapBytes32 ((UINT32)Condensed->PointerItem << 16 |\r
294 FW_CFG_DMA_CTL_SELECT | FW_CFG_DMA_CTL_SKIP);\r
295 Access->Length = SwapBytes32 (Condensed->PointerOffset);\r
296 Access->Address = 0;\r
297 Status = S3SaveState->Write (\r
298 S3SaveState, // This\r
299 EFI_BOOT_SCRIPT_MEM_WRITE_OPCODE, // OpCode\r
300 EfiBootScriptWidthUint8, // Width\r
301 (UINT64)(UINTN)Access, // Address\r
302 sizeof *Access, // Count\r
303 Access // Buffer\r
304 );\r
305 if (EFI_ERROR (Status)) {\r
306 DEBUG ((DEBUG_ERROR, "%a: Index %Lu opcode 1: %r\n", __FUNCTION__,\r
307 (UINT64)Index, Status));\r
308 goto FatalError;\r
309 }\r
310\r
311 //\r
312 // (2) call QEMU with the FW_CFG_DMA_ACCESS object,\r
313 //\r
314 Status = S3SaveState->Write (\r
315 S3SaveState, // This\r
316 EFI_BOOT_SCRIPT_IO_WRITE_OPCODE, // OpCode\r
317 EfiBootScriptWidthUint32, // Width\r
318 (UINT64)0x514, // Address\r
319 (UINTN)2, // Count\r
320 &BigEndianAddressOfAccess // Buffer\r
321 );\r
322 if (EFI_ERROR (Status)) {\r
323 DEBUG ((DEBUG_ERROR, "%a: Index %Lu opcode 2: %r\n", __FUNCTION__,\r
324 (UINT64)Index, Status));\r
325 goto FatalError;\r
326 }\r
327\r
328 //\r
329 // (3) wait for the select+skip to finish,\r
330 //\r
331 Status = S3SaveState->Write (\r
332 S3SaveState, // This\r
333 EFI_BOOT_SCRIPT_MEM_POLL_OPCODE, // OpCode\r
334 EfiBootScriptWidthUint32, // Width\r
335 (UINT64)(UINTN)&Access->Control, // Address\r
336 &ControlPollData, // Data\r
337 &ControlPollMask, // DataMask\r
338 MAX_UINT64 // Delay\r
339 );\r
340 if (EFI_ERROR (Status)) {\r
341 DEBUG ((DEBUG_ERROR, "%a: Index %Lu opcode 3: %r\n", __FUNCTION__,\r
342 (UINT64)Index, Status));\r
343 goto FatalError;\r
344 }\r
345\r
346 //\r
347 // (4) restore a SCRATCH_BUFFER object in reserved memory that writes\r
348 // PointerValue (base address of the allocated / downloaded\r
349 // PointeeFile), of size PointerSize, into the fw_cfg file selected in\r
350 // (1), at the offset sought to in (1),\r
351 //\r
352 Access->Control = SwapBytes32 (FW_CFG_DMA_CTL_WRITE);\r
353 Access->Length = SwapBytes32 (Condensed->PointerSize);\r
354 Access->Address = SwapBytes64 ((UINTN)&ScratchBuffer->PointerValue);\r
355 ScratchBuffer->PointerValue = Condensed->PointerValue;\r
356 Status = S3SaveState->Write (\r
357 S3SaveState, // This\r
358 EFI_BOOT_SCRIPT_MEM_WRITE_OPCODE, // OpCode\r
359 EfiBootScriptWidthUint8, // Width\r
360 (UINT64)(UINTN)ScratchBuffer, // Address\r
361 sizeof *ScratchBuffer, // Count\r
362 ScratchBuffer // Buffer\r
363 );\r
364 if (EFI_ERROR (Status)) {\r
365 DEBUG ((DEBUG_ERROR, "%a: Index %Lu opcode 4: %r\n", __FUNCTION__,\r
366 (UINT64)Index, Status));\r
367 goto FatalError;\r
368 }\r
369\r
370 //\r
371 // (5) call QEMU with the FW_CFG_DMA_ACCESS object,\r
372 //\r
373 Status = S3SaveState->Write (\r
374 S3SaveState, // This\r
375 EFI_BOOT_SCRIPT_IO_WRITE_OPCODE, // OpCode\r
376 EfiBootScriptWidthUint32, // Width\r
377 (UINT64)0x514, // Address\r
378 (UINTN)2, // Count\r
379 &BigEndianAddressOfAccess // Buffer\r
380 );\r
381 if (EFI_ERROR (Status)) {\r
382 DEBUG ((DEBUG_ERROR, "%a: Index %Lu opcode 5: %r\n", __FUNCTION__,\r
383 (UINT64)Index, Status));\r
384 goto FatalError;\r
385 }\r
386\r
387 //\r
388 // (6) wait for the write to finish.\r
389 //\r
390 Status = S3SaveState->Write (\r
391 S3SaveState, // This\r
392 EFI_BOOT_SCRIPT_MEM_POLL_OPCODE, // OpCode\r
393 EfiBootScriptWidthUint32, // Width\r
394 (UINT64)(UINTN)&Access->Control, // Address\r
395 &ControlPollData, // Data\r
396 &ControlPollMask, // DataMask\r
397 MAX_UINT64 // Delay\r
398 );\r
399 if (EFI_ERROR (Status)) {\r
400 DEBUG ((DEBUG_ERROR, "%a: Index %Lu opcode 6: %r\n", __FUNCTION__,\r
401 (UINT64)Index, Status));\r
402 goto FatalError;\r
403 }\r
404 }\r
405\r
406 DEBUG ((DEBUG_VERBOSE, "%a: boot script fragment saved, ScratchBuffer=%p\n",\r
407 __FUNCTION__, (VOID *)ScratchBuffer));\r
408 return EFI_SUCCESS;\r
409\r
410FatalError:\r
411 ASSERT (FALSE);\r
412 CpuDeadLoop ();\r
413 return Status;\r
414}\r