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