4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License along
15 * with this program; if not, write to the Free Software Foundation, Inc.,
16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 * Note: Most of this code comes from the old file "i386mem.c", which
19 * was Copyright (C) 1998-2003 Brian Palmer <brianp@sginet.com>
23 #include <arch/pc/x86common.h>
28 DBG_DEFAULT_CHANNEL(MEMORY
);
30 #define ULONGLONG_ALIGN_DOWN_BY(size, align) \
31 ((ULONGLONG)(size) & ~((ULONGLONG)(align) - 1))
33 #define ULONGLONG_ALIGN_UP_BY(size, align) \
34 (ULONGLONG_ALIGN_DOWN_BY(((ULONGLONG)(size) + align - 1), align))
36 #define MAX_BIOS_DESCRIPTORS 80ul
38 BIOS_MEMORY_MAP PcBiosMemoryMap
[MAX_BIOS_DESCRIPTORS
];
41 FREELDR_MEMORY_DESCRIPTOR PcMemoryMap
[MAX_BIOS_DESCRIPTORS
+ 1];
46 IN OUT PFREELDR_MEMORY_DESCRIPTOR List
,
48 IN PFN_NUMBER BasePage
,
49 IN PFN_NUMBER PageCount
,
50 IN TYPE_OF_MEMORY MemoryType
);
54 GetExtendedMemoryConfiguration(ULONG
* pMemoryAtOneMB
/* in KB */, ULONG
* pMemoryAtSixteenMB
/* in 64KB */)
59 TRACE("GetExtendedMemoryConfiguration()\n");
62 *pMemoryAtSixteenMB
= 0;
65 // Phoenix BIOS v4.0 - GET MEMORY SIZE FOR >64M CONFIGURATIONS
69 // CF clear if successful
70 // AX = extended memory between 1M and 16M, in K (max 3C00h = 15MB)
71 // BX = extended memory above 16M, in 64K blocks
72 // CX = configured memory 1M to 16M, in K
73 // DX = configured memory above 16M, in 64K blocks
76 Int386(0x15, &RegsIn
, &RegsOut
);
78 TRACE("Int15h AX=E801h\n");
79 TRACE("AX = 0x%x\n", RegsOut
.w
.ax
);
80 TRACE("BX = 0x%x\n", RegsOut
.w
.bx
);
81 TRACE("CX = 0x%x\n", RegsOut
.w
.cx
);
82 TRACE("DX = 0x%x\n", RegsOut
.w
.dx
);
83 TRACE("CF set = %s\n\n", (RegsOut
.x
.eflags
& EFLAGS_CF
) ? "TRUE" : "FALSE");
85 if (INT386_SUCCESS(RegsOut
))
87 // If AX=BX=0000h the use CX and DX
88 if (RegsOut
.w
.ax
== 0)
90 // Return extended memory size in K
91 *pMemoryAtSixteenMB
= RegsOut
.w
.dx
;
92 *pMemoryAtOneMB
= RegsOut
.w
.cx
;
97 // Return extended memory size in K
98 *pMemoryAtSixteenMB
= RegsOut
.w
.bx
;
99 *pMemoryAtOneMB
= RegsOut
.w
.ax
;
104 // If we get here then Int15 Func E801h didn't work
105 // So try Int15 Func 88h
107 // SYSTEM - GET EXTENDED MEMORY SIZE (286+)
111 // CF clear if successful
112 // AX = number of contiguous KB starting at absolute address 100000h
115 // 80h invalid command (PC,PCjr)
116 // 86h unsupported function (XT,PS30)
118 Int386(0x15, &RegsIn
, &RegsOut
);
120 TRACE("Int15h AH=88h\n");
121 TRACE("AX = 0x%x\n", RegsOut
.w
.ax
);
122 TRACE("CF set = %s\n\n", (RegsOut
.x
.eflags
& EFLAGS_CF
) ? "TRUE" : "FALSE");
124 if (INT386_SUCCESS(RegsOut
) && RegsOut
.w
.ax
!= 0)
126 *pMemoryAtOneMB
= RegsOut
.w
.ax
;
130 // If we get here then Int15 Func 88h didn't work
131 // So try reading the CMOS
132 WRITE_PORT_UCHAR((PUCHAR
)0x70, 0x31);
133 *pMemoryAtOneMB
= READ_PORT_UCHAR((PUCHAR
)0x71);
134 *pMemoryAtOneMB
= (*pMemoryAtOneMB
& 0xFFFF);
135 *pMemoryAtOneMB
= (*pMemoryAtOneMB
<< 8);
137 TRACE("Int15h Failed\n");
138 TRACE("CMOS reports: 0x%lx\n", *pMemoryAtOneMB
);
140 if (*pMemoryAtOneMB
!= 0)
149 PcMemGetConventionalMemorySize(VOID
)
153 TRACE("PcMemGetConventionalMemorySize()\n");
156 * BIOS - GET MEMORY SIZE
159 * AX = kilobytes of contiguous memory starting at absolute address 00000h
161 * This call returns the contents of the word at 0040h:0013h;
162 * in PC and XT, this value is set from the switches on the motherboard
165 Int386(0x12, &Regs
, &Regs
);
168 TRACE("AX = 0x%x\n\n", Regs
.w
.ax
);
170 return (ULONG
)Regs
.w
.ax
;
181 /* Get the address of the Extended BIOS Data Area (EBDA).
183 * SYSTEM - RETURN EXTENDED-BIOS DATA-AREA SEGMENT ADDRESS (PS)
187 * CF clear if successful
188 * ES = segment of data area
190 Regs
.x
.eax
= 0x0000C100;
191 Int386(0x15, &Regs
, &Regs
);
193 /* If the function fails, there is no EBDA */
194 if (!INT386_SUCCESS(Regs
))
199 /* Get Base address and (maximum) size */
200 *BaseAddress
= (ULONG
)Regs
.w
.es
<< 4;
201 *Size
= 0xA0000 - *BaseAddress
;
207 PcMemGetBiosMemoryMap(PFREELDR_MEMORY_DESCRIPTOR MemoryMap
, ULONG MaxMemoryMapSize
)
210 ULONGLONG RealBaseAddress
, EndAddress
, RealSize
;
211 TYPE_OF_MEMORY MemoryType
;
212 ULONG Size
, RequiredSize
;
214 ASSERT(PcBiosMapCount
== 0);
216 TRACE("PcMemGetBiosMemoryMap()\n");
218 /* Make sure the usable memory is large enough. To do this we check the 16
219 bit value at address 0x413 inside the BDA, which gives us the usable size
221 Size
= (*(PUSHORT
)(ULONG_PTR
)0x413) * 1024;
222 RequiredSize
= FREELDR_BASE
+ FrLdrImageSize
+ PAGE_SIZE
;
223 if (Size
< RequiredSize
)
225 FrLdrBugCheckWithMessage(
229 "The BIOS reported a usable memory range up to 0x%lx, which is too small!\n"
230 "Required size is 0x%lx\n\n"
231 "If you see this, please report to the ReactOS team!",
236 * Newer BIOSes - GET SYSTEM MEMORY MAP
240 * EDX = 534D4150h ('SMAP')
241 * EBX = continuation value or 00000000h to start at beginning of map
242 * ECX = size of buffer for result, in bytes (should be >= 20 bytes)
243 * ES:DI -> buffer for result
245 * CF clear if successful
246 * EAX = 534D4150h ('SMAP')
247 * ES:DI buffer filled
248 * EBX = next offset from which to copy or 00000000h if all done
249 * ECX = actual length returned in bytes
251 * AH = error code (86h)
253 Regs
.x
.ebx
= 0x00000000;
255 while (PcBiosMapCount
< MAX_BIOS_DESCRIPTORS
)
257 /* Setup the registers for the BIOS call */
258 Regs
.x
.eax
= 0x0000E820;
259 Regs
.x
.edx
= 0x534D4150; /* ('SMAP') */
260 /* Regs.x.ebx = 0x00000001; Continuation value already set */
261 Regs
.x
.ecx
= sizeof(BIOS_MEMORY_MAP
);
262 Regs
.w
.es
= BIOSCALLBUFSEGMENT
;
263 Regs
.w
.di
= BIOSCALLBUFOFFSET
;
264 Int386(0x15, &Regs
, &Regs
);
266 TRACE("Memory Map Entry %lu\n", PcBiosMapCount
);
267 TRACE("Int15h AX=E820h\n");
268 TRACE("EAX = 0x%lx\n", Regs
.x
.eax
);
269 TRACE("EBX = 0x%lx\n", Regs
.x
.ebx
);
270 TRACE("ECX = 0x%lx\n", Regs
.x
.ecx
);
271 TRACE("CF set = %s\n", (Regs
.x
.eflags
& EFLAGS_CF
) ? "TRUE" : "FALSE");
273 /* If the BIOS didn't return 'SMAP' in EAX then
274 * it doesn't support this call. */
275 if (Regs
.x
.eax
!= 0x534D4150)
277 WARN("BIOS doesn't support Int15h AX=E820h!\n\n");
281 /* If the carry flag is set,
282 * then this call was past the last entry, so we're done. */
283 if (!INT386_SUCCESS(Regs
))
285 TRACE("End of System Memory Map! (Past last)\n\n");
291 TRACE("Discard empty entry. (would-be-PcBiosMapCount = %lu)\n",
296 /* Extra safety: unexpected entry length.
297 * All in-between values are valid too, as x86 is little-indian
298 * and only lower byte is used per ACPI 6.2-A.
300 if (Regs
.x
.ecx
< RTL_SIZEOF_THROUGH_FIELD(BIOS_MEMORY_MAP
, Type
) ||
301 Regs
.x
.ecx
> sizeof(BIOS_MEMORY_MAP
))
303 ERR("Int 15h AX=E820h returned an invalid entry length! (would-be-PcBiosMapCount = %lu, Entry length = (%Iu <=) %lu (<= %Iu))\n\n",
304 PcBiosMapCount
, RTL_SIZEOF_THROUGH_FIELD(BIOS_MEMORY_MAP
, Type
), Regs
.x
.ecx
, sizeof(BIOS_MEMORY_MAP
));
305 /* Warn user, unless wrong case is "first and not too big entry", which is otherwise harmless. */
306 if (PcBiosMapCount
> 0 || Regs
.x
.ecx
> sizeof(BIOS_MEMORY_MAP
))
308 ASSERTMSG("Int 15h AX=E820h returned an invalid entry length!", FALSE
);
310 /* We keep previous entries (if any), but do not dare trying next entries.
311 * We assume these entries are good to use as is. If they are not, we are in trouble...
312 * (And don't ask what happens if BIOS actually overflowed our entry buffer...)
314 * FIXME: Safer = revert previous entries, Safest = blacklist this BIOS.
319 /* Copy data to global buffer */
320 RtlCopyMemory(&PcBiosMemoryMap
[PcBiosMapCount
], (PVOID
)BIOSCALLBUFFER
, Regs
.x
.ecx
);
322 TRACE("BaseAddress: 0x%llx\n", PcBiosMemoryMap
[PcBiosMapCount
].BaseAddress
);
323 TRACE("Length: 0x%llx\n", PcBiosMemoryMap
[PcBiosMapCount
].Length
);
324 TRACE("Type: 0x%lx\n", PcBiosMemoryMap
[PcBiosMapCount
].Type
);
325 TRACE("Reserved: 0x%lx\n", PcBiosMemoryMap
[PcBiosMapCount
].Reserved
);
328 if (PcBiosMemoryMap
[PcBiosMapCount
].Length
== 0)
330 TRACE("Discard empty range. (would-be-PcBiosMapCount = %lu, BaseAddress = %lu, Length = 0)\n",
331 PcBiosMapCount
, PcBiosMemoryMap
[PcBiosMapCount
].BaseAddress
);
335 /* Check if this is free memory */
336 if (PcBiosMemoryMap
[PcBiosMapCount
].Type
== BiosMemoryUsable
)
338 MemoryType
= LoaderFree
;
340 /* Align up base of memory range */
341 RealBaseAddress
= ULONGLONG_ALIGN_UP_BY(
342 PcBiosMemoryMap
[PcBiosMapCount
].BaseAddress
,
345 /* Calculate aligned EndAddress */
346 EndAddress
= PcBiosMemoryMap
[PcBiosMapCount
].BaseAddress
+
347 PcBiosMemoryMap
[PcBiosMapCount
].Length
;
348 EndAddress
= ULONGLONG_ALIGN_DOWN_BY(EndAddress
, PAGE_SIZE
);
350 /* Check if there is anything left */
351 if (EndAddress
<= RealBaseAddress
)
353 /* This doesn't span any page, so continue with next range */
354 TRACE("Skipping aligned range < PAGE_SIZE. (would-be-PcBiosMapCount = %lu, BaseAddress = %lu, Length = %lu)\n",
356 PcBiosMemoryMap
[PcBiosMapCount
].BaseAddress
,
357 PcBiosMemoryMap
[PcBiosMapCount
].Length
);
361 /* Calculate the length of the aligned range */
362 RealSize
= EndAddress
- RealBaseAddress
;
366 if (PcBiosMemoryMap
[PcBiosMapCount
].Type
== BiosMemoryReserved
)
367 MemoryType
= LoaderFirmwarePermanent
;
369 MemoryType
= LoaderSpecialMemory
;
371 /* Align down base of memory area */
372 RealBaseAddress
= ULONGLONG_ALIGN_DOWN_BY(
373 PcBiosMemoryMap
[PcBiosMapCount
].BaseAddress
,
376 /* Calculate the length after aligning the base */
377 RealSize
= PcBiosMemoryMap
[PcBiosMapCount
].BaseAddress
+
378 PcBiosMemoryMap
[PcBiosMapCount
].Length
- RealBaseAddress
;
379 RealSize
= ULONGLONG_ALIGN_UP_BY(RealSize
, PAGE_SIZE
);
382 /* Check if we can add this descriptor */
383 if (RealSize
< MM_PAGE_SIZE
)
385 TRACE("Skipping aligned range < MM_PAGE_SIZE. (PcBiosMapCount = %lu, BaseAddress = %lu, Length = %lu)\n",
387 PcBiosMemoryMap
[PcBiosMapCount
].BaseAddress
,
388 PcBiosMemoryMap
[PcBiosMapCount
].Length
);
390 else if (PcMapCount
>= MaxMemoryMapSize
)
392 ERR("PcMemoryMap is already full! (PcBiosMapCount = %lu, PcMapCount = %lu (>= %lu))\n",
393 PcBiosMapCount
, PcMapCount
, MaxMemoryMapSize
);
394 // NotWantedForPublicBuilds: ASSERTMSG("PcMemoryMap is already full!", FALSE);
395 /* We keep previous entries, and half-retrieve current/next entries.
396 * We assume all these entries are good to use as is. If they are not, we are in trouble...
398 * FIXME: Safer = revert (half-)retrieved entries, Safest = increase MaxMemoryMapSize.
403 /* Add the descriptor */
404 PcMapCount
= AddMemoryDescriptor(PcMemoryMap
,
405 MAX_BIOS_DESCRIPTORS
,
406 (PFN_NUMBER
)(RealBaseAddress
/ MM_PAGE_SIZE
),
407 (PFN_NUMBER
)(RealSize
/ MM_PAGE_SIZE
),
414 /* If the continuation value is zero,
415 * then this was the last entry, so we're done. */
416 if (Regs
.x
.ebx
== 0x00000000)
418 TRACE("End of System Memory Map! (Reset)\n\n");
422 /* Check whether there would be more entries to process. */
423 if (PcBiosMapCount
>= MAX_BIOS_DESCRIPTORS
&& Regs
.x
.ebx
!= 0x00000000)
425 ERR("PcBiosMapCount is already full! (PcBiosMapCount = %lu (>= %lu), PcMapCount = %lu)\n",
426 PcBiosMapCount
, MAX_BIOS_DESCRIPTORS
, PcMapCount
);
427 // NotWantedForPublicBuilds: ASSERTMSG("PcBiosMapCount is already full!", FALSE);
428 /* We keep retrieved entries, but ignore next entries.
429 * We assume these entries are good to use as is. If they are not, we are in trouble...
431 * FIXME: Safer = revert retrieved entries, Safest = increase MAX_BIOS_DESCRIPTORS.
435 TRACE("PcMemGetBiosMemoryMap end: PcBiosMapCount = %lu\n", PcBiosMapCount
);
436 return PcBiosMapCount
;
441 ULONG_PTR BaseAddress
,
443 TYPE_OF_MEMORY MemoryType
,
446 ULONG_PTR BasePage
, PageCount
;
449 BasePage
= BaseAddress
/ PAGE_SIZE
;
450 PageCount
= ADDRESS_AND_SIZE_TO_SPAN_PAGES(BaseAddress
, Size
);
452 for (i
= 0; i
< PcMapCount
; i
++)
454 /* Check for conflicting descriptor */
455 if ((PcMemoryMap
[i
].BasePage
< BasePage
+ PageCount
) &&
456 (PcMemoryMap
[i
].BasePage
+ PcMemoryMap
[i
].PageCount
> BasePage
))
458 /* Check if the memory is free */
459 if (PcMemoryMap
[i
].MemoryType
!= LoaderFree
)
461 FrLdrBugCheckWithMessage(
465 "Failed to reserve memory in the range 0x%Ix - 0x%Ix for %s",
473 /* Add the memory descriptor */
474 PcMapCount
= AddMemoryDescriptor(PcMemoryMap
,
475 MAX_BIOS_DESCRIPTORS
,
483 ULONG_PTR BaseAddress
,
485 TYPE_OF_MEMORY MemoryType
)
487 ULONG_PTR BasePage
, PageCount
;
489 BasePage
= BaseAddress
/ PAGE_SIZE
;
490 PageCount
= ADDRESS_AND_SIZE_TO_SPAN_PAGES(BaseAddress
, Size
);
492 /* Add the memory descriptor */
493 PcMapCount
= AddMemoryDescriptor(PcMemoryMap
,
494 MAX_BIOS_DESCRIPTORS
,
500 PFREELDR_MEMORY_DESCRIPTOR
501 PcMemGetMemoryMap(ULONG
*MemoryMapSize
)
504 ULONG ExtendedMemorySizeAtOneMB
;
505 ULONG ExtendedMemorySizeAtSixteenMB
;
506 ULONG EbdaBase
, EbdaSize
;
508 TRACE("PcMemGetMemoryMap()\n");
510 EntryCount
= PcMemGetBiosMemoryMap(PcMemoryMap
, MAX_BIOS_DESCRIPTORS
);
512 /* If the BIOS didn't provide a memory map, synthesize one */
515 GetExtendedMemoryConfiguration(&ExtendedMemorySizeAtOneMB
,
516 &ExtendedMemorySizeAtSixteenMB
);
518 /* Conventional memory */
519 AddMemoryDescriptor(PcMemoryMap
,
520 MAX_BIOS_DESCRIPTORS
,
522 PcMemGetConventionalMemorySize() * 1024 / PAGE_SIZE
,
525 /* Extended memory */
526 PcMapCount
= AddMemoryDescriptor(PcMemoryMap
,
527 MAX_BIOS_DESCRIPTORS
,
528 1024 * 1024 / PAGE_SIZE
,
529 ExtendedMemorySizeAtOneMB
* 1024 / PAGE_SIZE
,
532 if (ExtendedMemorySizeAtSixteenMB
!= 0)
534 /* Extended memory at 16MB */
535 PcMapCount
= AddMemoryDescriptor(PcMemoryMap
,
536 MAX_BIOS_DESCRIPTORS
,
537 0x1000000 / PAGE_SIZE
,
538 ExtendedMemorySizeAtSixteenMB
* 64 * 1024 / PAGE_SIZE
,
542 /* Check if we have an EBDA and get it's location */
543 if (GetEbdaLocation(&EbdaBase
, &EbdaSize
))
545 /* Add the descriptor */
546 PcMapCount
= AddMemoryDescriptor(PcMemoryMap
,
547 MAX_BIOS_DESCRIPTORS
,
548 (EbdaBase
/ PAGE_SIZE
),
549 ADDRESS_AND_SIZE_TO_SPAN_PAGES(EbdaBase
, EbdaSize
),
550 LoaderFirmwarePermanent
);
554 /* Setup some protected ranges */
555 SetMemory(0x000000, 0x01000, LoaderFirmwarePermanent
); // Realmode IVT / BDA
556 SetMemory(0x0A0000, 0x50000, LoaderFirmwarePermanent
); // Video memory
557 SetMemory(0x0F0000, 0x10000, LoaderSpecialMemory
); // ROM
558 SetMemory(0xFFF000, 0x01000, LoaderSpecialMemory
); // unusable memory (do we really need this?)
560 /* Reserve some static ranges for freeldr */
561 ReserveMemory(0x1000, STACKLOW
- 0x1000, LoaderFirmwareTemporary
, "BIOS area");
562 ReserveMemory(STACKLOW
, STACKADDR
- STACKLOW
, LoaderOsloaderStack
, "FreeLdr stack");
563 ReserveMemory(FREELDR_BASE
, FrLdrImageSize
, LoaderLoadedProgram
, "FreeLdr image");
565 /* Default to 1 page above freeldr for the disk read buffer */
566 DiskReadBuffer
= (PUCHAR
)ALIGN_UP_BY(FREELDR_BASE
+ FrLdrImageSize
, PAGE_SIZE
);
567 DiskReadBufferSize
= PAGE_SIZE
;
569 /* Scan for free range above freeldr image */
570 for (i
= 0; i
< PcMapCount
; i
++)
572 if ((PcMemoryMap
[i
].BasePage
> (FREELDR_BASE
/ PAGE_SIZE
)) &&
573 (PcMemoryMap
[i
].MemoryType
== LoaderFree
))
575 /* Use this range for the disk read buffer */
576 DiskReadBuffer
= (PVOID
)(PcMemoryMap
[i
].BasePage
* PAGE_SIZE
);
577 DiskReadBufferSize
= min(PcMemoryMap
[i
].PageCount
* PAGE_SIZE
,
578 MAX_DISKREADBUFFER_SIZE
);
583 TRACE("DiskReadBuffer=%p, DiskReadBufferSize=%lx\n",
584 DiskReadBuffer
, DiskReadBufferSize
);
586 /* Now reserve the range for the disk read buffer */
587 ReserveMemory((ULONG_PTR
)DiskReadBuffer
,
589 LoaderFirmwareTemporary
,
592 TRACE("Dumping resulting memory map:\n");
593 for (i
= 0; i
< PcMapCount
; i
++)
595 TRACE("BasePage=0x%lx, PageCount=0x%lx, Type=%s\n",
596 PcMemoryMap
[i
].BasePage
,
597 PcMemoryMap
[i
].PageCount
,
598 MmGetSystemMemoryMapTypeString(PcMemoryMap
[i
].MemoryType
));
601 *MemoryMapSize
= PcMapCount
;