summaryrefslogtreecommitdiffstats
path: root/OvmfPkg/CpuHotplugSmm/Smbase.c
blob: dc6f4f4b49949194d2b6383947f06ec423505c21 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
/** @file
  SMBASE relocation for hot-plugged CPUs.

  Copyright (c) 2020, Red Hat, Inc.

  SPDX-License-Identifier: BSD-2-Clause-Patent
**/

#include <Base.h>                             // BASE_1MB
#include <Library/BaseLib.h>                  // CpuPause()
#include <Library/BaseMemoryLib.h>            // CopyMem()
#include <Library/DebugLib.h>                 // DEBUG()
#include <Library/LocalApicLib.h>             // SendInitSipiSipi()
#include <Library/SynchronizationLib.h>       // InterlockedCompareExchange64()
#include <Register/Intel/SmramSaveStateMap.h> // SMM_DEFAULT_SMBASE

#include "FirstSmiHandlerContext.h"           // FIRST_SMI_HANDLER_CONTEXT

#include "Smbase.h"

extern CONST UINT8   mPostSmmPen[];
extern CONST UINT16  mPostSmmPenSize;
extern CONST UINT8   mFirstSmiHandler[];
extern CONST UINT16  mFirstSmiHandlerSize;

/**
  Allocate a non-SMRAM reserved memory page for the Post-SMM Pen for hot-added
  CPUs.

  This function may only be called from the entry point function of the driver.

  @param[out] PenAddress   The address of the allocated (normal RAM) reserved
                           page.

  @param[in] BootServices  Pointer to the UEFI boot services table. Used for
                           allocating the normal RAM (not SMRAM) reserved page.

  @retval EFI_SUCCESS          Allocation successful.

  @retval EFI_BAD_BUFFER_SIZE  The Post-SMM Pen template is not smaller than
                               EFI_PAGE_SIZE.

  @return                      Error codes propagated from underlying services.
                               DEBUG_ERROR messages have been logged. No
                               resources have been allocated.
**/
EFI_STATUS
SmbaseAllocatePostSmmPen (
  OUT UINT32                   *PenAddress,
  IN  CONST EFI_BOOT_SERVICES  *BootServices
  )
{
  EFI_STATUS            Status;
  EFI_PHYSICAL_ADDRESS  Address;

  //
  // The pen code must fit in one page, and the last byte must remain free for
  // signaling the SMM Monarch.
  //
  if (mPostSmmPenSize >= EFI_PAGE_SIZE) {
    Status = EFI_BAD_BUFFER_SIZE;
    DEBUG ((
      DEBUG_ERROR,
      "%a: mPostSmmPenSize=%u: %r\n",
      __func__,
      mPostSmmPenSize,
      Status
      ));
    return Status;
  }

  Address = BASE_1MB - 1;
  Status  = BootServices->AllocatePages (
                            AllocateMaxAddress,
                            EfiReservedMemoryType,
                            1,
                            &Address
                            );
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "%a: AllocatePages(): %r\n", __func__, Status));
    return Status;
  }

  DEBUG ((DEBUG_INFO, "%a: Post-SMM Pen at 0x%Lx\n", __func__, Address));
  *PenAddress = (UINT32)Address;
  return EFI_SUCCESS;
}

/**
  Copy the Post-SMM Pen template code into the reserved page allocated with
  SmbaseAllocatePostSmmPen().

  Note that this effects an "SMRAM to normal RAM" copy.

  The SMM Monarch is supposed to call this function from the root MMI handler.

  @param[in] PenAddress  The allocation address returned by
                         SmbaseAllocatePostSmmPen().
**/
VOID
SmbaseReinstallPostSmmPen (
  IN UINT32  PenAddress
  )
{
  CopyMem ((VOID *)(UINTN)PenAddress, mPostSmmPen, mPostSmmPenSize);
}

/**
  Release the reserved page allocated with SmbaseAllocatePostSmmPen().

  This function may only be called from the entry point function of the driver,
  on the error path.

  @param[in] PenAddress    The allocation address returned by
                           SmbaseAllocatePostSmmPen().

  @param[in] BootServices  Pointer to the UEFI boot services table. Used for
                           releasing the normal RAM (not SMRAM) reserved page.
**/
VOID
SmbaseReleasePostSmmPen (
  IN UINT32                   PenAddress,
  IN CONST EFI_BOOT_SERVICES  *BootServices
  )
{
  BootServices->FreePages (PenAddress, 1);
}

/**
  Place the handler routine for the first SMIs of hot-added CPUs at
  (SMM_DEFAULT_SMBASE + SMM_HANDLER_OFFSET).

  Note that this effects an "SMRAM to SMRAM" copy.

  Additionally, shut the APIC ID gate in FIRST_SMI_HANDLER_CONTEXT.

  This function may only be called from the entry point function of the driver,
  and only after PcdQ35SmramAtDefaultSmbase has been determined to be TRUE.
**/
VOID
SmbaseInstallFirstSmiHandler (
  VOID
  )
{
  FIRST_SMI_HANDLER_CONTEXT  *Context;

  CopyMem (
    (VOID *)(UINTN)(SMM_DEFAULT_SMBASE + SMM_HANDLER_OFFSET),
    mFirstSmiHandler,
    mFirstSmiHandlerSize
    );

  Context             = (VOID *)(UINTN)SMM_DEFAULT_SMBASE;
  Context->ApicIdGate = MAX_UINT64;
}

