4 * Copyright ... ... (See below.)
5 * Copyright 2017 Serge Gautherie
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 * Note: Most of this code comes from the old file "i386mem.c", which
22 * was Copyright (C) 1998-2003 Brian Palmer <brianp@sginet.com>
26 #include <arch/pc/x86common.h>
30 DBG_DEFAULT_CHANNEL(MEMORY
);
32 #define ULONGLONG_ALIGN_DOWN_BY(size, align) \
33 ((ULONGLONG)(size) & ~((ULONGLONG)(align) - 1))
35 #define ULONGLONG_ALIGN_UP_BY(size, align) \
36 (ULONGLONG_ALIGN_DOWN_BY(((ULONGLONG)(size) + align - 1), align))
38 #define MAX_BIOS_DESCRIPTORS 80ul
40 BIOS_MEMORY_MAP PcBiosMemoryMap
[MAX_BIOS_DESCRIPTORS
];
43 FREELDR_MEMORY_DESCRIPTOR PcMemoryMap
[MAX_BIOS_DESCRIPTORS
+ 1];
48 IN OUT PFREELDR_MEMORY_DESCRIPTOR List
,
50 IN PFN_NUMBER BasePage
,
51 IN PFN_NUMBER PageCount
,
52 IN TYPE_OF_MEMORY MemoryType
);
56 GetExtendedMemoryConfiguration(ULONG
* pMemoryAtOneMB
/* in KB */, ULONG
* pMemoryAtSixteenMB
/* in 64KB */)
61 TRACE("GetExtendedMemoryConfiguration()\n");
64 *pMemoryAtSixteenMB
= 0;
67 // Phoenix BIOS v4.0 - GET MEMORY SIZE FOR >64M CONFIGURATIONS
71 // CF clear if successful
72 // AX = extended memory between 1M and 16M, in K (max 3C00h = 15MB)
73 // BX = extended memory above 16M, in 64K blocks
74 // CX = configured memory 1M to 16M, in K
75 // DX = configured memory above 16M, in 64K blocks
78 Int386(0x15, &RegsIn
, &RegsOut
);
80 TRACE("Int15h AX=E801h\n");
81 TRACE("AX = 0x%x\n", RegsOut
.w
.ax
);
82 TRACE("BX = 0x%x\n", RegsOut
.w
.bx
);
83 TRACE("CX = 0x%x\n", RegsOut
.w
.cx
);
84 TRACE("DX = 0x%x\n", RegsOut
.w
.dx
);
85 TRACE("CF set = %s\n\n", (RegsOut
.x
.eflags
& EFLAGS_CF
) ? "TRUE" : "FALSE");
87 if (INT386_SUCCESS(RegsOut
))
89 // If AX=BX=0000h the use CX and DX
90 if (RegsOut
.w
.ax
== 0)
92 // Return extended memory size in K
93 *pMemoryAtSixteenMB
= RegsOut
.w
.dx
;
94 *pMemoryAtOneMB
= RegsOut
.w
.cx
;
99 // Return extended memory size in K
100 *pMemoryAtSixteenMB
= RegsOut
.w
.bx
;
101 *pMemoryAtOneMB
= RegsOut
.w
.ax
;
106 // If we get here then Int15 Func E801h didn't work
107 // So try Int15 Func 88h
109 // SYSTEM - GET EXTENDED MEMORY SIZE (286+)
113 // CF clear if successful
114 // AX = number of contiguous KB starting at absolute address 100000h
117 // 80h invalid command (PC,PCjr)
118 // 86h unsupported function (XT,PS30)
120 Int386(0x15, &RegsIn
, &RegsOut
);
122 TRACE("Int15h AH=88h\n");
123 TRACE("AX = 0x%x\n", RegsOut
.w
.ax
);
124 TRACE("CF set = %s\n\n", (RegsOut
.x
.eflags
& EFLAGS_CF
) ? "TRUE" : "FALSE");
126 if (INT386_SUCCESS(RegsOut
) && RegsOut
.w
.ax
!= 0)
128 *pMemoryAtOneMB
= RegsOut
.w
.ax
;
132 // If we get here then Int15 Func 88h didn't work
133 // So try reading the CMOS
134 WRITE_PORT_UCHAR((PUCHAR
)0x70, 0x31);
135 *pMemoryAtOneMB
= READ_PORT_UCHAR((PUCHAR
)0x71);
136 *pMemoryAtOneMB
= (*pMemoryAtOneMB
& 0xFFFF);
137 *pMemoryAtOneMB
= (*pMemoryAtOneMB
<< 8);
139 TRACE("Int15h Failed\n");
140 TRACE("CMOS reports: 0x%lx\n", *pMemoryAtOneMB
);
142 if (*pMemoryAtOneMB
!= 0)
152 PcMemGetConventionalMemorySize(VOID
)
156 TRACE("PcMemGetConventionalMemorySize()\n");
159 * BIOS - GET MEMORY SIZE
162 * AX = kilobytes of contiguous memory starting at absolute address 00000h
164 * This call returns the contents of the word at 0040h:0013h;
165 * in PC and XT, this value is set from the switches on the motherboard
168 Int386(0x12, &Regs
, &Regs
);
171 TRACE("AX = 0x%x\n\n", Regs
.w
.ax
);
173 return (ULONG
)Regs
.w
.ax
;
184 /* Get the address of the Extended BIOS Data Area (EBDA).
186 * SYSTEM - RETURN EXTENDED-BIOS DATA-AREA SEGMENT ADDRESS (PS)
190 * CF clear if successful
191 * ES = segment of data area
193 Regs
.x
.eax
= 0x0000C100;
194 Int386(0x15, &Regs
, &Regs
);
196 /* If the function fails, there is no EBDA */
197 if (!INT386_SUCCESS(Regs
))
202 /* Get Base address and (maximum) size */
203 *BaseAddress
= (ULONG
)Regs
.w
.es
<< 4;
204 *Size
= 0xA0000 - *BaseAddress
;
210 PcMemCheckUsableMemorySize(VOID
)
212 ULONG Size
, RequiredSize
;
214 /* Make sure the usable memory is large enough. To do this we check the 16
215 bit value at address 0x413 inside the BDA, which gives us the usable size
217 Size
= (*(PUSHORT
)(ULONG_PTR
)0x413) * 1024;
218 RequiredSize
= FREELDR_BASE
+ FrLdrImageSize
+ PAGE_SIZE
;
219 if (Size
< RequiredSize
)
221 FrLdrBugCheckWithMessage(
225 "The BIOS reported a usable memory range up to 0x%lx, which is too small!\n"
226 "Required size is 0x%lx\n\n"
227 "If you see this, please report to the ReactOS team!",
234 PcMemGetBiosMemoryMap(PFREELDR_MEMORY_DESCRIPTOR MemoryMap
, ULONG MaxMemoryMapSize
)
237 ULONGLONG RealBaseAddress
, EndAddress
, RealSize
;
238 TYPE_OF_MEMORY MemoryType
;
240 ASSERT(PcBiosMapCount
== 0);
242 TRACE("PcMemGetBiosMemoryMap()\n");
245 * Newer BIOSes - GET SYSTEM MEMORY MAP
249 * EDX = 534D4150h ('SMAP')
250 * EBX = continuation value or 00000000h to start at beginning of map
251 * ECX = size of buffer for result, in bytes (should be >= 20 bytes)
252 * ES:DI -> buffer for result
254 * CF clear if successful
255 * EAX = 534D4150h ('SMAP')
256 * ES:DI buffer filled
257 * EBX = next offset from which to copy or 00000000h if all done
258 * ECX = actual length returned in bytes
260 * AH = error code (86h)
262 Regs
.x
.ebx
= 0x00000000;
264 while (PcBiosMapCount
< MAX_BIOS_DESCRIPTORS
)
266 /* ACPI 3.0/4.0: Set Extended Attributes to enabled/valid by default, in case entry has no E.A.. */
267 ((PBIOS_MEMORY_MAP
)BIOSCALLBUFFER
)->ExtendedAttributesAsULONG
= 0;
268 ((PBIOS_MEMORY_MAP
)BIOSCALLBUFFER
)->ExtendedAttributes
.Enabled_Reserved
= 1;
270 /* Setup the registers for the BIOS call */
271 Regs
.x
.eax
= 0x0000E820;
272 Regs
.x
.edx
= 0x534D4150; /* ('SMAP') */
273 /* Regs.x.ebx = 0x00000001; Continuation value already set */
274 Regs
.x
.ecx
= sizeof(BIOS_MEMORY_MAP
);
275 Regs
.w
.es
= BIOSCALLBUFSEGMENT
;
276 Regs
.w
.di
= BIOSCALLBUFOFFSET
;
277 Int386(0x15, &Regs
, &Regs
);
279 TRACE("Memory Map Entry %lu\n", PcBiosMapCount
);
280 TRACE("Int15h AX=E820h\n");
281 TRACE("EAX = 0x%lx\n", Regs
.x
.eax
);
282 TRACE("EBX = 0x%lx\n", Regs
.x
.ebx
);
283 TRACE("ECX = %lu\n", Regs
.x
.ecx
);
284 TRACE("CF set = %s\n", (Regs
.x
.eflags
& EFLAGS_CF
) ? "TRUE" : "FALSE");
286 /* If the BIOS didn't return 'SMAP' in EAX then
287 * it doesn't support this call. */
288 if (Regs
.x
.eax
!= 0x534D4150)
290 WARN("BIOS doesn't support Int15h AX=E820h!\n\n");
294 /* If the carry flag is set,
295 * then this call was past the last entry, so we're done. */
296 if (!INT386_SUCCESS(Regs
))
298 TRACE("End of System Memory Map! (Past last)\n\n");
304 TRACE("Discard empty entry. (would-be-PcBiosMapCount = %lu)\n",
309 /* Extra safety: unexpected entry length.
310 * All in-between values are valid too, as x86 is little-indian
311 * and only lower byte is used per ACPI 6.2-A.
313 if (Regs
.x
.ecx
< RTL_SIZEOF_THROUGH_FIELD(BIOS_MEMORY_MAP
, Type
) ||
314 Regs
.x
.ecx
> sizeof(BIOS_MEMORY_MAP
))
316 ERR("Int 15h AX=E820h returned an invalid entry length! (would-be-PcBiosMapCount = %lu, Entry length = (%Iu <=) %lu (<= %Iu))\n\n",
317 PcBiosMapCount
, RTL_SIZEOF_THROUGH_FIELD(BIOS_MEMORY_MAP
, Type
), Regs
.x
.ecx
, sizeof(BIOS_MEMORY_MAP
));
318 /* Warn user, unless wrong case is "first and not too big entry", which is otherwise harmless. */
319 if (PcBiosMapCount
> 0 || Regs
.x
.ecx
> sizeof(BIOS_MEMORY_MAP
))
321 ASSERTMSG("Int 15h AX=E820h returned an invalid entry length!", FALSE
);
323 /* We keep previous entries (if any), but do not dare trying next entries.
324 * We assume these entries are good to use as is. If they are not, we are in trouble...
325 * (And don't ask what happens if BIOS actually overflowed our entry buffer...)
327 * FIXME: Safer = revert previous entries, Safest = blacklist this BIOS.
332 if (((PBIOS_MEMORY_MAP
)BIOSCALLBUFFER
)->ExtendedAttributes
.Enabled_Reserved
== 0)
334 WARN("Discard disabled/invalid entry. (would-be-PcBiosMapCount = %lu)\n",
336 /* This unlikely case was correct between ACPI 3.0 and 4.0, so assume all is fine.
337 * Unless we would be ready to drop ACPI 3.0 compatibility.
342 /* Copy data to global buffer */
343 RtlCopyMemory(&PcBiosMemoryMap
[PcBiosMapCount
], (PVOID
)BIOSCALLBUFFER
, sizeof(BIOS_MEMORY_MAP
));
345 TRACE("BaseAddress: 0x%llx\n", PcBiosMemoryMap
[PcBiosMapCount
].BaseAddress
);
346 TRACE("Length: 0x%llx\n", PcBiosMemoryMap
[PcBiosMapCount
].Length
);
347 TRACE("Type: 0x%lx\n", PcBiosMemoryMap
[PcBiosMapCount
].Type
);
348 TRACE("ExtendedAttributesAsULONG: 0x%08lx\n", PcBiosMemoryMap
[PcBiosMapCount
].ExtendedAttributesAsULONG
);
350 if (PcBiosMemoryMap
[PcBiosMapCount
].Length
== 0)
352 TRACE("Discard empty range. (would-be-PcBiosMapCount = %lu, BaseAddress = %lu, Length = 0)\n",
353 PcBiosMapCount
, PcBiosMemoryMap
[PcBiosMapCount
].BaseAddress
);
357 /* Check if this is free memory */
358 if (PcBiosMemoryMap
[PcBiosMapCount
].Type
== BiosMemoryUsable
)
360 MemoryType
= LoaderFree
;
362 /* Align up base of memory range */
363 RealBaseAddress
= ULONGLONG_ALIGN_UP_BY(
364 PcBiosMemoryMap
[PcBiosMapCount
].BaseAddress
,
367 /* Calculate aligned EndAddress */
368 EndAddress
= PcBiosMemoryMap
[PcBiosMapCount
].BaseAddress
+
369 PcBiosMemoryMap
[PcBiosMapCount
].Length
;
370 EndAddress
= ULONGLONG_ALIGN_DOWN_BY(EndAddress
, PAGE_SIZE
);
372 /* Check if there is anything left */
373 if (EndAddress
<= RealBaseAddress
)
375 /* This doesn't span any page, so continue with next range */
376 TRACE("Skipping aligned range < PAGE_SIZE. (would-be-PcBiosMapCount = %lu, BaseAddress = %lu, Length = %lu)\n",
378 PcBiosMemoryMap
[PcBiosMapCount
].BaseAddress
,
379 PcBiosMemoryMap
[PcBiosMapCount
].Length
);
383 /* Calculate the length of the aligned range */
384 RealSize
= EndAddress
- RealBaseAddress
;
388 if (PcBiosMemoryMap
[PcBiosMapCount
].Type
== BiosMemoryReserved
)
390 MemoryType
= LoaderFirmwarePermanent
;
394 MemoryType
= LoaderSpecialMemory
;
397 /* Align down base of memory area */
398 RealBaseAddress
= ULONGLONG_ALIGN_DOWN_BY(
399 PcBiosMemoryMap
[PcBiosMapCount
].BaseAddress
,
402 /* Calculate the length after aligning the base */
403 RealSize
= PcBiosMemoryMap
[PcBiosMapCount
].BaseAddress
+
404 PcBiosMemoryMap
[PcBiosMapCount
].Length
- RealBaseAddress
;
405 RealSize
= ULONGLONG_ALIGN_UP_BY(RealSize
, PAGE_SIZE
);
408 /* Check if we can add this descriptor */
409 if (RealSize
< MM_PAGE_SIZE
)
411 TRACE("Skipping aligned range < MM_PAGE_SIZE. (PcBiosMapCount = %lu, BaseAddress = %lu, Length = %lu)\n",
413 PcBiosMemoryMap
[PcBiosMapCount
].BaseAddress
,
414 PcBiosMemoryMap
[PcBiosMapCount
].Length
);
416 else if (PcMapCount
>= MaxMemoryMapSize
)
418 ERR("PcMemoryMap is already full! (PcBiosMapCount = %lu, PcMapCount = %lu (>= %lu))\n",
419 PcBiosMapCount
, PcMapCount
, MaxMemoryMapSize
);
420 // NotWantedForPublicBuilds: ASSERTMSG("PcMemoryMap is already full!", FALSE);
421 /* We keep previous entries, and half-retrieve current/next entries.
422 * We assume all these entries are good to use as is. If they are not, we are in trouble...
424 * FIXME: Safer = revert (half-)retrieved entries, Safest = increase MaxMemoryMapSize.
429 /* Add the descriptor */
430 PcMapCount
= AddMemoryDescriptor(PcMemoryMap
,
431 MAX_BIOS_DESCRIPTORS
,
432 (PFN_NUMBER
)(RealBaseAddress
/ MM_PAGE_SIZE
),
433 (PFN_NUMBER
)(RealSize
/ MM_PAGE_SIZE
),
442 /* If the continuation value is zero,
443 * then this was the last entry, so we're done. */
444 if (Regs
.x
.ebx
== 0x00000000)
446 TRACE("End of System Memory Map! (Reset)\n\n");
450 /* Check whether there would be more entries to process. */
451 if (PcBiosMapCount
>= MAX_BIOS_DESCRIPTORS
&& Regs
.x
.ebx
!= 0x00000000)
453 ERR("PcBiosMemoryMap is already full! (PcBiosMapCount = %lu (>= %lu), PcMapCount = %lu)\n",
454 PcBiosMapCount
, MAX_BIOS_DESCRIPTORS
, PcMapCount
);
455 // NotWantedForPublicBuilds: ASSERTMSG("PcBiosMemoryMap is already full!", FALSE);
456 /* We keep retrieved entries, but ignore next entries.
457 * We assume these entries are good to use as is. If they are not, we are in trouble...
459 * FIXME: Safer = revert retrieved entries, Safest = increase MAX_BIOS_DESCRIPTORS.
463 TRACE("PcMemGetBiosMemoryMap end: PcBiosMapCount = %lu\n", PcBiosMapCount
);
464 return PcBiosMapCount
;
469 ULONG_PTR BaseAddress
,
471 TYPE_OF_MEMORY MemoryType
,
474 ULONG_PTR BasePage
, PageCount
;
477 BasePage
= BaseAddress
/ PAGE_SIZE
;
478 PageCount
= ADDRESS_AND_SIZE_TO_SPAN_PAGES(BaseAddress
, Size
);
480 for (i
= 0; i
< PcMapCount
; i
++)
482 /* Check for conflicting descriptor */
483 if ((PcMemoryMap
[i
].BasePage
< BasePage
+ PageCount
) &&
484 (PcMemoryMap
[i
].BasePage
+ PcMemoryMap
[i
].PageCount
> BasePage
))
486 /* Check if the memory is free */
487 if (PcMemoryMap
[i
].MemoryType
!= LoaderFree
)
489 FrLdrBugCheckWithMessage(
493 "Failed to reserve memory in the range 0x%Ix - 0x%Ix for %s",
501 /* Add the memory descriptor */
502 PcMapCount
= AddMemoryDescriptor(PcMemoryMap
,
503 MAX_BIOS_DESCRIPTORS
,
511 ULONG_PTR BaseAddress
,
513 TYPE_OF_MEMORY MemoryType
)
515 ULONG_PTR BasePage
, PageCount
;
517 BasePage
= BaseAddress
/ PAGE_SIZE
;
518 PageCount
= ADDRESS_AND_SIZE_TO_SPAN_PAGES(BaseAddress
, Size
);
520 /* Add the memory descriptor */
521 PcMapCount
= AddMemoryDescriptor(PcMemoryMap
,
522 MAX_BIOS_DESCRIPTORS
,
528 PFREELDR_MEMORY_DESCRIPTOR
529 PcMemGetMemoryMap(ULONG
*MemoryMapSize
)
532 ULONG ExtendedMemorySizeAtOneMB
;
533 ULONG ExtendedMemorySizeAtSixteenMB
;
534 ULONG EbdaBase
, EbdaSize
;
536 TRACE("PcMemGetMemoryMap()\n");
538 PcMemCheckUsableMemorySize();
540 EntryCount
= PcMemGetBiosMemoryMap(PcMemoryMap
, MAX_BIOS_DESCRIPTORS
);
542 /* If the BIOS didn't provide a memory map, synthesize one */
545 GetExtendedMemoryConfiguration(&ExtendedMemorySizeAtOneMB
,
546 &ExtendedMemorySizeAtSixteenMB
);
548 /* Conventional memory */
549 AddMemoryDescriptor(PcMemoryMap
,
550 MAX_BIOS_DESCRIPTORS
,
552 PcMemGetConventionalMemorySize() * 1024 / PAGE_SIZE
,
555 /* Extended memory */
556 PcMapCount
= AddMemoryDescriptor(PcMemoryMap
,
557 MAX_BIOS_DESCRIPTORS
,
558 1024 * 1024 / PAGE_SIZE
,
559 ExtendedMemorySizeAtOneMB
* 1024 / PAGE_SIZE
,
562 if (ExtendedMemorySizeAtSixteenMB
!= 0)
564 /* Extended memory at 16MB */
565 PcMapCount
= AddMemoryDescriptor(PcMemoryMap
,
566 MAX_BIOS_DESCRIPTORS
,
567 0x1000000 / PAGE_SIZE
,
568 ExtendedMemorySizeAtSixteenMB
* 64 * 1024 / PAGE_SIZE
,
572 /* Check if we have an EBDA and get it's location */
573 if (GetEbdaLocation(&EbdaBase
, &EbdaSize
))
575 /* Add the descriptor */
576 PcMapCount
= AddMemoryDescriptor(PcMemoryMap
,
577 MAX_BIOS_DESCRIPTORS
,
578 (EbdaBase
/ PAGE_SIZE
),
579 ADDRESS_AND_SIZE_TO_SPAN_PAGES(EbdaBase
, EbdaSize
),
580 LoaderFirmwarePermanent
);
584 /* Setup some protected ranges */
585 SetMemory(0x000000, 0x01000, LoaderFirmwarePermanent
); // Realmode IVT / BDA
586 SetMemory(0x0A0000, 0x50000, LoaderFirmwarePermanent
); // Video memory
587 SetMemory(0x0F0000, 0x10000, LoaderSpecialMemory
); // ROM
588 SetMemory(0xFFF000, 0x01000, LoaderSpecialMemory
); // unusable memory (do we really need this?)
590 /* Reserve some static ranges for freeldr */
591 ReserveMemory(0x1000, STACKLOW
- 0x1000, LoaderFirmwareTemporary
, "BIOS area");
592 ReserveMemory(STACKLOW
, STACKADDR
- STACKLOW
, LoaderOsloaderStack
, "FreeLdr stack");
593 ReserveMemory(FREELDR_BASE
, FrLdrImageSize
, LoaderLoadedProgram
, "FreeLdr image");
595 /* Default to 1 page above freeldr for the disk read buffer */
596 DiskReadBuffer
= (PUCHAR
)ALIGN_UP_BY(FREELDR_BASE
+ FrLdrImageSize
, PAGE_SIZE
);
597 DiskReadBufferSize
= PAGE_SIZE
;
599 /* Scan for free range above freeldr image */
600 for (i
= 0; i
< PcMapCount
; i
++)
602 if ((PcMemoryMap
[i
].BasePage
> (FREELDR_BASE
/ PAGE_SIZE
)) &&
603 (PcMemoryMap
[i
].MemoryType
== LoaderFree
))
605 /* Use this range for the disk read buffer */
606 DiskReadBuffer
= (PVOID
)(PcMemoryMap
[i
].BasePage
* PAGE_SIZE
);
607 DiskReadBufferSize
= min(PcMemoryMap
[i
].PageCount
* PAGE_SIZE
,
608 MAX_DISKREADBUFFER_SIZE
);
613 TRACE("DiskReadBuffer=%p, DiskReadBufferSize=%lx\n",
614 DiskReadBuffer
, DiskReadBufferSize
);
616 /* Now reserve the range for the disk read buffer */
617 ReserveMemory((ULONG_PTR
)DiskReadBuffer
,
619 LoaderFirmwareTemporary
,
622 TRACE("Dumping resulting memory map:\n");
623 for (i
= 0; i
< PcMapCount
; i
++)
625 TRACE("BasePage=0x%lx, PageCount=0x%lx, Type=%s\n",
626 PcMemoryMap
[i
].BasePage
,
627 PcMemoryMap
[i
].PageCount
,
628 MmGetSystemMemoryMapTypeString(PcMemoryMap
[i
].MemoryType
));
631 *MemoryMapSize
= PcMapCount
;