Home | History | Annotate | Download | only in mboot
      1 /* ----------------------------------------------------------------------- *
      2  *
      3  *   Copyright 2007-2009 H. Peter Anvin - All Rights Reserved
      4  *   Copyright 2009 H. Peter Anvin - All Rights Reserved
      5  *
      6  *   Permission is hereby granted, free of charge, to any person
      7  *   obtaining a copy of this software and associated documentation
      8  *   files (the "Software"), to deal in the Software without
      9  *   restriction, including without limitation the rights to use,
     10  *   copy, modify, merge, publish, distribute, sublicense, and/or
     11  *   sell copies of the Software, and to permit persons to whom
     12  *   the Software is furnished to do so, subject to the following
     13  *   conditions:
     14  *
     15  *   The above copyright notice and this permission notice shall
     16  *   be included in all copies or substantial portions of the Software.
     17  *
     18  *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
     19  *   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
     20  *   OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
     21  *   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
     22  *   HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
     23  *   WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
     24  *   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
     25  *   OTHER DEALINGS IN THE SOFTWARE.
     26  *
     27  * ----------------------------------------------------------------------- */
     28 
     29 /*
     30  * mem.c
     31  *
     32  * Obtain a memory map for a Multiboot OS
     33  *
     34  * This differs from the libcom32 memory map functions in that it doesn't
     35  * attempt to filter out memory regions...
     36  */
     37 
     38 #include "mboot.h"
     39 #include <com32.h>
     40 
     41 struct e820_entry {
     42     uint64_t start;
     43     uint64_t len;
     44     uint32_t type;
     45 };
     46 
     47 #define RANGE_ALLOC_BLOCK	128
     48 
     49 static int mboot_scan_memory(struct AddrRangeDesc **ardp, uint32_t * dosmem)
     50 {
     51     com32sys_t ireg, oreg;
     52     struct e820_entry *e820buf;
     53     struct AddrRangeDesc *ard;
     54     size_t ard_count, ard_space;
     55     int rv = 0;
     56 
     57     /* Use INT 12h to get DOS memory */
     58     __intcall(0x12, &__com32_zero_regs, &oreg);
     59     *dosmem = oreg.eax.w[0] << 10;
     60     if (*dosmem < 32 * 1024 || *dosmem > 640 * 1024) {
     61 	/* INT 12h reports nonsense... now what? */
     62 	uint16_t ebda_seg = *(uint16_t *) 0x40e;
     63 	if (ebda_seg >= 0x8000 && ebda_seg < 0xa000)
     64 	    *dosmem = ebda_seg << 4;
     65 	else
     66 	    *dosmem = 640 * 1024;	/* Hope for the best... */
     67     }
     68 
     69     e820buf = lmalloc(sizeof(*e820buf));
     70     if (!e820buf)
     71 	return 0;
     72 
     73     /* Allocate initial space */
     74     *ardp = ard = malloc(RANGE_ALLOC_BLOCK * sizeof *ard);
     75     if (!ard)
     76 	goto out;
     77 
     78     ard_count = 0;
     79     ard_space = RANGE_ALLOC_BLOCK;
     80 
     81     /* First try INT 15h AX=E820h */
     82     memset(&ireg, 0, sizeof ireg);
     83     ireg.eax.l = 0xe820;
     84     ireg.edx.l = 0x534d4150;
     85     /* ireg.ebx.l    = 0; */
     86     ireg.ecx.l = sizeof(*e820buf);
     87     ireg.es = SEG(e820buf);
     88     ireg.edi.w[0] = OFFS(e820buf);
     89     memset(e820buf, 0, sizeof *e820buf);
     90 
     91     do {
     92 	__intcall(0x15, &ireg, &oreg);
     93 
     94 	if ((oreg.eflags.l & EFLAGS_CF) ||
     95 	    (oreg.eax.l != 0x534d4150) || (oreg.ecx.l < 20))
     96 	    break;
     97 
     98 	if (ard_count >= ard_space) {
     99 	    ard_space += RANGE_ALLOC_BLOCK;
    100 	    *ardp = ard = realloc(ard, ard_space * sizeof *ard);
    101 	    if (!ard) {
    102 		rv = ard_count;
    103 		goto out;
    104 	    }
    105 	}
    106 
    107 	ard[ard_count].size = 20;
    108 	ard[ard_count].BaseAddr = e820buf->start;
    109 	ard[ard_count].Length = e820buf->len;
    110 	ard[ard_count].Type = e820buf->type;
    111 	ard_count++;
    112 
    113 	ireg.ebx.l = oreg.ebx.l;
    114     } while (oreg.ebx.l);
    115 
    116     if (ard_count) {
    117 	rv = ard_count;
    118 	goto out;
    119     };
    120 
    121     ard[0].size = 20;
    122     ard[0].BaseAddr = 0;
    123     ard[0].Length = *dosmem << 10;
    124     ard[0].Type = 1;
    125 
    126     /* Next try INT 15h AX=E801h */
    127     memset(&ireg, 0, sizeof ireg);
    128     ireg.eax.w[0] = 0xe801;
    129     __intcall(0x15, &ireg, &oreg);
    130 
    131     if (!(oreg.eflags.l & EFLAGS_CF) && oreg.ecx.w[0]) {
    132 	ard[1].size = 20;
    133 	ard[1].BaseAddr = 1 << 20;
    134 	ard[1].Length = oreg.ecx.w[0] << 10;
    135 	ard[1].Type = 1;
    136 
    137 	if (oreg.edx.w[0]) {
    138 	    ard[2].size = 20;
    139 	    ard[2].BaseAddr = 16 << 20;
    140 	    ard[2].Length = oreg.edx.w[0] << 16;
    141 	    ard[2].Type = 1;
    142 	    rv = 3;
    143 	} else {
    144 	    rv = 2;
    145 	}
    146 
    147 	goto out;
    148     }
    149 
    150     /* Finally try INT 15h AH=88h */
    151     memset(&ireg, 0, sizeof ireg);
    152     ireg.eax.w[0] = 0x8800;
    153     __intcall(0x15, &ireg, &oreg);
    154     if (!(oreg.eflags.l & EFLAGS_CF) && oreg.eax.w[0]) {
    155 	ard[1].size = 20;
    156 	ard[1].BaseAddr = 1 << 20;
    157 	ard[1].Length = oreg.ecx.w[0] << 10;
    158 	ard[1].Type = 1;
    159 	rv = 2;
    160 	goto out;
    161     }
    162 
    163     rv = 1;			/* ... problematic ... */
    164 out:
    165     lfree(e820buf);
    166     return rv;
    167 }
    168 
    169 void mboot_make_memmap(void)
    170 {
    171     int i, nmap;
    172     struct AddrRangeDesc *ard;
    173     uint32_t lowmem, highmem;
    174     uint32_t highrsvd;
    175 
    176     /* Always report DOS memory as "lowmem", this may be overly conservative
    177        (e.g. if we're dropping PXE), but it should be *safe*... */
    178 
    179     nmap = mboot_scan_memory(&ard, &lowmem);
    180 
    181     highmem = 0x100000;
    182     highrsvd = 0xfff00000;
    183 
    184 again:
    185     for (i = 0; i < nmap; i++) {
    186 	uint64_t start, end;
    187 
    188 	start = ard[i].BaseAddr;
    189 	end = start + ard[i].Length;
    190 
    191 	if (end < start)
    192 	    end = ~0ULL;
    193 
    194 	if (start & 0xffffffff00000000ULL)
    195 	    continue;		/* Not interested in 64-bit memory */
    196 
    197 	if (start < highmem)
    198 	    start = highmem;
    199 
    200 	if (end <= start)
    201 	    continue;
    202 
    203 	if (ard[i].Type == 1 && start == highmem) {
    204 	    highmem = end;
    205 	    goto again;
    206 	} else if (ard[i].Type != 1 && start < highrsvd)
    207 	    highrsvd = start;
    208     }
    209 
    210     if (highmem > highrsvd)
    211 	highmem = highrsvd;
    212 
    213     mbinfo.mem_lower = lowmem >> 10;
    214     mbinfo.mem_upper = (highmem - 0x100000) >> 10;
    215     mbinfo.flags |= MB_INFO_MEMORY;
    216 
    217     /* The spec says this address should be +4, but Grub disagrees */
    218     mbinfo.mmap_addr = map_data(ard, nmap * sizeof *ard, 4, false);
    219     if (mbinfo.mmap_addr) {
    220 	mbinfo.mmap_length = nmap * sizeof *ard;
    221 	mbinfo.flags |= MB_INFO_MEM_MAP;
    222     }
    223 }
    224