blob: cd1a481b5b1877f1de168047fec1930dc9403cdf [file] [log] [blame]
/* Copyright (c) 2013 The Regents of the University of California.
* Copyright (c) 2016 Google Inc
* Barret Rhoden <brho@cs.berkeley.edu>
* See LICENSE for details. */
#include <page_alloc.h>
#include <pmap.h>
#include <kmalloc.h>
#include <multiboot.h>
#include <arena.h>
/* Helper. Adds free entries to the base arena. Most entries are page aligned,
* though on some machines below EXTPHYSMEM we may have some that aren't. */
static void parse_mboot_region(struct multiboot_mmap_entry *entry, void *data)
{
physaddr_t boot_freemem_paddr = (physaddr_t)data;
physaddr_t start = entry->addr;
size_t len = entry->len;
extern char end[];
if (entry->type != MULTIBOOT_MEMORY_AVAILABLE)
return;
/* Skip anything over max_paddr - might be bad entries(?) */
if (start >= max_paddr)
return;
if (start + len > max_paddr)
len = max_paddr - start;
/* For paranoia, skip anything below EXTPHYSMEM. If we ever change
* this, we'll need to deal with smp_boot's trampoline page. */
if ((start < EXTPHYSMEM) && (start + len < EXTPHYSMEM))
return;
if ((start < EXTPHYSMEM) && (EXTPHYSMEM <= start + len)) {
len = start + len - EXTPHYSMEM;
start = EXTPHYSMEM;
}
/* Skip over any pages already allocated in boot_alloc().
* (boot_freemem_paddr is the next free addr.) */
if ((start < boot_freemem_paddr)
&& (boot_freemem_paddr <= start + len)) {
len = start + len - boot_freemem_paddr;
start = boot_freemem_paddr;
}
/* Skip any part that intersects with the kernel, which is linked and
* loaded from EXTPHYSMEM to end in kernel64.ld */
if (regions_collide_unsafe(EXTPHYSMEM, PADDR(end), start, start + len))
{
len = start + len - PADDR(end);
start = PADDR(end);
}
/* We need to give the arena PGSIZE-quantum segments. */
if (PGOFF(start)) {
len -= PGOFF(start);
start = ROUNDUP(start, PGSIZE);
}
len = ROUNDDOWN(len, PGSIZE);
if (!len)
return;
arena_add(base_arena, KADDR(start), len, MEM_WAIT);
}
/* Since we can't parse multiboot mmap entries, we need to just guess at what
* pages are free and which ones aren't.
*
* Despite the lack of info from mbi, I know there is a magic hole in physical
* memory that we can't use, from the IOAPIC_PBASE on up [0xfec00000,
* 0xffffffff] (I'm being pessimistic). But, that's not pessimistic enough!
* Qemu still doesn't like that. From using 0xe0000000 instead works for mine.
* According to http://wiki.osdev.org/Memory_Map_(x86), some systems could
* reserve from [0xc0000000, 0xffffffff]. Anyway, in lieu of real memory
* detection, I'm just skipping that entire region.
*
* We may or may not have more free memory above this magic hole, depending on
* both the amount of RAM we have as well as 32 vs 64 bit.
*
* So we'll go with two free memory regions:
*
* [ 0, ROUNDUP(boot_freemem_paddr, PGSIZE) ) = busy
* [ ROUNDUP(boot_freemem_paddr, PGSIZE), TOP_OF_1 ) = free
* [ MAGIC_HOLE, 0x0000000100000000 ) = busy
* (and maybe this:)
* [ 0x0000000100000000, max_paddr ) = free
*
* where TOP_OF_1 is the min of IOAPIC_PBASE and max_paddr.
*
* As with parsing mbi regions, this will ignore the hairy areas below
* EXTPHYSMEM, and mark the entire kernel and anything we've boot alloc'd as
* busy. */
static void account_for_pages(physaddr_t boot_freemem_paddr)
{
physaddr_t top_of_busy = ROUNDUP(boot_freemem_paddr, PGSIZE);
physaddr_t top_of_free_1 = MIN(0xc0000000, max_paddr);
physaddr_t start_of_free_2;
printk("Warning: poor memory detection (qemu?). May lose 1GB of RAM\n");
arena_add(base_arena, KADDR(top_of_busy), top_of_free_1 - top_of_busy,
MEM_WAIT);
/* If max_paddr is less than the start of our potential second free mem
* region, we can just leave. We also don't want to poke around the
* pages array either (and accidentally run off the end of the array).
*
* Additionally, 32 bit doesn't acknowledge pmem above the 4GB mark. */
start_of_free_2 = 0x0000000100000000;
if (max_paddr < start_of_free_2)
return;
arena_add(base_arena, KADDR(start_of_free_2), max_paddr -
start_of_free_2, MEM_WAIT);
}
/* Initialize base arena based on available free memory. After this, do not use
* boot_alloc. */
void base_arena_init(struct multiboot_info *mbi)
{
/* First, all memory is busy / not free by default.
*
* To avoid a variety of headaches, any memory below 1MB is considered
* busy. Likewise, everything in the kernel, up to _end is also busy.
* And everything we've already boot_alloc'd is busy. These chunks of
* memory are reported as 'free' by multiboot. All of this memory is
* below boot_freemem_paddr. We don't treat anything below that as
* free.
*
* We'll also abort the mapping for any addresses over max_paddr, since
* we'll never use them. 'pages' does not track them either.
*
* One special note: we actually use the memory at 0x1000 for smp_boot.
* It'll never get freed; just FYI. */
physaddr_t boot_freemem_paddr;
void *base_pg;
/* Need to do the boot-allocs before our last look at the top of
* boot_freemem. */
base_pg = boot_alloc(PGSIZE, PGSHIFT);
base_arena = arena_builder(base_pg, "base", PGSIZE, NULL, NULL, NULL,
0);
boot_freemem_paddr = PADDR(ROUNDUP(boot_freemem, PGSIZE));
if (mboot_has_mmaps(mbi)) {
mboot_foreach_mmap(mbi, parse_mboot_region,
(void*)boot_freemem_paddr);
} else {
/* No multiboot mmap regions (probably run from qemu with
* -kernel) */
account_for_pages(boot_freemem_paddr);
}
}