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