/**
  Relocate the SMBASE on a hot-added CPU. Then pen the hot-added CPU in the
  normal RAM reserved memory page, set up earlier with
  SmbaseAllocatePostSmmPen() and SmbaseReinstallPostSmmPen().

  The SMM Monarch is supposed to call this function from the root MMI handler.

  The SMM Monarch is responsible for calling SmbaseInstallFirstSmiHandler(),
  SmbaseAllocatePostSmmPen(), and SmbaseReinstallPostSmmPen() before calling
  this function.

  If the OS maliciously boots the hot-added CPU ahead of letting the ACPI CPU
  hotplug event handler broadcast the CPU hotplug MMI, then the hot-added CPU
  returns to the OS rather than to the pen, upon RSM. In that case, this
  function will hang forever (unless the OS happens to signal back through the
  last byte of the pen page).

  @param[in] ApicId      The APIC ID of the hot-added CPU whose SMBASE should
                         be relocated.

  @param[in] Smbase      The new SMBASE address. The root MMI handler is
                         responsible for passing in a free ("unoccupied")
                         SMBASE address that was pre-configured by
                         PiSmmCpuDxeSmm in CPU_HOT_PLUG_DATA.

  @param[in] PenAddress  The address of the Post-SMM Pen for hot-added CPUs, as
                         returned by SmbaseAllocatePostSmmPen(), and installed
                         by SmbaseReinstallPostSmmPen().

  @retval EFI_SUCCESS            The SMBASE of the hot-added CPU with APIC ID
                                 ApicId has been relocated to Smbase. The
                                 hot-added CPU has reported back about leaving
                                 SMM.

  @retval EFI_PROTOCOL_ERROR     Synchronization bug encountered around
                                 FIRST_SMI_HANDLER_CONTEXT.ApicIdGate.

  @retval EFI_INVALID_PARAMETER  Smbase does not fit in 32 bits. No relocation
                                 has been attempted.
**/
EFI_STATUS
SmbaseRelocate (
  IN APIC_ID  ApicId,
  IN UINTN    Smbase,
  IN UINT32   PenAddress
  )
{
  EFI_STATUS                          Status;
  volatile UINT8                      *SmmVacated;
  volatile FIRST_SMI_HANDLER_CONTEXT  *Context;
  UINT64                              ExchangeResult;

  if (Smbase > MAX_UINT32) {
    Status = EFI_INVALID_PARAMETER;
    DEBUG ((
      DEBUG_ERROR,
      "%a: ApicId=" FMT_APIC_ID " Smbase=0x%Lx: %r\n",
      __func__,
      ApicId,
      (UINT64)Smbase,
      Status
      ));
    return Status;
  }

  SmmVacated = (UINT8 *)(UINTN)PenAddress + (EFI_PAGE_SIZE - 1);
  Context    = (VOID *)(UINTN)SMM_DEFAULT_SMBASE;

  //
  // Clear AboutToLeaveSmm, so we notice when the hot-added CPU is just about
  // to reach RSM, and we can proceed to polling the last byte of the reserved
  // page (which could be attacked by the OS).
  //
  Context->AboutToLeaveSmm = 0;

  //
  // Clear the last byte of the reserved page, so we notice when the hot-added
  // CPU checks back in from the pen.
  //
  *SmmVacated = 0;

  //
  // Boot the hot-added CPU.
  //
  // There are 2*2 cases to consider:
  //
  // (1) The CPU was hot-added before the SMI was broadcast.
  //
  // (1.1) The OS is benign.
  //
  //       The hot-added CPU is in RESET state, with the broadcast SMI pending
  //       for it. The directed SMI below will be ignored (it's idempotent),
  //       and the INIT-SIPI-SIPI will launch the CPU directly into SMM.
  //
  // (1.2) The OS is malicious.
  //
  //       The hot-added CPU has been booted, by the OS. Thus, the hot-added
  //       CPU is spinning on the APIC ID gate. In that case, both the SMI and
  //       the INIT-SIPI-SIPI below will be ignored.
  //
  // (2) The CPU was hot-added after the SMI was broadcast.
  //
  // (2.1) The OS is benign.
  //
  //       The hot-added CPU is in RESET state, with no SMI pending for it. The
  //       directed SMI will latch the SMI for the CPU. Then the INIT-SIPI-SIPI
  //       will launch the CPU into SMM.
  //
  // (2.2) The OS is malicious.
  //
  //       The hot-added CPU is executing OS code. The directed SMI will pull
  //       the hot-added CPU into SMM, where it will start spinning on the APIC
  //       ID gate. The INIT-SIPI-SIPI will be ignored.
  //
  SendSmiIpi (ApicId);
  SendInitSipiSipi (ApicId, PenAddress);

  //
  // Expose the desired new SMBASE value to the hot-added CPU.
  //
  Context->NewSmbase = (UINT32)Smbase;

  //
  // Un-gate SMBASE relocation for the hot-added CPU whose APIC ID is ApicId.
  //
  ExchangeResult = InterlockedCompareExchange64 (
                     &Context->ApicIdGate,
                     MAX_UINT64,
                     ApicId
                     );
  if (ExchangeResult != MAX_UINT64) {
    Status = EFI_PROTOCOL_ERROR;
    DEBUG ((
      DEBUG_ERROR,
      "%a: ApicId=" FMT_APIC_ID " ApicIdGate=0x%Lx: %r\n",
      __func__,
      ApicId,
      ExchangeResult,
      Status
      ));
    return Status;
  }

  //
  // Wait until the hot-added CPU is just about to execute RSM.
  //
  while (Context->AboutToLeaveSmm == 0) {
    CpuPause ();
  }

  //
  // Now wait until the hot-added CPU reports back from the pen (or the OS
  // attacks the last byte of the reserved page).
  //
  while (*SmmVacated == 0) {
    CpuPause ();
  }

  Status = EFI_SUCCESS;
  return Status;
}