1 /* ----------------------------------------------------------------------- * 2 * 3 * Copyright 2009 Pierre-Alexandre Meyer 4 * 5 * Some parts borrowed from meminfo.c32: 6 * 7 * Copyright 2003-2009 H. Peter Anvin - All Rights Reserved 8 * Copyright 2009 Intel Corporation; author: H. Peter Anvin 9 * 10 * Some parts borrowed from Linux: 11 * 12 * Copyright (C) 1991, 1992 Linus Torvalds 13 * Copyright 2007 rPath, Inc. - All Rights Reserved 14 * Copyright 2009 Intel Corporation; author H. Peter Anvin 15 * 16 * Interrupt list from Ralf Brown (http://www.cs.cmu.edu/~ralf/files.html) 17 * 18 * This file is part of Syslinux, and is made available under 19 * the terms of the GNU General Public License version 2. 20 * 21 * ----------------------------------------------------------------------- */ 22 23 #include <stdint.h> 24 #include <com32.h> 25 #include <string.h> 26 #include <memory.h> 27 28 const char *const e820_types[] = { 29 "usable", 30 "reserved", 31 "ACPI reclaim", 32 "ACPI NVS", 33 "unusable", 34 }; 35 36 struct e820_ext_entry { 37 struct e820entry std; 38 uint32_t ext_flags; 39 } __attribute__ ((packed)); 40 41 #define SMAP 0x534d4150 /* ASCII "SMAP" */ 42 43 void get_type(int type, char *type_ptr, int type_ptr_sz) 44 { 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); 48 } 49 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) 58 * 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) 66 * 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) 87 { 88 int count = 0; 89 static struct e820_ext_entry buf; /* static so it is zeroed */ 90 void *bounce; 91 92 com32sys_t ireg, oreg; 93 memset(&ireg, 0, sizeof ireg); 94 95 bounce = lmalloc(sizeof buf); 96 if (!bounce) 97 goto out; 98 99 ireg.eax.w[0] = 0xe820; 100 ireg.edx.l = SMAP; 101 ireg.ecx.l = sizeof(struct e820_ext_entry); 102 ireg.edi.w[0] = OFFS(bounce); 103 ireg.es = SEG(bounce); 104 105 /* 106 * Set this here so that if the BIOS doesn't change this field 107 * but still doesn't change %ecx, we're still okay... 108 */ 109 memset(&buf, 0, sizeof buf); 110 buf.ext_flags = 1; 111 112 do { 113 memcpy(bounce, &buf, sizeof buf); 114 115 /* Important: %edx and %esi are clobbered by some BIOSes, 116 so they must be either used for the error output 117 or explicitly marked clobbered. Given that, assume there 118 is something out there clobbering %ebp and %edi, too. */ 119 __intcall(0x15, &ireg, &oreg); 120 121 /* Some BIOSes stop returning SMAP in the middle of 122 the search loop. We don't know exactly how the BIOS 123 screwed up the map at that point, we might have a 124 partial map, the full map, or complete garbage, so 125 just return failure. */ 126 if (oreg.eax.l != SMAP) { 127 count = 0; 128 break; 129 } 130 131 if (oreg.eflags.l & EFLAGS_CF || oreg.ecx.l < 20) 132 break; 133 134 memcpy(&buf, bounce, sizeof buf); 135 136 /* 137 * ACPI 3.0 added the extended flags support. If bit 0 138 * in the extended flags is zero, we're supposed to simply 139 * ignore the entry -- a backwards incompatible change! 140 */ 141 if (oreg.ecx.l > 20 && !(buf.ext_flags & 1)) 142 continue; 143 144 memcpy(&desc[count], &buf, sizeof buf); 145 count++; 146 147 /* Set continuation value */ 148 ireg.ebx.l = oreg.ebx.l; 149 } while (ireg.ebx.l && count < size_map); 150 151 out: 152 lfree(bounce); 153 *size_found = count; 154 } 155 156 /** 157 * detect_memory_e801 158 * 159 *INT 15 - Phoenix BIOS v4.0 - GET MEMORY SIZE FOR >64M CONFIGURATIONS 160 * AX = E801h 161 * 162 * Return: CF clear if successful 163 * AX = extended memory between 1M and 16M, in K (max 3C00h = 15MB) 164 * BX = extended memory above 16M, in 64K blocks 165 * CX = configured memory 1M to 16M, in K 166 * DX = configured memory above 16M, in 64K blocks 167 * CF set on error 168 * 169 * Notes: supported by the A03 level (6/14/94) and later XPS P90 BIOSes, as well 170 * as the Compaq Contura, 3/8/93 DESKPRO/i, and 7/26/93 LTE Lite 386 ROM 171 * BIOS 172 * supported by AMI BIOSes dated 8/23/94 or later 173 * on some systems, the BIOS returns AX=BX=0000h; in this case, use CX 174 * and DX instead of AX and BX 175 * this interface is used by Windows NT 3.1, OS/2 v2.11/2.20, and is 176 * used as a fall-back by newer versions if AX=E820h is not supported 177 * this function is not used by MS-DOS 6.0 HIMEM.SYS when an EISA machine 178 * (for example with parameter /EISA) (see also MEM F000h:FFD9h), or no 179 * Compaq machine was detected, or parameter /NOABOVE16 was given. 180 **/ 181 int detect_memory_e801(int *mem_size_below_16, int *mem_size_above_16) 182 { 183 com32sys_t ireg, oreg; 184 memset(&ireg, 0, sizeof ireg); 185 186 ireg.eax.w[0] = 0xe801; 187 188 __intcall(0x15, &ireg, &oreg); 189 190 if (oreg.eflags.l & EFLAGS_CF) 191 return -1; 192 193 if (oreg.eax.w[0] > 0x3c00) 194 return -1; /* Bogus! */ 195 196 /* Linux seems to use ecx and edx by default if they are defined */ 197 if (oreg.eax.w[0] || oreg.eax.w[0]) { 198 oreg.eax.w[0] = oreg.ecx.w[0]; 199 oreg.ebx.w[0] = oreg.edx.w[0]; 200 } 201 202 *mem_size_below_16 = oreg.eax.w[0]; /* 1K blocks */ 203 *mem_size_above_16 = oreg.ebx.w[0]; /* 64K blocks */ 204 205 return 0; 206 } 207 208 int detect_memory_88(int *mem_size) 209 { 210 com32sys_t ireg, oreg; 211 memset(&ireg, 0, sizeof ireg); 212 213 ireg.eax.w[0] = 0x8800; 214 215 __intcall(0x15, &ireg, &oreg); 216 217 if (oreg.eflags.l & EFLAGS_CF) 218 return -1; 219 220 *mem_size = oreg.eax.w[0]; 221 return 0; 222 } 223 224 /* 225 * Sanitize the BIOS e820 map. 226 * 227 * This code come from the memtest86 project. It have been adjusted to match 228 * the syslinux environement. 229 * Some e820 responses include overlapping entries. The following 230 * replaces the original e820 map with a new one, removing overlaps. 231 * 232 * The following stuff could be merge once the addr_t will be set to 64bits. 233 * syslinux_scan_memory can be used for that purpose 234 */ 235 int sanitize_e820_map(struct e820entry *orig_map, struct e820entry *new_bios, 236 short old_nr) 237 { 238 struct change_member { 239 struct e820entry *pbios; /* pointer to original bios entry */ 240 unsigned long long addr; /* address for this change point */ 241 }; 242 struct change_member change_point_list[2 * E820MAX]; 243 struct change_member *change_point[2 * E820MAX]; 244 struct e820entry *overlap_list[E820MAX]; 245 struct e820entry biosmap[E820MAX]; 246 struct change_member *change_tmp; 247 unsigned long current_type, last_type; 248 unsigned long long last_addr; 249 int chgidx, still_changing; 250 int overlap_entries; 251 int new_bios_entry; 252 int i; 253 254 /* 255 Visually we're performing the following (1,2,3,4 = memory types)... 256 Sample memory map (w/overlaps): 257 ____22__________________ 258 ______________________4_ 259 ____1111________________ 260 _44_____________________ 261 11111111________________ 262 ____________________33__ 263 ___________44___________ 264 __________33333_________ 265 ______________22________ 266 ___________________2222_ 267 _________111111111______ 268 _____________________11_ 269 _________________4______ 270 271 Sanitized equivalent (no overlap): 272 1_______________________ 273 _44_____________________ 274 ___1____________________ 275 ____22__________________ 276 ______11________________ 277 _________1______________ 278 __________3_____________ 279 ___________44___________ 280 _____________33_________ 281 _______________2________ 282 ________________1_______ 283 _________________4______ 284 ___________________2____ 285 ____________________33__ 286 ______________________4_ 287 */ 288 /* First make a copy of the map */ 289 for (i = 0; i < old_nr; i++) { 290 biosmap[i].addr = orig_map[i].addr; 291 biosmap[i].size = orig_map[i].size; 292 biosmap[i].type = orig_map[i].type; 293 } 294 295 /* bail out if we find any unreasonable addresses in bios map */ 296 for (i = 0; i < old_nr; i++) { 297 if (biosmap[i].addr + biosmap[i].size < biosmap[i].addr) 298 return 0; 299 } 300 301 /* create pointers for initial change-point information (for sorting) */ 302 for (i = 0; i < 2 * old_nr; i++) 303 change_point[i] = &change_point_list[i]; 304 305 /* record all known change-points (starting and ending addresses) */ 306 chgidx = 0; 307 for (i = 0; i < old_nr; i++) { 308 change_point[chgidx]->addr = biosmap[i].addr; 309 change_point[chgidx++]->pbios = &biosmap[i]; 310 change_point[chgidx]->addr = biosmap[i].addr + biosmap[i].size; 311 change_point[chgidx++]->pbios = &biosmap[i]; 312 } 313 314 /* sort change-point list by memory addresses (low -> high) */ 315 still_changing = 1; 316 while (still_changing) { 317 still_changing = 0; 318 for (i = 1; i < 2 * old_nr; i++) { 319 /* if <current_addr> > <last_addr>, swap */ 320 /* or, if current=<start_addr> & last=<end_addr>, swap */ 321 if ((change_point[i]->addr < change_point[i - 1]->addr) || 322 ((change_point[i]->addr == change_point[i - 1]->addr) && 323 (change_point[i]->addr == change_point[i]->pbios->addr) && 324 (change_point[i - 1]->addr != 325 change_point[i - 1]->pbios->addr)) 326 ) { 327 change_tmp = change_point[i]; 328 change_point[i] = change_point[i - 1]; 329 change_point[i - 1] = change_tmp; 330 still_changing = 1; 331 } 332 } 333 } 334 335 /* create a new bios memory map, removing overlaps */ 336 overlap_entries = 0; /* number of entries in the overlap table */ 337 new_bios_entry = 0; /* index for creating new bios map entries */ 338 last_type = 0; /* start with undefined memory type */ 339 last_addr = 0; /* start with 0 as last starting address */ 340 /* loop through change-points, determining affect on the new bios map */ 341 for (chgidx = 0; chgidx < 2 * old_nr; chgidx++) { 342 /* keep track of all overlapping bios entries */ 343 if (change_point[chgidx]->addr == change_point[chgidx]->pbios->addr) { 344 /* add map entry to overlap list (> 1 entry implies an overlap) */ 345 overlap_list[overlap_entries++] = change_point[chgidx]->pbios; 346 } else { 347 /* remove entry from list (order independent, so swap with last) */ 348 for (i = 0; i < overlap_entries; i++) { 349 if (overlap_list[i] == change_point[chgidx]->pbios) 350 overlap_list[i] = overlap_list[overlap_entries - 1]; 351 } 352 overlap_entries--; 353 } 354 /* if there are overlapping entries, decide which "type" to use */ 355 /* (larger value takes precedence -- 1=usable, 2,3,4,4+=unusable) */ 356 current_type = 0; 357 for (i = 0; i < overlap_entries; i++) 358 if (overlap_list[i]->type > current_type) 359 current_type = overlap_list[i]->type; 360 /* continue building up new bios map based on this information */ 361 if (current_type != last_type) { 362 if (last_type != 0) { 363 new_bios[new_bios_entry].size = 364 change_point[chgidx]->addr - last_addr; 365 /* move forward only if the new size was non-zero */ 366 if (new_bios[new_bios_entry].size != 0) 367 if (++new_bios_entry >= E820MAX) 368 break; /* no more space left for new bios entries */ 369 } 370 if (current_type != 0) { 371 new_bios[new_bios_entry].addr = change_point[chgidx]->addr; 372 new_bios[new_bios_entry].type = current_type; 373 last_addr = change_point[chgidx]->addr; 374 } 375 last_type = current_type; 376 } 377 } 378 return (new_bios_entry); 379 } 380 381 /* The following stuff could be merge once the addr_t will be set to 64bits. 382 * syslinux_scan_memory can be used for that purpose */ 383 unsigned long detect_memsize(void) 384 { 385 unsigned long memory_size = 0; 386 387 /* Try to detect memory via e820 */ 388 struct e820entry map[E820MAX]; 389 int count = 0; 390 detect_memory_e820(map, E820MAX, &count); 391 memory_size = memsize_e820(map, count); 392 if (memory_size > 0) 393 return memory_size; 394 395 /*e820 failed, let's try e801 */ 396 int mem_low, mem_high = 0; 397 if (!detect_memory_e801(&mem_low, &mem_high)) 398 return mem_low + (mem_high << 6); 399 400 /*e801 failed, let's try e88 */ 401 int mem_size = 0; 402 if (!detect_memory_88(&mem_size)) 403 return mem_size; 404 405 /* We were enable to detect any kind of memory */ 406 return 0; 407 } 408 409 /* The following stuff could be merge once the addr_t will be set to 64bits. 410 * syslinux_scan_memory can be used for that purpose */ 411 unsigned long memsize_e820(struct e820entry *e820, int e820_nr) 412 { 413 int i, n, nr; 414 unsigned long memory_size = 0; 415 struct e820entry nm[E820MAX]; 416 417 /* Clean up, adjust and copy the BIOS-supplied E820-map. */ 418 nr = sanitize_e820_map(e820, nm, e820_nr); 419 420 /* If there is not a good 820 map returning 0 to indicate 421 that we don't have any idea of the amount of ram we have */ 422 if (nr < 1 || nr > E820MAX) { 423 return 0; 424 } 425 426 /* Build the memory map for testing */ 427 n = 0; 428 for (i = 0; i < nr; i++) { 429 if (nm[i].type == E820_RAM || nm[i].type == E820_ACPI) { 430 unsigned long long start; 431 unsigned long long end; 432 start = nm[i].addr; 433 end = start + nm[i].size; 434 435 /* Don't ever use memory between 640 and 1024k */ 436 if (start > RES_START && start < RES_END) { 437 if (end < RES_END) { 438 continue; 439 } 440 start = RES_END; 441 } 442 if (end > RES_START && end < RES_END) { 443 end = RES_START; 444 } 445 memory_size += (end >> 12) - ((start + 4095) >> 12); 446 n++; 447 } else if (nm[i].type == E820_NVS) { 448 memory_size += nm[i].size >> 12; 449 } 450 } 451 return memory_size * 4; 452 } 453