blob: 91ec3b63cfa0892d05563b82db0513afc4d19146 [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
#include <arch/x86/mmu.h>
#include <assert.h>
#include <efi/boot-services.h>
#include <err.h>
#include <inttypes.h>
#include <kernel/vm.h>
#include <lib/memory_limit.h>
#include <zircon/boot/multiboot.h>
#include <fbl/algorithm.h>
#include <platform.h>
#include <platform/pc/bootloader.h>
#include <platform/pc/memory.h>
#include <string.h>
#include <trace.h>
#include "platform_p.h"
#define LOCAL_TRACE 0
/* multiboot information passed in, if present */
extern multiboot_info_t *_multiboot_info;
struct addr_range {
uint64_t base;
uint64_t size;
bool is_mem;
};
/* Values that will store the largest low-memory contiguous address space
* that we can let the PCIe bus driver use for allocations */
paddr_t pcie_mem_lo_base;
size_t pcie_mem_lo_size;
#define DEFAULT_MEMEND (16*1024*1024)
/* boot_addr_range_t is an iterator which iterates over address ranges from
* the boot loader
*/
struct boot_addr_range;
typedef void (*boot_addr_range_advance_func)(
struct boot_addr_range *range_struct);
typedef void (*boot_addr_range_reset_func)(
struct boot_addr_range *range_struct);
typedef struct boot_addr_range {
/* the base of the current address range */
uint64_t base;
/* the size of the current address range */
uint64_t size;
/* whether this range contains memory */
int is_mem;
/* whether this range is currently reset and invalid */
int is_reset;
/* private information for the advance function to keep its place */
void *seq;
/* a function which advances this iterator to the next address range */
boot_addr_range_advance_func advance;
/* a function which resets this range and its sequencing information */
boot_addr_range_reset_func reset;
} boot_addr_range_t;
/* a utility function to reset the common parts of a boot_addr_range_t */
static void boot_addr_range_reset(boot_addr_range_t *range)
{
range->base = 0;
range->size = 0;
range->is_mem = 0;
range->is_reset = 1;
}
/* this function uses the boot_addr_range_t iterator to walk through address
* ranges described by the boot loader. it fills in the mem_arenas global
* array with the ranges of memory it finds, compacted to the start of the
* array. it returns the total count of arenas which have been populated.
*/
extern int _end;
static status_t mem_arena_init(boot_addr_range_t *range)
{
mem_limit_ctx ctx;
ctx.kernel_base = KERNEL_BASE + KERNEL_LOAD_OFFSET;
ctx.kernel_size = reinterpret_cast<uintptr_t>(&_end) - ctx.kernel_base;
ctx.ramdisk_base = reinterpret_cast<uintptr_t>(platform_get_ramdisk(&ctx.ramdisk_size));
bool have_limit = (mem_limit_init(&ctx) == ZX_OK);
// Set up a base arena template to use
pmm_arena_info_t base_arena;
snprintf(base_arena.name, sizeof(base_arena.name), "%s", "memory");
base_arena.priority = 1;
base_arena.flags = PMM_ARENA_FLAG_KMAP;
for (range->reset(range), range->advance(range); !range->is_reset; range->advance(range)) {
LTRACEF("Range at %#" PRIx64 " of %#" PRIx64 " bytes is %smemory.\n",
range->base, range->size, range->is_mem ? "" : "not ");
if (!range->is_mem)
continue;
/* trim off parts of memory ranges that are smaller than a page */
uint64_t base = ROUNDUP(range->base, PAGE_SIZE);
uint64_t size = ROUNDDOWN(range->base + range->size, PAGE_SIZE) -
base;
/* trim any memory below 1MB for safety and SMP booting purposes */
if (base < 1*MB) {
uint64_t adjust = 1*MB - base;
if (adjust >= size)
continue;
base += adjust;
size -= adjust;
}
status_t status = ZX_OK;
if (have_limit) {
status = mem_limit_add_arenas_from_range(&ctx, base, size, base_arena);
}
// If there is no limit, or we failed to add arenas from processing
// ranges then add the original range.
if (!have_limit || status != ZX_OK) {
auto arena = base_arena;
arena.base = base;
arena.size = size;
LTRACEF("Adding pmm range at %#" PRIxPTR " of %#zx bytes.\n", arena.base, arena.size);
status = pmm_add_arena(&arena);
// This will result in subsequent arenas not being added, but this
// is a fairly fatal event so it's justifiable.
if (status != ZX_OK) {
TRACEF("Failed to add pmm range at %#" PRIxPTR "\n", arena.base);
return status;
}
}
}
return ZX_OK;
}
typedef struct e820_range_seq {
e820entry_t* map;
int index;
int count;
} e820_range_seq_t;
static void e820_range_reset(boot_addr_range_t *range)
{
boot_addr_range_reset(range);
e820_range_seq_t *seq = (e820_range_seq_t *)(range->seq);
seq->index = -1;
}
static void e820_range_advance(boot_addr_range_t *range)
{
e820_range_seq_t *seq = (e820_range_seq_t *)(range->seq);
seq->index++;
if (seq->index == seq->count) {
/* reset range to signal that we're at the end of the map */
e820_range_reset(range);
return;
}
e820entry_t* entry = &seq->map[seq->index];
range->base = entry->addr;
range->size = entry->size;
range->is_mem = (entry->type == E820_RAM) ? 1 : 0;
range->is_reset = 0;
}
static int e820_range_init(boot_addr_range_t *range, e820_range_seq_t *seq)
{
range->seq = seq;
range->advance = &e820_range_advance;
range->reset = &e820_range_reset;
if (bootloader.e820_count) {
seq->count = static_cast<int>(bootloader.e820_count);
seq->map = static_cast<e820entry_t*>(bootloader.e820_table);
range->reset(range);
return 1;
}
return 0;
}
typedef struct efi_range_seq {
void* base;
size_t entrysz;
int index;
int count;
} efi_range_seq_t;
static void efi_range_reset(boot_addr_range_t *range)
{
boot_addr_range_reset(range);
efi_range_seq_t *seq = (efi_range_seq_t *)(range->seq);
seq->index = -1;
}
static int efi_is_mem(uint32_t type) {
switch (type) {
case EfiLoaderCode:
case EfiLoaderData:
case EfiBootServicesCode:
case EfiBootServicesData:
case EfiConventionalMemory:
return 1;
default:
return 0;
}
}
static void efi_print(const char* tag, efi_memory_descriptor* e) {
bool mb = e->NumberOfPages > 256;
LTRACEF("%s%016lx %08x %lu%s\n",
tag, e->PhysicalStart, e->Type,
mb ? e->NumberOfPages / 256 : e->NumberOfPages * 4,
mb ? "MB" : "KB");
}
static void efi_range_advance(boot_addr_range_t *range)
{
efi_range_seq_t *seq = (efi_range_seq_t *)(range->seq);
seq->index++;
if (seq->index == seq->count) {
/* reset range to signal that we're at the end of the map */
efi_range_reset(range);
return;
}
const uintptr_t addr = reinterpret_cast<uintptr_t>(seq->base) + (seq->index * seq->entrysz);
efi_memory_descriptor *entry = reinterpret_cast<efi_memory_descriptor *>(addr);
efi_print("EFI: ", entry);
range->base = entry->PhysicalStart;
range->size = entry->NumberOfPages * PAGE_SIZE;
range->is_reset = 0;
range->is_mem = efi_is_mem(entry->Type);
// coalesce adjacent memory ranges
while ((seq->index + 1) < seq->count) {
const uintptr_t addr = reinterpret_cast<uintptr_t>(seq->base) +
((seq->index + 1) * seq->entrysz);
efi_memory_descriptor *next = reinterpret_cast<efi_memory_descriptor *>(addr);
if ((range->base + range->size) != next->PhysicalStart) {
break;
}
if (efi_is_mem(next->Type) != range->is_mem) {
break;
}
efi_print("EFI+ ", next);
range->size += next->NumberOfPages * PAGE_SIZE;
seq->index++;
}
}
static int efi_range_init(boot_addr_range_t *range, efi_range_seq_t *seq)
{
range->seq = seq;
range->advance = &efi_range_advance;
range->reset = &efi_range_reset;
if (bootloader.efi_mmap &&
(bootloader.efi_mmap_size > sizeof(uint64_t))) {
seq->entrysz = *((uint64_t*) bootloader.efi_mmap);
if (seq->entrysz < sizeof(efi_memory_descriptor)) {
return 0;
}
seq->count = static_cast<int>((bootloader.efi_mmap_size - sizeof(uint64_t)) / seq->entrysz);
seq->base = reinterpret_cast<void *>(
reinterpret_cast<uintptr_t>(bootloader.efi_mmap) + sizeof(uint64_t));
range->reset(range);
return 1;
} else {
return 0;
}
}
typedef struct multiboot_range_seq {
multiboot_info_t* info;
memory_map_t* mmap;
int index;
int count;
} multiboot_range_seq_t;
static void multiboot_range_reset(boot_addr_range_t *range)
{
boot_addr_range_reset(range);
multiboot_range_seq_t *seq = (multiboot_range_seq_t *)(range->seq);
seq->index = -1;
}
static void multiboot_range_advance(boot_addr_range_t *range)
{
multiboot_range_seq_t *seq = (multiboot_range_seq_t *)(range->seq);
if (seq->mmap) {
/* memory map based range information */
seq->index++;
if (seq->index == seq->count) {
multiboot_range_reset(range);
return;
}
memory_map_t *entry = &seq->mmap[seq->index];
range->base = entry->base_addr_high;
range->base <<= 32;
range->base |= entry->base_addr_low;
range->size = entry->length_high;
range->size <<= 32;
range->size |= entry->length_low;
range->is_mem = (entry->type == MB_MMAP_TYPE_AVAILABLE) ? 1 : 0;
range->is_reset = 0;
} else {
/* scalar info about a single range */
if (!range->is_reset) {
/* toggle back and forth between reset and valid */
multiboot_range_reset(range);
return;
}
// mem_lower is memory (KB) available at base=0
// mem_upper is memory (KB) available at base=1MB
range->base = 1024*1024;
range->size = seq->info->mem_upper * 1024U;
range->is_mem = 1;
range->is_reset = 0;
}
}
static int multiboot_range_init(boot_addr_range_t *range,
multiboot_range_seq_t *seq)
{
LTRACEF("_multiboot_info %p\n", _multiboot_info);
range->seq = seq;
range->advance = &multiboot_range_advance;
range->reset = &multiboot_range_reset;
if (_multiboot_info == NULL) {
/* no multiboot info found. */
return 0;
}
seq->info = (multiboot_info_t *)X86_PHYS_TO_VIRT(_multiboot_info);
seq->mmap = NULL;
seq->count = 0;
// if the MMAP flag is set and the address/length seems sane, parse the multiboot
// memory map table
if ((seq->info->flags & MB_INFO_MMAP)
&& (seq->info->mmap_addr != 0) && (seq->info->mmap_length > 0)) {
void* mmap_addr = (void*)X86_PHYS_TO_VIRT(seq->info->mmap_addr);
/* we've been told the memory map is valid, so set it up */
seq->mmap = (memory_map_t *)(uintptr_t)(mmap_addr);
seq->count = static_cast<int>(seq->info->mmap_length / sizeof(memory_map_t));
multiboot_range_reset(range);
return 1;
}
if (seq->info->flags & MB_INFO_MEM_SIZE) {
/* no additional setup required for the scalar range */
return 1;
}
/* no memory information in the multiboot info */
return 0;
}
static int addr_range_cmp(const void* p1, const void* p2)
{
const struct addr_range *a1 = static_cast<const struct addr_range *>(p1);
const struct addr_range *a2 = static_cast<const struct addr_range *>(p2);
if (a1->base < a2->base)
return -1;
else if (a1->base == a2->base)
return 0;
return 1;
}
static status_t platform_mem_range_init(void)
{
boot_addr_range_t range;
/* first try the efi memory table */
efi_range_seq_t efi_seq;
if (efi_range_init(&range, &efi_seq) &&
(mem_arena_init(&range) == ZX_OK))
return ZX_OK;
/* then try getting range info from e820 */
e820_range_seq_t e820_seq;
if (e820_range_init(&range, &e820_seq) &&
(mem_arena_init(&range) == ZX_OK))
return ZX_OK;
/* if no ranges were found, try multiboot */
multiboot_range_seq_t multiboot_seq;
if (multiboot_range_init(&range, &multiboot_seq) &&
(mem_arena_init(&range) == ZX_OK))
return ZX_OK;
/* if still no ranges were found, make a safe guess */
e820_range_init(&range, &e820_seq);
e820entry_t entry = {
.addr = MEMBASE,
.size = DEFAULT_MEMEND,
.type = E820_RAM,
};
e820_seq.map = &entry;
e820_seq.count = 1;
return mem_arena_init(&range);
}
static size_t cached_e820_entry_count;
static struct addr_range cached_e820_entries[64];
status_t enumerate_e820(enumerate_e820_callback callback, void* ctx) {
if (callback == NULL)
return ZX_ERR_INVALID_ARGS;
if(!cached_e820_entry_count)
return ZX_ERR_BAD_STATE;
DEBUG_ASSERT(cached_e820_entry_count <= fbl::count_of(cached_e820_entries));
for (size_t i = 0; i < cached_e820_entry_count; ++i)
callback(cached_e820_entries[i].base, cached_e820_entries[i].size,
cached_e820_entries[i].is_mem, ctx);
return ZX_OK;
}
/* Discover the basic memory map */
void platform_mem_init(void)
{
if (platform_mem_range_init() != ZX_OK) {
TRACEF("Error adding arenas from provided memory tables.\n");
}
// Cache the e820 entries so that they will be available for enumeration
// later in the boot.
//
// TODO(teisenbe, johngro): do not hardcode a limit on the number of
// entries we may have. Find some other way to make this information
// available at any point in time after we boot.
boot_addr_range_t range;
efi_range_seq_t efi_seq;
e820_range_seq_t e820_seq;
multiboot_range_seq_t multiboot_seq;
cached_e820_entry_count = 0;
if (efi_range_init(&range, &efi_seq) ||
e820_range_init(&range, &e820_seq) ||
multiboot_range_init(&range, &multiboot_seq)) {
for (range.reset(&range),
range.advance(&range);
!range.is_reset; range.advance(&range)) {
if (cached_e820_entry_count >= fbl::count_of(cached_e820_entries)) {
TRACEF("ERROR - Too many e820 entries to hold in the cache!\n");
cached_e820_entry_count = 0;
break;
}
struct addr_range* entry = &cached_e820_entries[cached_e820_entry_count++];
entry->base = range.base;
entry->size = range.size;
entry->is_mem = range.is_mem ? true : false;
}
} else {
TRACEF("ERROR - No e820 range entries found! This is going to end badly for everyone.\n");
}
}