]> git.proxmox.com Git - mirror_edk2.git/blob - OvmfPkg/PlatformPei/MemDetect.c
OvmfPkg/PlatformPei: create one memory HOB at S3 resume too, for CpuMpPei
[mirror_edk2.git] / OvmfPkg / PlatformPei / MemDetect.c
1 /**@file
2 Memory Detection for Virtual Machines.
3
4 Copyright (c) 2006 - 2016, Intel Corporation. All rights reserved.<BR>
5 This program and the accompanying materials
6 are licensed and made available under the terms and conditions of the BSD License
7 which accompanies this distribution. The full text of the license may be found at
8 http://opensource.org/licenses/bsd-license.php
9
10 THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
11 WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
12
13 Module Name:
14
15 MemDetect.c
16
17 **/
18
19 //
20 // The package level header files this module uses
21 //
22 #include <PiPei.h>
23
24 //
25 // The Library classes this module consumes
26 //
27 #include <Library/BaseMemoryLib.h>
28 #include <Library/DebugLib.h>
29 #include <Library/HobLib.h>
30 #include <Library/IoLib.h>
31 #include <Library/PcdLib.h>
32 #include <Library/PeimEntryPoint.h>
33 #include <Library/ResourcePublicationLib.h>
34 #include <Library/MtrrLib.h>
35 #include <Library/QemuFwCfgLib.h>
36
37 #include "Platform.h"
38 #include "Cmos.h"
39
40 UINT8 mPhysMemAddressWidth;
41
42 UINT32
43 GetSystemMemorySizeBelow4gb (
44 VOID
45 )
46 {
47 UINT8 Cmos0x34;
48 UINT8 Cmos0x35;
49
50 //
51 // CMOS 0x34/0x35 specifies the system memory above 16 MB.
52 // * CMOS(0x35) is the high byte
53 // * CMOS(0x34) is the low byte
54 // * The size is specified in 64kb chunks
55 // * Since this is memory above 16MB, the 16MB must be added
56 // into the calculation to get the total memory size.
57 //
58
59 Cmos0x34 = (UINT8) CmosRead8 (0x34);
60 Cmos0x35 = (UINT8) CmosRead8 (0x35);
61
62 return (UINT32) (((UINTN)((Cmos0x35 << 8) + Cmos0x34) << 16) + SIZE_16MB);
63 }
64
65
66 STATIC
67 UINT64
68 GetSystemMemorySizeAbove4gb (
69 )
70 {
71 UINT32 Size;
72 UINTN CmosIndex;
73
74 //
75 // CMOS 0x5b-0x5d specifies the system memory above 4GB MB.
76 // * CMOS(0x5d) is the most significant size byte
77 // * CMOS(0x5c) is the middle size byte
78 // * CMOS(0x5b) is the least significant size byte
79 // * The size is specified in 64kb chunks
80 //
81
82 Size = 0;
83 for (CmosIndex = 0x5d; CmosIndex >= 0x5b; CmosIndex--) {
84 Size = (UINT32) (Size << 8) + (UINT32) CmosRead8 (CmosIndex);
85 }
86
87 return LShiftU64 (Size, 16);
88 }
89
90
91 /**
92 Return the highest address that DXE could possibly use, plus one.
93 **/
94 STATIC
95 UINT64
96 GetFirstNonAddress (
97 VOID
98 )
99 {
100 UINT64 FirstNonAddress;
101 UINT64 Pci64Base, Pci64Size;
102 CHAR8 MbString[7 + 1];
103 EFI_STATUS Status;
104 FIRMWARE_CONFIG_ITEM FwCfgItem;
105 UINTN FwCfgSize;
106 UINT64 HotPlugMemoryEnd;
107
108 FirstNonAddress = BASE_4GB + GetSystemMemorySizeAbove4gb ();
109
110 //
111 // If DXE is 32-bit, then we're done; PciBusDxe will degrade 64-bit MMIO
112 // resources to 32-bit anyway. See DegradeResource() in
113 // "PciResourceSupport.c".
114 //
115 #ifdef MDE_CPU_IA32
116 if (!FeaturePcdGet (PcdDxeIplSwitchToLongMode)) {
117 return FirstNonAddress;
118 }
119 #endif
120
121 //
122 // Otherwise, in order to calculate the highest address plus one, we must
123 // consider the 64-bit PCI host aperture too. Fetch the default size.
124 //
125 Pci64Size = PcdGet64 (PcdPciMmio64Size);
126
127 //
128 // See if the user specified the number of megabytes for the 64-bit PCI host
129 // aperture. The number of non-NUL characters in MbString allows for
130 // 9,999,999 MB, which is approximately 10 TB.
131 //
132 // As signaled by the "X-" prefix, this knob is experimental, and might go
133 // away at any time.
134 //
135 Status = QemuFwCfgFindFile ("opt/ovmf/X-PciMmio64Mb", &FwCfgItem,
136 &FwCfgSize);
137 if (!EFI_ERROR (Status)) {
138 if (FwCfgSize >= sizeof MbString) {
139 DEBUG ((EFI_D_WARN,
140 "%a: ignoring malformed 64-bit PCI host aperture size from fw_cfg\n",
141 __FUNCTION__));
142 } else {
143 QemuFwCfgSelectItem (FwCfgItem);
144 QemuFwCfgReadBytes (FwCfgSize, MbString);
145 MbString[FwCfgSize] = '\0';
146 Pci64Size = LShiftU64 (AsciiStrDecimalToUint64 (MbString), 20);
147 }
148 }
149
150 if (Pci64Size == 0) {
151 if (mBootMode != BOOT_ON_S3_RESUME) {
152 DEBUG ((EFI_D_INFO, "%a: disabling 64-bit PCI host aperture\n",
153 __FUNCTION__));
154 PcdSet64 (PcdPciMmio64Size, 0);
155 }
156
157 //
158 // There's nothing more to do; the amount of memory above 4GB fully
159 // determines the highest address plus one. The memory hotplug area (see
160 // below) plays no role for the firmware in this case.
161 //
162 return FirstNonAddress;
163 }
164
165 //
166 // The "etc/reserved-memory-end" fw_cfg file, when present, contains an
167 // absolute, exclusive end address for the memory hotplug area. This area
168 // starts right at the end of the memory above 4GB. The 64-bit PCI host
169 // aperture must be placed above it.
170 //
171 Status = QemuFwCfgFindFile ("etc/reserved-memory-end", &FwCfgItem,
172 &FwCfgSize);
173 if (!EFI_ERROR (Status) && FwCfgSize == sizeof HotPlugMemoryEnd) {
174 QemuFwCfgSelectItem (FwCfgItem);
175 QemuFwCfgReadBytes (FwCfgSize, &HotPlugMemoryEnd);
176
177 ASSERT (HotPlugMemoryEnd >= FirstNonAddress);
178 FirstNonAddress = HotPlugMemoryEnd;
179 }
180
181 //
182 // SeaBIOS aligns both boundaries of the 64-bit PCI host aperture to 1GB, so
183 // that the host can map it with 1GB hugepages. Follow suit.
184 //
185 Pci64Base = ALIGN_VALUE (FirstNonAddress, (UINT64)SIZE_1GB);
186 Pci64Size = ALIGN_VALUE (Pci64Size, (UINT64)SIZE_1GB);
187
188 //
189 // The 64-bit PCI host aperture should also be "naturally" aligned. The
190 // alignment is determined by rounding the size of the aperture down to the
191 // next smaller or equal power of two. That is, align the aperture by the
192 // largest BAR size that can fit into it.
193 //
194 Pci64Base = ALIGN_VALUE (Pci64Base, GetPowerOfTwo64 (Pci64Size));
195
196 if (mBootMode != BOOT_ON_S3_RESUME) {
197 //
198 // The core PciHostBridgeDxe driver will automatically add this range to
199 // the GCD memory space map through our PciHostBridgeLib instance; here we
200 // only need to set the PCDs.
201 //
202 PcdSet64 (PcdPciMmio64Base, Pci64Base);
203 PcdSet64 (PcdPciMmio64Size, Pci64Size);
204 DEBUG ((EFI_D_INFO, "%a: Pci64Base=0x%Lx Pci64Size=0x%Lx\n",
205 __FUNCTION__, Pci64Base, Pci64Size));
206 }
207
208 //
209 // The useful address space ends with the 64-bit PCI host aperture.
210 //
211 FirstNonAddress = Pci64Base + Pci64Size;
212 return FirstNonAddress;
213 }
214
215
216 /**
217 Initialize the mPhysMemAddressWidth variable, based on guest RAM size.
218 **/
219 VOID
220 AddressWidthInitialization (
221 VOID
222 )
223 {
224 UINT64 FirstNonAddress;
225
226 //
227 // As guest-physical memory size grows, the permanent PEI RAM requirements
228 // are dominated by the identity-mapping page tables built by the DXE IPL.
229 // The DXL IPL keys off of the physical address bits advertized in the CPU
230 // HOB. To conserve memory, we calculate the minimum address width here.
231 //
232 FirstNonAddress = GetFirstNonAddress ();
233 mPhysMemAddressWidth = (UINT8)HighBitSet64 (FirstNonAddress);
234
235 //
236 // If FirstNonAddress is not an integral power of two, then we need an
237 // additional bit.
238 //
239 if ((FirstNonAddress & (FirstNonAddress - 1)) != 0) {
240 ++mPhysMemAddressWidth;
241 }
242
243 //
244 // The minimum address width is 36 (covers up to and excluding 64 GB, which
245 // is the maximum for Ia32 + PAE). The theoretical architecture maximum for
246 // X64 long mode is 52 bits, but the DXE IPL clamps that down to 48 bits. We
247 // can simply assert that here, since 48 bits are good enough for 256 TB.
248 //
249 if (mPhysMemAddressWidth <= 36) {
250 mPhysMemAddressWidth = 36;
251 }
252 ASSERT (mPhysMemAddressWidth <= 48);
253 }
254
255
256 /**
257 Calculate the cap for the permanent PEI memory.
258 **/
259 STATIC
260 UINT32
261 GetPeiMemoryCap (
262 VOID
263 )
264 {
265 BOOLEAN Page1GSupport;
266 UINT32 RegEax;
267 UINT32 RegEdx;
268 UINT32 Pml4Entries;
269 UINT32 PdpEntries;
270 UINTN TotalPages;
271
272 //
273 // If DXE is 32-bit, then just return the traditional 64 MB cap.
274 //
275 #ifdef MDE_CPU_IA32
276 if (!FeaturePcdGet (PcdDxeIplSwitchToLongMode)) {
277 return SIZE_64MB;
278 }
279 #endif
280
281 //
282 // Dependent on physical address width, PEI memory allocations can be
283 // dominated by the page tables built for 64-bit DXE. So we key the cap off
284 // of those. The code below is based on CreateIdentityMappingPageTables() in
285 // "MdeModulePkg/Core/DxeIplPeim/X64/VirtualMemory.c".
286 //
287 Page1GSupport = FALSE;
288 if (PcdGetBool (PcdUse1GPageTable)) {
289 AsmCpuid (0x80000000, &RegEax, NULL, NULL, NULL);
290 if (RegEax >= 0x80000001) {
291 AsmCpuid (0x80000001, NULL, NULL, NULL, &RegEdx);
292 if ((RegEdx & BIT26) != 0) {
293 Page1GSupport = TRUE;
294 }
295 }
296 }
297
298 if (mPhysMemAddressWidth <= 39) {
299 Pml4Entries = 1;
300 PdpEntries = 1 << (mPhysMemAddressWidth - 30);
301 ASSERT (PdpEntries <= 0x200);
302 } else {
303 Pml4Entries = 1 << (mPhysMemAddressWidth - 39);
304 ASSERT (Pml4Entries <= 0x200);
305 PdpEntries = 512;
306 }
307
308 TotalPages = Page1GSupport ? Pml4Entries + 1 :
309 (PdpEntries + 1) * Pml4Entries + 1;
310 ASSERT (TotalPages <= 0x40201);
311
312 //
313 // Add 64 MB for miscellaneous allocations. Note that for
314 // mPhysMemAddressWidth values close to 36, the cap will actually be
315 // dominated by this increment.
316 //
317 return (UINT32)(EFI_PAGES_TO_SIZE (TotalPages) + SIZE_64MB);
318 }
319
320
321 /**
322 Publish PEI core memory
323
324 @return EFI_SUCCESS The PEIM initialized successfully.
325
326 **/
327 EFI_STATUS
328 PublishPeiMemory (
329 VOID
330 )
331 {
332 EFI_STATUS Status;
333 EFI_PHYSICAL_ADDRESS MemoryBase;
334 UINT64 MemorySize;
335 UINT64 LowerMemorySize;
336 UINT32 PeiMemoryCap;
337
338 if (mBootMode == BOOT_ON_S3_RESUME) {
339 MemoryBase = PcdGet32 (PcdS3AcpiReservedMemoryBase);
340 MemorySize = PcdGet32 (PcdS3AcpiReservedMemorySize);
341 } else {
342 LowerMemorySize = GetSystemMemorySizeBelow4gb ();
343 if (FeaturePcdGet (PcdSmmSmramRequire)) {
344 //
345 // TSEG is chipped from the end of low RAM
346 //
347 LowerMemorySize -= FixedPcdGet8 (PcdQ35TsegMbytes) * SIZE_1MB;
348 }
349
350 PeiMemoryCap = GetPeiMemoryCap ();
351 DEBUG ((EFI_D_INFO, "%a: mPhysMemAddressWidth=%d PeiMemoryCap=%u KB\n",
352 __FUNCTION__, mPhysMemAddressWidth, PeiMemoryCap >> 10));
353
354 //
355 // Determine the range of memory to use during PEI
356 //
357 // Technically we could lay the permanent PEI RAM over SEC's temporary
358 // decompression and scratch buffer even if "secure S3" is needed, since
359 // their lifetimes don't overlap. However, PeiFvInitialization() will cover
360 // RAM up to PcdOvmfDecompressionScratchEnd with an EfiACPIMemoryNVS memory
361 // allocation HOB, and other allocations served from the permanent PEI RAM
362 // shouldn't overlap with that HOB.
363 //
364 MemoryBase = mS3Supported && FeaturePcdGet (PcdSmmSmramRequire) ?
365 PcdGet32 (PcdOvmfDecompressionScratchEnd) :
366 PcdGet32 (PcdOvmfDxeMemFvBase) + PcdGet32 (PcdOvmfDxeMemFvSize);
367 MemorySize = LowerMemorySize - MemoryBase;
368 if (MemorySize > PeiMemoryCap) {
369 MemoryBase = LowerMemorySize - PeiMemoryCap;
370 MemorySize = PeiMemoryCap;
371 }
372 }
373
374 //
375 // Publish this memory to the PEI Core
376 //
377 Status = PublishSystemMemory(MemoryBase, MemorySize);
378 ASSERT_EFI_ERROR (Status);
379
380 return Status;
381 }
382
383
384 /**
385 Peform Memory Detection for QEMU / KVM
386
387 **/
388 STATIC
389 VOID
390 QemuInitializeRam (
391 VOID
392 )
393 {
394 UINT64 LowerMemorySize;
395 UINT64 UpperMemorySize;
396 MTRR_SETTINGS MtrrSettings;
397 EFI_STATUS Status;
398
399 DEBUG ((EFI_D_INFO, "%a called\n", __FUNCTION__));
400
401 //
402 // Determine total memory size available
403 //
404 LowerMemorySize = GetSystemMemorySizeBelow4gb ();
405 UpperMemorySize = GetSystemMemorySizeAbove4gb ();
406
407 if (mBootMode == BOOT_ON_S3_RESUME) {
408 //
409 // Create the following memory HOB as an exception on the S3 boot path.
410 //
411 // Normally we'd create memory HOBs only on the normal boot path. However,
412 // CpuMpPei specifically needs such a low-memory HOB on the S3 path as
413 // well, for "borrowing" a subset of it temporarily, for the AP startup
414 // vector.
415 //
416 // CpuMpPei saves the original contents of the borrowed area in permanent
417 // PEI RAM, in a backup buffer allocated with the normal PEI services.
418 // CpuMpPei restores the original contents ("returns" the borrowed area) at
419 // End-of-PEI. End-of-PEI in turn is emitted by S3Resume2Pei before
420 // transfering control to the OS's wakeup vector in the FACS.
421 //
422 // We expect any other PEIMs that "borrow" memory similarly to CpuMpPei to
423 // restore the original contents. Furthermore, we expect all such PEIMs
424 // (CpuMpPei included) to claim the borrowed areas by producing memory
425 // allocation HOBs, and to honor preexistent memory allocation HOBs when
426 // looking for an area to borrow.
427 //
428 AddMemoryRangeHob (0, BASE_512KB + BASE_128KB);
429 } else {
430 //
431 // Create memory HOBs
432 //
433 AddMemoryRangeHob (0, BASE_512KB + BASE_128KB);
434
435 if (FeaturePcdGet (PcdSmmSmramRequire)) {
436 UINT32 TsegSize;
437
438 TsegSize = FixedPcdGet8 (PcdQ35TsegMbytes) * SIZE_1MB;
439 AddMemoryRangeHob (BASE_1MB, LowerMemorySize - TsegSize);
440 AddReservedMemoryBaseSizeHob (LowerMemorySize - TsegSize, TsegSize,
441 TRUE);
442 } else {
443 AddMemoryRangeHob (BASE_1MB, LowerMemorySize);
444 }
445
446 if (UpperMemorySize != 0) {
447 AddMemoryBaseSizeHob (BASE_4GB, UpperMemorySize);
448 }
449 }
450
451 //
452 // We'd like to keep the following ranges uncached:
453 // - [640 KB, 1 MB)
454 // - [LowerMemorySize, 4 GB)
455 //
456 // Everything else should be WB. Unfortunately, programming the inverse (ie.
457 // keeping the default UC, and configuring the complement set of the above as
458 // WB) is not reliable in general, because the end of the upper RAM can have
459 // practically any alignment, and we may not have enough variable MTRRs to
460 // cover it exactly.
461 //
462 if (IsMtrrSupported ()) {
463 MtrrGetAllMtrrs (&MtrrSettings);
464
465 //
466 // MTRRs disabled, fixed MTRRs disabled, default type is uncached
467 //
468 ASSERT ((MtrrSettings.MtrrDefType & BIT11) == 0);
469 ASSERT ((MtrrSettings.MtrrDefType & BIT10) == 0);
470 ASSERT ((MtrrSettings.MtrrDefType & 0xFF) == 0);
471
472 //
473 // flip default type to writeback
474 //
475 SetMem (&MtrrSettings.Fixed, sizeof MtrrSettings.Fixed, 0x06);
476 ZeroMem (&MtrrSettings.Variables, sizeof MtrrSettings.Variables);
477 MtrrSettings.MtrrDefType |= BIT11 | BIT10 | 6;
478 MtrrSetAllMtrrs (&MtrrSettings);
479
480 //
481 // Set memory range from 640KB to 1MB to uncacheable
482 //
483 Status = MtrrSetMemoryAttribute (BASE_512KB + BASE_128KB,
484 BASE_1MB - (BASE_512KB + BASE_128KB), CacheUncacheable);
485 ASSERT_EFI_ERROR (Status);
486
487 //
488 // Set memory range from the "top of lower RAM" (RAM below 4GB) to 4GB as
489 // uncacheable
490 //
491 Status = MtrrSetMemoryAttribute (LowerMemorySize,
492 SIZE_4GB - LowerMemorySize, CacheUncacheable);
493 ASSERT_EFI_ERROR (Status);
494 }
495 }
496
497 /**
498 Publish system RAM and reserve memory regions
499
500 **/
501 VOID
502 InitializeRamRegions (
503 VOID
504 )
505 {
506 if (!mXen) {
507 QemuInitializeRam ();
508 } else {
509 XenPublishRamRegions ();
510 }
511
512 if (mS3Supported && mBootMode != BOOT_ON_S3_RESUME) {
513 //
514 // This is the memory range that will be used for PEI on S3 resume
515 //
516 BuildMemoryAllocationHob (
517 (EFI_PHYSICAL_ADDRESS)(UINTN) PcdGet32 (PcdS3AcpiReservedMemoryBase),
518 (UINT64)(UINTN) PcdGet32 (PcdS3AcpiReservedMemorySize),
519 EfiACPIMemoryNVS
520 );
521
522 //
523 // Cover the initial RAM area used as stack and temporary PEI heap.
524 //
525 // This is reserved as ACPI NVS so it can be used on S3 resume.
526 //
527 BuildMemoryAllocationHob (
528 PcdGet32 (PcdOvmfSecPeiTempRamBase),
529 PcdGet32 (PcdOvmfSecPeiTempRamSize),
530 EfiACPIMemoryNVS
531 );
532
533 //
534 // SEC stores its table of GUIDed section handlers here.
535 //
536 BuildMemoryAllocationHob (
537 PcdGet64 (PcdGuidedExtractHandlerTableAddress),
538 PcdGet32 (PcdGuidedExtractHandlerTableSize),
539 EfiACPIMemoryNVS
540 );
541
542 #ifdef MDE_CPU_X64
543 //
544 // Reserve the initial page tables built by the reset vector code.
545 //
546 // Since this memory range will be used by the Reset Vector on S3
547 // resume, it must be reserved as ACPI NVS.
548 //
549 BuildMemoryAllocationHob (
550 (EFI_PHYSICAL_ADDRESS)(UINTN) PcdGet32 (PcdOvmfSecPageTablesBase),
551 (UINT64)(UINTN) PcdGet32 (PcdOvmfSecPageTablesSize),
552 EfiACPIMemoryNVS
553 );
554 #endif
555 }
556
557 if (mBootMode != BOOT_ON_S3_RESUME) {
558 if (!FeaturePcdGet (PcdSmmSmramRequire)) {
559 //
560 // Reserve the lock box storage area
561 //
562 // Since this memory range will be used on S3 resume, it must be
563 // reserved as ACPI NVS.
564 //
565 // If S3 is unsupported, then various drivers might still write to the
566 // LockBox area. We ought to prevent DXE from serving allocation requests
567 // such that they would overlap the LockBox storage.
568 //
569 ZeroMem (
570 (VOID*)(UINTN) PcdGet32 (PcdOvmfLockBoxStorageBase),
571 (UINTN) PcdGet32 (PcdOvmfLockBoxStorageSize)
572 );
573 BuildMemoryAllocationHob (
574 (EFI_PHYSICAL_ADDRESS)(UINTN) PcdGet32 (PcdOvmfLockBoxStorageBase),
575 (UINT64)(UINTN) PcdGet32 (PcdOvmfLockBoxStorageSize),
576 mS3Supported ? EfiACPIMemoryNVS : EfiBootServicesData
577 );
578 }
579
580 if (FeaturePcdGet (PcdSmmSmramRequire)) {
581 UINT32 TsegSize;
582
583 //
584 // Make sure the TSEG area that we reported as a reserved memory resource
585 // cannot be used for reserved memory allocations.
586 //
587 TsegSize = FixedPcdGet8 (PcdQ35TsegMbytes) * SIZE_1MB;
588 BuildMemoryAllocationHob (
589 GetSystemMemorySizeBelow4gb() - TsegSize,
590 TsegSize,
591 EfiReservedMemoryType
592 );
593 }
594 }
595 }