Adding upstream version 4.00~pre61+dfsg.
[syslinux-debian/hramrach.git] / com32 / gpllib / memory.c
blob28a95ff4e55ba97e52051aa5c82643f1d90e9621
1 /* ----------------------------------------------------------------------- *
3 * Copyright 2009 Pierre-Alexandre Meyer
5 * Some parts borrowed from meminfo.c32:
7 * Copyright 2003-2009 H. Peter Anvin - All Rights Reserved
8 * Copyright 2009 Intel Corporation; author: H. Peter Anvin
10 * Some parts borrowed from Linux:
12 * Copyright (C) 1991, 1992 Linus Torvalds
13 * Copyright 2007 rPath, Inc. - All Rights Reserved
14 * Copyright 2009 Intel Corporation; author H. Peter Anvin
16 * Interrupt list from Ralf Brown (http://www.cs.cmu.edu/~ralf/files.html)
18 * This file is part of Syslinux, and is made available under
19 * the terms of the GNU General Public License version 2.
21 * ----------------------------------------------------------------------- */
23 #include <stdint.h>
24 #include <com32.h>
25 #include <string.h>
26 #include <memory.h>
28 const char *const e820_types[] = {
29 "usable",
30 "reserved",
31 "ACPI reclaim",
32 "ACPI NVS",
33 "unusable",
36 struct e820_ext_entry {
37 struct e820entry std;
38 uint32_t ext_flags;
39 } __attribute__ ((packed));
41 #define SMAP 0x534d4150 /* ASCII "SMAP" */
43 void get_type(int type, char *type_ptr, int type_ptr_sz)
45 unsigned int real_type = type - 1;
46 if (real_type < sizeof(e820_types) / sizeof(e820_types[0]))
47 strlcpy(type_ptr, e820_types[real_type], type_ptr_sz);
50 /**
51 *INT 15 - newer BIOSes - GET SYSTEM MEMORY MAP
52 * AX = E820h
53 * EAX = 0000E820h
54 * EDX = 534D4150h ('SMAP')
55 * EBX = continuation value or 00000000h to start at beginning of map
56 * ECX = size of buffer for result, in bytes (should be >= 20 bytes)
57 * ES:DI -> buffer for result (see #00581)
59 * Return: CF clear if successful
60 * EAX = 534D4150h ('SMAP')
61 * ES:DI buffer filled
62 * EBX = next offset from which to copy or 00000000h if all done
63 * ECX = actual length returned in bytes
64 * CF set on error
65 * AH = error code (86h) (see #00496 at INT 15/AH=80h)
67 * Notes: originally introduced with the Phoenix BIOS v4.0, this function is
68 * now supported by most newer BIOSes, since various versions of Windows
69 * call it to find out about the system memory
70 * a maximum of 20 bytes will be transferred at one time, even if ECX is
71 * higher; some BIOSes (e.g. Award Modular BIOS v4.50PG) ignore the
72 * value of ECX on entry, and always copy 20 bytes
73 * some BIOSes expect the high word of EAX to be clear on entry, i.e.
74 * EAX=0000E820h
75 * if this function is not supported, an application should fall back
76 * to AX=E802h, AX=E801h, and then AH=88h
77 * the BIOS is permitted to return a nonzero continuation value in EBX
78 * and indicate that the end of the list has already been reached by
79 * returning with CF set on the next iteration
80 * this function will return base memory and ISA/PCI memory contiguous
81 * with base memory as normal memory ranges; it will indicate
82 * chipset-defined address holes which are not in use and motherboard
83 * memory-mapped devices, and all occurrences of the system BIOS as
84 * reserved; standard PC address ranges will not be reported
85 **/
86 void detect_memory_e820(struct e820entry *desc, int size_map, int *size_found)
88 int count = 0;
89 static struct e820_ext_entry buf; /* static so it is zeroed */
91 com32sys_t ireg, oreg;
92 memset(&ireg, 0, sizeof ireg);
94 ireg.eax.w[0] = 0xe820;
95 ireg.edx.l = SMAP;
96 ireg.ecx.l = sizeof(struct e820_ext_entry);
97 ireg.edi.w[0] = OFFS(__com32.cs_bounce);
98 ireg.es = SEG(__com32.cs_bounce);
101 * Set this here so that if the BIOS doesn't change this field
102 * but still doesn't change %ecx, we're still okay...
104 memset(&buf, 0, sizeof buf);
105 buf.ext_flags = 1;
107 do {
108 memcpy(__com32.cs_bounce, &buf, sizeof buf);
110 /* Important: %edx and %esi are clobbered by some BIOSes,
111 so they must be either used for the error output
112 or explicitly marked clobbered. Given that, assume there
113 is something out there clobbering %ebp and %edi, too. */
114 __intcall(0x15, &ireg, &oreg);
116 /* Some BIOSes stop returning SMAP in the middle of
117 the search loop. We don't know exactly how the BIOS
118 screwed up the map at that point, we might have a
119 partial map, the full map, or complete garbage, so
120 just return failure. */
121 if (oreg.eax.l != SMAP) {
122 count = 0;
123 break;
126 if (oreg.eflags.l & EFLAGS_CF || oreg.ecx.l < 20)
127 break;
129 memcpy(&buf, __com32.cs_bounce, sizeof buf);
132 * ACPI 3.0 added the extended flags support. If bit 0
133 * in the extended flags is zero, we're supposed to simply
134 * ignore the entry -- a backwards incompatible change!
136 if (oreg.ecx.l > 20 && !(buf.ext_flags & 1))
137 continue;
139 memcpy(&desc[count], &buf, sizeof buf);
140 count++;
142 /* Set continuation value */
143 ireg.ebx.l = oreg.ebx.l;
144 } while (ireg.ebx.l && count < size_map);
146 *size_found = count;
150 * detect_memory_e801
152 *INT 15 - Phoenix BIOS v4.0 - GET MEMORY SIZE FOR >64M CONFIGURATIONS
153 * AX = E801h
155 * Return: CF clear if successful
156 * AX = extended memory between 1M and 16M, in K (max 3C00h = 15MB)
157 * BX = extended memory above 16M, in 64K blocks
158 * CX = configured memory 1M to 16M, in K
159 * DX = configured memory above 16M, in 64K blocks
160 * CF set on error
162 * Notes: supported by the A03 level (6/14/94) and later XPS P90 BIOSes, as well
163 * as the Compaq Contura, 3/8/93 DESKPRO/i, and 7/26/93 LTE Lite 386 ROM
164 * BIOS
165 * supported by AMI BIOSes dated 8/23/94 or later
166 * on some systems, the BIOS returns AX=BX=0000h; in this case, use CX
167 * and DX instead of AX and BX
168 * this interface is used by Windows NT 3.1, OS/2 v2.11/2.20, and is
169 * used as a fall-back by newer versions if AX=E820h is not supported
170 * this function is not used by MS-DOS 6.0 HIMEM.SYS when an EISA machine
171 * (for example with parameter /EISA) (see also MEM F000h:FFD9h), or no
172 * Compaq machine was detected, or parameter /NOABOVE16 was given.
174 int detect_memory_e801(int *mem_size_below_16, int *mem_size_above_16)
176 com32sys_t ireg, oreg;
177 memset(&ireg, 0, sizeof ireg);
179 ireg.eax.w[0] = 0xe801;
181 __intcall(0x15, &ireg, &oreg);
183 if (oreg.eflags.l & EFLAGS_CF)
184 return -1;
186 if (oreg.eax.w[0] > 0x3c00)
187 return -1; /* Bogus! */
189 /* Linux seems to use ecx and edx by default if they are defined */
190 if (oreg.eax.w[0] || oreg.eax.w[0]) {
191 oreg.eax.w[0] = oreg.ecx.w[0];
192 oreg.ebx.w[0] = oreg.edx.w[0];
195 *mem_size_below_16 = oreg.eax.w[0]; /* 1K blocks */
196 *mem_size_above_16 = oreg.ebx.w[0]; /* 64K blocks */
198 return 0;
201 int detect_memory_88(int *mem_size)
203 com32sys_t ireg, oreg;
204 memset(&ireg, 0, sizeof ireg);
206 ireg.eax.w[0] = 0x8800;
208 __intcall(0x15, &ireg, &oreg);
210 if (oreg.eflags.l & EFLAGS_CF)
211 return -1;
213 *mem_size = oreg.eax.w[0];
214 return 0;
218 * Sanitize the BIOS e820 map.
220 * This code come from the memtest86 project. It have been adjusted to match
221 * the syslinux environement.
222 * Some e820 responses include overlapping entries. The following
223 * replaces the original e820 map with a new one, removing overlaps.
225 * The following stuff could be merge once the addr_t will be set to 64bits.
226 * syslinux_scan_memory can be used for that purpose
228 int sanitize_e820_map(struct e820entry *orig_map, struct e820entry *new_bios,
229 short old_nr)
231 struct change_member {
232 struct e820entry *pbios; /* pointer to original bios entry */
233 unsigned long long addr; /* address for this change point */
235 struct change_member change_point_list[2 * E820MAX];
236 struct change_member *change_point[2 * E820MAX];
237 struct e820entry *overlap_list[E820MAX];
238 struct e820entry biosmap[E820MAX];
239 struct change_member *change_tmp;
240 unsigned long current_type, last_type;
241 unsigned long long last_addr;
242 int chgidx, still_changing;
243 int overlap_entries;
244 int new_bios_entry;
245 int i;
248 Visually we're performing the following (1,2,3,4 = memory types)...
249 Sample memory map (w/overlaps):
250 ____22__________________
251 ______________________4_
252 ____1111________________
253 _44_____________________
254 11111111________________
255 ____________________33__
256 ___________44___________
257 __________33333_________
258 ______________22________
259 ___________________2222_
260 _________111111111______
261 _____________________11_
262 _________________4______
264 Sanitized equivalent (no overlap):
265 1_______________________
266 _44_____________________
267 ___1____________________
268 ____22__________________
269 ______11________________
270 _________1______________
271 __________3_____________
272 ___________44___________
273 _____________33_________
274 _______________2________
275 ________________1_______
276 _________________4______
277 ___________________2____
278 ____________________33__
279 ______________________4_
281 /* First make a copy of the map */
282 for (i = 0; i < old_nr; i++) {
283 biosmap[i].addr = orig_map[i].addr;
284 biosmap[i].size = orig_map[i].size;
285 biosmap[i].type = orig_map[i].type;
288 /* bail out if we find any unreasonable addresses in bios map */
289 for (i = 0; i < old_nr; i++) {
290 if (biosmap[i].addr + biosmap[i].size < biosmap[i].addr)
291 return 0;
294 /* create pointers for initial change-point information (for sorting) */
295 for (i = 0; i < 2 * old_nr; i++)
296 change_point[i] = &change_point_list[i];
298 /* record all known change-points (starting and ending addresses) */
299 chgidx = 0;
300 for (i = 0; i < old_nr; i++) {
301 change_point[chgidx]->addr = biosmap[i].addr;
302 change_point[chgidx++]->pbios = &biosmap[i];
303 change_point[chgidx]->addr = biosmap[i].addr + biosmap[i].size;
304 change_point[chgidx++]->pbios = &biosmap[i];
307 /* sort change-point list by memory addresses (low -> high) */
308 still_changing = 1;
309 while (still_changing) {
310 still_changing = 0;
311 for (i = 1; i < 2 * old_nr; i++) {
312 /* if <current_addr> > <last_addr>, swap */
313 /* or, if current=<start_addr> & last=<end_addr>, swap */
314 if ((change_point[i]->addr < change_point[i - 1]->addr) ||
315 ((change_point[i]->addr == change_point[i - 1]->addr) &&
316 (change_point[i]->addr == change_point[i]->pbios->addr) &&
317 (change_point[i - 1]->addr !=
318 change_point[i - 1]->pbios->addr))
320 change_tmp = change_point[i];
321 change_point[i] = change_point[i - 1];
322 change_point[i - 1] = change_tmp;
323 still_changing = 1;
328 /* create a new bios memory map, removing overlaps */
329 overlap_entries = 0; /* number of entries in the overlap table */
330 new_bios_entry = 0; /* index for creating new bios map entries */
331 last_type = 0; /* start with undefined memory type */
332 last_addr = 0; /* start with 0 as last starting address */
333 /* loop through change-points, determining affect on the new bios map */
334 for (chgidx = 0; chgidx < 2 * old_nr; chgidx++) {
335 /* keep track of all overlapping bios entries */
336 if (change_point[chgidx]->addr == change_point[chgidx]->pbios->addr) {
337 /* add map entry to overlap list (> 1 entry implies an overlap) */
338 overlap_list[overlap_entries++] = change_point[chgidx]->pbios;
339 } else {
340 /* remove entry from list (order independent, so swap with last) */
341 for (i = 0; i < overlap_entries; i++) {
342 if (overlap_list[i] == change_point[chgidx]->pbios)
343 overlap_list[i] = overlap_list[overlap_entries - 1];
345 overlap_entries--;
347 /* if there are overlapping entries, decide which "type" to use */
348 /* (larger value takes precedence -- 1=usable, 2,3,4,4+=unusable) */
349 current_type = 0;
350 for (i = 0; i < overlap_entries; i++)
351 if (overlap_list[i]->type > current_type)
352 current_type = overlap_list[i]->type;
353 /* continue building up new bios map based on this information */
354 if (current_type != last_type) {
355 if (last_type != 0) {
356 new_bios[new_bios_entry].size =
357 change_point[chgidx]->addr - last_addr;
358 /* move forward only if the new size was non-zero */
359 if (new_bios[new_bios_entry].size != 0)
360 if (++new_bios_entry >= E820MAX)
361 break; /* no more space left for new bios entries */
363 if (current_type != 0) {
364 new_bios[new_bios_entry].addr = change_point[chgidx]->addr;
365 new_bios[new_bios_entry].type = current_type;
366 last_addr = change_point[chgidx]->addr;
368 last_type = current_type;
371 return (new_bios_entry);
374 /* The following stuff could be merge once the addr_t will be set to 64bits.
375 * syslinux_scan_memory can be used for that purpose */
376 unsigned long detect_memsize(void)
378 unsigned long memory_size = 0;
380 /* Try to detect memory via e820 */
381 struct e820entry map[E820MAX];
382 int count = 0;
383 detect_memory_e820(map, E820MAX, &count);
384 memory_size = memsize_e820(map, count);
385 if (memory_size > 0)
386 return memory_size;
388 /*e820 failed, let's try e801 */
389 int mem_low, mem_high = 0;
390 if (!detect_memory_e801(&mem_low, &mem_high))
391 return mem_low + (mem_high << 6);
393 /*e801 failed, let's try e88 */
394 int mem_size = 0;
395 if (!detect_memory_88(&mem_size))
396 return mem_size;
398 /* We were enable to detect any kind of memory */
399 return 0;
402 /* The following stuff could be merge once the addr_t will be set to 64bits.
403 * syslinux_scan_memory can be used for that purpose */
404 unsigned long memsize_e820(struct e820entry *e820, int e820_nr)
406 int i, n, nr;
407 unsigned long memory_size = 0;
408 struct e820entry nm[E820MAX];
410 /* Clean up, adjust and copy the BIOS-supplied E820-map. */
411 nr = sanitize_e820_map(e820, nm, e820_nr);
413 /* If there is not a good 820 map returning 0 to indicate
414 that we don't have any idea of the amount of ram we have */
415 if (nr < 1 || nr > E820MAX) {
416 return 0;
419 /* Build the memory map for testing */
420 n = 0;
421 for (i = 0; i < nr; i++) {
422 if (nm[i].type == E820_RAM || nm[i].type == E820_ACPI) {
423 unsigned long long start;
424 unsigned long long end;
425 start = nm[i].addr;
426 end = start + nm[i].size;
428 /* Don't ever use memory between 640 and 1024k */
429 if (start > RES_START && start < RES_END) {
430 if (end < RES_END) {
431 continue;
433 start = RES_END;
435 if (end > RES_START && end < RES_END) {
436 end = RES_START;
438 memory_size += (end >> 12) - ((start + 4095) >> 12);
439 n++;
440 } else if (nm[i].type == E820_NVS) {
441 memory_size += nm[i].size >> 12;
444 return memory_size * 4;