1/** @file
2X64 processor specific functions to enable SMM profile.
3
4Copyright (c) 2012 - 2016, Intel Corporation. All rights reserved.<BR>
5This program and the accompanying materials
6are licensed and made available under the terms and conditions of the BSD License
7which accompanies this distribution.  The full text of the license may be found at
8http://opensource.org/licenses/bsd-license.php
9
10THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
11WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
12
13**/
14
15#include "PiSmmCpuDxeSmm.h"
16#include "SmmProfileInternal.h"
17
18//
19// Current page index.
20//
21UINTN                     mPFPageIndex;
22
23//
24// Pool for dynamically creating page table in page fault handler.
25//
26UINT64                    mPFPageBuffer;
27
28//
29// Store the uplink information for each page being used.
30//
31UINT64                    *mPFPageUplink[MAX_PF_PAGE_COUNT];
32
33/**
34  Create SMM page table for S3 path.
35
36**/
37VOID
38InitSmmS3Cr3 (
39  VOID
40  )
41{
42  EFI_PHYSICAL_ADDRESS              Pages;
43  UINT64                            *PTEntry;
44
45  //
46  // Generate PAE page table for the first 4GB memory space
47  //
48  Pages = Gen4GPageTable (FALSE);
49
50  //
51  // Fill Page-Table-Level4 (PML4) entry
52  //
53  PTEntry = (UINT64*)AllocatePageTableMemory (1);
54  ASSERT (PTEntry != NULL);
55  *PTEntry = Pages | PAGE_ATTRIBUTE_BITS;
56  ZeroMem (PTEntry + 1, EFI_PAGE_SIZE - sizeof (*PTEntry));
57
58  //
59  // Return the address of PML4 (to set CR3)
60  //
61  mSmmS3ResumeState->SmmS3Cr3 = (UINT32)(UINTN)PTEntry;
62
63  return ;
64}
65
66/**
67  Allocate pages for creating 4KB-page based on 2MB-page when page fault happens.
68
69**/
70VOID
71InitPagesForPFHandler (
72  VOID
73  )
74{
75  VOID          *Address;
76
77  //
78  // Pre-Allocate memory for page fault handler
79  //
80  Address = NULL;
81  Address = AllocatePages (MAX_PF_PAGE_COUNT);
82  ASSERT (Address != NULL);
83
84  mPFPageBuffer =  (UINT64)(UINTN) Address;
85  mPFPageIndex = 0;
86  ZeroMem ((VOID *) (UINTN) mPFPageBuffer, EFI_PAGE_SIZE * MAX_PF_PAGE_COUNT);
87  ZeroMem (mPFPageUplink, sizeof (mPFPageUplink));
88
89  return;
90}
91
92/**
93  Allocate one page for creating 4KB-page based on 2MB-page.
94
95  @param  Uplink   The address of Page-Directory entry.
96
97**/
98VOID
99AcquirePage (
100  UINT64          *Uplink
101  )
102{
103  UINT64          Address;
104
105  //
106  // Get the buffer
107  //
108  Address = mPFPageBuffer + EFI_PAGES_TO_SIZE (mPFPageIndex);
109  ZeroMem ((VOID *) (UINTN) Address, EFI_PAGE_SIZE);
110
111  //
112  // Cut the previous uplink if it exists and wasn't overwritten
113  //
114  if ((mPFPageUplink[mPFPageIndex] != NULL) && ((*mPFPageUplink[mPFPageIndex] & PHYSICAL_ADDRESS_MASK) == Address)) {
115    *mPFPageUplink[mPFPageIndex] = 0;
116  }
117
118  //
119  // Link & Record the current uplink
120  //
121  *Uplink = Address | PAGE_ATTRIBUTE_BITS;
122  mPFPageUplink[mPFPageIndex] = Uplink;
123
124  mPFPageIndex = (mPFPageIndex + 1) % MAX_PF_PAGE_COUNT;
125}
126
127/**
128  Update page table to map the memory correctly in order to make the instruction
129  which caused page fault execute successfully. And it also save the original page
130  table to be restored in single-step exception.
131
132  @param  PageTable           PageTable Address.
133  @param  PFAddress           The memory address which caused page fault exception.
134  @param  CpuIndex            The index of the processor.
135  @param  ErrorCode           The Error code of exception.
136  @param  IsValidPFAddress    The flag indicates if SMM profile data need be added.
137
138**/
139VOID
140RestorePageTableAbove4G (
141  UINT64        *PageTable,
142  UINT64        PFAddress,
143  UINTN         CpuIndex,
144  UINTN         ErrorCode,
145  BOOLEAN       *IsValidPFAddress
146  )
147{
148  UINTN         PTIndex;
149  UINT64        Address;
150  BOOLEAN       Nx;
151  BOOLEAN       Existed;
152  UINTN         Index;
153  UINTN         PFIndex;
154
155  ASSERT ((PageTable != NULL) && (IsValidPFAddress != NULL));
156
157  //
158  // If page fault address is 4GB above.
159  //
160
161  //
162  // Check if page fault address has existed in page table.
163  // If it exists in page table but page fault is generated,
164  // there are 2 possible reasons: 1. present flag is set to 0; 2. instruction fetch in protected memory range.
165  //
166  Existed = FALSE;
167  PageTable = (UINT64*)(AsmReadCr3 () & PHYSICAL_ADDRESS_MASK);
168  PTIndex = BitFieldRead64 (PFAddress, 39, 47);
169  if ((PageTable[PTIndex] & IA32_PG_P) != 0) {
170    // PML4E
171    PageTable = (UINT64*)(UINTN)(PageTable[PTIndex] & PHYSICAL_ADDRESS_MASK);
172    PTIndex = BitFieldRead64 (PFAddress, 30, 38);
173    if ((PageTable[PTIndex] & IA32_PG_P) != 0) {
174      // PDPTE
175      PageTable = (UINT64*)(UINTN)(PageTable[PTIndex] & PHYSICAL_ADDRESS_MASK);
176      PTIndex = BitFieldRead64 (PFAddress, 21, 29);
177      // PD
178      if ((PageTable[PTIndex] & IA32_PG_PS) != 0) {
179        //
180        // 2MB page
181        //
182        Address = (UINT64)(PageTable[PTIndex] & PHYSICAL_ADDRESS_MASK);
183        if ((Address & PHYSICAL_ADDRESS_MASK & ~((1ull << 21) - 1)) == ((PFAddress & PHYSICAL_ADDRESS_MASK & ~((1ull << 21) - 1)))) {
184          Existed = TRUE;
185        }
186      } else {
187        //
188        // 4KB page
189        //
190        PageTable = (UINT64*)(UINTN)(PageTable[PTIndex] & PHYSICAL_ADDRESS_MASK);
191        if (PageTable != 0) {
192          //
193          // When there is a valid entry to map to 4KB page, need not create a new entry to map 2MB.
194          //
195          PTIndex = BitFieldRead64 (PFAddress, 12, 20);
196          Address = (UINT64)(PageTable[PTIndex] & PHYSICAL_ADDRESS_MASK);
197          if ((Address & PHYSICAL_ADDRESS_MASK & ~((1ull << 12) - 1)) == (PFAddress & PHYSICAL_ADDRESS_MASK & ~((1ull << 12) - 1))) {
198            Existed = TRUE;
199          }
200        }
201      }
202    }
203  }
204
205  //
206  // If page entry does not existed in page table at all, create a new entry.
207  //
208  if (!Existed) {
209
210    if (IsAddressValid (PFAddress, &Nx)) {
211      //
212      // If page fault address above 4GB is in protected range but it causes a page fault exception,
213      // Will create a page entry for this page fault address, make page table entry as present/rw and execution-disable.
214      // this access is not saved into SMM profile data.
215      //
216      *IsValidPFAddress = TRUE;
217    }
218
219    //
220    // Create one entry in page table for page fault address.
221    //
222    SmiDefaultPFHandler ();
223    //
224    // Find the page table entry created just now.
225    //
226    PageTable = (UINT64*)(AsmReadCr3 () & PHYSICAL_ADDRESS_MASK);
227    PFAddress = AsmReadCr2 ();
228    // PML4E
229    PTIndex = BitFieldRead64 (PFAddress, 39, 47);
230    PageTable = (UINT64*)(UINTN)(PageTable[PTIndex] & PHYSICAL_ADDRESS_MASK);
231    // PDPTE
232    PTIndex = BitFieldRead64 (PFAddress, 30, 38);
233    PageTable = (UINT64*)(UINTN)(PageTable[PTIndex] & PHYSICAL_ADDRESS_MASK);
234    // PD
235    PTIndex = BitFieldRead64 (PFAddress, 21, 29);
236    Address = PageTable[PTIndex] & PHYSICAL_ADDRESS_MASK;
237    //
238    // Check if 2MB-page entry need be changed to 4KB-page entry.
239    //
240    if (IsAddressSplit (Address)) {
241      AcquirePage (&PageTable[PTIndex]);
242
243      // PTE
244      PageTable = (UINT64*)(UINTN)(PageTable[PTIndex] & PHYSICAL_ADDRESS_MASK);
245      for (Index = 0; Index < 512; Index++) {
246        PageTable[Index] = Address | PAGE_ATTRIBUTE_BITS;
247        if (!IsAddressValid (Address, &Nx)) {
248          PageTable[Index] = PageTable[Index] & (INTN)(INT32)(~PAGE_ATTRIBUTE_BITS);
249        }
250        if (Nx && mXdSupported) {
251          PageTable[Index] = PageTable[Index] | IA32_PG_NX;
252        }
253        if (Address == (PFAddress & PHYSICAL_ADDRESS_MASK & ~((1ull << 12) - 1))) {
254          PTIndex = Index;
255        }
256        Address += SIZE_4KB;
257      } // end for PT
258    } else {
259      //
260      // Update 2MB page entry.
261      //
262      if (!IsAddressValid (Address, &Nx)) {
263        //
264        // Patch to remove present flag and rw flag.
265        //
266        PageTable[PTIndex] = PageTable[PTIndex] & (INTN)(INT32)(~PAGE_ATTRIBUTE_BITS);
267      }
268      //
269      // Set XD bit to 1
270      //
271      if (Nx && mXdSupported) {
272        PageTable[PTIndex] = PageTable[PTIndex] | IA32_PG_NX;
273      }
274    }
275  }
276
277  //
278  // Record old entries with non-present status
279  // Old entries include the memory which instruction is at and the memory which instruction access.
280  //
281  //
282  ASSERT (mPFEntryCount[CpuIndex] < MAX_PF_ENTRY_COUNT);
283  if (mPFEntryCount[CpuIndex] < MAX_PF_ENTRY_COUNT) {
284    PFIndex = mPFEntryCount[CpuIndex];
285    mLastPFEntryValue[CpuIndex][PFIndex]   = PageTable[PTIndex];
286    mLastPFEntryPointer[CpuIndex][PFIndex] = &PageTable[PTIndex];
287    mPFEntryCount[CpuIndex]++;
288  }
289
290  //
291  // Add present flag or clear XD flag to make page fault handler succeed.
292  //
293  PageTable[PTIndex] |= (UINT64)(PAGE_ATTRIBUTE_BITS);
294  if ((ErrorCode & IA32_PF_EC_ID) != 0) {
295    //
296    // If page fault is caused by instruction fetch, clear XD bit in the entry.
297    //
298    PageTable[PTIndex] &= ~IA32_PG_NX;
299  }
300
301  return;
302}
303
304/**
305  Clear TF in FLAGS.
306
307  @param  SystemContext    A pointer to the processor context when
308                           the interrupt occurred on the processor.
309
310**/
311VOID
312ClearTrapFlag (
313  IN OUT EFI_SYSTEM_CONTEXT   SystemContext
314  )
315{
316  SystemContext.SystemContextX64->Rflags &= (UINTN) ~BIT8;
317}
318