[FREELDR] Discard empty entries in PcMemGetBiosMemoryMap. CORE-13332
[reactos.git] / boot / freeldr / freeldr / arch / i386 / pcmem.c
1 /*
2 * FreeLoader
3 *
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.
8 *
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.
13 *
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.
17 *
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>
20 */
21
22 #include <freeldr.h>
23 #include <arch/pc/x86common.h>
24
25 #define NDEBUG
26 #include <debug.h>
27
28 DBG_DEFAULT_CHANNEL(MEMORY);
29
30 #define ULONGLONG_ALIGN_DOWN_BY(size, align) \
31 ((ULONGLONG)(size) & ~((ULONGLONG)(align) - 1))
32
33 #define ULONGLONG_ALIGN_UP_BY(size, align) \
34 (ULONGLONG_ALIGN_DOWN_BY(((ULONGLONG)(size) + align - 1), align))
35
36 #define MAX_BIOS_DESCRIPTORS 80
37
38 BIOS_MEMORY_MAP PcBiosMemoryMap[MAX_BIOS_DESCRIPTORS];
39 ULONG PcBiosMapCount;
40
41 FREELDR_MEMORY_DESCRIPTOR PcMemoryMap[MAX_BIOS_DESCRIPTORS + 1];
42 ULONG PcMapCount;
43
44 ULONG
45 AddMemoryDescriptor(
46 IN OUT PFREELDR_MEMORY_DESCRIPTOR List,
47 IN ULONG MaxCount,
48 IN PFN_NUMBER BasePage,
49 IN PFN_NUMBER PageCount,
50 IN TYPE_OF_MEMORY MemoryType);
51
52 static
53 BOOLEAN
54 GetExtendedMemoryConfiguration(ULONG* pMemoryAtOneMB /* in KB */, ULONG* pMemoryAtSixteenMB /* in 64KB */)
55 {
56 REGS RegsIn;
57 REGS RegsOut;
58
59 TRACE("GetExtendedMemoryConfiguration()\n");
60
61 *pMemoryAtOneMB = 0;
62 *pMemoryAtSixteenMB = 0;
63
64 // Int 15h AX=E801h
65 // Phoenix BIOS v4.0 - GET MEMORY SIZE FOR >64M CONFIGURATIONS
66 //
67 // AX = E801h
68 // Return:
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
74 // CF set on error
75 RegsIn.w.ax = 0xE801;
76 Int386(0x15, &RegsIn, &RegsOut);
77
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");
84
85 if (INT386_SUCCESS(RegsOut))
86 {
87 // If AX=BX=0000h the use CX and DX
88 if (RegsOut.w.ax == 0)
89 {
90 // Return extended memory size in K
91 *pMemoryAtSixteenMB = RegsOut.w.dx;
92 *pMemoryAtOneMB = RegsOut.w.cx;
93 return TRUE;
94 }
95 else
96 {
97 // Return extended memory size in K
98 *pMemoryAtSixteenMB = RegsOut.w.bx;
99 *pMemoryAtOneMB = RegsOut.w.ax;
100 return TRUE;
101 }
102 }
103
104 // If we get here then Int15 Func E801h didn't work
105 // So try Int15 Func 88h
106 // Int 15h AH=88h
107 // SYSTEM - GET EXTENDED MEMORY SIZE (286+)
108 //
109 // AH = 88h
110 // Return:
111 // CF clear if successful
112 // AX = number of contiguous KB starting at absolute address 100000h
113 // CF set on error
114 // AH = status
115 // 80h invalid command (PC,PCjr)
116 // 86h unsupported function (XT,PS30)
117 RegsIn.b.ah = 0x88;
118 Int386(0x15, &RegsIn, &RegsOut);
119
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");
123
124 if (INT386_SUCCESS(RegsOut) && RegsOut.w.ax != 0)
125 {
126 *pMemoryAtOneMB = RegsOut.w.ax;
127 return TRUE;
128 }
129
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);
136
137 TRACE("Int15h Failed\n");
138 TRACE("CMOS reports: 0x%lx\n", *pMemoryAtOneMB);
139
140 if (*pMemoryAtOneMB != 0)
141 {
142 return TRUE;
143 }
144
145 return FALSE;
146 }
147
148 static ULONG
149 PcMemGetConventionalMemorySize(VOID)
150 {
151 REGS Regs;
152
153 TRACE("PcMemGetConventionalMemorySize()\n");
154
155 /* Int 12h
156 * BIOS - GET MEMORY SIZE
157 *
158 * Return:
159 * AX = kilobytes of contiguous memory starting at absolute address 00000h
160 *
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
163 */
164 Regs.w.ax = 0;
165 Int386(0x12, &Regs, &Regs);
166
167 TRACE("Int12h\n");
168 TRACE("AX = 0x%x\n\n", Regs.w.ax);
169
170 return (ULONG)Regs.w.ax;
171 }
172
173 static
174 BOOLEAN
175 GetEbdaLocation(
176 PULONG BaseAddress,
177 PULONG Size)
178 {
179 REGS Regs;
180
181 /* Get the address of the Extended BIOS Data Area (EBDA).
182 * Int 15h, AH=C1h
183 * SYSTEM - RETURN EXTENDED-BIOS DATA-AREA SEGMENT ADDRESS (PS)
184 *
185 * Return:
186 * CF set on error
187 * CF clear if successful
188 * ES = segment of data area
189 */
190 Regs.x.eax = 0x0000C100;
191 Int386(0x15, &Regs, &Regs);
192
193 /* If the function fails, there is no EBDA */
194 if (!INT386_SUCCESS(Regs))
195 {
196 return FALSE;
197 }
198
199 /* Get Base address and (maximum) size */
200 *BaseAddress = (ULONG)Regs.w.es << 4;
201 *Size = 0xA0000 - *BaseAddress;
202 return TRUE;
203 }
204
205 static
206 ULONG
207 PcMemGetBiosMemoryMap(PFREELDR_MEMORY_DESCRIPTOR MemoryMap, ULONG MaxMemoryMapSize)
208 {
209 REGS Regs;
210 ULONGLONG RealBaseAddress, EndAddress, RealSize;
211 TYPE_OF_MEMORY MemoryType;
212 ULONG Size, RequiredSize;
213
214 ASSERT(PcBiosMapCount == 0);
215
216 TRACE("PcMemGetBiosMemoryMap()\n");
217
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
220 in KB */
221 Size = (*(PUSHORT)(ULONG_PTR)0x413) * 1024;
222 RequiredSize = FREELDR_BASE + FrLdrImageSize + PAGE_SIZE;
223 if (Size < RequiredSize)
224 {
225 FrLdrBugCheckWithMessage(
226 MEMORY_INIT_FAILURE,
227 __FILE__,
228 __LINE__,
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!",
232 Size, RequiredSize);
233 }
234
235 /* Int 15h AX=E820h
236 * Newer BIOSes - GET SYSTEM MEMORY MAP
237 *
238 * AX = E820h
239 * EAX = 0000E820h
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
244 * Return:
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
250 * CF set on error
251 * AH = error code (86h)
252 */
253 Regs.x.ebx = 0x00000000;
254
255 while (PcBiosMapCount < MAX_BIOS_DESCRIPTORS)
256 {
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);
265
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");
272
273 /* If the BIOS didn't return 'SMAP' in EAX then
274 * it doesn't support this call. */
275 if (Regs.x.eax != 0x534D4150)
276 {
277 WARN("BIOS doesn't support Int15h AX=E820h!\n\n");
278 break;
279 }
280
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))
284 {
285 TRACE("End of System Memory Map! (Past last)\n\n");
286 break;
287 }
288
289 if (Regs.x.ecx == 0)
290 {
291 TRACE("Discard empty entry. (would-be-PcBiosMapCount = %lu)\n",
292 PcBiosMapCount);
293 goto nextRange;
294 }
295
296 /* Copy data to global buffer */
297 RtlCopyMemory(&PcBiosMemoryMap[PcBiosMapCount], (PVOID)BIOSCALLBUFFER, Regs.x.ecx);
298
299 TRACE("BaseAddress: 0x%llx\n", PcBiosMemoryMap[PcBiosMapCount].BaseAddress);
300 TRACE("Length: 0x%llx\n", PcBiosMemoryMap[PcBiosMapCount].Length);
301 TRACE("Type: 0x%lx\n", PcBiosMemoryMap[PcBiosMapCount].Type);
302 TRACE("Reserved: 0x%lx\n", PcBiosMemoryMap[PcBiosMapCount].Reserved);
303 TRACE("\n");
304
305 if (PcBiosMemoryMap[PcBiosMapCount].Length == 0)
306 {
307 TRACE("Discard empty range. (would-be-PcBiosMapCount = %lu, BaseAddress = %lu, Length = 0)\n",
308 PcBiosMapCount, PcBiosMemoryMap[PcBiosMapCount].BaseAddress);
309 goto nextRange;
310 }
311
312 /* Check if this is free memory */
313 if (PcBiosMemoryMap[PcBiosMapCount].Type == BiosMemoryUsable)
314 {
315 MemoryType = LoaderFree;
316
317 /* Align up base of memory range */
318 RealBaseAddress = ULONGLONG_ALIGN_UP_BY(
319 PcBiosMemoryMap[PcBiosMapCount].BaseAddress,
320 PAGE_SIZE);
321
322 /* Calculate aligned EndAddress */
323 EndAddress = PcBiosMemoryMap[PcBiosMapCount].BaseAddress +
324 PcBiosMemoryMap[PcBiosMapCount].Length;
325 EndAddress = ULONGLONG_ALIGN_DOWN_BY(EndAddress, PAGE_SIZE);
326
327 /* Check if there is anything left */
328 if (EndAddress <= RealBaseAddress)
329 {
330 /* This doesn't span any page, so continue with next range */
331 TRACE("Skipping aligned range < PAGE_SIZE. (would-be-PcBiosMapCount = %lu, BaseAddress = %lu, Length = %lu)\n",
332 PcBiosMapCount,
333 PcBiosMemoryMap[PcBiosMapCount].BaseAddress,
334 PcBiosMemoryMap[PcBiosMapCount].Length);
335 goto nextRange;
336 }
337
338 /* Calculate the length of the aligned range */
339 RealSize = EndAddress - RealBaseAddress;
340 }
341 else
342 {
343 if (PcBiosMemoryMap[PcBiosMapCount].Type == BiosMemoryReserved)
344 MemoryType = LoaderFirmwarePermanent;
345 else
346 MemoryType = LoaderSpecialMemory;
347
348 /* Align down base of memory area */
349 RealBaseAddress = ULONGLONG_ALIGN_DOWN_BY(
350 PcBiosMemoryMap[PcBiosMapCount].BaseAddress,
351 PAGE_SIZE);
352
353 /* Calculate the length after aligning the base */
354 RealSize = PcBiosMemoryMap[PcBiosMapCount].BaseAddress +
355 PcBiosMemoryMap[PcBiosMapCount].Length - RealBaseAddress;
356 RealSize = ULONGLONG_ALIGN_UP_BY(RealSize, PAGE_SIZE);
357 }
358
359 /* Check if we can add this descriptor */
360 if (RealSize < MM_PAGE_SIZE)
361 {
362 TRACE("Skipping aligned range < MM_PAGE_SIZE. (PcBiosMapCount = %lu, BaseAddress = %lu, Length = %lu)\n",
363 PcBiosMapCount,
364 PcBiosMemoryMap[PcBiosMapCount].BaseAddress,
365 PcBiosMemoryMap[PcBiosMapCount].Length);
366 }
367 else if (PcMapCount >= MaxMemoryMapSize)
368 {
369 ERR("PcMemoryMap is already full! (PcBiosMapCount = %lu, PcMapCount = %lu (>= %lu))\n",
370 PcBiosMapCount, PcMapCount, MaxMemoryMapSize);
371 }
372 else
373 {
374 /* Add the descriptor */
375 PcMapCount = AddMemoryDescriptor(PcMemoryMap,
376 MAX_BIOS_DESCRIPTORS,
377 (PFN_NUMBER)(RealBaseAddress / MM_PAGE_SIZE),
378 (PFN_NUMBER)(RealSize / MM_PAGE_SIZE),
379 MemoryType);
380 }
381
382 PcBiosMapCount++;
383
384 nextRange:
385 /* If the continuation value is zero,
386 * then this was the last entry, so we're done. */
387 if (Regs.x.ebx == 0x00000000)
388 {
389 TRACE("End of System Memory Map! (Reset)\n\n");
390 break;
391 }
392 }
393
394 TRACE("PcMemGetBiosMemoryMap end: PcBiosMapCount = %lu\n", PcBiosMapCount);
395 return PcBiosMapCount;
396 }
397
398 VOID
399 ReserveMemory(
400 ULONG_PTR BaseAddress,
401 SIZE_T Size,
402 TYPE_OF_MEMORY MemoryType,
403 PCHAR Usage)
404 {
405 ULONG_PTR BasePage, PageCount;
406 ULONG i;
407
408 BasePage = BaseAddress / PAGE_SIZE;
409 PageCount = ADDRESS_AND_SIZE_TO_SPAN_PAGES(BaseAddress, Size);
410
411 for (i = 0; i < PcMapCount; i++)
412 {
413 /* Check for conflicting descriptor */
414 if ((PcMemoryMap[i].BasePage < BasePage + PageCount) &&
415 (PcMemoryMap[i].BasePage + PcMemoryMap[i].PageCount > BasePage))
416 {
417 /* Check if the memory is free */
418 if (PcMemoryMap[i].MemoryType != LoaderFree)
419 {
420 FrLdrBugCheckWithMessage(
421 MEMORY_INIT_FAILURE,
422 __FILE__,
423 __LINE__,
424 "Failed to reserve memory in the range 0x%Ix - 0x%Ix for %s",
425 BaseAddress,
426 Size,
427 Usage);
428 }
429 }
430 }
431
432 /* Add the memory descriptor */
433 PcMapCount = AddMemoryDescriptor(PcMemoryMap,
434 MAX_BIOS_DESCRIPTORS,
435 BasePage,
436 PageCount,
437 MemoryType);
438 }
439
440 VOID
441 SetMemory(
442 ULONG_PTR BaseAddress,
443 SIZE_T Size,
444 TYPE_OF_MEMORY MemoryType)
445 {
446 ULONG_PTR BasePage, PageCount;
447
448 BasePage = BaseAddress / PAGE_SIZE;
449 PageCount = ADDRESS_AND_SIZE_TO_SPAN_PAGES(BaseAddress, Size);
450
451 /* Add the memory descriptor */
452 PcMapCount = AddMemoryDescriptor(PcMemoryMap,
453 MAX_BIOS_DESCRIPTORS,
454 BasePage,
455 PageCount,
456 MemoryType);
457 }
458
459 PFREELDR_MEMORY_DESCRIPTOR
460 PcMemGetMemoryMap(ULONG *MemoryMapSize)
461 {
462 ULONG i, EntryCount;
463 ULONG ExtendedMemorySizeAtOneMB;
464 ULONG ExtendedMemorySizeAtSixteenMB;
465 ULONG EbdaBase, EbdaSize;
466
467 TRACE("PcMemGetMemoryMap()\n");
468
469 EntryCount = PcMemGetBiosMemoryMap(PcMemoryMap, MAX_BIOS_DESCRIPTORS);
470
471 /* If the BIOS didn't provide a memory map, synthesize one */
472 if (EntryCount == 0)
473 {
474 GetExtendedMemoryConfiguration(&ExtendedMemorySizeAtOneMB,
475 &ExtendedMemorySizeAtSixteenMB);
476
477 /* Conventional memory */
478 AddMemoryDescriptor(PcMemoryMap,
479 MAX_BIOS_DESCRIPTORS,
480 0,
481 PcMemGetConventionalMemorySize() * 1024 / PAGE_SIZE,
482 LoaderFree);
483
484 /* Extended memory */
485 PcMapCount = AddMemoryDescriptor(PcMemoryMap,
486 MAX_BIOS_DESCRIPTORS,
487 1024 * 1024 / PAGE_SIZE,
488 ExtendedMemorySizeAtOneMB * 1024 / PAGE_SIZE,
489 LoaderFree);
490
491 if (ExtendedMemorySizeAtSixteenMB != 0)
492 {
493 /* Extended memory at 16MB */
494 PcMapCount = AddMemoryDescriptor(PcMemoryMap,
495 MAX_BIOS_DESCRIPTORS,
496 0x1000000 / PAGE_SIZE,
497 ExtendedMemorySizeAtSixteenMB * 64 * 1024 / PAGE_SIZE,
498 LoaderFree);
499 }
500
501 /* Check if we have an EBDA and get it's location */
502 if (GetEbdaLocation(&EbdaBase, &EbdaSize))
503 {
504 /* Add the descriptor */
505 PcMapCount = AddMemoryDescriptor(PcMemoryMap,
506 MAX_BIOS_DESCRIPTORS,
507 (EbdaBase / PAGE_SIZE),
508 ADDRESS_AND_SIZE_TO_SPAN_PAGES(EbdaBase, EbdaSize),
509 LoaderFirmwarePermanent);
510 }
511 }
512
513 /* Setup some protected ranges */
514 SetMemory(0x000000, 0x01000, LoaderFirmwarePermanent); // Realmode IVT / BDA
515 SetMemory(0x0A0000, 0x50000, LoaderFirmwarePermanent); // Video memory
516 SetMemory(0x0F0000, 0x10000, LoaderSpecialMemory); // ROM
517 SetMemory(0xFFF000, 0x01000, LoaderSpecialMemory); // unusable memory (do we really need this?)
518
519 /* Reserve some static ranges for freeldr */
520 ReserveMemory(0x1000, STACKLOW - 0x1000, LoaderFirmwareTemporary, "BIOS area");
521 ReserveMemory(STACKLOW, STACKADDR - STACKLOW, LoaderOsloaderStack, "FreeLdr stack");
522 ReserveMemory(FREELDR_BASE, FrLdrImageSize, LoaderLoadedProgram, "FreeLdr image");
523
524 /* Default to 1 page above freeldr for the disk read buffer */
525 DiskReadBuffer = (PUCHAR)ALIGN_UP_BY(FREELDR_BASE + FrLdrImageSize, PAGE_SIZE);
526 DiskReadBufferSize = PAGE_SIZE;
527
528 /* Scan for free range above freeldr image */
529 for (i = 0; i < PcMapCount; i++)
530 {
531 if ((PcMemoryMap[i].BasePage > (FREELDR_BASE / PAGE_SIZE)) &&
532 (PcMemoryMap[i].MemoryType == LoaderFree))
533 {
534 /* Use this range for the disk read buffer */
535 DiskReadBuffer = (PVOID)(PcMemoryMap[i].BasePage * PAGE_SIZE);
536 DiskReadBufferSize = min(PcMemoryMap[i].PageCount * PAGE_SIZE,
537 MAX_DISKREADBUFFER_SIZE);
538 break;
539 }
540 }
541
542 TRACE("DiskReadBuffer=%p, DiskReadBufferSize=%lx\n",
543 DiskReadBuffer, DiskReadBufferSize);
544
545 /* Now reserve the range for the disk read buffer */
546 ReserveMemory((ULONG_PTR)DiskReadBuffer,
547 DiskReadBufferSize,
548 LoaderFirmwareTemporary,
549 "Disk read buffer");
550
551 TRACE("Dumping resulting memory map:\n");
552 for (i = 0; i < PcMapCount; i++)
553 {
554 TRACE("BasePage=0x%lx, PageCount=0x%lx, Type=%s\n",
555 PcMemoryMap[i].BasePage,
556 PcMemoryMap[i].PageCount,
557 MmGetSystemMemoryMapTypeString(PcMemoryMap[i].MemoryType));
558 }
559
560 *MemoryMapSize = PcMapCount;
561 return PcMemoryMap;
562 }
563
564
565 /* EOF */