299 lines
12 KiB
C
299 lines
12 KiB
C
/// MentOS, The Mentoring Operating system project
|
|
/// @file boot.c
|
|
/// @brief Bootloader.
|
|
/// @copyright (c) 2014-2021 This file is distributed under the MIT License.
|
|
/// See LICENSE.md for details.
|
|
|
|
#include "link_access.h"
|
|
#include "multiboot.h"
|
|
#include "mem/paging.h"
|
|
#include "sys/module.h"
|
|
#include "stdint.h"
|
|
#include "elf/elf.h"
|
|
|
|
/// @defgroup bootloader Bootloader
|
|
/// @brief Set of functions and variables for booting the kernel.
|
|
/// @{
|
|
|
|
/// @brief External function implemented in `boot.S`.
|
|
/// @param stack_pointer The stack base pointer, usually at the end of the lowmem.
|
|
/// @param entry
|
|
/// @param boot_info
|
|
extern void boot_kernel(uint32_t stack_pointer, uint32_t entry, struct boot_info_t *boot_info);
|
|
|
|
/// @brief Size of the kernel's stack.
|
|
#define KERNEL_STACK_SIZE 0x100000
|
|
|
|
/// Serial port for QEMU.
|
|
#define SERIAL_COM1 (0x03F8)
|
|
|
|
/// @brief Linker symbols for where the .data section of `kernel.bin.o`.
|
|
EXTLD(kernel_bin)
|
|
|
|
/// @brief Linker symbol for where the bootloader starts.
|
|
extern char _bootloader_start[];
|
|
/// @brief Linker symbol for where the bootloader ends.
|
|
extern char _bootloader_end[];
|
|
|
|
/// @brief Boot info provided to the kmain function.
|
|
static boot_info_t boot_info;
|
|
/// @brief Boot page directory.
|
|
static page_directory_t boot_pgdir;
|
|
/// @brief Boot page tables.
|
|
static page_table_t boot_pgtables[1024];
|
|
|
|
static inline void __outportb(uint16_t port, uint8_t data)
|
|
{
|
|
__asm__ __volatile__("outb %%al, %%dx" ::"a"(data), "d"(port));
|
|
}
|
|
|
|
static inline void __debug_putchar(char c)
|
|
{
|
|
#if (defined(DEBUG_STDIO) || defined(DEBUG_LOG))
|
|
__outportb(SERIAL_COM1, c);
|
|
#endif
|
|
}
|
|
|
|
static inline void __debug_puts(char *s)
|
|
{
|
|
#if (defined(DEBUG_STDIO) || defined(DEBUG_LOG))
|
|
while ((*s) != 0)
|
|
__outportb(SERIAL_COM1, *s++);
|
|
#endif
|
|
}
|
|
|
|
/// @brief Align memory to the specified value (round up).
|
|
static inline uint32_t __align_rup(uint32_t addr, uint32_t value)
|
|
{
|
|
uint32_t reminder = (addr % value);
|
|
return addr + (reminder ? (value - reminder) : 0);
|
|
}
|
|
|
|
/// @brief Align memory to the specified value (round down).
|
|
static inline uint32_t __align_rdown(uint32_t addr, uint32_t value)
|
|
{
|
|
return addr - (addr % value);
|
|
}
|
|
|
|
static void __setup_pages(uint32_t pfn_virt_start, uint32_t pfn_phys_start, uint32_t pfn_count)
|
|
{
|
|
uint32_t base_pgtable = pfn_virt_start / 1024;
|
|
uint32_t base_pgentry = pfn_virt_start % 1024;
|
|
|
|
uint32_t pg_offset = 0;
|
|
for (int i = base_pgtable; i < 1024 && pfn_count; i++) {
|
|
page_table_t *table = boot_pgtables + i;
|
|
|
|
uint32_t pgentry_start = (i == base_pgtable) ? base_pgentry : 0;
|
|
|
|
for (int j = pgentry_start; j < 1024 && pfn_count; j++, pfn_count--) {
|
|
table->pages[j].frame = pfn_phys_start + pg_offset++;
|
|
table->pages[j].rw = 1;
|
|
table->pages[j].present = 1;
|
|
table->pages[j].global = 0;
|
|
table->pages[j].user = 0;
|
|
}
|
|
|
|
boot_pgdir.entries[i].rw = 1;
|
|
boot_pgdir.entries[i].present = 1;
|
|
boot_pgdir.entries[i].available = 1;
|
|
boot_pgdir.entries[i].frame = ((uint32_t)table) >> 12u;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Setup paging mapping all the low memory to two places:
|
|
* one is the physical address of the memory itself
|
|
* the other is in the virtual kernel address space
|
|
* */
|
|
static inline void __setup_boot_paging()
|
|
{
|
|
uint32_t kernel_base_phy_page = boot_info.kernel_phy_start >> 12U;
|
|
uint32_t kernel_base_virt_page = boot_info.kernel_start >> 12U;
|
|
|
|
uint32_t lowmem_last_phy_page = ((uint32_t)(boot_info.lowmem_phy_end - 1)) >> 12U;
|
|
|
|
uint32_t num_pages = lowmem_last_phy_page - kernel_base_phy_page + 1;
|
|
|
|
// Map lowmem physical pages also to their physical address (to keep bootloader working)
|
|
__setup_pages(0, 0, lowmem_last_phy_page);
|
|
|
|
// Setup kernel virtual address space + lowmem
|
|
__setup_pages(kernel_base_virt_page, kernel_base_phy_page, num_pages);
|
|
}
|
|
|
|
/// @brief Extract the starting and ending address of the kernel.
|
|
/// @param elf_hdr The elf header of the kernel.
|
|
/// @param virt_low Output variable where we store the lowest address of the kernel.
|
|
/// @param virt_high Output variable where we store the highest address of the kernel.
|
|
static void __get_kernel_low_high(elf_header_t *elf_hdr,
|
|
uint32_t *virt_low,
|
|
uint32_t *virt_high)
|
|
{
|
|
// Prepare a pointer to a program header.
|
|
elf_program_header_t *program_header;
|
|
// Compute the offset for accessing the program headers.
|
|
uint32_t offset = (uint32_t)elf_hdr + (uint32_t)elf_hdr->phoff;
|
|
// In this two variables we will store the start and end addresses of the segment.
|
|
uint32_t segment_start, segment_end;
|
|
// Iterate for each program header.
|
|
for (int i = 0; i < elf_hdr->phnum; i++) {
|
|
program_header = (elf_program_header_t *)(offset + elf_hdr->phentsize * i);
|
|
if (program_header->type == PT_LOAD) {
|
|
// Take the start and end addresses of the segment from the program header.
|
|
segment_start = program_header->vaddr;
|
|
segment_end = segment_start + program_header->memsz;
|
|
// Take the lowest and highest virtual address.
|
|
*virt_low = min(*virt_low, segment_start);
|
|
*virt_high = max(*virt_high, segment_end);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// @brief Returns the first address after the modules.
|
|
/// @param header The multiboot info structure from which we extract the info.
|
|
/// @return The address after the modules.
|
|
static inline uint32_t __get_address_after_modules(multiboot_info_t *header)
|
|
{
|
|
// We set by default the address to the ending physical address
|
|
// of the bootloader.
|
|
uint32_t addr = boot_info.bootloader_phy_end;
|
|
// Get the pointer to the mods.
|
|
multiboot_module_t *mod = (multiboot_module_t *)header->mods_addr;
|
|
for (int i = 0; (i < header->mods_count) && (i < MAX_MODULES); ++i, ++mod) {
|
|
addr = max(max(addr, mod->mod_start), mod->mod_end);
|
|
}
|
|
return addr;
|
|
}
|
|
|
|
/// @brief Relocate the kernel image.
|
|
/// @param elf_hdr The elf header of the kernel.
|
|
static inline void __relocate_kernel_image(elf_header_t *elf_hdr)
|
|
{
|
|
// Support variables.
|
|
elf_program_header_t *program_header;
|
|
char *kernel_start, *virtual_address, *physical_address;
|
|
uint32_t offset, valid_size;
|
|
|
|
// Get the elf file starting address.
|
|
kernel_start = (char *)elf_hdr;
|
|
// Compute the offset for accessing the program headers.
|
|
offset = (uint32_t)kernel_start + (uint32_t)elf_hdr->phoff;
|
|
// Iterate over the program headers.
|
|
for (int i = 0; i < elf_hdr->phnum; i++) {
|
|
// Get the program header.
|
|
program_header = (elf_program_header_t *)(offset + elf_hdr->phentsize * i);
|
|
// Get the virtual address of the program header.
|
|
virtual_address = (char *)program_header->vaddr;
|
|
// Get the physical address of the program header.
|
|
physical_address = (char *)(kernel_start + program_header->offset);
|
|
// Move only the loadable segments.
|
|
if (program_header->type == PT_LOAD) {
|
|
// Get the valid size of the segment by taking the minimum between
|
|
// the size in bytes of the segment in the file image, in memory.
|
|
valid_size = min(program_header->filesz, program_header->memsz);
|
|
// Copy the physical data of the image to the corresponding virtual address.
|
|
for (int j = 0; j < valid_size; j++)
|
|
virtual_address[j] = physical_address[j];
|
|
// Set to 0 parts not present in memory!
|
|
for (int j = valid_size; j < program_header->memsz; j++)
|
|
virtual_address[j] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// @brief Entry point of the bootloader.
|
|
/// @param magic The magic number coming from the multiboot assembly code.
|
|
/// @param header Multiboot header provided by the bootloader.
|
|
/// @param esp The initial stack pointer.
|
|
void boot_main(uint32_t magic, multiboot_info_t *header, uint32_t esp)
|
|
{
|
|
__debug_puts("\nbootloader: Start...\n");
|
|
elf_header_t *elf_hdr = (elf_header_t *)LDVAR(kernel_bin);
|
|
|
|
// Get the physical addresses of where the kernel starts and ends.
|
|
uint32_t boot_start = (uint32_t)_bootloader_start;
|
|
uint32_t boot_end = (uint32_t)_bootloader_end;
|
|
|
|
// Extract the lowest and highest address of the kernel.
|
|
uint32_t kernel_virt_low = 0xFFFFFFFF;
|
|
uint32_t kernel_virt_high = 0;
|
|
__get_kernel_low_high(elf_hdr, &kernel_virt_low, &kernel_virt_high);
|
|
|
|
// Initialize the boot_info_t structure.
|
|
__debug_puts("bootloader: Initializing the boot_info structure...\n");
|
|
boot_info.magic = magic;
|
|
boot_info.bootloader_phy_start = boot_start;
|
|
boot_info.bootloader_phy_end = boot_end;
|
|
boot_info.kernel_start = kernel_virt_low;
|
|
boot_info.kernel_end = kernel_virt_high;
|
|
boot_info.kernel_size = kernel_virt_high - kernel_virt_low;
|
|
boot_info.multiboot_header = header;
|
|
|
|
// Get the address after the modules.
|
|
boot_info.module_end = __get_address_after_modules(header);
|
|
|
|
// Get the starting address of the physical pages at the end of the modules.
|
|
uint32_t kernel_phy_page_start = __align_rup(boot_info.module_end, PAGE_SIZE);
|
|
// Get the starting address of the virtual pages.
|
|
uint32_t kernel_virt_page_start = __align_rdown(kernel_virt_low, PAGE_SIZE);
|
|
|
|
// Compute the absolute offset of the first virtual page, by subtracting
|
|
// the starting address of the virtual pages and the lowest virtual address
|
|
// of the kernel.
|
|
uint32_t kernel_page_offset = kernel_virt_page_start - kernel_virt_low;
|
|
|
|
// If we add the offset we computed earlier to the physical address where
|
|
// the modules ends, we obtain the starting address of the physical memory.
|
|
boot_info.kernel_phy_start = kernel_phy_page_start + kernel_page_offset;
|
|
// The ending address of the physical memory is just the start plus the
|
|
// size of the kernel (virt_high - virt_low).
|
|
boot_info.kernel_phy_end = boot_info.kernel_phy_start + boot_info.kernel_size;
|
|
|
|
boot_info.lowmem_phy_start = __align_rup(boot_info.kernel_phy_end, PAGE_SIZE);
|
|
boot_info.lowmem_phy_end = 896 * 1024 * 1024; // 896 MB of low memory max
|
|
|
|
uint32_t lowmem_size = boot_info.lowmem_phy_end - boot_info.lowmem_phy_start;
|
|
|
|
boot_info.lowmem_start = __align_rup(boot_info.kernel_end, PAGE_SIZE);
|
|
boot_info.lowmem_end = boot_info.lowmem_start + lowmem_size;
|
|
|
|
boot_info.highmem_phy_start = boot_info.lowmem_phy_end;
|
|
boot_info.highmem_phy_end = header->mem_upper * 1024;
|
|
boot_info.stack_end = boot_info.lowmem_end;
|
|
|
|
// Setup the page directory and page tables for the boot.
|
|
__debug_puts("bootloader: Setting up paging...\n");
|
|
__setup_boot_paging();
|
|
|
|
// Switch to the newly created page directory.
|
|
__debug_puts("bootloader: Switching page directory...\n");
|
|
paging_switch_directory(&boot_pgdir);
|
|
|
|
// Enable paging.
|
|
__debug_puts("bootloader: Enabling paging...\n");
|
|
paging_enable();
|
|
|
|
// Reserve space for the kernel stack at the end of lowmem.
|
|
boot_info.stack_base = boot_info.lowmem_end;
|
|
boot_info.lowmem_phy_end = boot_info.lowmem_phy_end - KERNEL_STACK_SIZE;
|
|
boot_info.lowmem_end = boot_info.lowmem_end - KERNEL_STACK_SIZE;
|
|
|
|
__debug_puts("bootloader: Relocating kernel image...\n");
|
|
__relocate_kernel_image(elf_hdr);
|
|
|
|
#if 0
|
|
for (int i = 0; i < elf_hdr->shnum; i++) {
|
|
struct elf_section_header *section_header =
|
|
(elf_section_header_t *)(LDVAR(kernel_bin) + elf_hdr->shoff + elf_hdr->shentsize * i);
|
|
for (int j = 0; j < section_header->; j++) {
|
|
((char *)section_header->vaddr)[j] = (LDVAR(kernel_bin) + section_header->offset)[j];
|
|
}
|
|
}
|
|
#endif
|
|
|
|
__debug_puts("bootloader: Calling `boot_kernel`...\n\n");
|
|
boot_kernel(boot_info.stack_base, elf_hdr->entry, &boot_info);
|
|
}
|
|
|
|
/// @}
|