1 /** @file
2   PCH SPI SMM Driver implements the SPI Host Controller Compatibility Interface.
3 
4   Copyright (c) 2021, Intel Corporation. All rights reserved.<BR>
5   SPDX-License-Identifier: BSD-2-Clause-Patent
6 **/
7 
8 #include <Library/IoLib.h>
9 #include <Library/DebugLib.h>
10 #include <Library/UefiDriverEntryPoint.h>
11 #include <Library/UefiBootServicesTableLib.h>
12 #include <Library/BaseLib.h>
13 #include <Library/BaseMemoryLib.h>
14 #include <Library/SmmServicesTableLib.h>
15 #include <Library/PciSegmentLib.h>
16 #include <Protocol/Spi.h>
17 #include <Protocol/SmmCpu.h>
18 #include <Library/SpiCommonLib.h>
19 #include <PchReservedResources.h>
20 #include <Library/SmmPchPrivateLib.h>
21 #include <Library/PchPciBdfLib.h>
22 #include <IndustryStandard/Pci30.h>
23 #include <Register/PchRegs.h>
24 #include <Register/SpiRegs.h>
25 
26 //
27 // Global variables
28 //
29 GLOBAL_REMOVE_IF_UNREFERENCED SPI_INSTANCE          *mSpiInstance;
30 GLOBAL_REMOVE_IF_UNREFERENCED EFI_SMM_CPU_PROTOCOL  *mSmmCpuProtocol;
31 //
32 // mPchSpiResvMmioAddr keeps the reserved MMIO range assiged to SPI.
33 // In SMM it always set back the reserved MMIO address to SPI BAR0 to ensure the MMIO range
34 // won't overlap with SMRAM range, and trusted.
35 //
36 GLOBAL_REMOVE_IF_UNREFERENCED UINT32                mSpiResvMmioAddr;
37 
38 /**
39   <b>SPI Runtime SMM Module Entry Point</b>\n
40   - <b>Introduction</b>\n
41     The SPI SMM module provide a standard way for other modules to use the PCH SPI Interface in SMM.
42 
43   - @pre
44     - EFI_SMM_BASE2_PROTOCOL
45       - Documented in System Management Mode Core Interface Specification .
46 
47   - @result
48     The SPI SMM driver produces @link _PCH_SPI_PROTOCOL PCH_SPI_PROTOCOL @endlink with GUID
49     gPchSmmSpiProtocolGuid which is different from SPI RUNTIME driver.
50 
51   - <b>Integration Check List</b>\n
52     - This driver supports Descriptor Mode only.
53     - This driver supports Hardware Sequence only.
54     - When using SMM SPI Protocol to perform flash access in an SMI handler,
55       and the SMI occurrence is asynchronous to normal mode code execution,
56       proper synchronization mechanism must be applied, e.g. disable SMI before
57       the normal mode SendSpiCmd() starts and re-enable SMI after
58       the normal mode SendSpiCmd() completes.
59       @note The implementation of SendSpiCmd() uses GBL_SMI_EN in
60       SMI_EN register (ABase + 30h) to disable and enable SMIs. But this may
61       not be effective as platform may well set the SMI_LOCK bit (i.e., PMC PCI Offset A0h [4]).
62       So the synchronization at caller level is likely needed.
63 
64   @param[in] ImageHandle          Image handle of this driver.
65   @param[in] SystemTable          Global system service table.
66 
67   @retval EFI_SUCCESS             Initialization complete.
68   @exception EFI_UNSUPPORTED      The chipset is unsupported by this driver.
69   @retval EFI_OUT_OF_RESOURCES    Do not have enough resources to initialize the driver.
70   @retval EFI_DEVICE_ERROR        Device error, driver exits abnormally.
71 **/
72 EFI_STATUS
73 EFIAPI
InstallPchSpi(IN EFI_HANDLE ImageHandle,IN EFI_SYSTEM_TABLE * SystemTable)74 InstallPchSpi (
75   IN EFI_HANDLE            ImageHandle,
76   IN EFI_SYSTEM_TABLE      *SystemTable
77   )
78 {
79   EFI_STATUS  Status;
80 
81   //
82   // Init PCH spi reserved MMIO address.
83   //
84   mSpiResvMmioAddr = PCH_SPI_BASE_ADDRESS;
85 
86   ///
87   /// Allocate pool for SPI protocol instance
88   ///
89   Status = gSmst->SmmAllocatePool (
90                     EfiRuntimeServicesData, /// MemoryType don't care
91                     sizeof (SPI_INSTANCE),
92                     (VOID **) &mSpiInstance
93                     );
94   if (EFI_ERROR (Status)) {
95     return Status;
96   }
97 
98   if (mSpiInstance == NULL) {
99     return EFI_OUT_OF_RESOURCES;
100   }
101 
102   ZeroMem ((VOID *) mSpiInstance, sizeof (SPI_INSTANCE));
103   ///
104   /// Initialize the SPI protocol instance
105   ///
106   Status = SpiProtocolConstructor (mSpiInstance);
107   if (EFI_ERROR (Status)) {
108     return Status;
109   }
110   ///
111   /// Install the SMM PCH_SPI_PROTOCOL interface
112   ///
113   Status = gSmst->SmmInstallProtocolInterface (
114                     &(mSpiInstance->Handle),
115                     &gPchSmmSpiProtocolGuid,
116                     EFI_NATIVE_INTERFACE,
117                     &(mSpiInstance->SpiProtocol)
118                     );
119   if (EFI_ERROR (Status)) {
120     gSmst->SmmFreePool (mSpiInstance);
121     return EFI_DEVICE_ERROR;
122   }
123 
124   return EFI_SUCCESS;
125 }
126 
127 /**
128   Acquire PCH spi mmio address.
129   If it is ever different from the preallocated address, reassign it back.
130   In SMM, it always override the BAR0 and returns the reserved MMIO range for SPI.
131 
132   @param[in] SpiInstance          Pointer to SpiInstance to initialize
133 
134   @retval PchSpiBar0              return SPI MMIO address
135 **/
136 UINTN
AcquireSpiBar0(IN SPI_INSTANCE * SpiInstance)137 AcquireSpiBar0 (
138   IN  SPI_INSTANCE                *SpiInstance
139   )
140 {
141   UINT32                          SpiBar0;
142   //
143   // Save original SPI physical MMIO address
144   //
145   SpiBar0 = PciSegmentRead32 (SpiInstance->PchSpiBase + R_SPI_CFG_BAR0) & ~(B_SPI_CFG_BAR0_MASK);
146 
147   if (SpiBar0 != mSpiResvMmioAddr) {
148     //
149     // Temporary disable MSE, and override with SPI reserved MMIO address, then enable MSE.
150     //
151     PciSegmentAnd8 (SpiInstance->PchSpiBase + PCI_COMMAND_OFFSET, (UINT8) ~EFI_PCI_COMMAND_MEMORY_SPACE);
152     PciSegmentWrite32 (SpiInstance->PchSpiBase + R_SPI_CFG_BAR0, mSpiResvMmioAddr);
153     PciSegmentOr8 (SpiInstance->PchSpiBase + PCI_COMMAND_OFFSET, EFI_PCI_COMMAND_MEMORY_SPACE);
154   }
155   //
156   // SPIBAR0 will be different before and after PCI enum so need to get it from SPI BAR0 reg.
157   //
158   return mSpiResvMmioAddr;
159 }
160 
161 /**
162   Release pch spi mmio address. Do nothing.
163 
164   @param[in] SpiInstance          Pointer to SpiInstance to initialize
165 
166   @retval None
167 **/
168 VOID
ReleaseSpiBar0(IN SPI_INSTANCE * SpiInstance)169 ReleaseSpiBar0 (
170   IN  SPI_INSTANCE                *SpiInstance
171   )
172 {
173 }
174 
175 /**
176   This function is a hook for Spi to disable BIOS Write Protect
177 
178   @retval EFI_SUCCESS             The protocol instance was properly initialized
179   @retval EFI_ACCESS_DENIED       The BIOS Region can only be updated in SMM phase
180 
181 **/
182 EFI_STATUS
183 EFIAPI
DisableBiosWriteProtect(VOID)184 DisableBiosWriteProtect (
185   VOID
186   )
187 {
188   UINT64     SpiBaseAddress;
189 
190   SpiBaseAddress = SpiPciCfgBase ();
191   // Write clear BC_SYNC_SS prior to change WPD from 0 to 1.
192   //
193   PciSegmentOr8 (
194     SpiBaseAddress + R_SPI_CFG_BC + 1,
195     (B_SPI_CFG_BC_SYNC_SS >> 8)
196     );
197   ///
198   /// Set BIOSWE bit (SPI PCI Offset DCh [0]) = 1b
199   /// Enable the access to the BIOS space for both read and write cycles
200   ///
201   PciSegmentOr8 (
202     SpiBaseAddress + R_SPI_CFG_BC,
203     B_SPI_CFG_BC_WPD
204     );
205 
206   ///
207   /// PCH BIOS Spec Section 3.7 BIOS Region SMM Protection Enabling
208   /// If the following steps are implemented:
209   ///  - Set the EISS bit (SPI PCI Offset DCh [5]) = 1b
210   ///  - Follow the 1st recommendation in section 3.6
211   /// the BIOS Region can only be updated by following the steps bellow:
212   ///  - Once all threads enter SMM
213   ///  - Read memory location FED30880h OR with 00000001h, place the result in EAX,
214   ///    and write data to lower 32 bits of MSR 1FEh (sample code available)
215   ///  - Set BIOSWE bit (SPI PCI Offset DCh [0]) = 1b
216   ///  - Modify BIOS Region
217   ///  - Clear BIOSWE bit (SPI PCI Offset DCh [0]) = 0b
218   ///
219   if ((PciSegmentRead8 (SpiBaseAddress + R_SPI_CFG_BC) & B_SPI_CFG_BC_EISS) != 0) {
220     PchSetInSmmSts ();
221   }
222 
223   return EFI_SUCCESS;
224 }
225 
226 /**
227   This function is a hook for Spi to enable BIOS Write Protect
228 **/
229 VOID
230 EFIAPI
EnableBiosWriteProtect(VOID)231 EnableBiosWriteProtect (
232   VOID
233   )
234 {
235   UINT64     SpiBaseAddress;
236 
237   SpiBaseAddress = SpiPciCfgBase ();
238   ///
239   /// Clear BIOSWE bit (SPI PCI Offset DCh [0]) = 0b
240   /// Disable the access to the BIOS space for write cycles
241   ///
242   PciSegmentAnd8 (
243     SpiBaseAddress + R_SPI_CFG_BC,
244     (UINT8) (~B_SPI_CFG_BC_WPD)
245     );
246 
247   ///
248   /// Check if EISS bit is set
249   ///
250   if (((PciSegmentRead8 (SpiBaseAddress + R_SPI_CFG_BC)) & B_SPI_CFG_BC_EISS) == B_SPI_CFG_BC_EISS) {
251     PchClearInSmmSts ();
252   }
253 }
254 
255 /**
256   Check if it's granted to do flash write.
257 
258   @retval TRUE    It's secure to do flash write.
259   @retval FALSE   It's not secure to do flash write.
260 **/
261 BOOLEAN
IsSpiFlashWriteGranted(VOID)262 IsSpiFlashWriteGranted (
263   VOID
264   )
265 {
266   EFI_STATUS    Status;
267   UINT32        CpuIndex;
268   UINT64        ProcessorId;
269 
270   if (mSmmCpuProtocol == NULL) {
271     Status = gSmst->SmmLocateProtocol (&gEfiSmmCpuProtocolGuid, NULL, (VOID **)&mSmmCpuProtocol);
272     ASSERT_EFI_ERROR (Status);
273     if (mSmmCpuProtocol == NULL) {
274       return TRUE;
275     }
276   }
277 
278   for (CpuIndex = 0; CpuIndex < gSmst->NumberOfCpus; CpuIndex++) {
279     Status = mSmmCpuProtocol->ReadSaveState (
280                                 mSmmCpuProtocol,
281                                 sizeof (ProcessorId),
282                                 EFI_SMM_SAVE_STATE_REGISTER_PROCESSOR_ID,
283                                 CpuIndex,
284                                 &ProcessorId
285                                 );
286     //
287     // If the processor is in SMM at the time the SMI occurred,
288     // it will return success. Otherwise, EFI_NOT_FOUND is returned.
289     //
290     if (EFI_ERROR (Status)) {
291       return FALSE;
292     }
293   }
294 
295   return TRUE;
296 }
297