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