]> git.proxmox.com Git - mirror_edk2.git/blame - OvmfPkg/SmmControl2Dxe/SmmControl2Dxe.c
UefiCpuPkg: Move AsmRelocateApLoopStart from Mpfuncs.nasm to AmdSev.nasm
[mirror_edk2.git] / OvmfPkg / SmmControl2Dxe / SmmControl2Dxe.c
CommitLineData
c40e1a0c
LE
1/** @file\r
2\r
3 A DXE_RUNTIME_DRIVER providing synchronous SMI activations via the\r
4 EFI_SMM_CONTROL2_PROTOCOL.\r
5\r
6 We expect the PEI phase to have covered the following:\r
7 - ensure that the underlying QEMU machine type be Q35\r
8 (responsible: OvmfPkg/SmmAccess/SmmAccessPei.inf)\r
9 - ensure that the ACPI PM IO space be configured\r
10 (responsible: OvmfPkg/PlatformPei/PlatformPei.inf)\r
11\r
12 Our own entry point is responsible for confirming the SMI feature and for\r
13 configuring it.\r
14\r
15 Copyright (C) 2013, 2015, Red Hat, Inc.<BR>\r
16 Copyright (c) 2009 - 2010, Intel Corporation. All rights reserved.<BR>\r
17\r
b26f0cf9 18 SPDX-License-Identifier: BSD-2-Clause-Patent\r
c40e1a0c
LE
19\r
20**/\r
21\r
22#include <IndustryStandard/Q35MchIch9.h>\r
23#include <Library/BaseLib.h>\r
24#include <Library/DebugLib.h>\r
25#include <Library/IoLib.h>\r
26#include <Library/PcdLib.h>\r
27#include <Library/PciLib.h>\r
c40e1a0c
LE
28#include <Library/UefiBootServicesTableLib.h>\r
29#include <Protocol/S3SaveState.h>\r
30#include <Protocol/SmmControl2.h>\r
31\r
a316d7ac
LE
32#include "SmiFeatures.h"\r
33\r
c40e1a0c
LE
34//\r
35// Forward declaration.\r
36//\r
37STATIC\r
38VOID\r
39EFIAPI\r
40OnS3SaveStateInstalled (\r
ac0a286f
MK
41 IN EFI_EVENT Event,\r
42 IN VOID *Context\r
c40e1a0c
LE
43 );\r
44\r
45//\r
46// The absolute IO port address of the SMI Control and Enable Register. It is\r
47// only used to carry information from the entry point function to the\r
48// S3SaveState protocol installation callback, strictly before the runtime\r
49// phase.\r
50//\r
ac0a286f 51STATIC UINTN mSmiEnable;\r
c40e1a0c 52\r
a316d7ac
LE
53//\r
54// Captures whether SMI feature negotiation is supported. The variable is only\r
55// used to carry this information from the entry point function to the\r
56// S3SaveState protocol installation callback.\r
57//\r
ac0a286f 58STATIC BOOLEAN mSmiFeatureNegotiation;\r
a316d7ac 59\r
c40e1a0c
LE
60//\r
61// Event signaled when an S3SaveState protocol interface is installed.\r
62//\r
ac0a286f 63STATIC EFI_EVENT mS3SaveStateInstalled;\r
c40e1a0c
LE
64\r
65/**\r
66 Invokes SMI activation from either the preboot or runtime environment.\r
67\r
68 This function generates an SMI.\r
69\r
70 @param[in] This The EFI_SMM_CONTROL2_PROTOCOL instance.\r
71 @param[in,out] CommandPort The value written to the command port.\r
72 @param[in,out] DataPort The value written to the data port.\r
73 @param[in] Periodic Optional mechanism to engender a periodic\r
74 stream.\r
75 @param[in] ActivationInterval Optional parameter to repeat at this\r
76 period one time or, if the Periodic\r
77 Boolean is set, periodically.\r
78\r
79 @retval EFI_SUCCESS The SMI/PMI has been engendered.\r
80 @retval EFI_DEVICE_ERROR The timing is unsupported.\r
81 @retval EFI_INVALID_PARAMETER The activation period is unsupported.\r
82 @retval EFI_INVALID_PARAMETER The last periodic activation has not been\r
83 cleared.\r
84 @retval EFI_NOT_STARTED The SMM base service has not been initialized.\r
85**/\r
86STATIC\r
87EFI_STATUS\r
88EFIAPI\r
89SmmControl2DxeTrigger (\r
90 IN CONST EFI_SMM_CONTROL2_PROTOCOL *This,\r
91 IN OUT UINT8 *CommandPort OPTIONAL,\r
92 IN OUT UINT8 *DataPort OPTIONAL,\r
93 IN BOOLEAN Periodic OPTIONAL,\r
94 IN UINTN ActivationInterval OPTIONAL\r
95 )\r
96{\r
97 //\r
98 // No support for queued or periodic activation.\r
99 //\r
ac0a286f 100 if (Periodic || (ActivationInterval > 0)) {\r
c40e1a0c
LE
101 return EFI_DEVICE_ERROR;\r
102 }\r
103\r
104 //\r
105 // The so-called "Advanced Power Management Status Port Register" is in fact\r
106 // a generic data passing register, between the caller and the SMI\r
107 // dispatcher. The ICH9 spec calls it "scratchpad register" -- calling it\r
108 // "status" elsewhere seems quite the misnomer. Status registers usually\r
109 // report about hardware status, while this register is fully governed by\r
110 // software.\r
111 //\r
112 // Write to the status register first, as this won't trigger the SMI just\r
113 // yet. Then write to the control register.\r
114 //\r
115 IoWrite8 (ICH9_APM_STS, DataPort == NULL ? 0 : *DataPort);\r
116 IoWrite8 (ICH9_APM_CNT, CommandPort == NULL ? 0 : *CommandPort);\r
117 return EFI_SUCCESS;\r
118}\r
119\r
120/**\r
121 Clears any system state that was created in response to the Trigger() call.\r
122\r
123 This function acknowledges and causes the deassertion of the SMI activation\r
124 source.\r
125\r
126 @param[in] This The EFI_SMM_CONTROL2_PROTOCOL instance.\r
127 @param[in] Periodic Optional parameter to repeat at this period\r
128 one time\r
129\r
130 @retval EFI_SUCCESS The SMI/PMI has been engendered.\r
131 @retval EFI_DEVICE_ERROR The source could not be cleared.\r
132 @retval EFI_INVALID_PARAMETER The service did not support the Periodic input\r
133 argument.\r
134**/\r
135STATIC\r
136EFI_STATUS\r
137EFIAPI\r
138SmmControl2DxeClear (\r
139 IN CONST EFI_SMM_CONTROL2_PROTOCOL *This,\r
140 IN BOOLEAN Periodic OPTIONAL\r
141 )\r
142{\r
143 if (Periodic) {\r
144 return EFI_INVALID_PARAMETER;\r
145 }\r
146\r
147 //\r
148 // The PI spec v1.4 explains that Clear() is only supposed to clear software\r
149 // status; it is not in fact responsible for deasserting the SMI. It gives\r
150 // two reasons for this: (a) many boards clear the SMI automatically when\r
151 // entering SMM, (b) if Clear() actually deasserted the SMI, then it could\r
152 // incorrectly suppress an SMI that was asynchronously asserted between the\r
153 // last return of the SMI handler and the call made to Clear().\r
154 //\r
155 // In fact QEMU automatically deasserts CPU_INTERRUPT_SMI in:\r
156 // - x86_cpu_exec_interrupt() [target-i386/seg_helper.c], and\r
157 // - kvm_arch_pre_run() [target-i386/kvm.c].\r
158 //\r
159 // So, nothing to do here.\r
160 //\r
161 return EFI_SUCCESS;\r
162}\r
163\r
ac0a286f 164STATIC EFI_SMM_CONTROL2_PROTOCOL mControl2 = {\r
c40e1a0c
LE
165 &SmmControl2DxeTrigger,\r
166 &SmmControl2DxeClear,\r
167 MAX_UINTN // MinimumTriggerPeriod -- we don't support periodic SMIs\r
168};\r
169\r
170//\r
171// Entry point of this driver.\r
172//\r
173EFI_STATUS\r
174EFIAPI\r
175SmmControl2DxeEntryPoint (\r
ac0a286f
MK
176 IN EFI_HANDLE ImageHandle,\r
177 IN EFI_SYSTEM_TABLE *SystemTable\r
c40e1a0c
LE
178 )\r
179{\r
ac0a286f
MK
180 UINT32 PmBase;\r
181 UINT32 SmiEnableVal;\r
182 EFI_STATUS Status;\r
c40e1a0c
LE
183\r
184 //\r
185 // This module should only be included if SMRAM support is required.\r
186 //\r
187 ASSERT (FeaturePcdGet (PcdSmmSmramRequire));\r
188\r
189 //\r
190 // Calculate the absolute IO port address of the SMI Control and Enable\r
191 // Register. (As noted at the top, the PEI phase has left us with a working\r
192 // ACPI PM IO space.)\r
193 //\r
194 PmBase = PciRead32 (POWER_MGMT_REGISTER_Q35 (ICH9_PMBASE)) &\r
ac0a286f 195 ICH9_PMBASE_MASK;\r
c40e1a0c
LE
196 mSmiEnable = PmBase + ICH9_PMBASE_OFS_SMI_EN;\r
197\r
198 //\r
199 // If APMC_EN is pre-set in SMI_EN, that's QEMU's way to tell us that SMI\r
200 // support is not available. (For example due to KVM lacking it.) Otherwise,\r
201 // this bit is clear after each reset.\r
202 //\r
203 SmiEnableVal = IoRead32 (mSmiEnable);\r
204 if ((SmiEnableVal & ICH9_SMI_EN_APMC_EN) != 0) {\r
ac0a286f
MK
205 DEBUG ((\r
206 DEBUG_ERROR,\r
207 "%a: this Q35 implementation lacks SMI\n",\r
208 __FUNCTION__\r
209 ));\r
c40e1a0c
LE
210 goto FatalError;\r
211 }\r
212\r
213 //\r
214 // Otherwise, configure the board to inject an SMI when ICH9_APM_CNT is\r
215 // written to. (See the Trigger() method above.)\r
216 //\r
217 SmiEnableVal |= ICH9_SMI_EN_APMC_EN | ICH9_SMI_EN_GBL_SMI_EN;\r
218 IoWrite32 (mSmiEnable, SmiEnableVal);\r
219\r
220 //\r
221 // Prevent software from undoing the above (until platform reset).\r
222 //\r
ac0a286f
MK
223 PciOr16 (\r
224 POWER_MGMT_REGISTER_Q35 (ICH9_GEN_PMCON_1),\r
225 ICH9_GEN_PMCON_1_SMI_LOCK\r
226 );\r
c40e1a0c
LE
227\r
228 //\r
229 // If we can clear GBL_SMI_EN now, that means QEMU's SMI support is not\r
230 // appropriate.\r
231 //\r
232 IoWrite32 (mSmiEnable, SmiEnableVal & ~(UINT32)ICH9_SMI_EN_GBL_SMI_EN);\r
233 if (IoRead32 (mSmiEnable) != SmiEnableVal) {\r
ac0a286f
MK
234 DEBUG ((\r
235 DEBUG_ERROR,\r
236 "%a: failed to lock down GBL_SMI_EN\n",\r
237 __FUNCTION__\r
238 ));\r
c40e1a0c
LE
239 goto FatalError;\r
240 }\r
241\r
a316d7ac
LE
242 //\r
243 // QEMU can inject SMIs in different ways, negotiate our preferences.\r
244 //\r
245 mSmiFeatureNegotiation = NegotiateSmiFeatures ();\r
246\r
5b5f10d7 247 if (PcdGetBool (PcdAcpiS3Enable)) {\r
ac0a286f 248 VOID *Registration;\r
c40e1a0c
LE
249\r
250 //\r
251 // On S3 resume the above register settings have to be repeated. Register a\r
252 // protocol notify callback that, when boot script saving becomes\r
253 // available, saves operations equivalent to the above to the boot script.\r
254 //\r
ac0a286f
MK
255 Status = gBS->CreateEvent (\r
256 EVT_NOTIFY_SIGNAL,\r
257 TPL_CALLBACK,\r
258 OnS3SaveStateInstalled,\r
259 NULL /* Context */,\r
260 &mS3SaveStateInstalled\r
261 );\r
c40e1a0c 262 if (EFI_ERROR (Status)) {\r
70d5086c 263 DEBUG ((DEBUG_ERROR, "%a: CreateEvent: %r\n", __FUNCTION__, Status));\r
c40e1a0c
LE
264 goto FatalError;\r
265 }\r
266\r
ac0a286f
MK
267 Status = gBS->RegisterProtocolNotify (\r
268 &gEfiS3SaveStateProtocolGuid,\r
269 mS3SaveStateInstalled,\r
270 &Registration\r
271 );\r
c40e1a0c 272 if (EFI_ERROR (Status)) {\r
ac0a286f
MK
273 DEBUG ((\r
274 DEBUG_ERROR,\r
275 "%a: RegisterProtocolNotify: %r\n",\r
276 __FUNCTION__,\r
277 Status\r
278 ));\r
c40e1a0c
LE
279 goto ReleaseEvent;\r
280 }\r
281\r
282 //\r
283 // Kick the event right now -- maybe the boot script is already saveable.\r
284 //\r
285 Status = gBS->SignalEvent (mS3SaveStateInstalled);\r
286 if (EFI_ERROR (Status)) {\r
70d5086c 287 DEBUG ((DEBUG_ERROR, "%a: SignalEvent: %r\n", __FUNCTION__, Status));\r
c40e1a0c
LE
288 goto ReleaseEvent;\r
289 }\r
290 }\r
291\r
292 //\r
293 // We have no pointers to convert to virtual addresses. The handle itself\r
294 // doesn't matter, as protocol services are not accessible at runtime.\r
295 //\r
ac0a286f
MK
296 Status = gBS->InstallMultipleProtocolInterfaces (\r
297 &ImageHandle,\r
298 &gEfiSmmControl2ProtocolGuid,\r
299 &mControl2,\r
300 NULL\r
301 );\r
c40e1a0c 302 if (EFI_ERROR (Status)) {\r
ac0a286f
MK
303 DEBUG ((\r
304 DEBUG_ERROR,\r
305 "%a: InstallMultipleProtocolInterfaces: %r\n",\r
306 __FUNCTION__,\r
307 Status\r
308 ));\r
c40e1a0c
LE
309 goto ReleaseEvent;\r
310 }\r
311\r
312 return EFI_SUCCESS;\r
313\r
314ReleaseEvent:\r
315 if (mS3SaveStateInstalled != NULL) {\r
316 gBS->CloseEvent (mS3SaveStateInstalled);\r
317 }\r
318\r
319FatalError:\r
320 //\r
321 // We really don't want to continue in this case.\r
322 //\r
323 ASSERT (FALSE);\r
324 CpuDeadLoop ();\r
325 return EFI_UNSUPPORTED;\r
326}\r
327\r
328/**\r
329 Notification callback for S3SaveState installation.\r
330\r
331 @param[in] Event Event whose notification function is being invoked.\r
332\r
333 @param[in] Context The pointer to the notification function's context, which\r
334 is implementation-dependent.\r
335**/\r
336STATIC\r
337VOID\r
338EFIAPI\r
339OnS3SaveStateInstalled (\r
ac0a286f
MK
340 IN EFI_EVENT Event,\r
341 IN VOID *Context\r
c40e1a0c
LE
342 )\r
343{\r
ac0a286f
MK
344 EFI_STATUS Status;\r
345 EFI_S3_SAVE_STATE_PROTOCOL *S3SaveState;\r
346 UINT32 SmiEnOrMask, SmiEnAndMask;\r
347 UINT64 GenPmCon1Address;\r
348 UINT16 GenPmCon1OrMask, GenPmCon1AndMask;\r
c40e1a0c
LE
349\r
350 ASSERT (Event == mS3SaveStateInstalled);\r
351\r
ac0a286f
MK
352 Status = gBS->LocateProtocol (\r
353 &gEfiS3SaveStateProtocolGuid,\r
354 NULL /* Registration */,\r
355 (VOID **)&S3SaveState\r
356 );\r
c40e1a0c
LE
357 if (EFI_ERROR (Status)) {\r
358 return;\r
359 }\r
360\r
361 //\r
362 // These operations were originally done, verified and explained in the entry\r
363 // point function of the driver.\r
364 //\r
365 SmiEnOrMask = ICH9_SMI_EN_APMC_EN | ICH9_SMI_EN_GBL_SMI_EN;\r
366 SmiEnAndMask = MAX_UINT32;\r
ac0a286f
MK
367 Status = S3SaveState->Write (\r
368 S3SaveState,\r
369 EFI_BOOT_SCRIPT_IO_READ_WRITE_OPCODE,\r
370 EfiBootScriptWidthUint32,\r
371 (UINT64)mSmiEnable,\r
372 &SmiEnOrMask,\r
373 &SmiEnAndMask\r
374 );\r
c40e1a0c 375 if (EFI_ERROR (Status)) {\r
ac0a286f
MK
376 DEBUG ((\r
377 DEBUG_ERROR,\r
378 "%a: EFI_BOOT_SCRIPT_IO_READ_WRITE_OPCODE: %r\n",\r
379 __FUNCTION__,\r
380 Status\r
381 ));\r
c40e1a0c
LE
382 ASSERT (FALSE);\r
383 CpuDeadLoop ();\r
384 }\r
385\r
7ecfa0aa 386 GenPmCon1Address = POWER_MGMT_REGISTER_Q35_EFI_PCI_ADDRESS (\r
ac0a286f
MK
387 ICH9_GEN_PMCON_1\r
388 );\r
c40e1a0c
LE
389 GenPmCon1OrMask = ICH9_GEN_PMCON_1_SMI_LOCK;\r
390 GenPmCon1AndMask = MAX_UINT16;\r
ac0a286f
MK
391 Status = S3SaveState->Write (\r
392 S3SaveState,\r
393 EFI_BOOT_SCRIPT_PCI_CONFIG_READ_WRITE_OPCODE,\r
394 EfiBootScriptWidthUint16,\r
395 GenPmCon1Address,\r
396 &GenPmCon1OrMask,\r
397 &GenPmCon1AndMask\r
398 );\r
c40e1a0c 399 if (EFI_ERROR (Status)) {\r
ac0a286f
MK
400 DEBUG ((\r
401 DEBUG_ERROR,\r
402 "%a: EFI_BOOT_SCRIPT_PCI_CONFIG_READ_WRITE_OPCODE: %r\n",\r
403 __FUNCTION__,\r
404 Status\r
405 ));\r
c40e1a0c
LE
406 ASSERT (FALSE);\r
407 CpuDeadLoop ();\r
408 }\r
409\r
36a6aa6c
LE
410 DEBUG ((DEBUG_VERBOSE, "%a: chipset boot script saved\n", __FUNCTION__));\r
411\r
a316d7ac
LE
412 //\r
413 // Append a boot script fragment that re-selects the negotiated SMI features.\r
414 //\r
415 if (mSmiFeatureNegotiation) {\r
36a6aa6c 416 SaveSmiFeatures ();\r
a316d7ac
LE
417 }\r
418\r
c40e1a0c
LE
419 gBS->CloseEvent (Event);\r
420 mS3SaveStateInstalled = NULL;\r
421}\r