From b98aa7633450fdfcc81160ef48891b8d96c92f99 Mon Sep 17 00:00:00 2001 From: Aaron Lindsay Date: Mon, 24 Dec 2012 12:44:48 -0500 Subject: [PATCH] arch/i386: Add multiboot support --- arch/i386/include/arch/multiboot.h | 119 +++++++++++++++++++++++++++++ arch/i386/kernel.ld | 2 + arch/i386/kernel/kernel.mk | 3 +- arch/i386/kernel/mmu.c | 96 ++++++++++++++++++++++- arch/i386/kernel/multiboot.c | 64 ++++++++++++++++ arch/i386/start.S | 12 +-- 6 files changed, 287 insertions(+), 9 deletions(-) create mode 100644 arch/i386/include/arch/multiboot.h create mode 100644 arch/i386/kernel/multiboot.c diff --git a/arch/i386/include/arch/multiboot.h b/arch/i386/include/arch/multiboot.h new file mode 100644 index 0000000..3fd617c --- /dev/null +++ b/arch/i386/include/arch/multiboot.h @@ -0,0 +1,119 @@ +/* multiboot.h - the header for Multiboot */ +/* Copyright (C) 1999, 2001 Free Software Foundation, Inc. + + 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, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ + +/* Macros. */ + +/* The magic number for the Multiboot header. */ +#define MULTIBOOT_HEADER_MAGIC 0x1BADB002 + +/* The flags for the Multiboot header. */ +#ifdef __ELF__ +# define MULTIBOOT_HEADER_FLAGS 0x00000003 +#else +# define MULTIBOOT_HEADER_FLAGS 0x00010003 +#endif + +/* The magic number passed by a Multiboot-compliant boot loader. */ +#define MULTIBOOT_BOOTLOADER_MAGIC 0x2BADB002 + +/* The size of our stack (16KB). */ +#define STACK_SIZE 0x4000 + +/* C symbol format. HAVE_ASM_USCORE is defined by configure. */ +#ifdef HAVE_ASM_USCORE +# define EXT_C(sym) _ ## sym +#else +# define EXT_C(sym) sym +#endif + +#ifndef ASM +/* Do not include here in boot.S. */ + +/* Types. */ + +/* The Multiboot header. */ +typedef struct multiboot_header +{ + unsigned long magic; + unsigned long flags; + unsigned long checksum; + unsigned long header_addr; + unsigned long load_addr; + unsigned long load_end_addr; + unsigned long bss_end_addr; + unsigned long entry_addr; +} multiboot_header_t; + +/* The symbol table for a.out. */ +typedef struct aout_symbol_table +{ + unsigned long tabsize; + unsigned long strsize; + unsigned long addr; + unsigned long reserved; +} aout_symbol_table_t; + +/* The section header table for ELF. */ +typedef struct elf_section_header_table +{ + unsigned long num; + unsigned long size; + unsigned long addr; + unsigned long shndx; +} elf_section_header_table_t; + +/* The Multiboot information. */ +typedef struct multiboot_info +{ + unsigned long flags; + unsigned long mem_lower; + unsigned long mem_upper; + unsigned long boot_device; + unsigned long cmdline; + unsigned long mods_count; + unsigned long mods_addr; + union + { + aout_symbol_table_t aout_sym; + elf_section_header_table_t elf_sec; + } u; + unsigned long mmap_length; + unsigned long mmap_addr; +} multiboot_info_t; + +/* The module structure. */ +typedef struct module +{ + unsigned long mod_start; + unsigned long mod_end; + unsigned long string; + unsigned long reserved; +} module_t; + +/* The memory map. Be careful that the offset 0 is base_addr_low + but no size. */ +typedef struct memory_map +{ + unsigned long size; + unsigned long base_addr_low; + unsigned long base_addr_high; + unsigned long length_low; + unsigned long length_high; + unsigned long type; +} memory_map_t; + +#endif /* ! ASM */ diff --git a/arch/i386/kernel.ld b/arch/i386/kernel.ld index 5a1e95a..37d53c6 100644 --- a/arch/i386/kernel.ld +++ b/arch/i386/kernel.ld @@ -4,6 +4,7 @@ SECTIONS { . = 0xc0100000; kernel_start = .; + kernel_start_phys = . - 0xc0000000; .text ALIGN(0x1000) : AT(ADDR(.text) - 0xc0000000) { *(.text*) *(.rodata*) } @@ -24,4 +25,5 @@ SECTIONS *(.bss*) *(COMMON*) } kernel_end = .; + kernel_end_phys = . - 0xc0000000; } diff --git a/arch/i386/kernel/kernel.mk b/arch/i386/kernel/kernel.mk index 71b6419..612e9cf 100644 --- a/arch/i386/kernel/kernel.mk +++ b/arch/i386/kernel/kernel.mk @@ -3,7 +3,8 @@ SUBDIRS := include $(BASEDIR)/header.mk -OBJS_$(d) := $(d)/mmu.o +OBJS_$(d) := $(d)/mmu.o \ + $(d)/multiboot.o KOBJS += $(OBJS_$(d)) diff --git a/arch/i386/kernel/mmu.c b/arch/i386/kernel/mmu.c index e91a3f7..d403277 100644 --- a/arch/i386/kernel/mmu.c +++ b/arch/i386/kernel/mmu.c @@ -22,9 +22,101 @@ #include #include +extern uint32 kernel_start_phys, kernel_end_phys; +extern uint32 kernel_start, kernel_end; + +#define PAGE_SIZE 0x00400000 +#define ROUND_DOWN_PAGE_SIZE(addr) ((typeof(addr))((uint32)(addr) & 0xff800000)) + +static inline int page_intersects(uint32 *page_start, uint32 *lower, uint32 *upper) { + return (lower >= page_start && lower < (page_start + PAGE_SIZE)) || + (upper >= page_start && upper < (page_start + PAGE_SIZE)) || + (lower < page_start && upper >= (page_start + PAGE_SIZE)); +} + void mmu_reinit() { + int virt_phys_offset; /* CAUTION: 1 = 4 bytes */ + uint32 *curr_tbl_entry; + uint32 *curr_addr; + uint32 *page_dir; + asm("movl %%cr3, %0" : "=r"(page_dir) : : ); + + virt_phys_offset = &kernel_start - &kernel_start_phys; + curr_tbl_entry = page_dir + virt_phys_offset; + + //do first loop iteration outside th eloop, because we have to check against wrapping back around to know we're done + *curr_tbl_entry = 0x83; + curr_tbl_entry++; + + //create identity mapping for entire address space using sections. + //BUT, if we've relocated the kernel from where it is in physical + //memory, make sure we keep those mappings correct, and we'll actually + //swap the two mappings so all of memory is addressable. + for (curr_addr = (uint32 *)PAGE_SIZE; curr_addr != 0; curr_addr += (PAGE_SIZE>>2)) { + if (page_intersects(curr_addr, &kernel_start_phys, &kernel_end_phys)) { + *curr_tbl_entry = (uint32)ROUND_DOWN_PAGE_SIZE(curr_addr + virt_phys_offset) | 0x83; + } else if (page_intersects(curr_addr, &kernel_start, &kernel_end)) { + *curr_tbl_entry = (uint32)ROUND_DOWN_PAGE_SIZE(curr_addr - virt_phys_offset) | 0x83; + } else { + *curr_tbl_entry = (uint32)curr_addr | 0x83; + } + /* Force the entries to reload */ + asm("invlpg (%0)" : : "r"(curr_addr) : ); + curr_tbl_entry++; + } } + +//TODO merge the rest of this file with the similar section in arch/arm. This is clearly mostly duplicated code. + +int mmu_region_contains(void *lower_a, void *upper_a, void *lower_b, void *upper_b) { + return lower_b >= lower_a && upper_b <= upper_a; +} +int mmu_region_contains_single(void *lower_a, void *upper_a, void *ptr) { + return lower_a <= ptr && ptr <= upper_a; +} + +#define section_round_down(ptr) (((uint32)ptr) & ~(PAGE_SIZE-1)) +#define section_round_up(ptr) (((((uint32)ptr) & ~1) + (PAGE_SIZE-1) ) & ~(PAGE_SIZE-1)) + +/* Called once per physical memory region by bootup code. This function is + * responsible for only adding (via mm_add_free_region()) those parts of the + * memory region which are still available (i.e. aren't in the kernel and + * haven't been remapped anywhere else. */ void declare_memory_region(void *lower, void *upper) { - (void)lower; - (void)upper; + void *k_section_start_phys = (void *)section_round_down(&kernel_start_phys); + void *k_section_end_phys = (void *)(section_round_up(&kernel_end_phys) - 1); + void *k_section_start_virt = (void *)section_round_down(&kernel_start); + void *k_section_end_virt = (void *)(section_round_up(&kernel_end) - 1); + + if (upper - lower < 1) { + print("Warning: declare_memory_region() called with lower=%x, upper=%x. Ignoring.\n", lower, upper); + return; + } + + //TODO It's possible (though highly unlikely) that the kernel (virtual) + //is split across two different memory regions. We should probably + //handle this. + if (mmu_region_contains(lower, upper, k_section_start_phys, k_section_end_phys)) { + //Don't map any of the physical kernel's memory + declare_memory_region(lower, (void *) ((char *)k_section_start_phys - 1)); + declare_memory_region((void *) ((char *)k_section_end_phys + 1), upper); + mm_add_free_region(&kernel_end, k_section_end_virt); + } else if (mmu_region_contains(lower, upper, k_section_start_virt, k_section_end_virt)) { + declare_memory_region(lower, (void *) ((char *)k_section_start_virt - 1)); + declare_memory_region((void *) ((char *)k_section_end_virt + 1), upper); + } else if (mmu_region_contains_single(lower, upper, k_section_start_phys)) { + if ((void*)((char*)lower + 1) < k_section_start_phys) + declare_memory_region(lower, (void *) ((char *)k_section_start_phys - 1)); + } else if (mmu_region_contains_single(lower, upper, k_section_end_phys)) { + if (k_section_end_phys < (void*)((char*)upper - 1)) + declare_memory_region((void *) ((char *)k_section_end_phys + 1), upper); + } else if (mmu_region_contains_single(lower, upper, k_section_start_virt)) { + if ((void*)((char*)lower + 1) < k_section_start_virt) + declare_memory_region(lower, (void *) ((char *)k_section_start_virt - 1)); + } else if (mmu_region_contains_single(lower, upper, k_section_end_virt)) { + if (k_section_end_virt < (void*)((char*)upper - 1)) + declare_memory_region((void *) ((char *)k_section_end_virt + 1), upper); + } else { + mm_add_free_region(lower, upper); + } } diff --git a/arch/i386/kernel/multiboot.c b/arch/i386/kernel/multiboot.c new file mode 100644 index 0000000..13659d1 --- /dev/null +++ b/arch/i386/kernel/multiboot.c @@ -0,0 +1,64 @@ +/* + Copyright (C) 2012, Aaron Lindsay + + This file is part of Aedrix. + + 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, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include + +struct multiboot_mmap { + unsigned int size; + unsigned int base_addr_low, base_addr_high; + unsigned int length_low, length_high; + unsigned int type; +}; + +extern multiboot_info_t* multiboot_mbd; +extern uint32 multiboot_magic; + +int detect_memory_mmap() { + struct multiboot_mmap* mmap = (struct multiboot_mmap*)multiboot_mbd->mmap_addr; + int found_region = 0; + + while ((char*)mmap < (char*)multiboot_mbd->mmap_addr + multiboot_mbd->mmap_length) { + if (mmap->base_addr_high || mmap->length_high) + return -1; + if (mmap->type == 0x1) { /* ignore non-usable memory */ + declare_memory_region((void*)mmap->base_addr_low, + (void*)(mmap->base_addr_low + mmap->length_low)); + found_region = 1; + } + mmap = (struct multiboot_mmap*) ((unsigned int)mmap + mmap->size + sizeof(unsigned int)); + } + + return !found_region; +} + +int detect_memory_contiguous() { + declare_memory_region((void*)0x0, (void*)((uint32)multiboot_mbd->mem_lower * 1024)); + declare_memory_region((void*)0x1000000, (void*)((uint32)multiboot_mbd->mem_upper*1024 + 0x100000)); + return 0; +} + +int detect_memory() { + if (multiboot_mbd->flags & (1<<6)) + return detect_memory_mmap(); + else if (multiboot_mbd->flags & (1<<0)) + return detect_memory_contiguous(); + return -1; +} diff --git a/arch/i386/start.S b/arch/i386/start.S index 72ba07c..c1fe07e 100644 --- a/arch/i386/start.S +++ b/arch/i386/start.S @@ -47,8 +47,8 @@ */ .global start start: - movl %eax, virt_to_phys(magic) /* Save off multiboot magic number */ - movl %ebx, virt_to_phys(mbd) /* Save off multiboot data structure */ + movl %eax, virt_to_phys(multiboot_magic)/* Save off multiboot magic number */ + movl %ebx, virt_to_phys(multiboot_mbd) /* Save off multiboot data structure */ movl $virt_to_phys(init_page_dir), %eax /* load page directory base register */ movl %eax, %cr3 @@ -134,10 +134,10 @@ init_page_dir: .lcomm stack, STACK_SIZE /* reserve stack space on a doubleword boundary */ /* place to store multiboot header information */ -.global mbd -.comm mbd, 4 /* we will use this in i386_main */ -.global magic -.comm magic, 4 /* we will use this in i386_main */ +.global multiboot_mbd +.comm multiboot_mbd, 4 /* we will use this in i386_main */ +.global multiboot_magic +.comm multiboot_magic, 4 /* we will use this in i386_main */ /* TODO FIXME remove this - needs fix to dependencies first */ .global atags_ptr