Home | History | Annotate | Download | only in gpllib
      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