3f15155fdbd042ad0dc484c293c20cfab0e72ebc
[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 MAX_BIOS_DESCRIPTORS 80
31
32 BIOS_MEMORY_MAP PcBiosMemoryMap[MAX_BIOS_DESCRIPTORS];
33 ULONG PcBiosMapCount;
34
35 FREELDR_MEMORY_DESCRIPTOR PcMemoryMap[MAX_BIOS_DESCRIPTORS + 1];
36 ULONG PcMapCount;
37
38 ULONG
39 AddMemoryDescriptor(
40 IN OUT PFREELDR_MEMORY_DESCRIPTOR List,
41 IN ULONG MaxCount,
42 IN PFN_NUMBER BasePage,
43 IN PFN_NUMBER PageCount,
44 IN TYPE_OF_MEMORY MemoryType);
45
46 static
47 BOOLEAN
48 GetExtendedMemoryConfiguration(ULONG* pMemoryAtOneMB /* in KB */, ULONG* pMemoryAtSixteenMB /* in 64KB */)
49 {
50 REGS RegsIn;
51 REGS RegsOut;
52
53 TRACE("GetExtendedMemoryConfiguration()\n");
54
55 *pMemoryAtOneMB = 0;
56 *pMemoryAtSixteenMB = 0;
57
58 // Int 15h AX=E801h
59 // Phoenix BIOS v4.0 - GET MEMORY SIZE FOR >64M CONFIGURATIONS
60 //
61 // AX = E801h
62 // Return:
63 // CF clear if successful
64 // AX = extended memory between 1M and 16M, in K (max 3C00h = 15MB)
65 // BX = extended memory above 16M, in 64K blocks
66 // CX = configured memory 1M to 16M, in K
67 // DX = configured memory above 16M, in 64K blocks
68 // CF set on error
69 RegsIn.w.ax = 0xE801;
70 Int386(0x15, &RegsIn, &RegsOut);
71
72 TRACE("Int15h AX=E801h\n");
73 TRACE("AX = 0x%x\n", RegsOut.w.ax);
74 TRACE("BX = 0x%x\n", RegsOut.w.bx);
75 TRACE("CX = 0x%x\n", RegsOut.w.cx);
76 TRACE("DX = 0x%x\n", RegsOut.w.dx);
77 TRACE("CF set = %s\n\n", (RegsOut.x.eflags & EFLAGS_CF) ? "TRUE" : "FALSE");
78
79 if (INT386_SUCCESS(RegsOut))
80 {
81 // If AX=BX=0000h the use CX and DX
82 if (RegsOut.w.ax == 0)
83 {
84 // Return extended memory size in K
85 *pMemoryAtSixteenMB = RegsOut.w.dx;
86 *pMemoryAtOneMB = RegsOut.w.cx;
87 return TRUE;
88 }
89 else
90 {
91 // Return extended memory size in K
92 *pMemoryAtSixteenMB = RegsOut.w.bx;
93 *pMemoryAtOneMB = RegsOut.w.ax;
94 return TRUE;
95 }
96 }
97
98 // If we get here then Int15 Func E801h didn't work
99 // So try Int15 Func 88h
100 // Int 15h AH=88h
101 // SYSTEM - GET EXTENDED MEMORY SIZE (286+)
102 //
103 // AH = 88h
104 // Return:
105 // CF clear if successful
106 // AX = number of contiguous KB starting at absolute address 100000h
107 // CF set on error
108 // AH = status
109 // 80h invalid command (PC,PCjr)
110 // 86h unsupported function (XT,PS30)
111 RegsIn.b.ah = 0x88;
112 Int386(0x15, &RegsIn, &RegsOut);
113
114 TRACE("Int15h AH=88h\n");
115 TRACE("AX = 0x%x\n", RegsOut.w.ax);
116 TRACE("CF set = %s\n\n", (RegsOut.x.eflags & EFLAGS_CF) ? "TRUE" : "FALSE");
117
118 if (INT386_SUCCESS(RegsOut) && RegsOut.w.ax != 0)
119 {
120 *pMemoryAtOneMB = RegsOut.w.ax;
121 return TRUE;
122 }
123
124 // If we get here then Int15 Func 88h didn't work
125 // So try reading the CMOS
126 WRITE_PORT_UCHAR((PUCHAR)0x70, 0x31);
127 *pMemoryAtOneMB = READ_PORT_UCHAR((PUCHAR)0x71);
128 *pMemoryAtOneMB = (*pMemoryAtOneMB & 0xFFFF);
129 *pMemoryAtOneMB = (*pMemoryAtOneMB << 8);
130
131 TRACE("Int15h Failed\n");
132 TRACE("CMOS reports: 0x%x\n", *pMemoryAtOneMB);
133
134 if (*pMemoryAtOneMB != 0)
135 {
136 return TRUE;
137 }
138
139 return FALSE;
140 }
141
142 static ULONG
143 PcMemGetConventionalMemorySize(VOID)
144 {
145 REGS Regs;
146
147 TRACE("GetConventionalMemorySize()\n");
148
149 /* Int 12h
150 * BIOS - GET MEMORY SIZE
151 *
152 * Return:
153 * AX = kilobytes of contiguous memory starting at absolute address 00000h
154 *
155 * This call returns the contents of the word at 0040h:0013h;
156 * in PC and XT, this value is set from the switches on the motherboard
157 */
158 Regs.w.ax = 0;
159 Int386(0x12, &Regs, &Regs);
160
161 TRACE("Int12h\n");
162 TRACE("AX = 0x%x\n\n", Regs.w.ax);
163
164 return (ULONG)Regs.w.ax;
165 }
166
167 static
168 BOOLEAN
169 GetEbdaLocation(
170 PULONG BaseAddress,
171 PULONG Size)
172 {
173 REGS Regs;
174
175 /* Get the address of the Extended BIOS Data Area (EBDA).
176 * Int 15h, AH=C1h
177 * SYSTEM - RETURN EXTENDED-BIOS DATA-AREA SEGMENT ADDRESS (PS)
178 *
179 * Return:
180 * CF set on error
181 * CF clear if successful
182 * ES = segment of data area
183 */
184 Regs.x.eax = 0x0000C100;
185 Int386(0x15, &Regs, &Regs);
186
187 /* If the function fails, there is no EBDA */
188 if (!INT386_SUCCESS(Regs))
189 {
190 return FALSE;
191 }
192
193 /* Get Base address and (maximum) size */
194 *BaseAddress = (ULONG)Regs.w.es << 4;
195 *Size = 0xA0000 - *BaseAddress;
196 return TRUE;
197 }
198
199 static
200 ULONG
201 PcMemGetBiosMemoryMap(PFREELDR_MEMORY_DESCRIPTOR MemoryMap, ULONG MaxMemoryMapSize)
202 {
203 REGS Regs;
204 ULONGLONG RealBaseAddress, EndAddress, RealSize;
205 TYPE_OF_MEMORY MemoryType;
206 ULONG Size, RequiredSize;
207 ASSERT(PcBiosMapCount == 0);
208
209 TRACE("GetBiosMemoryMap()\n");
210
211 /* Make sure the usable memory is large enough. To do this we check the 16
212 bit value at address 0x413 inside the BDA, which gives us the usable size
213 in KB */
214 Size = (*(PUSHORT)(ULONG_PTR)0x413) * 1024;
215 RequiredSize = FREELDR_BASE + FrLdrImageSize + PAGE_SIZE;
216 if (Size < RequiredSize)
217 {
218 FrLdrBugCheckWithMessage(
219 MEMORY_INIT_FAILURE,
220 __FILE__,
221 __LINE__,
222 "The BIOS reported a usable memory range up to 0x%x, which is too small!\n"
223 "Required size is 0x%x\n\n"
224 "If you see this, please report to the ReactOS team!",
225 Size, RequiredSize);
226 }
227
228 /* Int 15h AX=E820h
229 * Newer BIOSes - GET SYSTEM MEMORY MAP
230 *
231 * AX = E820h
232 * EAX = 0000E820h
233 * EDX = 534D4150h ('SMAP')
234 * EBX = continuation value or 00000000h to start at beginning of map
235 * ECX = size of buffer for result, in bytes (should be >= 20 bytes)
236 * ES:DI -> buffer for result
237 * Return:
238 * CF clear if successful
239 * EAX = 534D4150h ('SMAP')
240 * ES:DI buffer filled
241 * EBX = next offset from which to copy or 00000000h if all done
242 * ECX = actual length returned in bytes
243 * CF set on error
244 * AH = error code (86h)
245 */
246 Regs.x.ebx = 0x00000000;
247
248 while (PcBiosMapCount < MAX_BIOS_DESCRIPTORS)
249 {
250 /* Setup the registers for the BIOS call */
251 Regs.x.eax = 0x0000E820;
252 Regs.x.edx = 0x534D4150; /* ('SMAP') */
253 /* Regs.x.ebx = 0x00000001; Continuation value already set */
254 Regs.x.ecx = sizeof(BIOS_MEMORY_MAP);
255 Regs.w.es = BIOSCALLBUFSEGMENT;
256 Regs.w.di = BIOSCALLBUFOFFSET;
257 Int386(0x15, &Regs, &Regs);
258
259 TRACE("Memory Map Entry %d\n", PcBiosMapCount);
260 TRACE("Int15h AX=E820h\n");
261 TRACE("EAX = 0x%x\n", Regs.x.eax);
262 TRACE("EBX = 0x%x\n", Regs.x.ebx);
263 TRACE("ECX = 0x%x\n", Regs.x.ecx);
264 TRACE("CF set = %s\n", (Regs.x.eflags & EFLAGS_CF) ? "TRUE" : "FALSE");
265
266 /* If the BIOS didn't return 'SMAP' in EAX then
267 * it doesn't support this call. If CF is set, we're done */
268 if (Regs.x.eax != 0x534D4150 || !INT386_SUCCESS(Regs))
269 {
270 break;
271 }
272
273 /* Copy data to global buffer */
274 RtlCopyMemory(&PcBiosMemoryMap[PcBiosMapCount], (PVOID)BIOSCALLBUFFER, Regs.x.ecx);
275
276 TRACE("BaseAddress: 0x%llx\n", PcBiosMemoryMap[PcBiosMapCount].BaseAddress);
277 TRACE("Length: 0x%llx\n", PcBiosMemoryMap[PcBiosMapCount].Length);
278 TRACE("Type: 0x%lx\n", PcBiosMemoryMap[PcBiosMapCount].Type);
279 TRACE("Reserved: 0x%lx\n", PcBiosMemoryMap[PcBiosMapCount].Reserved);
280 TRACE("\n");
281
282 /* Check if this is free memory */
283 if (PcBiosMemoryMap[PcBiosMapCount].Type == BiosMemoryUsable)
284 {
285 MemoryType = LoaderFree;
286
287 /* Align up base of memory range */
288 RealBaseAddress = ALIGN_UP_BY(PcBiosMemoryMap[PcBiosMapCount].BaseAddress,
289 PAGE_SIZE);
290
291 /* Calculate aligned EndAddress */
292 EndAddress = PcBiosMemoryMap[PcBiosMapCount].BaseAddress +
293 PcBiosMemoryMap[PcBiosMapCount].Length;
294 EndAddress = ALIGN_DOWN_BY(EndAddress, PAGE_SIZE);
295
296 /* Check if there is anything left */
297 if (EndAddress <= RealBaseAddress)
298 {
299 /* This doesn't span any page, so continue with next range */
300 continue;
301 }
302
303 /* Calculate the length of the aligned range */
304 RealSize = EndAddress - RealBaseAddress;
305 }
306 else
307 {
308 if (PcBiosMemoryMap[PcBiosMapCount].Type == BiosMemoryReserved)
309 MemoryType = LoaderFirmwarePermanent;
310 else
311 MemoryType = LoaderSpecialMemory;
312
313 /* Align down base of memory area */
314 RealBaseAddress = ALIGN_DOWN_BY(PcBiosMemoryMap[PcBiosMapCount].BaseAddress,
315 PAGE_SIZE);
316
317 /* Calculate the length after aligning the base */
318 RealSize = PcBiosMemoryMap[PcBiosMapCount].BaseAddress +
319 PcBiosMemoryMap[PcBiosMapCount].Length - RealBaseAddress;
320 RealSize = ALIGN_UP_BY(RealSize, PAGE_SIZE);
321 }
322
323 /* Check if we can add this descriptor */
324 if ((RealSize >= MM_PAGE_SIZE) && (PcMapCount < MaxMemoryMapSize))
325 {
326 /* Add the descriptor */
327 PcMapCount = AddMemoryDescriptor(PcMemoryMap,
328 MAX_BIOS_DESCRIPTORS,
329 (PFN_NUMBER)(RealBaseAddress / MM_PAGE_SIZE),
330 (PFN_NUMBER)(RealSize / MM_PAGE_SIZE),
331 MemoryType);
332 }
333
334 PcBiosMapCount++;
335
336 /* If the continuation value is zero or the
337 * carry flag is set then this was
338 * the last entry so we're done */
339 if (Regs.x.ebx == 0x00000000)
340 {
341 TRACE("End Of System Memory Map!\n\n");
342 break;
343 }
344 }
345
346 TRACE("GetBiosMemoryMap end, PcBiosMapCount = %ld\n", PcBiosMapCount);
347 return PcBiosMapCount;
348 }
349
350 VOID
351 ReserveMemory(
352 ULONG_PTR BaseAddress,
353 SIZE_T Size,
354 TYPE_OF_MEMORY MemoryType,
355 PCHAR Usage)
356 {
357 ULONG_PTR BasePage, PageCount;
358 ULONG i;
359
360 BasePage = BaseAddress / PAGE_SIZE;
361 PageCount = ADDRESS_AND_SIZE_TO_SPAN_PAGES(BaseAddress, Size);
362
363 for (i = 0; i < PcMapCount; i++)
364 {
365 /* Check for conflicting descriptor */
366 if ((PcMemoryMap[i].BasePage < BasePage + PageCount) &&
367 (PcMemoryMap[i].BasePage + PcMemoryMap[i].PageCount > BasePage))
368 {
369 /* Check if the memory is free */
370 if (PcMemoryMap[i].MemoryType != LoaderFree)
371 {
372 FrLdrBugCheckWithMessage(
373 MEMORY_INIT_FAILURE,
374 __FILE__,
375 __LINE__,
376 "Failed to reserve memory in the range 0x%Ix - 0x%Ix for %s",
377 BaseAddress,
378 Size,
379 Usage);
380 }
381 }
382 }
383
384 /* Add the memory descriptor */
385 PcMapCount = AddMemoryDescriptor(PcMemoryMap,
386 MAX_BIOS_DESCRIPTORS,
387 BasePage,
388 PageCount,
389 MemoryType);
390 }
391
392 VOID
393 SetMemory(
394 ULONG_PTR BaseAddress,
395 SIZE_T Size,
396 TYPE_OF_MEMORY MemoryType)
397 {
398 ULONG_PTR BasePage, PageCount;
399
400 BasePage = BaseAddress / PAGE_SIZE;
401 PageCount = ADDRESS_AND_SIZE_TO_SPAN_PAGES(BaseAddress, Size);
402
403 /* Add the memory descriptor */
404 PcMapCount = AddMemoryDescriptor(PcMemoryMap,
405 MAX_BIOS_DESCRIPTORS,
406 BasePage,
407 PageCount,
408 MemoryType);
409 }
410
411 PFREELDR_MEMORY_DESCRIPTOR
412 PcMemGetMemoryMap(ULONG *MemoryMapSize)
413 {
414 ULONG i, EntryCount;
415 ULONG ExtendedMemorySizeAtOneMB;
416 ULONG ExtendedMemorySizeAtSixteenMB;
417 ULONG EbdaBase, EbdaSize;
418 TRACE("PcMemGetMemoryMap()\n");
419
420 EntryCount = PcMemGetBiosMemoryMap(PcMemoryMap, MAX_BIOS_DESCRIPTORS);
421
422 /* If the BIOS didn't provide a memory map, synthesize one */
423 if (EntryCount == 0)
424 {
425 GetExtendedMemoryConfiguration(&ExtendedMemorySizeAtOneMB,
426 &ExtendedMemorySizeAtSixteenMB);
427
428 /* Conventional memory */
429 AddMemoryDescriptor(PcMemoryMap,
430 MAX_BIOS_DESCRIPTORS,
431 0,
432 PcMemGetConventionalMemorySize() * 1024 / PAGE_SIZE,
433 LoaderFree);
434
435 /* Extended memory */
436 PcMapCount = AddMemoryDescriptor(PcMemoryMap,
437 MAX_BIOS_DESCRIPTORS,
438 1024 * 1024 / PAGE_SIZE,
439 ExtendedMemorySizeAtOneMB * 1024 / PAGE_SIZE,
440 LoaderFree);
441
442 if (ExtendedMemorySizeAtSixteenMB != 0)
443 {
444 /* Extended memory at 16MB */
445 PcMapCount = AddMemoryDescriptor(PcMemoryMap,
446 MAX_BIOS_DESCRIPTORS,
447 0x1000000 / PAGE_SIZE,
448 ExtendedMemorySizeAtSixteenMB * 64 * 1024 / PAGE_SIZE,
449 LoaderFree);
450 }
451
452 /* Check if we have an EBDA and get it's location */
453 if (GetEbdaLocation(&EbdaBase, &EbdaSize))
454 {
455 /* Add the descriptor */
456 PcMapCount = AddMemoryDescriptor(PcMemoryMap,
457 MAX_BIOS_DESCRIPTORS,
458 (EbdaBase / PAGE_SIZE),
459 ADDRESS_AND_SIZE_TO_SPAN_PAGES(EbdaBase, EbdaSize),
460 LoaderFirmwarePermanent);
461 }
462 }
463
464 /* Setup some protected ranges */
465 SetMemory(0x000000, 0x01000, LoaderFirmwarePermanent); // Realmode IVT / BDA
466 SetMemory(0x0A0000, 0x50000, LoaderFirmwarePermanent); // Video memory
467 SetMemory(0x0F0000, 0x10000, LoaderSpecialMemory); // ROM
468 SetMemory(0xFFF000, 0x01000, LoaderSpecialMemory); // unusable memory (do we really need this?)
469
470 /* Reserve some static ranges for freeldr */
471 ReserveMemory(0x1000, STACKLOW - 0x1000, LoaderFirmwareTemporary, "BIOS area");
472 ReserveMemory(STACKLOW, STACKADDR - STACKLOW, LoaderOsloaderStack, "FreeLdr stack");
473 ReserveMemory(FREELDR_BASE, FrLdrImageSize, LoaderLoadedProgram, "FreeLdr image");
474
475 /* Default to 1 page above freeldr for the disk read buffer */
476 DiskReadBuffer = (PUCHAR)ALIGN_UP_BY(FREELDR_BASE + FrLdrImageSize, PAGE_SIZE);
477 DiskReadBufferSize = PAGE_SIZE;
478
479 /* Scan for free range above freeldr image */
480 for (i = 0; i < PcMapCount; i++)
481 {
482 if ((PcMemoryMap[i].BasePage > (FREELDR_BASE / PAGE_SIZE)) &&
483 (PcMemoryMap[i].MemoryType == LoaderFree))
484 {
485 /* Use this range for the disk read buffer */
486 DiskReadBuffer = (PVOID)(PcMemoryMap[i].BasePage * PAGE_SIZE);
487 DiskReadBufferSize = min(PcMemoryMap[i].PageCount * PAGE_SIZE,
488 MAX_DISKREADBUFFER_SIZE);
489 break;
490 }
491 }
492
493 TRACE("DiskReadBuffer=%p, DiskReadBufferSize=%lx\n",
494 DiskReadBuffer, DiskReadBufferSize);
495
496 /* Now reserve the range for the disk read buffer */
497 ReserveMemory((ULONG_PTR)DiskReadBuffer,
498 DiskReadBufferSize,
499 LoaderFirmwareTemporary,
500 "Disk read buffer");
501
502 TRACE("Dumping resulting memory map:\n");
503 for (i = 0; i < PcMapCount; i++)
504 {
505 TRACE("BasePage=0x%lx, PageCount=0x%lx, Type=%s\n",
506 PcMemoryMap[i].BasePage,
507 PcMemoryMap[i].PageCount,
508 MmGetSystemMemoryMapTypeString(PcMemoryMap[i].MemoryType));
509 }
510
511 *MemoryMapSize = PcMapCount;
512 return PcMemoryMap;
513 }
514
515
516 /* EOF */