diff options
Diffstat (limited to 'i386')
-rw-r--r-- | i386/Makefrag.am | 2 | ||||
-rw-r--r-- | i386/i386/vm_param.h | 40 | ||||
-rw-r--r-- | i386/i386at/biosmem.c | 844 | ||||
-rw-r--r-- | i386/i386at/biosmem.h | 84 | ||||
-rw-r--r-- | i386/i386at/elf.h | 61 | ||||
-rw-r--r-- | i386/i386at/model_dep.c | 286 | ||||
-rw-r--r-- | i386/include/mach/i386/multiboot.h | 105 | ||||
-rw-r--r-- | i386/include/mach/i386/vm_types.h | 5 |
8 files changed, 1155 insertions, 272 deletions
diff --git a/i386/Makefrag.am b/i386/Makefrag.am index ef393d5d..e6cfedd7 100644 --- a/i386/Makefrag.am +++ b/i386/Makefrag.am @@ -29,6 +29,8 @@ libkernel_a_SOURCES += \ if PLATFORM_at libkernel_a_SOURCES += \ + i386/i386at/biosmem.c \ + i386/i386at/biosmem.h \ i386/i386at/boothdr.S \ i386/i386at/com.c \ i386/i386at/com.h \ diff --git a/i386/i386/vm_param.h b/i386/i386/vm_param.h index 6292ca25..da3126c0 100644 --- a/i386/i386/vm_param.h +++ b/i386/i386/vm_param.h @@ -23,6 +23,8 @@ #ifndef _I386_KERNEL_I386_VM_PARAM_ #define _I386_KERNEL_I386_VM_PARAM_ +#include <kern/macros.h> + /* XXX use xu/vm_param.h */ #include <mach/vm_param.h> #ifdef MACH_PV_PAGETABLES @@ -101,4 +103,42 @@ #define kvtolin(a) ((vm_offset_t)(a) - VM_MIN_KERNEL_ADDRESS + LINEAR_MIN_KERNEL_ADDRESS) #define lintokv(a) ((vm_offset_t)(a) - LINEAR_MIN_KERNEL_ADDRESS + VM_MIN_KERNEL_ADDRESS) +/* + * Physical memory properties. + */ +#define VM_PAGE_DMA_LIMIT DECL_CONST(0x1000000, UL) + +#ifdef __LP64__ +#define VM_PAGE_MAX_SEGS 4 +#define VM_PAGE_DMA32_LIMIT DECL_CONST(0x100000000, UL) +#define VM_PAGE_DIRECTMAP_LIMIT DECL_CONST(0x400000000000, UL) +#define VM_PAGE_HIGHMEM_LIMIT DECL_CONST(0x10000000000000, UL) +#else /* __LP64__ */ +#define VM_PAGE_DIRECTMAP_LIMIT (VM_MAX_KERNEL_ADDRESS \ + - VM_MIN_KERNEL_ADDRESS \ + - VM_KERNEL_MAP_SIZE + 1) +#ifdef PAE +#define VM_PAGE_MAX_SEGS 3 +#define VM_PAGE_HIGHMEM_LIMIT DECL_CONST(0x10000000000000, ULL) +#else /* PAE */ +#define VM_PAGE_MAX_SEGS 3 +#define VM_PAGE_HIGHMEM_LIMIT DECL_CONST(0xfffff000, UL) +#endif /* PAE */ +#endif /* __LP64__ */ + +/* + * Physical segment indexes. + */ +#define VM_PAGE_SEG_DMA 0 + +#ifdef __LP64__ +#define VM_PAGE_SEG_DMA32 1 +#define VM_PAGE_SEG_DIRECTMAP 2 +#define VM_PAGE_SEG_HIGHMEM 3 +#else /* __LP64__ */ +#define VM_PAGE_SEG_DMA32 1 /* Alias for the DIRECTMAP segment */ +#define VM_PAGE_SEG_DIRECTMAP 1 +#define VM_PAGE_SEG_HIGHMEM 2 +#endif /* __LP64__ */ + #endif /* _I386_KERNEL_I386_VM_PARAM_ */ diff --git a/i386/i386at/biosmem.c b/i386/i386at/biosmem.c new file mode 100644 index 00000000..5b4fbddc --- /dev/null +++ b/i386/i386at/biosmem.c @@ -0,0 +1,844 @@ +/* + * Copyright (c) 2010-2014 Richard Braun. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <string.h> +#include <i386/model_dep.h> +#include <i386at/biosmem.h> +#include <i386at/elf.h> +#include <kern/assert.h> +#include <kern/debug.h> +#include <kern/macros.h> +#include <kern/printf.h> +#include <mach/vm_param.h> +#include <mach/machine/multiboot.h> +#include <sys/types.h> +#include <vm/vm_page.h> + +#define __boot +#define __bootdata +#define __init + +#define boot_memmove memmove +#define boot_panic panic +#define boot_strlen strlen + +#define BOOT_CGAMEM phystokv(0xb8000) +#define BOOT_CGACHARS (80 * 25) +#define BOOT_CGACOLOR 0x7 + +extern char _start, _end; + +/* + * Maximum number of entries in the BIOS memory map. + * + * Because of adjustments of overlapping ranges, the memory map can grow + * to twice this size. + */ +#define BIOSMEM_MAX_MAP_SIZE 128 + +/* + * Memory range types. + */ +#define BIOSMEM_TYPE_AVAILABLE 1 +#define BIOSMEM_TYPE_RESERVED 2 +#define BIOSMEM_TYPE_ACPI 3 +#define BIOSMEM_TYPE_NVS 4 +#define BIOSMEM_TYPE_UNUSABLE 5 +#define BIOSMEM_TYPE_DISABLED 6 + +/* + * Memory map entry. + */ +struct biosmem_map_entry { + uint64_t base_addr; + uint64_t length; + unsigned int type; +}; + +/* + * Contiguous block of physical memory. + * + * Tha "available" range records what has been passed to the VM system as + * available inside the segment. + */ +struct biosmem_segment { + phys_addr_t start; + phys_addr_t end; + phys_addr_t avail_start; + phys_addr_t avail_end; +}; + +/* + * Memory map built from the information passed by the boot loader. + * + * If the boot loader didn't pass a valid memory map, a simple map is built + * based on the mem_lower and mem_upper multiboot fields. + */ +static struct biosmem_map_entry biosmem_map[BIOSMEM_MAX_MAP_SIZE * 2] + __bootdata; +static unsigned int biosmem_map_size __bootdata; + +/* + * Physical segment boundaries. + */ +static struct biosmem_segment biosmem_segments[VM_PAGE_MAX_SEGS] __bootdata; + +/* + * Boundaries of the simple bootstrap heap. + * + * This heap is located above BIOS memory. + */ +static uint32_t biosmem_heap_start __bootdata; +static uint32_t biosmem_heap_cur __bootdata; +static uint32_t biosmem_heap_end __bootdata; + +static char biosmem_panic_toobig_msg[] __bootdata + = "biosmem: too many memory map entries"; +static char biosmem_panic_setup_msg[] __bootdata + = "biosmem: unable to set up the early memory allocator"; +static char biosmem_panic_noseg_msg[] __bootdata + = "biosmem: unable to find any memory segment"; +static char biosmem_panic_inval_msg[] __bootdata + = "biosmem: attempt to allocate 0 page"; +static char biosmem_panic_nomem_msg[] __bootdata + = "biosmem: unable to allocate memory"; + +static void __boot +biosmem_map_build(const struct multiboot_raw_info *mbi) +{ + struct multiboot_raw_mmap_entry *mb_entry, *mb_end; + struct biosmem_map_entry *start, *entry, *end; + unsigned long addr; + + addr = phystokv(mbi->mmap_addr); + mb_entry = (struct multiboot_raw_mmap_entry *)addr; + mb_end = (struct multiboot_raw_mmap_entry *)(addr + mbi->mmap_length); + start = biosmem_map; + entry = start; + end = entry + BIOSMEM_MAX_MAP_SIZE; + + while ((mb_entry < mb_end) && (entry < end)) { + entry->base_addr = mb_entry->base_addr; + entry->length = mb_entry->length; + entry->type = mb_entry->type; + + mb_entry = (void *)mb_entry + sizeof(mb_entry->size) + mb_entry->size; + entry++; + } + + biosmem_map_size = entry - start; +} + +static void __boot +biosmem_map_build_simple(const struct multiboot_raw_info *mbi) +{ + struct biosmem_map_entry *entry; + + entry = biosmem_map; + entry->base_addr = 0; + entry->length = mbi->mem_lower << 10; + entry->type = BIOSMEM_TYPE_AVAILABLE; + + entry++; + entry->base_addr = BIOSMEM_END; + entry->length = mbi->mem_upper << 10; + entry->type = BIOSMEM_TYPE_AVAILABLE; + + biosmem_map_size = 2; +} + +static int __boot +biosmem_map_entry_is_invalid(const struct biosmem_map_entry *entry) +{ + return (entry->base_addr + entry->length) <= entry->base_addr; +} + +static void __boot +biosmem_map_filter(void) +{ + struct biosmem_map_entry *entry; + unsigned int i; + + i = 0; + + while (i < biosmem_map_size) { + entry = &biosmem_map[i]; + + if (biosmem_map_entry_is_invalid(entry)) { + biosmem_map_size--; + boot_memmove(entry, entry + 1, + (biosmem_map_size - i) * sizeof(*entry)); + continue; + } + + i++; + } +} + +static void __boot +biosmem_map_sort(void) +{ + struct biosmem_map_entry tmp; + unsigned int i, j; + + /* + * Simple insertion sort. + */ + for (i = 1; i < biosmem_map_size; i++) { + tmp = biosmem_map[i]; + + for (j = i - 1; j < i; j--) { + if (biosmem_map[j].base_addr < tmp.base_addr) + break; + + biosmem_map[j + 1] = biosmem_map[j]; + } + + biosmem_map[j + 1] = tmp; + } +} + +static void __boot +biosmem_map_adjust(void) +{ + struct biosmem_map_entry tmp, *a, *b, *first, *second; + uint64_t a_end, b_end, last_end; + unsigned int i, j, last_type; + + biosmem_map_filter(); + + /* + * Resolve overlapping areas, giving priority to most restrictive + * (i.e. numerically higher) types. + */ + for (i = 0; i < biosmem_map_size; i++) { + a = &biosmem_map[i]; + a_end = a->base_addr + a->length; + + j = i + 1; + + while (j < biosmem_map_size) { + b = &biosmem_map[j]; + b_end = b->base_addr + b->length; + + if ((a->base_addr >= b_end) || (a_end <= b->base_addr)) { + j++; + continue; + } + + if (a->base_addr < b->base_addr) { + first = a; + second = b; + } else { + first = b; + second = a; + } + + if (a_end > b_end) { + last_end = a_end; + last_type = a->type; + } else { + last_end = b_end; + last_type = b->type; + } + + tmp.base_addr = second->base_addr; + tmp.length = MIN(a_end, b_end) - tmp.base_addr; + tmp.type = MAX(a->type, b->type); + first->length = tmp.base_addr - first->base_addr; + second->base_addr += tmp.length; + second->length = last_end - second->base_addr; + second->type = last_type; + + /* + * Filter out invalid entries. + */ + if (biosmem_map_entry_is_invalid(a) + && biosmem_map_entry_is_invalid(b)) { + *a = tmp; + biosmem_map_size--; + memmove(b, b + 1, (biosmem_map_size - j) * sizeof(*b)); + continue; + } else if (biosmem_map_entry_is_invalid(a)) { + *a = tmp; + j++; + continue; + } else if (biosmem_map_entry_is_invalid(b)) { + *b = tmp; + j++; + continue; + } + + if (tmp.type == a->type) + first = a; + else if (tmp.type == b->type) + first = b; + else { + + /* + * If the overlapping area can't be merged with one of its + * neighbors, it must be added as a new entry. + */ + + if (biosmem_map_size >= ARRAY_SIZE(biosmem_map)) + boot_panic(biosmem_panic_toobig_msg); + + biosmem_map[biosmem_map_size] = tmp; + biosmem_map_size++; + j++; + continue; + } + + if (first->base_addr > tmp.base_addr) + first->base_addr = tmp.base_addr; + + first->length += tmp.length; + j++; + } + } + + biosmem_map_sort(); +} + +static int __boot +biosmem_map_find_avail(phys_addr_t *phys_start, phys_addr_t *phys_end) +{ + const struct biosmem_map_entry *entry, *map_end; + phys_addr_t seg_start, seg_end; + uint64_t start, end; + + seg_start = (phys_addr_t)-1; + seg_end = (phys_addr_t)-1; + map_end = biosmem_map + biosmem_map_size; + + for (entry = biosmem_map; entry < map_end; entry++) { + if (entry->type != BIOSMEM_TYPE_AVAILABLE) + continue; + + start = vm_page_round(entry->base_addr); + + if (start >= *phys_end) + break; + + end = vm_page_trunc(entry->base_addr + entry->length); + + if ((start < end) && (start < *phys_end) && (end > *phys_start)) { + if (seg_start == (phys_addr_t)-1) + seg_start = start; + + seg_end = end; + } + } + + if ((seg_start == (phys_addr_t)-1) || (seg_end == (phys_addr_t)-1)) + return -1; + + if (seg_start > *phys_start) + *phys_start = seg_start; + + if (seg_end < *phys_end) + *phys_end = seg_end; + + return 0; +} + +static void __boot +biosmem_set_segment(unsigned int seg_index, phys_addr_t start, phys_addr_t end) +{ + biosmem_segments[seg_index].start = start; + biosmem_segments[seg_index].end = end; +} + +static phys_addr_t __boot +biosmem_segment_end(unsigned int seg_index) +{ + return biosmem_segments[seg_index].end; +} + +static phys_addr_t __boot +biosmem_segment_size(unsigned int seg_index) +{ + return biosmem_segments[seg_index].end - biosmem_segments[seg_index].start; +} + +static void __boot +biosmem_save_cmdline_sizes(struct multiboot_raw_info *mbi) +{ + struct multiboot_raw_module *mod; + uint32_t i, va; + + if (mbi->flags & MULTIBOOT_LOADER_CMDLINE) { + va = phystokv(mbi->cmdline); + mbi->unused0 = boot_strlen((char *)va) + 1; + } + + if (mbi->flags & MULTIBOOT_LOADER_MODULES) { + unsigned long addr; + + addr = phystokv(mbi->mods_addr); + + for (i = 0; i < mbi->mods_count; i++) { + mod = (struct multiboot_raw_module *)addr + i; + va = phystokv(mod->string); + mod->reserved = boot_strlen((char *)va) + 1; + } + } +} + +static void __boot +biosmem_find_boot_data_update(uint32_t min, uint32_t *start, uint32_t *end, + uint32_t data_start, uint32_t data_end) +{ + if ((min <= data_start) && (data_start < *start)) { + *start = data_start; + *end = data_end; + } +} + +/* + * Find the first boot data in the given range, and return their containing + * area (start address is returned directly, end address is returned in end). + * The following are considered boot data : + * - the kernel + * - the kernel command line + * - the module table + * - the modules + * - the modules command lines + * - the ELF section header table + * - the ELF .shstrtab, .symtab and .strtab sections + * + * If no boot data was found, 0 is returned, and the end address isn't set. + */ +static uint32_t __boot +biosmem_find_boot_data(const struct multiboot_raw_info *mbi, uint32_t min, + uint32_t max, uint32_t *endp) +{ + struct multiboot_raw_module *mod; + struct elf_shdr *shdr; + uint32_t i, start, end = end; + unsigned long tmp; + + start = max; + + biosmem_find_boot_data_update(min, &start, &end, _kvtophys(&_start), + _kvtophys(&_end)); + + if ((mbi->flags & MULTIBOOT_LOADER_CMDLINE) && (mbi->cmdline != 0)) + biosmem_find_boot_data_update(min, &start, &end, mbi->cmdline, + mbi->cmdline + mbi->unused0); + + if (mbi->flags & MULTIBOOT_LOADER_MODULES) { + i = mbi->mods_count * sizeof(struct multiboot_raw_module); + biosmem_find_boot_data_update(min, &start, &end, mbi->mods_addr, + mbi->mods_addr + i); + tmp = phystokv(mbi->mods_addr); + + for (i = 0; i < mbi->mods_count; i++) { + mod = (struct multiboot_raw_module *)tmp + i; + biosmem_find_boot_data_update(min, &start, &end, mod->mod_start, + mod->mod_end); + + if (mod->string != 0) + biosmem_find_boot_data_update(min, &start, &end, mod->string, + mod->string + mod->reserved); + } + } + + if (mbi->flags & MULTIBOOT_LOADER_SHDR) { + tmp = mbi->shdr_num * mbi->shdr_size; + biosmem_find_boot_data_update(min, &start, &end, mbi->shdr_addr, + mbi->shdr_addr + tmp); + tmp = phystokv(mbi->shdr_addr); + + for (i = 0; i < mbi->shdr_num; i++) { + shdr = (struct elf_shdr *)(tmp + (i * mbi->shdr_size)); + + if ((shdr->type != ELF_SHT_SYMTAB) + && (shdr->type != ELF_SHT_STRTAB)) + continue; + + biosmem_find_boot_data_update(min, &start, &end, shdr->addr, + shdr->addr + shdr->size); + } + } + + if (start == max) + return 0; + + *endp = end; + return start; +} + +static void __boot +biosmem_setup_allocator(struct multiboot_raw_info *mbi) +{ + uint32_t heap_start, heap_end, max_heap_start, max_heap_end; + uint32_t mem_end, next; + + /* + * Find some memory for the heap. Look for the largest unused area in + * upper memory, carefully avoiding all boot data. + */ + mem_end = vm_page_trunc((mbi->mem_upper + 1024) << 10); + +#ifndef __LP64__ + if (mem_end > VM_PAGE_DIRECTMAP_LIMIT) + mem_end = VM_PAGE_DIRECTMAP_LIMIT; +#endif /* __LP64__ */ + + max_heap_start = 0; + max_heap_end = 0; + next = BIOSMEM_END; + + do { + heap_start = next; + heap_end = biosmem_find_boot_data(mbi, heap_start, mem_end, &next); + + if (heap_end == 0) { + heap_end = mem_end; + next = 0; + } + + if ((heap_end - heap_start) > (max_heap_end - max_heap_start)) { + max_heap_start = heap_start; + max_heap_end = heap_end; + } + } while (next != 0); + + max_heap_start = vm_page_round(max_heap_start); + max_heap_end = vm_page_trunc(max_heap_end); + + if (max_heap_start >= max_heap_end) + boot_panic(biosmem_panic_setup_msg); + + biosmem_heap_start = max_heap_start; + biosmem_heap_end = max_heap_end; + biosmem_heap_cur = biosmem_heap_end; +} + +void __boot +biosmem_bootstrap(struct multiboot_raw_info *mbi) +{ + phys_addr_t phys_start, phys_end, last_addr; + int error; + + if (mbi->flags & MULTIBOOT_LOADER_MMAP) + biosmem_map_build(mbi); + else + biosmem_map_build_simple(mbi); + + biosmem_map_adjust(); + + phys_start = BIOSMEM_BASE; + phys_end = VM_PAGE_DMA_LIMIT; + error = biosmem_map_find_avail(&phys_start, &phys_end); + + if (error) + boot_panic(biosmem_panic_noseg_msg); + + biosmem_set_segment(VM_PAGE_SEG_DMA, phys_start, phys_end); + last_addr = phys_end; + + phys_start = VM_PAGE_DMA_LIMIT; +#ifdef VM_PAGE_DMA32_LIMIT + phys_end = VM_PAGE_DMA32_LIMIT; + error = biosmem_map_find_avail(&phys_start, &phys_end); + + if (error) + goto out; + + biosmem_set_segment(VM_PAGE_SEG_DMA32, phys_start, phys_end); + last_addr = phys_end; + + phys_start = VM_PAGE_DMA32_LIMIT; +#endif /* VM_PAGE_DMA32_LIMIT */ + phys_end = VM_PAGE_DIRECTMAP_LIMIT; + error = biosmem_map_find_avail(&phys_start, &phys_end); + + if (error) + goto out; + + biosmem_set_segment(VM_PAGE_SEG_DIRECTMAP, phys_start, phys_end); + last_addr = phys_end; + + phys_start = VM_PAGE_DIRECTMAP_LIMIT; + phys_end = VM_PAGE_HIGHMEM_LIMIT; + error = biosmem_map_find_avail(&phys_start, &phys_end); + + if (error) + goto out; + + biosmem_set_segment(VM_PAGE_SEG_HIGHMEM, phys_start, phys_end); + +out: + + /* + * The kernel and modules command lines will be memory mapped later + * during initialization. Their respective sizes must be saved. + */ + biosmem_save_cmdline_sizes(mbi); + biosmem_setup_allocator(mbi); + + /* XXX phys_last_addr must be part of the direct physical mapping */ + phys_last_addr = last_addr; +} + +unsigned long __boot +biosmem_bootalloc(unsigned int nr_pages) +{ + unsigned long addr, size; + + assert(!vm_page_ready()); + + size = vm_page_ptoa(nr_pages); + + if (size == 0) + boot_panic(biosmem_panic_inval_msg); + + /* Top-down allocation to avoid unnecessarily filling DMA segments */ + addr = biosmem_heap_cur - size; + + if ((addr < biosmem_heap_start) || (addr > biosmem_heap_cur)) + boot_panic(biosmem_panic_nomem_msg); + + biosmem_heap_cur = addr; + return addr; +} + +phys_addr_t __boot +biosmem_directmap_size(void) +{ + if (biosmem_segment_size(VM_PAGE_SEG_DIRECTMAP) != 0) + return biosmem_segment_end(VM_PAGE_SEG_DIRECTMAP); + else if (biosmem_segment_size(VM_PAGE_SEG_DMA32) != 0) + return biosmem_segment_end(VM_PAGE_SEG_DMA32); + else + return biosmem_segment_end(VM_PAGE_SEG_DMA); +} + +static const char * __init +biosmem_type_desc(unsigned int type) +{ + switch (type) { + case BIOSMEM_TYPE_AVAILABLE: + return "available"; + case BIOSMEM_TYPE_RESERVED: + return "reserved"; + case BIOSMEM_TYPE_ACPI: + return "ACPI"; + case BIOSMEM_TYPE_NVS: + return "ACPI NVS"; + case BIOSMEM_TYPE_UNUSABLE: + return "unusable"; + default: + return "unknown (reserved)"; + } +} + +static void __init +biosmem_map_show(void) +{ + const struct biosmem_map_entry *entry, *end; + + printf("biosmem: physical memory map:\n"); + + for (entry = biosmem_map, end = entry + biosmem_map_size; + entry < end; + entry++) + printf("biosmem: %018llx:%018llx, %s\n", entry->base_addr, + entry->base_addr + entry->length, + biosmem_type_desc(entry->type)); + + printf("biosmem: heap: %x-%x\n", biosmem_heap_start, biosmem_heap_end); +} + +static void __init +biosmem_load_segment(struct biosmem_segment *seg, uint64_t max_phys_end, + phys_addr_t phys_start, phys_addr_t phys_end, + phys_addr_t avail_start, phys_addr_t avail_end) +{ + unsigned int seg_index; + + seg_index = seg - biosmem_segments; + + if (phys_end > max_phys_end) { + if (max_phys_end <= phys_start) { + printf("biosmem: warning: segment %s physically unreachable, " + "not loaded\n", vm_page_seg_name(seg_index)); + return; + } + + printf("biosmem: warning: segment %s truncated to %#llx\n", + vm_page_seg_name(seg_index), max_phys_end); + phys_end = max_phys_end; + } + + if ((avail_start < phys_start) || (avail_start >= phys_end)) + avail_start = phys_start; + + if ((avail_end <= phys_start) || (avail_end > phys_end)) + avail_end = phys_end; + + seg->avail_start = avail_start; + seg->avail_end = avail_end; + vm_page_load(seg_index, phys_start, phys_end, avail_start, avail_end); +} + +void __init +biosmem_setup(void) +{ + struct biosmem_segment *seg; + unsigned int i; + + biosmem_map_show(); + + for (i = 0; i < ARRAY_SIZE(biosmem_segments); i++) { + if (biosmem_segment_size(i) == 0) + break; + + seg = &biosmem_segments[i]; + biosmem_load_segment(seg, VM_PAGE_HIGHMEM_LIMIT, seg->start, seg->end, + biosmem_heap_start, biosmem_heap_cur); + } +} + +static void __init +biosmem_free_usable_range(phys_addr_t start, phys_addr_t end) +{ + struct vm_page *page; + + printf("biosmem: release to vm_page: %llx-%llx (%lluk)\n", + (unsigned long long)start, (unsigned long long)end, + (unsigned long long)((end - start) >> 10)); + + while (start < end) { + page = vm_page_lookup_pa(start); + assert(page != NULL); + vm_page_manage(page); + start += PAGE_SIZE; + } +} + +static void __init +biosmem_free_usable_update_start(phys_addr_t *start, phys_addr_t res_start, + phys_addr_t res_end) +{ + if ((*start >= res_start) && (*start < res_end)) + *start = res_end; +} + +static phys_addr_t __init +biosmem_free_usable_start(phys_addr_t start) +{ + const struct biosmem_segment *seg; + unsigned int i; + + biosmem_free_usable_update_start(&start, _kvtophys(&_start), + _kvtophys(&_end)); + biosmem_free_usable_update_start(&start, biosmem_heap_start, + biosmem_heap_end); + + for (i = 0; i < ARRAY_SIZE(biosmem_segments); i++) { + seg = &biosmem_segments[i]; + biosmem_free_usable_update_start(&start, seg->avail_start, + seg->avail_end); + } + + return start; +} + +static int __init +biosmem_free_usable_reserved(phys_addr_t addr) +{ + const struct biosmem_segment *seg; + unsigned int i; + + if ((addr >= _kvtophys(&_start)) + && (addr < _kvtophys(&_end))) + return 1; + + if ((addr >= biosmem_heap_start) && (addr < biosmem_heap_end)) + return 1; + + for (i = 0; i < ARRAY_SIZE(biosmem_segments); i++) { + seg = &biosmem_segments[i]; + + if ((addr >= seg->avail_start) && (addr < seg->avail_end)) + return 1; + } + + return 0; +} + +static phys_addr_t __init +biosmem_free_usable_end(phys_addr_t start, phys_addr_t entry_end) +{ + while (start < entry_end) { + if (biosmem_free_usable_reserved(start)) + break; + + start += PAGE_SIZE; + } + + return start; +} + +static void __init +biosmem_free_usable_entry(phys_addr_t start, phys_addr_t end) +{ + phys_addr_t entry_end; + + entry_end = end; + + for (;;) { + start = biosmem_free_usable_start(start); + + if (start >= entry_end) + return; + + end = biosmem_free_usable_end(start, entry_end); + biosmem_free_usable_range(start, end); + start = end; + } +} + +void __init +biosmem_free_usable(void) +{ + struct biosmem_map_entry *entry; + uint64_t start, end; + unsigned int i; + + for (i = 0; i < biosmem_map_size; i++) { + entry = &biosmem_map[i]; + + if (entry->type != BIOSMEM_TYPE_AVAILABLE) + continue; + + start = vm_page_round(entry->base_addr); + + if (start >= VM_PAGE_HIGHMEM_LIMIT) + break; + + end = vm_page_trunc(entry->base_addr + entry->length); + + if (start < BIOSMEM_BASE) + start = BIOSMEM_BASE; + + biosmem_free_usable_entry(start, end); + } +} diff --git a/i386/i386at/biosmem.h b/i386/i386at/biosmem.h new file mode 100644 index 00000000..0cc4f8a6 --- /dev/null +++ b/i386/i386at/biosmem.h @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2010-2014 Richard Braun. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _X86_BIOSMEM_H +#define _X86_BIOSMEM_H + +#include <mach/machine/vm_types.h> +#include <mach/machine/multiboot.h> + +/* + * Address where the address of the Extended BIOS Data Area segment can be + * found. + */ +#define BIOSMEM_EBDA_PTR 0x40e + +/* + * Significant low memory addresses. + * + * The first 64 KiB are reserved for various reasons (e.g. to preserve BIOS + * data and to work around data corruption on some hardware). + */ +#define BIOSMEM_BASE 0x010000 +#define BIOSMEM_BASE_END 0x0a0000 +#define BIOSMEM_EXT_ROM 0x0e0000 +#define BIOSMEM_ROM 0x0f0000 +#define BIOSMEM_END 0x100000 + +/* + * Early initialization of the biosmem module. + * + * This function processes the given multiboot data for BIOS-provided + * memory information, and sets up a bootstrap physical page allocator. + * + * It is called before paging is enabled. + */ +void biosmem_bootstrap(struct multiboot_raw_info *mbi); + +/* + * Allocate contiguous physical pages during bootstrap. + * + * This function is called before paging is enabled. It should only be used + * to allocate initial page table pages. Those pages are later loaded into + * the VM system (as reserved pages) which means they can be freed like other + * regular pages. Users should fix up the type of those pages once the VM + * system is initialized. + */ +unsigned long biosmem_bootalloc(unsigned int nr_pages); + +/* + * Return the amount of physical memory that can be directly mapped. + * + * This includes the size of both the DMA/DMA32 and DIRECTMAP segments. + */ +phys_addr_t biosmem_directmap_size(void); + +/* + * Set up physical memory based on the information obtained during bootstrap + * and load it in the VM system. + */ +void biosmem_setup(void); + +/* + * Free all usable memory. + * + * This includes ranges that weren't part of the bootstrap allocator initial + * heap, e.g. because they contained boot data. + */ +void biosmem_free_usable(void); + +#endif /* _X86_BIOSMEM_H */ diff --git a/i386/i386at/elf.h b/i386/i386at/elf.h new file mode 100644 index 00000000..26f4d87b --- /dev/null +++ b/i386/i386at/elf.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2013 Richard Braun. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _X86_ELF_H +#define _X86_ELF_H + +#define ELF_SHT_SYMTAB 2 +#define ELF_SHT_STRTAB 3 + +struct elf_shdr { + unsigned int name; + unsigned int type; + unsigned int flags; + unsigned long addr; + unsigned long offset; + unsigned int size; + unsigned int link; + unsigned int info; + unsigned int addralign; + unsigned int entsize; +}; + +#ifdef __LP64__ + +struct elf_sym { + unsigned int name; + unsigned char info; + unsigned char other; + unsigned short shndx; + unsigned long value; + unsigned long size; +}; + +#else /* __LP64__ */ + +struct elf_sym { + unsigned int name; + unsigned long value; + unsigned long size; + unsigned char info; + unsigned char other; + unsigned short shndx; +}; + +#endif /* __LP64__ */ + +#endif /* _X86_ELF_H */ diff --git a/i386/i386at/model_dep.c b/i386/i386at/model_dep.c index 04cf6958..dbb9d8b6 100644 --- a/i386/i386at/model_dep.c +++ b/i386/i386at/model_dep.c @@ -64,6 +64,7 @@ #include <i386/locore.h> #include <i386/model_dep.h> #include <i386at/autoconf.h> +#include <i386at/biosmem.h> #include <i386at/idt.h> #include <i386at/int_init.h> #include <i386at/kd.h> @@ -127,20 +128,6 @@ struct multiboot_info boot_info; /* Command line supplied to kernel. */ char *kernel_cmdline = ""; -/* This is used for memory initialization: - it gets bumped up through physical memory - that exists and is not occupied by boot gunk. - It is not necessarily page-aligned. */ -static vm_offset_t avail_next -#ifndef MACH_HYP - = RESERVED_BIOS /* XX end of BIOS data area */ -#endif /* MACH_HYP */ - ; - -/* Possibly overestimated amount of available memory - still remaining to be handed to the VM system. */ -static vm_size_t avail_remaining; - extern char version[]; /* If set, reboot the system on ctrl-alt-delete. */ @@ -275,91 +262,6 @@ void db_reset_cpu(void) halt_all_cpus(1); } - -/* - * Compute physical memory size and other parameters. - */ -void -mem_size_init(void) -{ - vm_offset_t max_phys_size; - - /* Physical memory on all PCs starts at physical address 0. - XX make it a constant. */ - phys_first_addr = 0; - -#ifdef MACH_HYP - if (boot_info.nr_pages >= 0x100000) { - printf("Truncating memory size to 4GiB\n"); - phys_last_addr = 0xffffffffU; - } else - phys_last_addr = boot_info.nr_pages * 0x1000; -#else /* MACH_HYP */ - vm_size_t phys_last_kb; - - if (boot_info.flags & MULTIBOOT_MEM_MAP) { - struct multiboot_mmap *map, *map_end; - - map = (void*) phystokv(boot_info.mmap_addr); - map_end = (void*) map + boot_info.mmap_count; - - while (map + 1 <= map_end) { - if (map->Type == MB_ARD_MEMORY) { - unsigned long long start = map->BaseAddr, end = map->BaseAddr + map->Length;; - - if (start >= 0x100000000ULL) { - printf("Ignoring %luMiB RAM region above 4GiB\n", (unsigned long) (map->Length >> 20)); - } else { - if (end >= 0x100000000ULL) { - printf("Truncating memory region to 4GiB\n"); - end = 0x0ffffffffU; - } - if (end > phys_last_addr) - phys_last_addr = end; - - printf("AT386 boot: physical memory map from 0x%lx to 0x%lx\n", - (unsigned long) start, - (unsigned long) end); - } - } - map = (void*) map + map->size + sizeof(map->size); - } - } else { - phys_last_kb = 0x400 + boot_info.mem_upper; - /* Avoid 4GiB overflow. */ - if (phys_last_kb < 0x400 || phys_last_kb >= 0x400000) { - printf("Truncating memory size to 4GiB\n"); - phys_last_addr = 0xffffffffU; - } else - phys_last_addr = phys_last_kb * 0x400; - } -#endif /* MACH_HYP */ - - printf("AT386 boot: physical memory from 0x%lx to 0x%lx\n", - phys_first_addr, phys_last_addr); - - /* Reserve room for virtual mappings. - * Yes, this loses memory. Blame i386. */ - max_phys_size = VM_MAX_KERNEL_ADDRESS - VM_MIN_KERNEL_ADDRESS - VM_KERNEL_MAP_SIZE; - if (phys_last_addr - phys_first_addr > max_phys_size) { - phys_last_addr = phys_first_addr + max_phys_size; - printf("Truncating memory to %luMiB\n", (phys_last_addr - phys_first_addr) / (1024 * 1024)); - /* TODO Xen: be nice, free lost memory */ - } - - phys_first_addr = round_page(phys_first_addr); - phys_last_addr = trunc_page(phys_last_addr); - -#ifdef MACH_HYP - /* Memory is just contiguous */ - avail_remaining = phys_last_addr; -#else /* MACH_HYP */ - avail_remaining - = phys_last_addr - (0x100000 - (boot_info.mem_lower * 0x400) - - RESERVED_BIOS); -#endif /* MACH_HYP */ -} - /* * Basic PC VM initialization. * Turns on paging and changes the kernel segments to use high linear addresses. @@ -382,9 +284,9 @@ i386at_init(void) #endif /* MACH_HYP */ /* - * Find memory size parameters. + * Read memory map and load it into the physical page allocator. */ - mem_size_init(); + biosmem_bootstrap((struct multiboot_raw_info *) &boot_info); #ifdef MACH_XEN kernel_cmdline = (char*) boot_info.cmd_line; @@ -432,6 +334,13 @@ i386at_init(void) pmap_bootstrap(); /* + * Load physical segments into the VM system. + * The early allocation functions become unusable after + * this point. + */ + biosmem_setup(); + + /* * We'll have to temporarily install a direct mapping * between physical memory and low linear memory, * until we start using our new kernel segment descriptors. @@ -706,187 +615,20 @@ resettodr(void) unsigned int pmap_free_pages(void) { - return atop(avail_remaining); + return vm_page_atop(phys_last_addr); /* XXX */ } -/* Always returns page-aligned regions. */ boolean_t init_alloc_aligned(vm_size_t size, vm_offset_t *addrp) { - vm_offset_t addr; + *addrp = biosmem_bootalloc(vm_page_atop(vm_page_round(size))); -#ifdef MACH_HYP - /* There is none */ - if (!avail_next) - avail_next = _kvtophys(boot_info.pt_base) + (boot_info.nr_pt_frames + 3) * 0x1000; -#else /* MACH_HYP */ - extern char start[], end[]; - int i; - static int wrapped = 0; - - /* Memory regions to skip. */ - vm_offset_t cmdline_start_pa = boot_info.flags & MULTIBOOT_CMDLINE - ? boot_info.cmdline : 0; - vm_offset_t cmdline_end_pa = cmdline_start_pa - ? cmdline_start_pa+strlen((char*)phystokv(cmdline_start_pa))+1 - : 0; - vm_offset_t mods_start_pa = boot_info.flags & MULTIBOOT_MODS - ? boot_info.mods_addr : 0; - vm_offset_t mods_end_pa = mods_start_pa - ? mods_start_pa - + boot_info.mods_count * sizeof(struct multiboot_module) - : 0; - - retry: -#endif /* MACH_HYP */ - - /* Page-align the start address. */ - avail_next = round_page(avail_next); - -#ifndef MACH_HYP - /* Start with memory above 16MB, reserving the low memory for later. */ - /* Don't care on Xen */ - if (!wrapped && phys_last_addr > 16 * 1024*1024) - { - if (avail_next < 16 * 1024*1024) - avail_next = 16 * 1024*1024; - else if (avail_next == phys_last_addr) - { - /* We have used all the memory above 16MB, so now start on - the low memory. This will wind up at the end of the list - of free pages, so it should not have been allocated to any - other use in early initialization before the Linux driver - glue initialization needs to allocate low memory. */ - avail_next = RESERVED_BIOS; - wrapped = 1; - } - } -#endif /* MACH_HYP */ - - /* Check if we have reached the end of memory. */ - if (avail_next == - ( -#ifndef MACH_HYP - wrapped ? 16 * 1024*1024 : -#endif /* MACH_HYP */ - phys_last_addr)) + if (*addrp == 0) return FALSE; - /* Tentatively assign the current location to the caller. */ - addr = avail_next; - - /* Bump the pointer past the newly allocated region - and see where that puts us. */ - avail_next += size; - -#ifndef MACH_HYP - /* Skip past the I/O and ROM area. */ - if (boot_info.flags & MULTIBOOT_MEM_MAP) - { - struct multiboot_mmap *map, *map_end, *current = NULL, *next = NULL; - unsigned long long minimum_next = ~0ULL; - - map = (void*) phystokv(boot_info.mmap_addr); - map_end = (void*) map + boot_info.mmap_count; - - /* Find both our current map, and the next one */ - while (map + 1 <= map_end) - { - if (map->Type == MB_ARD_MEMORY) - { - unsigned long long start = map->BaseAddr; - unsigned long long end = start + map->Length;; - - if (start <= addr && avail_next <= end) - { - /* Ok, fits in the current map */ - current = map; - break; - } - else if (avail_next <= start && start < minimum_next) - { - /* This map is not far from avail_next */ - next = map; - minimum_next = start; - } - } - map = (void*) map + map->size + sizeof(map->size); - } - - if (!current) { - /* Area does not fit in the current map, switch to next - * map if any */ - if (!next || next->BaseAddr >= phys_last_addr) - { - /* No further reachable map, we have reached - * the end of memory, but possibly wrap around - * 16MiB. */ - avail_next = phys_last_addr; - goto retry; - } - - /* Start from next map */ - avail_next = next->BaseAddr; - goto retry; - } - } - else if ((avail_next > (boot_info.mem_lower * 0x400)) && (addr < 0x100000)) - { - avail_next = 0x100000; - goto retry; - } - - /* Skip our own kernel code, data, and bss. */ - if ((phystokv(avail_next) > (vm_offset_t)start) && (phystokv(addr) < (vm_offset_t)end)) - { - avail_next = _kvtophys(end); - goto retry; - } - - /* Skip any areas occupied by valuable boot_info data. */ - if ((avail_next > cmdline_start_pa) && (addr < cmdline_end_pa)) - { - avail_next = cmdline_end_pa; - goto retry; - } - if ((avail_next > mods_start_pa) && (addr < mods_end_pa)) - { - avail_next = mods_end_pa; - goto retry; - } - if ((phystokv(avail_next) > kern_sym_start) && (phystokv(addr) < kern_sym_end)) - { - avail_next = _kvtophys(kern_sym_end); - goto retry; - } - if (boot_info.flags & MULTIBOOT_MODS) - { - struct multiboot_module *m = (struct multiboot_module *) - phystokv(boot_info.mods_addr); - for (i = 0; i < boot_info.mods_count; i++) - { - if ((avail_next > m[i].mod_start) - && (addr < m[i].mod_end)) - { - avail_next = m[i].mod_end; - goto retry; - } - /* XXX string */ - } - } -#endif /* MACH_HYP */ - - avail_remaining -= size; - - *addrp = addr; return TRUE; } -boolean_t pmap_next_page(vm_offset_t *addrp) -{ - return init_alloc_aligned(PAGE_SIZE, addrp); -} - /* Grab a physical page: the standard memory allocation mechanism during system initialization. */ @@ -894,7 +636,7 @@ vm_offset_t pmap_grab_page(void) { vm_offset_t addr; - if (!pmap_next_page(&addr)) + if (!init_alloc_aligned(PAGE_SIZE, &addr)) panic("Not enough memory to initialize Mach"); return addr; } diff --git a/i386/include/mach/i386/multiboot.h b/i386/include/mach/i386/multiboot.h index 8f1c47b0..c66ca032 100644 --- a/i386/include/mach/i386/multiboot.h +++ b/i386/include/mach/i386/multiboot.h @@ -188,5 +188,110 @@ struct multiboot_mmap /* usable memory "Type", all others are reserved. */ #define MB_ARD_MEMORY 1 +/* + * Copyright (c) 2010, 2012 Richard Braun. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/* + * Versions used by the biosmem module. + */ + +#include <kern/macros.h> + +/* + * Magic number provided by the OS to the boot loader. + */ +#define MULTIBOOT_OS_MAGIC 0x1badb002 + +/* + * Multiboot flags requesting services from the boot loader. + */ +#define MULTIBOOT_OS_MEMORY_INFO 0x2 + +#define MULTIBOOT_OS_FLAGS MULTIBOOT_OS_MEMORY_INFO + +/* + * Magic number to identify a multiboot compliant boot loader. + */ +#define MULTIBOOT_LOADER_MAGIC 0x2badb002 + +/* + * Multiboot flags set by the boot loader. + */ +#define MULTIBOOT_LOADER_MEMORY 0x01 +#define MULTIBOOT_LOADER_CMDLINE 0x04 +#define MULTIBOOT_LOADER_MODULES 0x08 +#define MULTIBOOT_LOADER_SHDR 0x20 +#define MULTIBOOT_LOADER_MMAP 0x40 + +/* + * A multiboot module. + */ +struct multiboot_raw_module { + uint32_t mod_start; + uint32_t mod_end; + uint32_t string; + uint32_t reserved; +} __packed; + +/* + * Memory map entry. + */ +struct multiboot_raw_mmap_entry { + uint32_t size; + uint64_t base_addr; + uint64_t length; + uint32_t type; +} __packed; + +/* + * Multiboot information structure as passed by the boot loader. + */ +struct multiboot_raw_info { + uint32_t flags; + uint32_t mem_lower; + uint32_t mem_upper; + uint32_t unused0; + uint32_t cmdline; + uint32_t mods_count; + uint32_t mods_addr; + uint32_t shdr_num; + uint32_t shdr_size; + uint32_t shdr_addr; + uint32_t shdr_strndx; + uint32_t mmap_length; + uint32_t mmap_addr; + uint32_t unused1[9]; +} __packed; + +/* + * Versions of the multiboot structures suitable for use with 64-bit pointers. + */ + +struct multiboot_os_module { + void *mod_start; + void *mod_end; + char *string; +}; + +struct multiboot_os_info { + uint32_t flags; + char *cmdline; + struct multiboot_module *mods_addr; + uint32_t mods_count; +}; #endif /* _MACH_I386_MULTIBOOT_H_ */ diff --git a/i386/include/mach/i386/vm_types.h b/i386/include/mach/i386/vm_types.h index 1439940b..41272e39 100644 --- a/i386/include/mach/i386/vm_types.h +++ b/i386/include/mach/i386/vm_types.h @@ -77,6 +77,11 @@ typedef unsigned long vm_offset_t; typedef vm_offset_t * vm_offset_array_t; /* + * A type for physical addresses. + */ +typedef unsigned long phys_addr_t; + +/* * A vm_size_t is the proper type for e.g. * expressing the difference between two * vm_offset_t entities. |