1 /** @file
2   This is the driver that publishes the SMM Control Protocol.
3 
4   Copyright (c) 2021, Intel Corporation. All rights reserved.<BR>
5   SPDX-License-Identifier: BSD-2-Clause-Patent
6 **/
7 #include <Library/IoLib.h>
8 #include <Library/DebugLib.h>
9 #include <Library/UefiDriverEntryPoint.h>
10 #include <Library/UefiBootServicesTableLib.h>
11 #include <Library/UefiRuntimeServicesTableLib.h>
12 #include <Guid/EventGroup.h>
13 #include <Library/PmcLib.h>
14 #include <Library/GpioLib.h>
15 #include <IndustryStandard/Pci30.h>
16 #include <Register/PchRegsLpc.h>
17 #include <Register/SpiRegs.h>
18 #include <Register/PmcRegs.h>
19 #include "SmmControlDriver.h"
20 
21 STATIC SMM_CONTROL_PRIVATE_DATA mSmmControl;
22 GLOBAL_REMOVE_IF_UNREFERENCED UINT16                          mABase;
23 
24 VOID
25 EFIAPI
26 DisablePendingSmis (
27   VOID
28   );
29 
30 /**
31   Fixup internal data pointers so that the services can be called in virtual mode.
32 
33   @param[in] Event                The event registered.
34   @param[in] Context              Event context.
35 
36 **/
37 VOID
38 EFIAPI
SmmControlVirtualAddressChangeEvent(IN EFI_EVENT Event,IN VOID * Context)39 SmmControlVirtualAddressChangeEvent (
40   IN EFI_EVENT                  Event,
41   IN VOID                       *Context
42   )
43 {
44   gRT->ConvertPointer (0, (VOID *) &(mSmmControl.SmmControl.Trigger));
45   gRT->ConvertPointer (0, (VOID *) &(mSmmControl.SmmControl.Clear));
46 }
47 
48 /**
49   <b>SmmControl DXE RUNTIME Module Entry Point</b>\n
50   - <b>Introduction</b>\n
51     The SmmControl module is a DXE RUNTIME driver that provides a standard way
52     for other drivers to trigger software SMIs.
53 
54   - @pre
55     - PCH Power Management I/O space base address has already been programmed.
56       If SmmControl Runtime DXE driver is run before Status Code Runtime Protocol
57       is installed and there is the need to use Status code in the driver, it will
58       be necessary to add EFI_STATUS_CODE_RUNTIME_PROTOCOL_GUID to the dependency file.
59     - EFI_SMM_BASE2_PROTOCOL
60       - Documented in the System Management Mode Core Interface Specification.
61 
62   - @result
63     The SmmControl driver produces the EFI_SMM_CONTROL_PROTOCOL documented in
64     System Management Mode Core Interface Specification.
65 
66   @param[in] ImageHandle          Handle for the image of this driver
67   @param[in] SystemTable          Pointer to the EFI System Table
68 
69   @retval EFI_STATUS              Results of the installation of the SMM Control Protocol
70 **/
71 EFI_STATUS
72 EFIAPI
SmmControlDriverEntryInit(IN EFI_HANDLE ImageHandle,IN EFI_SYSTEM_TABLE * SystemTable)73 SmmControlDriverEntryInit (
74   IN EFI_HANDLE         ImageHandle,
75   IN EFI_SYSTEM_TABLE   *SystemTable
76   )
77 {
78   EFI_STATUS  Status;
79   EFI_EVENT   Event;
80 
81   DEBUG ((DEBUG_INFO, "SmmControlDriverEntryInit() Start\n"));
82 
83   //
84   // Get the Power Management I/O space base address. We assume that
85   // this base address has already been programmed if this driver is
86   // being run.
87   //
88   mABase = PmcGetAcpiBase ();
89 
90   Status = EFI_SUCCESS;
91   if (mABase != 0) {
92     //
93     // Install the instance of the protocol
94     //
95     mSmmControl.Signature                       = SMM_CONTROL_PRIVATE_DATA_SIGNATURE;
96     mSmmControl.Handle                          = ImageHandle;
97 
98     mSmmControl.SmmControl.Trigger              = Activate;
99     mSmmControl.SmmControl.Clear                = Deactivate;
100     mSmmControl.SmmControl.MinimumTriggerPeriod = 0;
101 
102     //
103     // Install our protocol interfaces on the device's handle
104     //
105     Status = gBS->InstallMultipleProtocolInterfaces (
106                     &mSmmControl.Handle,
107                     &gEfiSmmControl2ProtocolGuid,
108                     &mSmmControl.SmmControl,
109                     NULL
110                     );
111   } else {
112     Status = EFI_DEVICE_ERROR;
113     return Status;
114   }
115 
116   Status = gBS->CreateEventEx (
117                   EVT_NOTIFY_SIGNAL,
118                   TPL_NOTIFY,
119                   SmmControlVirtualAddressChangeEvent,
120                   NULL,
121                   &gEfiEventVirtualAddressChangeGuid,
122                   &Event
123                   );
124   //
125   // Disable any PCH SMIs that, for whatever reason, are asserted after the boot.
126   //
127   DisablePendingSmis ();
128 
129   DEBUG ((DEBUG_INFO, "SmmControlDriverEntryInit() End\n"));
130 
131   return Status;
132 }
133 
134 /**
135   Trigger the software SMI
136 
137   @param[in] Data                 The value to be set on the software SMI data port
138 
139   @retval EFI_SUCCESS             Function completes successfully
140 **/
141 EFI_STATUS
142 EFIAPI
SmmTrigger(IN UINT8 Data)143 SmmTrigger (
144   IN UINT8   Data
145   )
146 {
147   UINT32  OutputData;
148   UINT32  OutputPort;
149 
150   //
151   // Enable the APMC SMI
152   //
153   OutputPort  = mABase + R_ACPI_IO_SMI_EN;
154   OutputData  = IoRead32 ((UINTN) OutputPort);
155   OutputData |= (B_ACPI_IO_SMI_EN_APMC | B_ACPI_IO_SMI_EN_GBL_SMI);
156   DEBUG (
157     (DEBUG_VERBOSE,
158      "The SMI Control Port at address %x will be written to %x.\n",
159      OutputPort,
160      OutputData)
161     );
162   IoWrite32 (
163     (UINTN) OutputPort,
164     (UINT32) (OutputData)
165     );
166 
167   OutputPort  = R_PCH_IO_APM_CNT;
168   OutputData  = Data;
169 
170   //
171   // Generate the APMC SMI
172   //
173   IoWrite8 (
174     (UINTN) OutputPort,
175     (UINT8) (OutputData)
176     );
177 
178   return EFI_SUCCESS;
179 }
180 
181 /**
182   Clear the SMI status
183 
184 
185   @retval EFI_SUCCESS             The function completes successfully
186   @retval EFI_DEVICE_ERROR        Something error occurred
187 **/
188 EFI_STATUS
189 EFIAPI
SmmClear(VOID)190 SmmClear (
191   VOID
192   )
193 {
194   EFI_STATUS  Status;
195   UINT32      OutputData;
196   UINT32      OutputPort;
197 
198   Status = EFI_SUCCESS;
199 
200   //
201   // Clear the Power Button Override Status Bit, it gates EOS from being set.
202   //
203   OutputPort  = mABase + R_ACPI_IO_PM1_STS;
204   OutputData  = B_ACPI_IO_PM1_STS_PRBTNOR;
205   DEBUG (
206     (DEBUG_VERBOSE,
207      "The PM1 Status Port at address %x will be written to %x.\n",
208      OutputPort,
209      OutputData)
210     );
211   IoWrite16 (
212     (UINTN) OutputPort,
213     (UINT16) (OutputData)
214     );
215 
216   //
217   // Clear the APM SMI Status Bit
218   //
219   OutputPort  = mABase + R_ACPI_IO_SMI_STS;
220   OutputData  = B_ACPI_IO_SMI_STS_APM;
221   DEBUG (
222     (DEBUG_VERBOSE,
223      "The SMI Status Port at address %x will be written to %x.\n",
224      OutputPort,
225      OutputData)
226     );
227   IoWrite32 (
228     (UINTN) OutputPort,
229     (UINT32) (OutputData)
230     );
231 
232   //
233   // Set the EOS Bit
234   //
235   OutputPort  = mABase + R_ACPI_IO_SMI_EN;
236   OutputData  = IoRead32 ((UINTN) OutputPort);
237   OutputData |= B_ACPI_IO_SMI_EN_EOS;
238   DEBUG (
239     (DEBUG_VERBOSE,
240      "The SMI Control Port at address %x will be written to %x.\n",
241      OutputPort,
242      OutputData)
243     );
244   IoWrite32 (
245     (UINTN) OutputPort,
246     (UINT32) (OutputData)
247     );
248 
249   //
250   // There is no need to read EOS back and check if it is set.
251   // This can lead to a reading of zero if an SMI occurs right after the SMI_EN port read
252   // but before the data is returned to the CPU.
253   // SMM Dispatcher should make sure that EOS is set after all SMI sources are processed.
254   //
255   return Status;
256 }
257 
258 /**
259   This routine generates an SMI
260 
261   @param[in] This                       The EFI SMM Control protocol instance
262   @param[in, out] CommandPort           The buffer contains data to the command port
263   @param[in, out] DataPort              The buffer contains data to the data port
264   @param[in] Periodic                   Periodic or not
265   @param[in] ActivationInterval         Interval of periodic SMI
266 
267   @retval EFI Status                    Describing the result of the operation
268   @retval EFI_INVALID_PARAMETER         Some parameter value passed is not supported
269 **/
270 EFI_STATUS
271 EFIAPI
Activate(IN CONST EFI_SMM_CONTROL2_PROTOCOL * This,IN OUT UINT8 * CommandPort OPTIONAL,IN OUT UINT8 * DataPort OPTIONAL,IN BOOLEAN Periodic OPTIONAL,IN UINTN ActivationInterval OPTIONAL)272 Activate (
273   IN CONST EFI_SMM_CONTROL2_PROTOCOL                    * This,
274   IN OUT  UINT8                                         *CommandPort       OPTIONAL,
275   IN OUT  UINT8                                         *DataPort          OPTIONAL,
276   IN      BOOLEAN                                       Periodic           OPTIONAL,
277   IN      UINTN                                         ActivationInterval OPTIONAL
278   )
279 {
280   EFI_STATUS  Status;
281   UINT8       Data;
282 
283   if (Periodic) {
284     DEBUG ((DEBUG_WARN, "Invalid parameter\n"));
285     return EFI_INVALID_PARAMETER;
286   }
287 
288   if (CommandPort == NULL) {
289     Data = 0xFF;
290   } else {
291     Data = *CommandPort;
292   }
293   //
294   // Clear any pending the APM SMI
295   //
296   Status = SmmClear ();
297   if (EFI_ERROR (Status)) {
298     return Status;
299   }
300 
301   return SmmTrigger (Data);
302 }
303 
304 /**
305   This routine clears an SMI
306 
307   @param[in] This                 The EFI SMM Control protocol instance
308   @param[in] Periodic             Periodic or not
309 
310   @retval EFI Status              Describing the result of the operation
311   @retval EFI_INVALID_PARAMETER   Some parameter value passed is not supported
312 **/
313 EFI_STATUS
314 EFIAPI
Deactivate(IN CONST EFI_SMM_CONTROL2_PROTOCOL * This,IN BOOLEAN Periodic OPTIONAL)315 Deactivate (
316   IN CONST EFI_SMM_CONTROL2_PROTOCOL       *This,
317   IN  BOOLEAN                              Periodic OPTIONAL
318   )
319 {
320   if (Periodic) {
321     return EFI_INVALID_PARAMETER;
322   }
323 
324   return SmmClear ();
325 }
326 /**
327   Disable all pending SMIs
328 
329 **/
330 VOID
331 EFIAPI
DisablePendingSmis(VOID)332 DisablePendingSmis (
333   VOID
334   )
335 {
336   UINT32               Data;
337   BOOLEAN              SciEn;
338 
339   //
340   // Determine whether an ACPI OS is present (via the SCI_EN bit)
341   //
342   Data      = IoRead16 ((UINTN) mABase + R_ACPI_IO_PM1_CNT);
343   SciEn     = (BOOLEAN) ((Data & B_ACPI_IO_PM1_CNT_SCI_EN) == B_ACPI_IO_PM1_CNT_SCI_EN);
344 
345   if (!SciEn) {
346     //
347     // Clear any SMIs that double as SCIs (when SCI_EN==0)
348     //
349     IoWrite16 ((UINTN) mABase + R_ACPI_IO_PM1_STS, 0xFFFF);
350     IoWrite16 ((UINTN) mABase + R_ACPI_IO_PM1_EN, 0);
351     IoWrite16 ((UINTN) mABase + R_ACPI_IO_PM1_CNT, 0);
352     IoWrite32 (
353       (UINTN) mABase + R_ACPI_IO_GPE0_STS_127_96,
354       (UINT32)( B_ACPI_IO_GPE0_STS_127_96_WADT |
355                 B_ACPI_IO_GPE0_STS_127_96_USB_CON_DSX_STS |
356                 B_ACPI_IO_GPE0_STS_127_96_LAN_WAKE |
357                 B_ACPI_IO_GPE0_STS_127_96_PME_B0 |
358                 B_ACPI_IO_GPE0_STS_127_96_PME |
359                 B_ACPI_IO_GPE0_STS_127_96_BATLOW |
360                 B_ACPI_IO_GPE0_STS_127_96_RI |
361                 B_ACPI_IO_GPE0_STS_127_96_SWGPE)
362       );
363     IoWrite32 ((UINTN) mABase + R_ACPI_IO_GPE0_EN_127_96, (UINT32) B_ACPI_IO_GPE0_EN_127_96_WADT);
364   }
365   //
366   // Clear and disable all SMIs that are unaffected by SCI_EN
367   //
368   GpioDisableAllGpiSmi ();
369 
370   GpioClearAllGpiSmiSts ();
371 
372   IoWrite32 ((UINTN) mABase + R_ACPI_IO_DEVACT_STS, 0x0000FFFF);
373 
374   IoWrite32 ((UINTN) mABase + R_ACPI_IO_SMI_STS, ~0u);
375 
376   //
377   // (Make sure to write this register last -- EOS re-enables SMIs for the PCH)
378   //
379   Data  = IoRead32 ((UINTN) mABase + R_ACPI_IO_SMI_EN);
380   //
381   // clear all bits except those tied to SCI_EN
382   //
383   Data &= B_ACPI_IO_SMI_EN_BIOS_RLS;
384   //
385   // enable SMIs and specifically enable writes to APM_CNT.
386   //
387   Data |= B_ACPI_IO_SMI_EN_GBL_SMI | B_ACPI_IO_SMI_EN_APMC;
388   //
389   //  NOTE: Default value of EOS is set in PCH, it will be automatically cleared Once the PCH asserts SMI# low,
390   //  we don't need to do anything to clear it
391   //
392   IoWrite32 ((UINTN) mABase + R_ACPI_IO_SMI_EN, Data);
393 }
394 
395