| /* ----------------------------------------------------------------------- * |
| * |
| * Copyright 2009 Pierre-Alexandre Meyer |
| * |
| * Some parts borrowed from meminfo.c32: |
| * |
| * Copyright 2003-2009 H. Peter Anvin - All Rights Reserved |
| * Copyright 2009 Intel Corporation; author: H. Peter Anvin |
| * |
| * Some parts borrowed from Linux: |
| * |
| * Copyright (C) 1991, 1992 Linus Torvalds |
| * Copyright 2007 rPath, Inc. - All Rights Reserved |
| * Copyright 2009 Intel Corporation; author H. Peter Anvin |
| * |
| * Interrupt list from Ralf Brown (http://www.cs.cmu.edu/~ralf/files.html) |
| * |
| * This file is part of Syslinux, and is made available under |
| * the terms of the GNU General Public License version 2. |
| * |
| * ----------------------------------------------------------------------- */ |
| |
| #include <stdint.h> |
| #include <com32.h> |
| #include <string.h> |
| #include <memory.h> |
| |
| const char *const e820_types[] = { |
| "usable", |
| "reserved", |
| "ACPI reclaim", |
| "ACPI NVS", |
| "unusable", |
| }; |
| |
| struct e820_ext_entry { |
| struct e820entry std; |
| uint32_t ext_flags; |
| } __attribute__ ((packed)); |
| |
| #define SMAP 0x534d4150 /* ASCII "SMAP" */ |
| |
| void get_type(int type, char *type_ptr, int type_ptr_sz) |
| { |
| unsigned int real_type = type - 1; |
| if (real_type < sizeof(e820_types) / sizeof(e820_types[0])) |
| strlcpy(type_ptr, e820_types[real_type], type_ptr_sz); |
| } |
| |
| /** |
| *INT 15 - newer BIOSes - GET SYSTEM MEMORY MAP |
| * AX = E820h |
| * EAX = 0000E820h |
| * EDX = 534D4150h ('SMAP') |
| * EBX = continuation value or 00000000h to start at beginning of map |
| * ECX = size of buffer for result, in bytes (should be >= 20 bytes) |
| * ES:DI -> buffer for result (see #00581) |
| * |
| * Return: CF clear if successful |
| * EAX = 534D4150h ('SMAP') |
| * ES:DI buffer filled |
| * EBX = next offset from which to copy or 00000000h if all done |
| * ECX = actual length returned in bytes |
| * CF set on error |
| * AH = error code (86h) (see #00496 at INT 15/AH=80h) |
| * |
| * Notes: originally introduced with the Phoenix BIOS v4.0, this function is |
| * now supported by most newer BIOSes, since various versions of Windows |
| * call it to find out about the system memory |
| * a maximum of 20 bytes will be transferred at one time, even if ECX is |
| * higher; some BIOSes (e.g. Award Modular BIOS v4.50PG) ignore the |
| * value of ECX on entry, and always copy 20 bytes |
| * some BIOSes expect the high word of EAX to be clear on entry, i.e. |
| * EAX=0000E820h |
| * if this function is not supported, an application should fall back |
| * to AX=E802h, AX=E801h, and then AH=88h |
| * the BIOS is permitted to return a nonzero continuation value in EBX |
| * and indicate that the end of the list has already been reached by |
| * returning with CF set on the next iteration |
| * this function will return base memory and ISA/PCI memory contiguous |
| * with base memory as normal memory ranges; it will indicate |
| * chipset-defined address holes which are not in use and motherboard |
| * memory-mapped devices, and all occurrences of the system BIOS as |
| * reserved; standard PC address ranges will not be reported |
| **/ |
| void detect_memory_e820(struct e820entry *desc, int size_map, int *size_found) |
| { |
| int count = 0; |
| static struct e820_ext_entry buf; /* static so it is zeroed */ |
| void *bounce; |
| |
| com32sys_t ireg, oreg; |
| memset(&ireg, 0, sizeof ireg); |
| |
| bounce = lmalloc(sizeof buf); |
| if (!bounce) |
| goto out; |
| |
| ireg.eax.w[0] = 0xe820; |
| ireg.edx.l = SMAP; |
| ireg.ecx.l = sizeof(struct e820_ext_entry); |
| ireg.edi.w[0] = OFFS(bounce); |
| ireg.es = SEG(bounce); |
| |
| /* |
| * Set this here so that if the BIOS doesn't change this field |
| * but still doesn't change %ecx, we're still okay... |
| */ |
| memset(&buf, 0, sizeof buf); |
| buf.ext_flags = 1; |
| |
| do { |
| memcpy(bounce, &buf, sizeof buf); |
| |
| /* Important: %edx and %esi are clobbered by some BIOSes, |
| so they must be either used for the error output |
| or explicitly marked clobbered. Given that, assume there |
| is something out there clobbering %ebp and %edi, too. */ |
| __intcall(0x15, &ireg, &oreg); |
| |
| /* Some BIOSes stop returning SMAP in the middle of |
| the search loop. We don't know exactly how the BIOS |
| screwed up the map at that point, we might have a |
| partial map, the full map, or complete garbage, so |
| just return failure. */ |
| if (oreg.eax.l != SMAP) { |
| count = 0; |
| break; |
| } |
| |
| if (oreg.eflags.l & EFLAGS_CF || oreg.ecx.l < 20) |
| break; |
| |
| memcpy(&buf, bounce, sizeof buf); |
| |
| /* |
| * ACPI 3.0 added the extended flags support. If bit 0 |
| * in the extended flags is zero, we're supposed to simply |
| * ignore the entry -- a backwards incompatible change! |
| */ |
| if (oreg.ecx.l > 20 && !(buf.ext_flags & 1)) |
| continue; |
| |
| memcpy(&desc[count], &buf, sizeof buf); |
| count++; |
| |
| /* Set continuation value */ |
| ireg.ebx.l = oreg.ebx.l; |
| } while (ireg.ebx.l && count < size_map); |
| |
| out: |
| lfree(bounce); |
| *size_found = count; |
| } |
| |
| /** |
| * detect_memory_e801 |
| * |
| *INT 15 - Phoenix BIOS v4.0 - GET MEMORY SIZE FOR >64M CONFIGURATIONS |
| * AX = E801h |
| * |
| * Return: CF clear if successful |
| * AX = extended memory between 1M and 16M, in K (max 3C00h = 15MB) |
| * BX = extended memory above 16M, in 64K blocks |
| * CX = configured memory 1M to 16M, in K |
| * DX = configured memory above 16M, in 64K blocks |
| * CF set on error |
| * |
| * Notes: supported by the A03 level (6/14/94) and later XPS P90 BIOSes, as well |
| * as the Compaq Contura, 3/8/93 DESKPRO/i, and 7/26/93 LTE Lite 386 ROM |
| * BIOS |
| * supported by AMI BIOSes dated 8/23/94 or later |
| * on some systems, the BIOS returns AX=BX=0000h; in this case, use CX |
| * and DX instead of AX and BX |
| * this interface is used by Windows NT 3.1, OS/2 v2.11/2.20, and is |
| * used as a fall-back by newer versions if AX=E820h is not supported |
| * this function is not used by MS-DOS 6.0 HIMEM.SYS when an EISA machine |
| * (for example with parameter /EISA) (see also MEM F000h:FFD9h), or no |
| * Compaq machine was detected, or parameter /NOABOVE16 was given. |
| **/ |
| int detect_memory_e801(int *mem_size_below_16, int *mem_size_above_16) |
| { |
| com32sys_t ireg, oreg; |
| memset(&ireg, 0, sizeof ireg); |
| |
| ireg.eax.w[0] = 0xe801; |
| |
| __intcall(0x15, &ireg, &oreg); |
| |
| if (oreg.eflags.l & EFLAGS_CF) |
| return -1; |
| |
| if (oreg.eax.w[0] > 0x3c00) |
| return -1; /* Bogus! */ |
| |
| /* Linux seems to use ecx and edx by default if they are defined */ |
| if (oreg.eax.w[0] || oreg.eax.w[0]) { |
| oreg.eax.w[0] = oreg.ecx.w[0]; |
| oreg.ebx.w[0] = oreg.edx.w[0]; |
| } |
| |
| *mem_size_below_16 = oreg.eax.w[0]; /* 1K blocks */ |
| *mem_size_above_16 = oreg.ebx.w[0]; /* 64K blocks */ |
| |
| return 0; |
| } |
| |
| int detect_memory_88(int *mem_size) |
| { |
| com32sys_t ireg, oreg; |
| memset(&ireg, 0, sizeof ireg); |
| |
| ireg.eax.w[0] = 0x8800; |
| |
| __intcall(0x15, &ireg, &oreg); |
| |
| if (oreg.eflags.l & EFLAGS_CF) |
| return -1; |
| |
| *mem_size = oreg.eax.w[0]; |
| return 0; |
| } |
| |
| /* |
| * Sanitize the BIOS e820 map. |
| * |
| * This code come from the memtest86 project. It have been adjusted to match |
| * the syslinux environement. |
| * Some e820 responses include overlapping entries. The following |
| * replaces the original e820 map with a new one, removing overlaps. |
| * |
| * The following stuff could be merge once the addr_t will be set to 64bits. |
| * syslinux_scan_memory can be used for that purpose |
| */ |
| int sanitize_e820_map(struct e820entry *orig_map, struct e820entry *new_bios, |
| short old_nr) |
| { |
| struct change_member { |
| struct e820entry *pbios; /* pointer to original bios entry */ |
| unsigned long long addr; /* address for this change point */ |
| }; |
| struct change_member change_point_list[2 * E820MAX]; |
| struct change_member *change_point[2 * E820MAX]; |
| struct e820entry *overlap_list[E820MAX]; |
| struct e820entry biosmap[E820MAX]; |
| struct change_member *change_tmp; |
| unsigned long current_type, last_type; |
| unsigned long long last_addr; |
| int chgidx, still_changing; |
| int overlap_entries; |
| int new_bios_entry; |
| int i; |
| |
| /* |
| Visually we're performing the following (1,2,3,4 = memory types)... |
| Sample memory map (w/overlaps): |
| ____22__________________ |
| ______________________4_ |
| ____1111________________ |
| _44_____________________ |
| 11111111________________ |
| ____________________33__ |
| ___________44___________ |
| __________33333_________ |
| ______________22________ |
| ___________________2222_ |
| _________111111111______ |
| _____________________11_ |
| _________________4______ |
| |
| Sanitized equivalent (no overlap): |
| 1_______________________ |
| _44_____________________ |
| ___1____________________ |
| ____22__________________ |
| ______11________________ |
| _________1______________ |
| __________3_____________ |
| ___________44___________ |
| _____________33_________ |
| _______________2________ |
| ________________1_______ |
| _________________4______ |
| ___________________2____ |
| ____________________33__ |
| ______________________4_ |
| */ |
| /* First make a copy of the map */ |
| for (i = 0; i < old_nr; i++) { |
| biosmap[i].addr = orig_map[i].addr; |
| biosmap[i].size = orig_map[i].size; |
| biosmap[i].type = orig_map[i].type; |
| } |
| |
| /* bail out if we find any unreasonable addresses in bios map */ |
| for (i = 0; i < old_nr; i++) { |
| if (biosmap[i].addr + biosmap[i].size < biosmap[i].addr) |
| return 0; |
| } |
| |
| /* create pointers for initial change-point information (for sorting) */ |
| for (i = 0; i < 2 * old_nr; i++) |
| change_point[i] = &change_point_list[i]; |
| |
| /* record all known change-points (starting and ending addresses) */ |
| chgidx = 0; |
| for (i = 0; i < old_nr; i++) { |
| change_point[chgidx]->addr = biosmap[i].addr; |
| change_point[chgidx++]->pbios = &biosmap[i]; |
| change_point[chgidx]->addr = biosmap[i].addr + biosmap[i].size; |
| change_point[chgidx++]->pbios = &biosmap[i]; |
| } |
| |
| /* sort change-point list by memory addresses (low -> high) */ |
| still_changing = 1; |
| while (still_changing) { |
| still_changing = 0; |
| for (i = 1; i < 2 * old_nr; i++) { |
| /* if <current_addr> > <last_addr>, swap */ |
| /* or, if current=<start_addr> & last=<end_addr>, swap */ |
| if ((change_point[i]->addr < change_point[i - 1]->addr) || |
| ((change_point[i]->addr == change_point[i - 1]->addr) && |
| (change_point[i]->addr == change_point[i]->pbios->addr) && |
| (change_point[i - 1]->addr != |
| change_point[i - 1]->pbios->addr)) |
| ) { |
| change_tmp = change_point[i]; |
| change_point[i] = change_point[i - 1]; |
| change_point[i - 1] = change_tmp; |
| still_changing = 1; |
| } |
| } |
| } |
| |
| /* create a new bios memory map, removing overlaps */ |
| overlap_entries = 0; /* number of entries in the overlap table */ |
| new_bios_entry = 0; /* index for creating new bios map entries */ |
| last_type = 0; /* start with undefined memory type */ |
| last_addr = 0; /* start with 0 as last starting address */ |
| /* loop through change-points, determining affect on the new bios map */ |
| for (chgidx = 0; chgidx < 2 * old_nr; chgidx++) { |
| /* keep track of all overlapping bios entries */ |
| if (change_point[chgidx]->addr == change_point[chgidx]->pbios->addr) { |
| /* add map entry to overlap list (> 1 entry implies an overlap) */ |
| overlap_list[overlap_entries++] = change_point[chgidx]->pbios; |
| } else { |
| /* remove entry from list (order independent, so swap with last) */ |
| for (i = 0; i < overlap_entries; i++) { |
| if (overlap_list[i] == change_point[chgidx]->pbios) |
| overlap_list[i] = overlap_list[overlap_entries - 1]; |
| } |
| overlap_entries--; |
| } |
| /* if there are overlapping entries, decide which "type" to use */ |
| /* (larger value takes precedence -- 1=usable, 2,3,4,4+=unusable) */ |
| current_type = 0; |
| for (i = 0; i < overlap_entries; i++) |
| if (overlap_list[i]->type > current_type) |
| current_type = overlap_list[i]->type; |
| /* continue building up new bios map based on this information */ |
| if (current_type != last_type) { |
| if (last_type != 0) { |
| new_bios[new_bios_entry].size = |
| change_point[chgidx]->addr - last_addr; |
| /* move forward only if the new size was non-zero */ |
| if (new_bios[new_bios_entry].size != 0) |
| if (++new_bios_entry >= E820MAX) |
| break; /* no more space left for new bios entries */ |
| } |
| if (current_type != 0) { |
| new_bios[new_bios_entry].addr = change_point[chgidx]->addr; |
| new_bios[new_bios_entry].type = current_type; |
| last_addr = change_point[chgidx]->addr; |
| } |
| last_type = current_type; |
| } |
| } |
| return (new_bios_entry); |
| } |
| |
| /* The following stuff could be merge once the addr_t will be set to 64bits. |
| * syslinux_scan_memory can be used for that purpose */ |
| unsigned long detect_memsize(void) |
| { |
| unsigned long memory_size = 0; |
| |
| /* Try to detect memory via e820 */ |
| struct e820entry map[E820MAX]; |
| int count = 0; |
| detect_memory_e820(map, E820MAX, &count); |
| memory_size = memsize_e820(map, count); |
| if (memory_size > 0) |
| return memory_size; |
| |
| /*e820 failed, let's try e801 */ |
| int mem_low, mem_high = 0; |
| if (!detect_memory_e801(&mem_low, &mem_high)) |
| return mem_low + (mem_high << 6); |
| |
| /*e801 failed, let's try e88 */ |
| int mem_size = 0; |
| if (!detect_memory_88(&mem_size)) |
| return mem_size; |
| |
| /* We were enable to detect any kind of memory */ |
| return 0; |
| } |
| |
| /* The following stuff could be merge once the addr_t will be set to 64bits. |
| * syslinux_scan_memory can be used for that purpose */ |
| unsigned long memsize_e820(struct e820entry *e820, int e820_nr) |
| { |
| int i, n, nr; |
| unsigned long memory_size = 0; |
| struct e820entry nm[E820MAX]; |
| |
| /* Clean up, adjust and copy the BIOS-supplied E820-map. */ |
| nr = sanitize_e820_map(e820, nm, e820_nr); |
| |
| /* If there is not a good 820 map returning 0 to indicate |
| that we don't have any idea of the amount of ram we have */ |
| if (nr < 1 || nr > E820MAX) { |
| return 0; |
| } |
| |
| /* Build the memory map for testing */ |
| n = 0; |
| for (i = 0; i < nr; i++) { |
| if (nm[i].type == E820_RAM || nm[i].type == E820_ACPI) { |
| unsigned long long start; |
| unsigned long long end; |
| start = nm[i].addr; |
| end = start + nm[i].size; |
| |
| /* Don't ever use memory between 640 and 1024k */ |
| if (start > RES_START && start < RES_END) { |
| if (end < RES_END) { |
| continue; |
| } |
| start = RES_END; |
| } |
| if (end > RES_START && end < RES_END) { |
| end = RES_START; |
| } |
| memory_size += (end >> 12) - ((start + 4095) >> 12); |
| n++; |
| } else if (nm[i].type == E820_NVS) { |
| memory_size += nm[i].size >> 12; |
| } |
| } |
| return memory_size * 4; |
| } |