diff options
-rw-r--r-- | i386/i386at/biosmem.c | 408 | ||||
-rw-r--r-- | i386/i386at/biosmem.h | 37 | ||||
-rw-r--r-- | i386/i386at/model_dep.c | 65 |
3 files changed, 316 insertions, 194 deletions
diff --git a/i386/i386at/biosmem.c b/i386/i386at/biosmem.c index 567cc745..90ae54a9 100644 --- a/i386/i386at/biosmem.c +++ b/i386/i386at/biosmem.c @@ -18,7 +18,6 @@ #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> @@ -43,7 +42,26 @@ #define BOOT_CGACHARS (80 * 25) #define BOOT_CGACOLOR 0x7 -extern char _start, _end; +#define BIOSMEM_MAX_BOOT_DATA 64 + +/* + * Boot data descriptor. + * + * The start and end addresses must not be page-aligned, since there + * could be more than one range inside a single page. + */ +struct biosmem_boot_data { + phys_addr_t start; + phys_addr_t end; + boolean_t temporary; +}; + +/* + * Sorted array of boot data descriptors. + */ +static struct biosmem_boot_data biosmem_boot_data_array[BIOSMEM_MAX_BOOT_DATA] + __bootdata; +static unsigned int biosmem_nr_boot_data __bootdata; /* * Maximum number of entries in the BIOS memory map. @@ -73,14 +91,6 @@ struct biosmem_map_entry { }; /* - * Contiguous block of physical memory. - */ -struct biosmem_segment { - phys_addr_t start; - phys_addr_t 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 @@ -91,6 +101,14 @@ static struct biosmem_map_entry biosmem_map[BIOSMEM_MAX_MAP_SIZE * 2] static unsigned int biosmem_map_size __bootdata; /* + * Contiguous block of physical memory. + */ +struct biosmem_segment { + phys_addr_t start; + phys_addr_t end; +}; + +/* * Physical segment boundaries. */ static struct biosmem_segment biosmem_segments[VM_PAGE_MAX_SEGS] __bootdata; @@ -100,10 +118,10 @@ static struct biosmem_segment biosmem_segments[VM_PAGE_MAX_SEGS] __bootdata; * * This heap is located above BIOS memory. */ -static uint32_t biosmem_heap_start __bootdata; -static uint32_t biosmem_heap_bottom __bootdata; -static uint32_t biosmem_heap_top __bootdata; -static uint32_t biosmem_heap_end __bootdata; +static phys_addr_t biosmem_heap_start __bootdata; +static phys_addr_t biosmem_heap_bottom __bootdata; +static phys_addr_t biosmem_heap_top __bootdata; +static phys_addr_t biosmem_heap_end __bootdata; /* * Boot allocation policy. @@ -113,7 +131,11 @@ static uint32_t biosmem_heap_end __bootdata; */ static boolean_t biosmem_heap_topdown __bootdata; -static char biosmem_panic_toobig_msg[] __bootdata +static char biosmem_panic_inval_boot_data[] __bootdata + = "biosmem: invalid boot data"; +static char biosmem_panic_too_many_boot_data[] __bootdata + = "biosmem: too many boot data ranges"; +static char biosmem_panic_too_big_msg[] __bootdata = "biosmem: too many memory map entries"; #ifndef MACH_HYP static char biosmem_panic_setup_msg[] __bootdata @@ -126,6 +148,103 @@ static char biosmem_panic_inval_msg[] __bootdata static char biosmem_panic_nomem_msg[] __bootdata = "biosmem: unable to allocate memory"; +void __boot +biosmem_register_boot_data(phys_addr_t start, phys_addr_t end, + boolean_t temporary) +{ + unsigned int i; + + if (start >= end) { + boot_panic(biosmem_panic_inval_boot_data); + } + + if (biosmem_nr_boot_data == ARRAY_SIZE(biosmem_boot_data_array)) { + boot_panic(biosmem_panic_too_many_boot_data); + } + + for (i = 0; i < biosmem_nr_boot_data; i++) { + /* Check if the new range overlaps */ + if ((end > biosmem_boot_data_array[i].start) + && (start < biosmem_boot_data_array[i].end)) { + + /* + * If it does, check whether it's part of another range. + * For example, this applies to debugging symbols directly + * taken from the kernel image. + */ + if ((start >= biosmem_boot_data_array[i].start) + && (end <= biosmem_boot_data_array[i].end)) { + + /* + * If it's completely included, make sure that a permanent + * range remains permanent. + * + * XXX This means that if one big range is first registered + * as temporary, and a smaller range inside of it is + * registered as permanent, the bigger range becomes + * permanent. It's not easy nor useful in practice to do + * better than that. + */ + if (biosmem_boot_data_array[i].temporary != temporary) { + biosmem_boot_data_array[i].temporary = FALSE; + } + + return; + } + + boot_panic(biosmem_panic_inval_boot_data); + } + + if (end <= biosmem_boot_data_array[i].start) { + break; + } + } + + boot_memmove(&biosmem_boot_data_array[i + 1], + &biosmem_boot_data_array[i], + (biosmem_nr_boot_data - i) * sizeof(*biosmem_boot_data_array)); + + biosmem_boot_data_array[i].start = start; + biosmem_boot_data_array[i].end = end; + biosmem_boot_data_array[i].temporary = temporary; + biosmem_nr_boot_data++; +} + +static void __init +biosmem_unregister_boot_data(phys_addr_t start, phys_addr_t end) +{ + unsigned int i; + + if (start >= end) { + panic(biosmem_panic_inval_boot_data); + } + + assert(biosmem_nr_boot_data != 0); + + for (i = 0; biosmem_nr_boot_data; i++) { + if ((start == biosmem_boot_data_array[i].start) + && (end == biosmem_boot_data_array[i].end)) { + break; + } + } + + if (i == biosmem_nr_boot_data) { + return; + } + +#if DEBUG + printf("biosmem: unregister boot data: %llx:%llx\n", + (unsigned long long)biosmem_boot_data_array[i].start, + (unsigned long long)biosmem_boot_data_array[i].end); +#endif /* DEBUG */ + + biosmem_nr_boot_data--; + + boot_memmove(&biosmem_boot_data_array[i], + &biosmem_boot_data_array[i + 1], + (biosmem_nr_boot_data - i) * sizeof(*biosmem_boot_data_array)); +} + #ifndef MACH_HYP static void __boot @@ -308,7 +427,7 @@ biosmem_map_adjust(void) */ if (biosmem_map_size >= ARRAY_SIZE(biosmem_map)) - boot_panic(biosmem_panic_toobig_msg); + boot_panic(biosmem_panic_too_big_msg); biosmem_map[biosmem_map_size] = tmp; biosmem_map_size++; @@ -327,6 +446,16 @@ biosmem_map_adjust(void) biosmem_map_sort(); } +/* + * Find addresses of physical memory within a given range. + * + * This function considers the memory map with the [*phys_start, *phys_end] + * range on entry, and returns the lowest address of physical memory + * in *phys_start, and the highest address of unusable memory immediately + * following physical memory in *phys_end. + * + * These addresses are normally used to establish the range of a segment. + */ static int __boot biosmem_map_find_avail(phys_addr_t *phys_start, phys_addr_t *phys_end) { @@ -388,166 +517,86 @@ biosmem_segment_size(unsigned int seg_index) return biosmem_segments[seg_index].end - biosmem_segments[seg_index].start; } -#ifndef MACH_HYP - -static void __boot -biosmem_save_cmdline_sizes(struct multiboot_raw_info *mbi) +static int __boot +biosmem_find_avail_clip(phys_addr_t *avail_start, phys_addr_t *avail_end, + phys_addr_t data_start, phys_addr_t data_end) { - struct multiboot_raw_module *mod; - uint32_t i, va; + phys_addr_t orig_end; - 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; + assert(data_start < data_end); - addr = phystokv(mbi->mods_addr); + orig_end = data_end; + data_start = vm_page_trunc(data_start); + data_end = vm_page_round(data_end); - 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; - } + if (data_end < orig_end) { + boot_panic(biosmem_panic_inval_boot_data); } -} -static int __boot -biosmem_find_heap_clip(phys_addr_t *heap_start, phys_addr_t *heap_end, - phys_addr_t data_start, phys_addr_t data_end) -{ - assert(data_start < data_end); - - if ((data_end <= *heap_start) || (data_start >= *heap_end)) { + if ((data_end <= *avail_start) || (data_start >= *avail_end)) { return 0; } - if (data_start > *heap_start) { - *heap_end = data_start; + if (data_start > *avail_start) { + *avail_end = data_start; } else { - if (data_end >= *heap_end) { + if (data_end >= *avail_end) { return -1; } - *heap_start = data_end; + *avail_start = data_end; } return 0; } /* - * Find available memory for an allocation heap. + * Find available memory in the given range. * * The search starts at the given start address, up to the given end address. - * If a range is found, it is stored through the heap_startp and heap_endp + * If a range is found, it is stored through the avail_startp and avail_endp * pointers. * - * The search skips boot data, that is : - * - 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 + * The range boundaries are page-aligned on return. */ static int __boot -biosmem_find_heap(const struct multiboot_raw_info *mbi, - phys_addr_t start, phys_addr_t end, - phys_addr_t *heap_start, phys_addr_t *heap_end) +biosmem_find_avail(phys_addr_t start, phys_addr_t end, + phys_addr_t *avail_start, phys_addr_t *avail_end) { - struct multiboot_raw_module *mod; - struct elf_shdr *shdr; - unsigned long tmp; + phys_addr_t orig_start; unsigned int i; int error; - if (start >= end) { - return -1; - } - - *heap_start = start; - *heap_end = end; - - error = biosmem_find_heap_clip(heap_start, heap_end, - _kvtophys(&_start), _kvtophys(&_end)); - - if (error) { - return error; - } + assert(start <= end); - if ((mbi->flags & MULTIBOOT_LOADER_CMDLINE) && (mbi->cmdline != 0)) { - error = biosmem_find_heap_clip(heap_start, heap_end, - mbi->cmdline, - mbi->cmdline + mbi->unused0); + orig_start = start; + start = vm_page_round(start); + end = vm_page_trunc(end); - if (error) { - return error; - } + if ((start < orig_start) || (start >= end)) { + return -1; } - if (mbi->flags & MULTIBOOT_LOADER_MODULES) { - i = mbi->mods_count * sizeof(struct multiboot_raw_module); - error = biosmem_find_heap_clip(heap_start, heap_end, - mbi->mods_addr, mbi->mods_addr + i); - - if (error) { - return error; - } - - tmp = phystokv(mbi->mods_addr); - - for (i = 0; i < mbi->mods_count; i++) { - mod = (struct multiboot_raw_module *)tmp + i; - error = biosmem_find_heap_clip(heap_start, heap_end, - mod->mod_start, mod->mod_end); - - if (error) { - return error; - } + *avail_start = start; + *avail_end = end; - if (mod->string != 0) { - error = biosmem_find_heap_clip(heap_start, heap_end, - mod->string, - mod->string + mod->reserved); - - if (error) { - return error; - } - } - } - } - - if (mbi->flags & MULTIBOOT_LOADER_SHDR) { - tmp = mbi->shdr_num * mbi->shdr_size; - error = biosmem_find_heap_clip(heap_start, heap_end, - mbi->shdr_addr, mbi->shdr_addr + tmp); + for (i = 0; i < biosmem_nr_boot_data; i++) { + error = biosmem_find_avail_clip(avail_start, avail_end, + biosmem_boot_data_array[i].start, + biosmem_boot_data_array[i].end); if (error) { - return error; - } - - 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; - - error = biosmem_find_heap_clip(heap_start, heap_end, - shdr->addr, shdr->addr + shdr->size); + return -1; } } return 0; } +#ifndef MACH_HYP + static void __boot -biosmem_setup_allocator(struct multiboot_raw_info *mbi) +biosmem_setup_allocator(const struct multiboot_raw_info *mbi) { phys_addr_t heap_start, heap_end, max_heap_start, max_heap_end; phys_addr_t start, end; @@ -569,7 +618,7 @@ biosmem_setup_allocator(struct multiboot_raw_info *mbi) start = BIOSMEM_END; for (;;) { - error = biosmem_find_heap(mbi, start, end, &heap_start, &heap_end); + error = biosmem_find_avail(start, end, &heap_start, &heap_end); if (error) { break; @@ -586,17 +635,14 @@ biosmem_setup_allocator(struct multiboot_raw_info *mbi) if (max_heap_start >= max_heap_end) boot_panic(biosmem_panic_setup_msg); - 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_bottom = biosmem_heap_start; biosmem_heap_top = biosmem_heap_end; biosmem_heap_topdown = TRUE; + + /* Prevent biosmem_free_usable() from releasing the heap */ + biosmem_register_boot_data(biosmem_heap_start, biosmem_heap_end, FALSE); } #endif /* MACH_HYP */ @@ -676,7 +722,6 @@ biosmem_xen_bootstrap(void) biosmem_heap_end = boot_info.nr_pages << PAGE_SHIFT; #ifndef __LP64__ - /* TODO Check that this actually makes sense */ if (biosmem_heap_end > VM_PAGE_DIRECTMAP_LIMIT) biosmem_heap_end = VM_PAGE_DIRECTMAP_LIMIT; #endif /* __LP64__ */ @@ -693,12 +738,18 @@ biosmem_xen_bootstrap(void) * first. */ biosmem_heap_topdown = FALSE; + + /* + * Prevent biosmem_free_usable() from releasing the Xen boot information + * and the heap. + */ + biosmem_register_boot_data(0, biosmem_heap_end, FALSE); } #else /* MACH_HYP */ void __boot -biosmem_bootstrap(struct multiboot_raw_info *mbi) +biosmem_bootstrap(const struct multiboot_raw_info *mbi) { if (mbi->flags & MULTIBOOT_LOADER_MMAP) biosmem_map_build(mbi); @@ -706,12 +757,6 @@ biosmem_bootstrap(struct multiboot_raw_info *mbi) biosmem_map_build_simple(mbi); biosmem_bootstrap_common(); - - /* - * 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); } @@ -722,8 +767,6 @@ biosmem_bootalloc(unsigned int nr_pages) { unsigned long addr, size; - assert(!vm_page_ready()); - size = vm_page_ptoa(nr_pages); if (size == 0) @@ -764,6 +807,8 @@ biosmem_directmap_end(void) return biosmem_segment_end(VM_PAGE_SEG_DMA); } +#if DEBUG + static const char * __init biosmem_type_desc(unsigned int type) { @@ -797,9 +842,15 @@ biosmem_map_show(void) entry->base_addr + entry->length, biosmem_type_desc(entry->type)); - printf("biosmem: heap: %x-%x\n", biosmem_heap_start, biosmem_heap_end); + printf("biosmem: heap: %llx:%llx\n", + (unsigned long long)biosmem_heap_start, + (unsigned long long)biosmem_heap_end); } +#else /* DEBUG */ +#define biosmem_map_show() +#endif /* DEBUG */ + static void __init biosmem_load_segment(struct biosmem_segment *seg, uint64_t max_phys_end) { @@ -865,24 +916,31 @@ biosmem_setup(void) } } -#ifndef MACH_HYP - static void __init -biosmem_free_usable_range(phys_addr_t start, phys_addr_t end) +biosmem_unregister_temporary_boot_data(void) { - struct vm_page *page; + struct biosmem_boot_data *data; + unsigned int i; - assert(start < end); + for (i = 0; i < biosmem_nr_boot_data; i++) { + data = &biosmem_boot_data_array[i]; - start = vm_page_round(start); - end = vm_page_round(end); + if (!data->temporary) { + continue; + } - if (start >= end) { - return; + biosmem_unregister_boot_data(data->start, data->end); + i = (unsigned int)-1; } +} + +static void __init +biosmem_free_usable_range(phys_addr_t start, phys_addr_t end) +{ + struct vm_page *page; #if DEBUG - printf("biosmem: release to vm_page: %llx-%llx (%lluk)\n", + printf("biosmem: release to vm_page: %llx:%llx (%lluk)\n", (unsigned long long)start, (unsigned long long)end, (unsigned long long)((end - start) >> 10)); #endif @@ -896,40 +954,32 @@ biosmem_free_usable_range(phys_addr_t start, phys_addr_t end) } static void __init -biosmem_free_usable_entry(struct multiboot_raw_info *mbi, phys_addr_t start, - phys_addr_t end) +biosmem_free_usable_entry(phys_addr_t start, phys_addr_t end) { - phys_addr_t heap_start, heap_end; + phys_addr_t avail_start, avail_end; int error; - /* XXX Abuse biosmem_find_heap to locate usable areas */ - for (;;) { - error = biosmem_find_heap(mbi, start, end, &heap_start, &heap_end); - - if (error) { - break; - } - - error = biosmem_find_heap_clip(&heap_start, &heap_end, - biosmem_heap_start, biosmem_heap_end); + error = biosmem_find_avail(start, end, &avail_start, &avail_end); if (error) { break; } - biosmem_free_usable_range(heap_start, heap_end); - start = heap_end; + biosmem_free_usable_range(avail_start, avail_end); + start = avail_end; } } void __init -biosmem_free_usable(struct multiboot_raw_info *mbi) +biosmem_free_usable(void) { struct biosmem_map_entry *entry; uint64_t start, end; unsigned int i; + biosmem_unregister_temporary_boot_data(); + for (i = 0; i < biosmem_map_size; i++) { entry = &biosmem_map[i]; @@ -955,8 +1005,6 @@ biosmem_free_usable(struct multiboot_raw_info *mbi) continue; } - biosmem_free_usable_entry(mbi, start, end); + biosmem_free_usable_entry(start, end); } } - -#endif /* MACH_HYP */ diff --git a/i386/i386at/biosmem.h b/i386/i386at/biosmem.h index f48cfc30..7824c168 100644 --- a/i386/i386at/biosmem.h +++ b/i386/i386at/biosmem.h @@ -40,25 +40,39 @@ #define BIOSMEM_END 0x100000 /* - * Early initialization of the biosmem module. + * Report reserved addresses to the biosmem module. * - * This function processes the given multiboot data for BIOS-provided - * memory information, and sets up a bootstrap physical page allocator. + * Once all boot data have been registered, the user can set up the + * early page allocator. * - * It is called before paging is enabled. + * If the range is marked temporary, it will be unregistered when + * biosmem_free_usable() is called, so that pages that used to store + * these boot data may be released to the VM system. + */ +void biosmem_register_boot_data(phys_addr_t start, phys_addr_t end, + boolean_t temporary); + +/* + * Initialize the early page allocator. + * + * This function uses the memory map provided by the boot loader along + * with the registered boot data addresses to set up a heap of free pages + * of physical memory. + * + * Note that on Xen, this function registers all the Xen boot information + * as boot data itself. */ #ifdef MACH_HYP void biosmem_xen_bootstrap(void); #else /* MACH_HYP */ -void biosmem_bootstrap(struct multiboot_raw_info *mbi); +void biosmem_bootstrap(const struct multiboot_raw_info *mbi); #endif /* MACH_HYP */ /* * Allocate contiguous physical pages during bootstrap. * - * This function is called before paging is enabled. The pages returned - * are guaranteed to be part of the direct physical mapping when paging - * is enabled. + * The pages returned are guaranteed to be part of the direct physical + * mapping when paging is enabled. * * This function should only be used to allocate initial page table pages. * Those pages are later loaded into the VM system (as reserved pages) @@ -80,9 +94,10 @@ void biosmem_setup(void); /* * Free all usable memory. + * + * This function releases all pages that aren't used by boot data and have + * not already been loaded into the VM system. */ -#ifndef MACH_HYP -void biosmem_free_usable(struct multiboot_raw_info *mbi); -#endif /* MACH_HYP */ +void biosmem_free_usable(void); #endif /* _X86_BIOSMEM_H */ diff --git a/i386/i386at/model_dep.c b/i386/i386at/model_dep.c index 8e98bd9e..87d6cefa 100644 --- a/i386/i386at/model_dep.c +++ b/i386/i386at/model_dep.c @@ -66,6 +66,7 @@ #include <i386/model_dep.h> #include <i386at/autoconf.h> #include <i386at/biosmem.h> +#include <i386at/elf.h> #include <i386at/idt.h> #include <i386at/int_init.h> #include <i386at/kd.h> @@ -158,9 +159,7 @@ void machine_init(void) * This is particularly important for the Linux drivers which * require available DMA memory. */ -#ifndef MACH_HYP - biosmem_free_usable((struct multiboot_raw_info *) &boot_info); -#endif /* MACH_HYP */ + biosmem_free_usable(); /* * Set up to use floating point. @@ -274,6 +273,65 @@ void db_reset_cpu(void) halt_all_cpus(1); } +#ifndef MACH_HYP + +static void +register_boot_data(const struct multiboot_raw_info *mbi) +{ + struct multiboot_raw_module *mod; + struct elf_shdr *shdr; + unsigned long tmp; + unsigned int i; + + extern char _start[], _end[]; + + /* XXX For now, register all boot data as permanent */ + + biosmem_register_boot_data(_kvtophys(&_start), _kvtophys(&_end), FALSE); + + if ((mbi->flags & MULTIBOOT_LOADER_CMDLINE) && (mbi->cmdline != 0)) { + biosmem_register_boot_data(mbi->cmdline, + mbi->cmdline + strlen((void *)mbi->cmdline) + 1, FALSE); + } + + if (mbi->flags & MULTIBOOT_LOADER_MODULES) { + i = mbi->mods_count * sizeof(struct multiboot_raw_module); + biosmem_register_boot_data(mbi->mods_addr, mbi->mods_addr + i, FALSE); + + tmp = phystokv(mbi->mods_addr); + + for (i = 0; i < mbi->mods_count; i++) { + mod = (struct multiboot_raw_module *)tmp + i; + biosmem_register_boot_data(mod->mod_start, mod->mod_end, FALSE); + + if (mod->string != 0) { + biosmem_register_boot_data(mod->string, + mod->string + strlen((void *)mod->string) + 1, + FALSE); + } + } + } + + if (mbi->flags & MULTIBOOT_LOADER_SHDR) { + tmp = mbi->shdr_num * mbi->shdr_size; + biosmem_register_boot_data(mbi->shdr_addr, mbi->shdr_addr + tmp, FALSE); + + 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_register_boot_data(shdr->addr, shdr->addr + shdr->size, FALSE); + } + } +} + +#endif /* MACH_HYP */ + /* * Basic PC VM initialization. * Turns on paging and changes the kernel segments to use high linear addresses. @@ -301,6 +359,7 @@ i386at_init(void) #ifdef MACH_HYP biosmem_xen_bootstrap(); #else /* MACH_HYP */ + register_boot_data((struct multiboot_raw_info *) &boot_info); biosmem_bootstrap((struct multiboot_raw_info *) &boot_info); #endif /* MACH_HYP */ |