Handout
This commit is contained in:
245
boot/longmode.asm
Normal file
245
boot/longmode.asm
Normal file
@@ -0,0 +1,245 @@
|
||||
; The stony path to Long Mode (64-bit)...
|
||||
; ... begins in 32-bit Protected Mode
|
||||
[BITS 32]
|
||||
|
||||
; Pointer to Long Mode Global Descriptor Table (GDT, arch/gdt.cc)
|
||||
[EXTERN gdt_long_mode_pointer]
|
||||
|
||||
[GLOBAL long_mode]
|
||||
long_mode:
|
||||
|
||||
; You can check if the CPU supports Long Mode by using the `cpuid` command.
|
||||
; Problem: You first have to figure out if the `cpuid` command itself is
|
||||
; supported. Therefore, you have to try to reverse the 21st bit in the EFLAGS
|
||||
; register -- if it works, then there is the 'cpuid' instruction.
|
||||
CPUID_BIT_MASK equ 1 << 21
|
||||
|
||||
check_cpuid:
|
||||
; Save EFLAGS on stack
|
||||
pushfd
|
||||
|
||||
; Copy stored EFLAGS from stack to EAX register
|
||||
mov eax, [esp]
|
||||
|
||||
; Flip the 21st bit (ID) in EAX
|
||||
xor eax, CPUID_BIT_MASK
|
||||
|
||||
; Copy EAX to EFLAGS (using the stack)
|
||||
push eax
|
||||
popfd
|
||||
|
||||
; And reverse: copy EFLAGS to EAX (using the stack)
|
||||
; (but the 21st bit should now still be flipped, if `cpuid` is supported)
|
||||
pushfd
|
||||
pop eax
|
||||
|
||||
; Compare the new EFLAGS copy (residing in EAX) with the EFLAGS stored at
|
||||
; the beginning of this function by using an exclusive OR -- all different
|
||||
; (flipped) bits will be stored in EAX.
|
||||
xor eax, [esp]
|
||||
|
||||
; Restore original EFLAGS
|
||||
popfd
|
||||
|
||||
; If 21st Bit in EAX is set, `cpuid` is supported -- continue at check_long_mode
|
||||
and eax, CPUID_BIT_MASK
|
||||
jnz check_long_mode
|
||||
|
||||
; Show error message "No CPUID" and stop CPU
|
||||
mov dword [0xb8000], 0xcf6fcf4e
|
||||
mov dword [0xb8004], 0xcf43cf20
|
||||
mov dword [0xb8008], 0xcf55cf50
|
||||
mov dword [0xb800c], 0xcf44cf49
|
||||
hlt
|
||||
|
||||
; Now you are able to use the `cpuid` instruction to check if Long Mode is
|
||||
; available -- after you've checked if the `cpuid` is able to perform the
|
||||
; check itself (since it is an extended `cpuid` function)...
|
||||
|
||||
CPUID_GET_LARGEST_EXTENDED_FUNCTION_NUMBER equ 0x80000000
|
||||
CPUID_GET_EXTENDED_PROCESSOR_FEATURES equ 0x80000001
|
||||
CPUID_HAS_LONGMODE equ 1 << 29
|
||||
|
||||
check_long_mode:
|
||||
; Set argument for `cpuid` to check the availability of extended functions
|
||||
; and call cpuid
|
||||
mov eax, CPUID_GET_LARGEST_EXTENDED_FUNCTION_NUMBER
|
||||
cpuid
|
||||
; The return value contains the maximum function number supported by `cpuid`,
|
||||
; You'll need the function number for extended processor features
|
||||
cmp eax, CPUID_GET_EXTENDED_PROCESSOR_FEATURES
|
||||
; If not present, the CPU is definitely too old to support long mode
|
||||
jb no_long_mode
|
||||
|
||||
; Finally, you are able to check the Long Mode support itself
|
||||
mov eax, CPUID_GET_EXTENDED_PROCESSOR_FEATURES
|
||||
cpuid
|
||||
; If the return value in the EDX register has set the 29th bit,
|
||||
; then long mode is supported -- continue with setup_paging
|
||||
test edx, CPUID_HAS_LONGMODE
|
||||
jnz setup_paging
|
||||
|
||||
no_long_mode:
|
||||
; Show error message "No 64bit" and stop CPU
|
||||
mov dword [0xb8000], 0xcf6fcf4e
|
||||
mov dword [0xb8004], 0xcf36cf20
|
||||
mov dword [0xb8008], 0xcf62cf34
|
||||
mov dword [0xb800c], 0xcf74cf69
|
||||
hlt
|
||||
|
||||
; Paging is required for Long Mode.
|
||||
; Since an extensive page manager might be a bit of an overkill to start with,
|
||||
; the following code creates an identity mapping for the first four gigabytes
|
||||
; (using huge pages): each virtual address will point to the same physical one.
|
||||
; This area (up to 4 GiB) is important for some memory mapped devices (APIC)
|
||||
; and you don't want to remap them yet for simplicity reasons.
|
||||
; In the advanced operating systems lecture, this topic is covered in detail,
|
||||
; however, if you want a quick overview, have a look at
|
||||
; https://wiki.osdev.org/Page_Tables#2_MiB_pages_2
|
||||
|
||||
PAGE_SIZE equ 4096
|
||||
PAGE_FLAGS_PRESENT equ 1 << 0
|
||||
PAGE_FLAGS_WRITEABLE equ 1 << 1
|
||||
PAGE_FLAGS_USER equ 1 << 2
|
||||
PAGE_FLAGS_HUGE equ 1 << 7
|
||||
|
||||
setup_paging:
|
||||
; Unlike in Protected Mode, an entry in the page table has a size of 8 bytes
|
||||
; (vs 4 bytes), so there are only 512 (and not 1024) entries per table.
|
||||
; Structure of the 3-level PAE paging: One entry in the
|
||||
; - lv2: Page-Directory-Table (PDT) covers 2 MiB (1 Huge Page)
|
||||
; - lv3: Page-Directory-Pointer-Table (PDPT) covers 1 GiB (512 * 2 MiB)
|
||||
; - lv4: Page-Map-Level-4-Table (PML4) covers 512 GiB (512 * 1 GiB)
|
||||
|
||||
; To address 4 GiB only four level-2 tables are required.
|
||||
; All entries of the level-2 tables should be marked as writeable (attributes)
|
||||
; and map (point to) the corresponding physical memory.
|
||||
|
||||
; This is done in a loop using ECX as counter
|
||||
mov ecx, 0
|
||||
|
||||
.identitymap_level2:
|
||||
; Calculate physical address in EAX (2 MiB multiplied by the counter)
|
||||
mov eax, 0x200000
|
||||
mul ecx
|
||||
; Configure page attributes
|
||||
or eax, PAGE_FLAGS_PRESENT | PAGE_FLAGS_WRITEABLE | PAGE_FLAGS_HUGE | PAGE_FLAGS_USER
|
||||
; Write (8 byte) entry in the level-2 table
|
||||
mov [paging_level2_tables + ecx * 8], eax
|
||||
|
||||
; Increment counter...
|
||||
inc ecx
|
||||
; ... until all four level-2 tables are filled
|
||||
cmp ecx, 512 * 4
|
||||
jne .identitymap_level2
|
||||
|
||||
; The first four entries of the level-3 table should point to the
|
||||
; four level-2 tables (and be writeable as well).
|
||||
; Again, ECX acts as counter for the loop
|
||||
mov ecx, 0
|
||||
|
||||
.identitymap_level3:
|
||||
; Calculate the address: ECX * PAGE_SIZE + paging_level2_tables
|
||||
mov eax, ecx
|
||||
; The size of a page is stored in the EDX register
|
||||
mov edx, PAGE_SIZE
|
||||
mul edx
|
||||
add eax, paging_level2_tables
|
||||
; Configure attributes
|
||||
or eax, PAGE_FLAGS_PRESENT | PAGE_FLAGS_WRITEABLE | PAGE_FLAGS_USER
|
||||
; Write (8 byte) entry in the level-3 table
|
||||
mov [paging_level3_table + ecx * 8], eax
|
||||
|
||||
; Increment counter...
|
||||
inc ecx
|
||||
; ... until all four entries of the table are written
|
||||
cmp ecx, 4
|
||||
jne .identitymap_level3
|
||||
|
||||
mov eax, paging_level2_tables
|
||||
or eax, PAGE_FLAGS_PRESENT | PAGE_FLAGS_WRITEABLE | PAGE_FLAGS_USER
|
||||
mov [paging_level3_table], eax
|
||||
|
||||
; The first entry of the level-4 table should point to to the level-3 table
|
||||
mov eax, paging_level3_table
|
||||
or eax, PAGE_FLAGS_PRESENT | PAGE_FLAGS_WRITEABLE | PAGE_FLAGS_USER
|
||||
mov [paging_level4_table], eax
|
||||
|
||||
; Time to activate paging
|
||||
paging_enable:
|
||||
; First setup the control registers
|
||||
|
||||
; Write the address of the level-4 table into the CR3 register
|
||||
mov eax, paging_level4_table
|
||||
mov cr3, eax
|
||||
|
||||
; Activate Physical Address Extension (PAE)
|
||||
; by setting the 5th bits in the CR4 register
|
||||
mov eax, cr4
|
||||
or eax, 1 << 5
|
||||
mov cr4, eax
|
||||
|
||||
; Set the Long Mode Enable Bit in den EFER MSR
|
||||
; (Extended Feature Enable Register Model Specific Register)
|
||||
mov ecx, 0xC0000080
|
||||
rdmsr
|
||||
or eax, 1 << 8
|
||||
wrmsr
|
||||
|
||||
; Finally, the 31st bit in CR0 is set to enable Paging
|
||||
mov eax, cr0
|
||||
or eax, 1 << 31
|
||||
mov cr0, eax
|
||||
|
||||
; Load Long Mode Global Descriptor Table
|
||||
lgdt [gdt_long_mode_pointer]
|
||||
|
||||
; Far jump to the 64-bit start code
|
||||
jmp 0x8:long_mode_start
|
||||
|
||||
; print `KO` to screen
|
||||
mov dword [0xb8000], 0x3f4f3f4b
|
||||
hlt
|
||||
|
||||
; Memory reserved for page tables
|
||||
[SECTION .bss]
|
||||
|
||||
align 4096
|
||||
|
||||
[GLOBAL paging_level4_table]
|
||||
[GLOBAL paging_level3_table]
|
||||
[GLOBAL paging_level2_tables]
|
||||
; 1x Level-4 Table (Page Map Level 4)
|
||||
paging_level4_table:
|
||||
resb PAGE_SIZE
|
||||
|
||||
; 1x Level-3 Table (Page Directory Pointer Table)
|
||||
paging_level3_table:
|
||||
resb PAGE_SIZE
|
||||
|
||||
; 4x Level-2 Table (Page Directory)
|
||||
paging_level2_tables:
|
||||
resb PAGE_SIZE * 4
|
||||
|
||||
[SECTION .text]
|
||||
[EXTERN kernel_init] ; C++ entry function
|
||||
|
||||
; Continue with 64 bit code
|
||||
[BITS 64]
|
||||
|
||||
long_mode_start:
|
||||
; Zero all segment register
|
||||
mov ax, 0x0
|
||||
mov ss, ax
|
||||
mov ds, ax
|
||||
mov es, ax
|
||||
mov fs, ax
|
||||
mov gs, ax
|
||||
|
||||
; Call high-level (C++) kernel initialization function
|
||||
call kernel_init
|
||||
|
||||
; Print `STOP` to screen and stop
|
||||
mov rax, 0x2f502f4f2f544f53
|
||||
mov qword [0xb8000], rax
|
||||
hlt
|
||||
22
boot/multiboot/config.inc
Normal file
22
boot/multiboot/config.inc
Normal file
@@ -0,0 +1,22 @@
|
||||
; Magic Header, has to be present in Kernel to indicate Multiboot compliance
|
||||
MULTIBOOT_HEADER_MAGIC_OS equ 0x1badb002
|
||||
|
||||
; Answer by the boot loader for Multiboot compliance, written in eax register
|
||||
MULTIBOOT_HEADER_MAGIC_LOADER equ 0x2badb002
|
||||
|
||||
; Flags instructing the Multiboot compliant boot loader to setup the system
|
||||
; according to your needs
|
||||
MULTIBOOT_PAGE_ALIGN equ 1<<0 ; Align boot modules (initrds) at 4 KiB border
|
||||
MULTIBOOT_MEMORY_INFO equ 1<<1 ; Request Memory Map information
|
||||
MULTIBOOT_VIDEO_MODE equ 1<<2 ; Configure video mode
|
||||
|
||||
MULTIBOOT_HEADER_FLAGS equ 0
|
||||
|
||||
; Desired video mode (only considered if MULTIBOOT_VIDEO_MODE set)
|
||||
; (boot loader will choose the best fitting mode, which might differ from the settings below)
|
||||
MULTIBOOT_VIDEO_WIDTH equ 1280 ; Desired width
|
||||
MULTIBOOT_VIDEO_HEIGHT equ 1024 ; Desired height
|
||||
MULTIBOOT_VIDEO_BITDEPTH equ 32 ; Desired bit depth
|
||||
|
||||
; Checksum
|
||||
MULTIBOOT_HEADER_CHKSUM equ -(MULTIBOOT_HEADER_MAGIC_OS + MULTIBOOT_HEADER_FLAGS)
|
||||
167
boot/multiboot/data.cc
Normal file
167
boot/multiboot/data.cc
Normal file
@@ -0,0 +1,167 @@
|
||||
#include "boot/multiboot/data.h"
|
||||
|
||||
/*! \brief Multiboot Information Structure according to Specification
|
||||
* \see [Multiboot Specification]{@ref multiboot}
|
||||
*/
|
||||
struct multiboot_info {
|
||||
/*! \brief Helper Structure
|
||||
*/
|
||||
struct Array {
|
||||
uint32_t size; ///< Length
|
||||
uint32_t addr; ///< Begin (physical address)
|
||||
} __attribute__((packed));
|
||||
|
||||
enum Flag : uint32_t {
|
||||
Memory = 1 << 0, ///< is there basic lower/upper memory information?
|
||||
BootDev = 1 << 1, ///< is there a boot device set?
|
||||
CmdLine = 1 << 2, ///< is the command-line defined?
|
||||
Modules = 1 << 3, ///< are there modules to do something with?
|
||||
/* These next two are mutually exclusive */
|
||||
SymbolTable = 1 << 4, ///< is there an a.out symbol table loaded?
|
||||
SectionHeader = 1 << 5, ///< is there an ELF section header table?
|
||||
|
||||
MemoryMap = 1 << 6, ///< is there a full memory map?
|
||||
DriveInfo = 1 << 7, ///< Is there drive info?
|
||||
ConfigTable = 1 << 8, ///< Is there a config table?
|
||||
BootLoaderName = 1 << 9, ///< Is there a boot loader name?
|
||||
ApmTable = 1 << 10, ///< Is there a APM table?
|
||||
|
||||
// Is there video information?
|
||||
VbeInfo = 1 << 11, ///< Vesa bios extension
|
||||
FramebufferInfo = 1 << 12 ///< Framebuffer
|
||||
} flags;
|
||||
|
||||
/*! \brief Available memory retrieved from BIOS
|
||||
*/
|
||||
struct {
|
||||
uint32_t lower; ///< Amount of memory below 1 MiB in kilobytes
|
||||
uint32_t upper; ///< Amount of memory above 1 MiB in kilobytes
|
||||
} mem __attribute__((packed));
|
||||
uint32_t boot_device; ///< "root" partition
|
||||
uint32_t cmdline; ///< Kernel command line
|
||||
Array mods; ///< List of boot modules
|
||||
union {
|
||||
/*! \brief Symbol table for kernel in a.out format
|
||||
*/
|
||||
struct {
|
||||
uint32_t tabsize;
|
||||
uint32_t strsize;
|
||||
uint32_t addr;
|
||||
uint32_t reserved;
|
||||
} aout_symbol_table __attribute__((packed));
|
||||
|
||||
/*! \brief Section header table for kernel in ELF
|
||||
*/
|
||||
struct {
|
||||
uint32_t num; ///< Number of entries
|
||||
uint32_t size; ///< Size per entry
|
||||
uint32_t addr; ///< Start of the header table
|
||||
uint32_t shndx; ///< String table index
|
||||
} elf_section_header_table __attribute__((packed));
|
||||
};
|
||||
|
||||
struct Array mmap; ///< Memory Map
|
||||
struct Array drives; ///< Drive Information
|
||||
uint32_t config_table; ///< ROM configuration table
|
||||
uint32_t boot_loader_name; ///< Boot Loader Name
|
||||
uint32_t apm_table; ///< APM table
|
||||
|
||||
struct Multiboot::VBE vbe; ///< VBE Information
|
||||
struct Multiboot::Framebuffer framebuffer; ///< Framebuffer information
|
||||
|
||||
/*! \brief Check if setting is available
|
||||
* \param flag Flag to check
|
||||
* \return `true` if available
|
||||
*/
|
||||
bool has(enum Flag flag) const { return (flags & flag) != 0; }
|
||||
} __attribute__((packed));
|
||||
assert_size(multiboot_info, 116);
|
||||
|
||||
/*! \brief The pointer to the multiboot structures will be assigned in the
|
||||
* assembler startup code (multiboot.inc)
|
||||
*/
|
||||
struct multiboot_info *multiboot_addr = 0;
|
||||
|
||||
namespace Multiboot {
|
||||
Module *getModule(unsigned i) {
|
||||
if (multiboot_addr != nullptr &&
|
||||
multiboot_addr->has(multiboot_info::Flag::Modules) &&
|
||||
i < multiboot_addr->mods.size) {
|
||||
return i + reinterpret_cast<Module *>(
|
||||
static_cast<uintptr_t>(multiboot_addr->mods.addr));
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned getModuleCount() { return multiboot_addr->mods.size; }
|
||||
|
||||
void *Memory::getStartAddress() const {
|
||||
if (sizeof(void *) == 4 && (addr >> 32) != 0) {
|
||||
return reinterpret_cast<void *>(addr & 0xffffffff);
|
||||
} else {
|
||||
return reinterpret_cast<void *>(static_cast<uintptr_t>(addr));
|
||||
}
|
||||
}
|
||||
|
||||
void *Memory::getEndAddress() const {
|
||||
uint64_t end = addr + len;
|
||||
if (sizeof(void *) == 4 && (end >> 32) != 0) {
|
||||
return reinterpret_cast<void *>(addr & 0xffffffff);
|
||||
} else {
|
||||
return reinterpret_cast<void *>(static_cast<uintptr_t>(end));
|
||||
}
|
||||
}
|
||||
|
||||
bool Memory::isAvailable() const { return type == AVAILABLE; }
|
||||
|
||||
Memory *Memory::getNext() const {
|
||||
if (multiboot_addr != nullptr &&
|
||||
multiboot_addr->has(multiboot_info::Flag::MemoryMap)) {
|
||||
uintptr_t next = reinterpret_cast<uintptr_t>(this) + size + sizeof(size);
|
||||
if (next < multiboot_addr->mmap.addr + multiboot_addr->mmap.size) {
|
||||
return reinterpret_cast<Memory *>(next);
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Memory *getMemoryMap() {
|
||||
if (multiboot_addr != nullptr &&
|
||||
multiboot_addr->has(multiboot_info::Flag::MemoryMap) &&
|
||||
multiboot_addr->mmap.size > 0) {
|
||||
return reinterpret_cast<Memory *>(
|
||||
static_cast<uintptr_t>(multiboot_addr->mmap.addr));
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
char *getCommandLine() {
|
||||
return reinterpret_cast<char *>(
|
||||
static_cast<uintptr_t>(multiboot_addr->cmdline));
|
||||
}
|
||||
|
||||
char *getBootLoader() {
|
||||
return reinterpret_cast<char *>(
|
||||
static_cast<uintptr_t>(multiboot_addr->boot_loader_name));
|
||||
}
|
||||
|
||||
VBE *getVesaBiosExtensionInfo() {
|
||||
if (multiboot_addr != nullptr &&
|
||||
multiboot_addr->has(multiboot_info::Flag::VbeInfo)) {
|
||||
return &(multiboot_addr->vbe);
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
Framebuffer *getFramebufferInfo() {
|
||||
if (multiboot_addr != nullptr &&
|
||||
multiboot_addr->has(multiboot_info::Flag::FramebufferInfo)) {
|
||||
return &(multiboot_addr->framebuffer);
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
} // namespace Multiboot
|
||||
230
boot/multiboot/data.h
Normal file
230
boot/multiboot/data.h
Normal file
@@ -0,0 +1,230 @@
|
||||
/*! \file
|
||||
* \brief \ref Multiboot Interface
|
||||
*/
|
||||
#pragma once
|
||||
#include "../../compiler/fix.h"
|
||||
#include "../../debug/assert.h"
|
||||
#include "../../types.h"
|
||||
|
||||
/*! \brief Interface for Multiboot
|
||||
*
|
||||
* Due to historical reasons, a normal BIOS allows you to do quite an egg dance
|
||||
* until you finally reach the actual kernel (especially with only 512 bytes
|
||||
* available in the master boot record...).
|
||||
* Fortunately, there are [boot loaders](https://wiki.osdev.org/Bootloader) that
|
||||
* (partly) do this ungrateful job for you:
|
||||
* They load your kernel into memory, switch (the bootstrap processor) to
|
||||
* protected mode (32 bit) and jump to the entry point of our kernel -- saving
|
||||
* you a lot of boring (or enlightening?) work: reading ancient systems
|
||||
* documentation. One of the most famous representatives is the [Grand Unified
|
||||
* Bootloader (GRUB)](https://www.gnu.org/software/grub/), which is also the
|
||||
* reference implementation of the [Multiboot Specification]{@ref multiboot}.
|
||||
*
|
||||
* A Multiboot compliant boot loader will prepare the system according to your
|
||||
* needs and can hand you a lot of useful information (e.g. references to
|
||||
* initial ramdisks).
|
||||
*
|
||||
* However, you have to inform the loader that you are also compliant to the
|
||||
* specification, and (if required) instruct the loader to adjust specific
|
||||
* settings (e.g. the graphics mode).
|
||||
*
|
||||
* For this purpose you have to configure the beginning of the kernel (the first
|
||||
* 8192 bytes of the kernel binary) accordingly (see `compiler/section.ld`) --
|
||||
* this is were the boot loader will search for a magic header and parse the
|
||||
* subsequent entries containing the desired system configuration.
|
||||
* In StuBS these flags are set in `boot/multiboot/config.inc` and the header
|
||||
* structure is generated in `boot/multiboot/header.asm`.
|
||||
*
|
||||
* The first step in your \ref startup_bsp() "kernel entry function" is saving
|
||||
* the pointer to the struct with the information from the boot loader
|
||||
* (transferred via register `ebx`) -- and \ref Multiboot provides you the
|
||||
* interface to comfortably access its contents!
|
||||
*/
|
||||
namespace Multiboot {
|
||||
/*! \brief Boot Module
|
||||
* (also known as `initrd` = initial Ramdisk)
|
||||
*
|
||||
* \see [1.7 Boot modules]{@ref multiboot}
|
||||
* \see [3.3 Boot information format]{@ref multiboot}
|
||||
*/
|
||||
class Module {
|
||||
uint32_t start; ///< Start address
|
||||
uint32_t end; ///< End address (excluded)
|
||||
uint32_t cmdline; ///< commandline parameter
|
||||
uint32_t pad [[maybe_unused]]; ///< alignment; must be 0
|
||||
|
||||
public:
|
||||
/*! \brief Get start of this boot module
|
||||
* \return Pointer to begin of modules physical address
|
||||
*/
|
||||
void* getStartAddress() const {
|
||||
return reinterpret_cast<void*>(static_cast<uintptr_t>(start));
|
||||
}
|
||||
|
||||
/*! \brief Get end of this boot module
|
||||
* \return Pointer beyond the modules physical address
|
||||
*/
|
||||
void* getEndAddress() const {
|
||||
return reinterpret_cast<void*>(static_cast<uintptr_t>(end));
|
||||
}
|
||||
|
||||
/*! \brief Get the size of this boot module
|
||||
* \return Module size in bytes (difference of end and start address)
|
||||
*/
|
||||
size_t getSize() const { return static_cast<size_t>(end - start); }
|
||||
|
||||
/*! \brief Get the command line for this module
|
||||
* \return pointer to zero terminated string
|
||||
*/
|
||||
char* getCommandLine() const {
|
||||
return reinterpret_cast<char*>(static_cast<uintptr_t>(cmdline));
|
||||
}
|
||||
} __attribute__((packed));
|
||||
assert_size(Module, 16);
|
||||
|
||||
/*! \brief Retrieve a certain boot module
|
||||
* \param i boot module number
|
||||
* \return Pointer to structure with boot module information
|
||||
*/
|
||||
Module* getModule(unsigned i);
|
||||
|
||||
/*! \brief Get the number of modules
|
||||
* \return Pointer to structure with boot module information
|
||||
*/
|
||||
unsigned getModuleCount();
|
||||
|
||||
/*! \brief Get the kernel command line
|
||||
* \return pointer to zero terminated string
|
||||
*/
|
||||
char* getCommandLine();
|
||||
|
||||
/*! \brief Get the name of the boot loader
|
||||
* \return pointer to zero terminated string
|
||||
*/
|
||||
char* getBootLoader();
|
||||
|
||||
/*! \brief Memory Map
|
||||
*
|
||||
* The boot loader queries the BIOS for a memory map and stores its result in
|
||||
* (something like) a linked list. However, this list may not be complete,
|
||||
* can have contradictory entries and does not take the location of your kernel
|
||||
* or any boot modules into account.
|
||||
* (Anyways, it is still the best memory map you will have in StuBS...)
|
||||
*
|
||||
* \note Needs to be enabled explicitly by setting the `MULTIBOOT_MEMORY_INFO`
|
||||
* flag in the multiboot header (see `boot/multiboot/config.inc`)!
|
||||
*
|
||||
* \see [Detecting Memory](https://wiki.osdev.org/Detecting_Memory_(x86))
|
||||
*/
|
||||
class Memory {
|
||||
uint32_t size; ///< Size of this entry (can exceed size of the class, rest
|
||||
///< will be padding bits)
|
||||
uint64_t addr; ///< Begin of memory area
|
||||
uint64_t len; ///< length of the memory area
|
||||
|
||||
/*! \brief Usage Type
|
||||
*/
|
||||
enum Type : uint32_t {
|
||||
AVAILABLE = 1, ///< Memory is available and usable in kernel
|
||||
RESERVED = 2, ///< Memory is reserved (without further explanation)
|
||||
ACPI = 3, ///< Memory may be reclaimed by ACPI
|
||||
NVS = 4, ///< Memory is non volatile storage for ACPI
|
||||
BADRAM = 5 ///< Area contains bad memory
|
||||
} type;
|
||||
|
||||
public:
|
||||
/*! \brief Get start of this memory area
|
||||
* \return Pointer to begin of the physical address of the memory area
|
||||
*/
|
||||
void* getStartAddress() const;
|
||||
|
||||
/*! \brief Get end of this memory area
|
||||
* \return Pointer beyond the physical address of this memory area
|
||||
*/
|
||||
void* getEndAddress() const;
|
||||
|
||||
/*! \brief Is the memory marked as usable
|
||||
* \return `true` if available, `false` if not usable.
|
||||
*/
|
||||
bool isAvailable() const;
|
||||
|
||||
/*! \brief Get the next memory area
|
||||
* \return pointer to the next memory area entry
|
||||
*/
|
||||
Memory* getNext() const;
|
||||
} __attribute__((packed));
|
||||
assert_size(Memory, 24);
|
||||
|
||||
/*! \brief Retrieve the first entry of the memory map
|
||||
*/
|
||||
Memory* getMemoryMap();
|
||||
|
||||
/*! \brief Video mode: Vesa BIOS Extension
|
||||
*
|
||||
* \see [VESA BIOS Extension (VBE) Core Functions (Version 3)](vbe3.pdf)
|
||||
*/
|
||||
struct VBE {
|
||||
uint32_t control_info; ///< Pointer to VBE control information
|
||||
uint32_t mode_info; ///< Pointer to VBE mode information
|
||||
uint16_t mode; ///< Selected video mode (as defined in the standard)
|
||||
uint16_t interface_seg; ///< Protected mode interface (unused)
|
||||
uint16_t interface_off; ///< Protected mode interface (unused)
|
||||
uint16_t interface_len; ///< Protected mode interface (unused)
|
||||
} __attribute__((packed));
|
||||
assert_size(VBE, 16);
|
||||
|
||||
/*! \brief Get pointer to Vesa BIOS Extension information
|
||||
*
|
||||
* \note Only available if the `MULTIBOOT_VIDEO_MODE` flag was explicitly set
|
||||
* in the multiboot header (see `boot/multiboot/config.inc`)!
|
||||
*/
|
||||
VBE* getVesaBiosExtensionInfo();
|
||||
|
||||
/*! \brief Video mode: Framebuffer
|
||||
*
|
||||
* This beautiful structure contains everything required for using the graphic
|
||||
* framebuffer in a very handy manner -- however, it may not be well supported
|
||||
* by current boot loaders...
|
||||
* These information can be retrieved from \ref VBE as well, though you then
|
||||
* have to parse these huge structures containing a lot of useless stuff.
|
||||
*/
|
||||
struct Framebuffer {
|
||||
uint64_t address; ///< Physical address of the framebuffer
|
||||
uint32_t pitch; ///< Number of bytes per row
|
||||
uint32_t width; ///< Width of framebuffer
|
||||
uint32_t height; ///< Height of framebuffer
|
||||
uint8_t bpp; ///< Bits per pixel
|
||||
enum Type : uint8_t {
|
||||
INDEXED = 0, ///< Using a custom color palette
|
||||
RGB = 1, ///< Standard red-green-blue
|
||||
EGA_TEXT = 2 ///< Enhanced Graphics Adapter color palette
|
||||
} type;
|
||||
union {
|
||||
/*! \brief For INDEXED type
|
||||
*/
|
||||
struct {
|
||||
uint32_t palette_addr; ///< Address of an array with RGB values
|
||||
uint16_t palette_num_colors; ///< Number of colors (in array above)
|
||||
} __attribute__((packed));
|
||||
|
||||
/*! \brief For RGB type
|
||||
*/
|
||||
struct {
|
||||
uint8_t offset_red; ///< Offset of red value
|
||||
uint8_t bits_red; ///< Bits used in red value
|
||||
uint8_t offset_green; ///< Offset of green value
|
||||
uint8_t bits_green; ///< Bits used in green value
|
||||
uint8_t offset_blue; ///< Offset of blue value
|
||||
uint8_t bits_blue; ///< Bits used in blue value
|
||||
} __attribute__((packed));
|
||||
} __attribute__((packed));
|
||||
} __attribute__((packed));
|
||||
assert_size(Framebuffer, 28);
|
||||
|
||||
/*! \brief Get pointer to framebuffer information
|
||||
*
|
||||
* \note Only available if the `MULTIBOOT_VIDEO_MODE` flag was explicitly set
|
||||
* in the multiboot header (see `boot/multiboot/config.inc`)!
|
||||
*/
|
||||
Framebuffer* getFramebufferInfo();
|
||||
} // namespace Multiboot
|
||||
33
boot/multiboot/header.asm
Normal file
33
boot/multiboot/header.asm
Normal file
@@ -0,0 +1,33 @@
|
||||
; The first 8192 bytes of the kernel binary must contain a header with
|
||||
; predefined (and sometimes "magic") values according to the Multiboot standard.
|
||||
; Based on these values, the boot loader decides whether and how to load the
|
||||
; kernel -- which is compiled and linked into an ELF file.
|
||||
; To make this possible with your StuBS kernel, the linker places the following
|
||||
; entry `multiboot_header` at the very beginning of the file thanks to the
|
||||
; linker script (located in compiler/sections.ld).
|
||||
|
||||
[SECTION .multiboot_header]
|
||||
|
||||
; Include configuration
|
||||
%include 'boot/multiboot/config.inc'
|
||||
|
||||
; Multiboot Header
|
||||
align 4
|
||||
multiboot_header:
|
||||
dd MULTIBOOT_HEADER_MAGIC_OS ; Magic Header Value
|
||||
dd MULTIBOOT_HEADER_FLAGS ; Flags (affects following entries)
|
||||
dd MULTIBOOT_HEADER_CHKSUM ; Header Checksum
|
||||
|
||||
; Following fields would have been required to be defined
|
||||
; if flag A_OUT KLUDGE was set (but we don't need this)
|
||||
dd 0 ; Header address
|
||||
dd 0 ; Begin of load address
|
||||
dd 0 ; end of load address
|
||||
dd 0 ; end of bss segment
|
||||
dd 0 ; address of entry function
|
||||
|
||||
; Following fields are required for video mode (flag MULTIBOOT_VIDEO_MODE)
|
||||
dd 0 ; Mode: 0 = Graphic / 1 = Text
|
||||
dd MULTIBOOT_VIDEO_WIDTH ; Width (pixels / columns)
|
||||
dd MULTIBOOT_VIDEO_HEIGHT ; Height (pixels / rows)
|
||||
dd MULTIBOOT_VIDEO_BITDEPTH ; color depth / number of colors
|
||||
77
boot/startup.asm
Normal file
77
boot/startup.asm
Normal file
@@ -0,0 +1,77 @@
|
||||
; This is the actual entry point of the kernel.
|
||||
; The switch into the 32-bit 'Protected Mode' has already been performed
|
||||
; (by the boot loader).
|
||||
; The assembly code just performs the absolute necessary steps (like setting up
|
||||
; the stack) to be able to jump into the C++ code -- and continue further
|
||||
; initialization in a (more) high-level language.
|
||||
|
||||
[BITS 32]
|
||||
|
||||
; External functions and variables
|
||||
[EXTERN CPU_CORE_STACK_SIZE] ; Constant containing the initial stack size (per CPU core), see `arch/core.cc`
|
||||
[EXTERN cpu_core_stack_pointer] ; Pointer to reserved memory for CPU core stacks, see `arch/core.cc`
|
||||
[EXTERN gdt_protected_mode_pointer] ; Pointer to 32 Bit Global Descriptor Table (located in `arch/gdt.cc`)
|
||||
[EXTERN long_mode] ; Low level function to jump into the 64-bit mode ('Long Mode', see `boot/longmode.asm`)
|
||||
[EXTERN multiboot_addr] ; Variable, in which the Pointer to Multiboot information
|
||||
; structure should be stored (`boot/multiboot/data.cc`)
|
||||
|
||||
; Load Multiboot settings
|
||||
%include "boot/multiboot/config.inc"
|
||||
|
||||
[SECTION .text]
|
||||
|
||||
; Entry point for the bootstrap processor (CPU0)
|
||||
[GLOBAL startup_bsp]
|
||||
startup_bsp:
|
||||
; Check if kernel was booted by a Multiboot compliant boot loader
|
||||
cmp eax, MULTIBOOT_HEADER_MAGIC_LOADER
|
||||
jne skip_multiboot
|
||||
; Pointer to Multiboot information structure has been stored in ebx by the
|
||||
; boot loader -- copy to a variable for later usage.
|
||||
mov [multiboot_addr], ebx
|
||||
|
||||
skip_multiboot:
|
||||
; Disable interrupts
|
||||
cli
|
||||
; Disable non maskable interrupts (NMI)
|
||||
; (we are going to ignore them)
|
||||
mov al, 0x80
|
||||
out 0x70, al
|
||||
|
||||
jmp load_cs
|
||||
|
||||
; Segment initialization
|
||||
; (code used by bootstrap and application processors as well)
|
||||
[GLOBAL segment_init]
|
||||
segment_init:
|
||||
; Load temporary protected mode Global Descriptor Table (GDT)
|
||||
lgdt [gdt_protected_mode_pointer]
|
||||
|
||||
; Initialize segment register
|
||||
mov ax, 0x10
|
||||
mov ds, ax
|
||||
mov es, ax
|
||||
mov fs, ax
|
||||
mov gs, ax
|
||||
mov ss, ax
|
||||
|
||||
; Load code segment register
|
||||
jmp 0x8:load_cs
|
||||
|
||||
load_cs:
|
||||
; Initialize stack pointer:
|
||||
; Atomic increment of `cpu_core_stack_pointer` by `CPU_CORE_STACK_SIZE`
|
||||
; (to avoid race conditions at application processor boot)
|
||||
mov eax, [CPU_CORE_STACK_SIZE]
|
||||
lock xadd [cpu_core_stack_pointer], eax
|
||||
; Since the stack grows into the opposite direction,
|
||||
; Add `CPU_CORE_STACK_SIZE` again
|
||||
add eax, [CPU_CORE_STACK_SIZE]
|
||||
; Assign stack pointer
|
||||
mov esp, eax
|
||||
|
||||
; Clear direction flag for string operations
|
||||
cld
|
||||
|
||||
; Switch to long mode (64 bit)
|
||||
jmp long_mode
|
||||
73
boot/startup.cc
Normal file
73
boot/startup.cc
Normal file
@@ -0,0 +1,73 @@
|
||||
#include "startup.h"
|
||||
|
||||
#include "../arch/acpi.h"
|
||||
#include "../arch/apic.h"
|
||||
#include "../arch/core.h"
|
||||
#include "../arch/idt.h"
|
||||
#include "../arch/pic.h"
|
||||
#include "../compiler/libc.h"
|
||||
#include "../debug/output.h"
|
||||
#include "../interrupt/handlers.h"
|
||||
|
||||
/*! \brief The first processor is the Bootstrap Processor (BSP)
|
||||
*/
|
||||
static bool isBootstrapProcessor = true;
|
||||
|
||||
extern "C" [[noreturn]] void kernel_init() {
|
||||
if (isBootstrapProcessor) {
|
||||
isBootstrapProcessor = false;
|
||||
// Setup and load Interrupt Description Table (IDT)
|
||||
initInterruptHandlers();
|
||||
|
||||
// Initialize PICs
|
||||
PIC::initialize();
|
||||
|
||||
// Call global constructors
|
||||
CSU::initializer();
|
||||
|
||||
// Initialize ACPI
|
||||
if (!ACPI::init()) {
|
||||
DBG_VERBOSE << "No ACPI!";
|
||||
Core::die();
|
||||
}
|
||||
// Initialize APIC (using ACPI)
|
||||
if (!APIC::init()) {
|
||||
DBG_VERBOSE << "APIC Initialization failed";
|
||||
Core::die();
|
||||
}
|
||||
|
||||
// Initialize the Bootstrap Processor
|
||||
Core::init();
|
||||
|
||||
// Go to main function
|
||||
main();
|
||||
|
||||
// Exit CPU
|
||||
DBG_VERBOSE << "CPU core " << Core::getID() << " (BSP) shutdown." << endl;
|
||||
Core::exit();
|
||||
} else {
|
||||
// Load Interrupt Description Table (IDT)
|
||||
IDT::load();
|
||||
|
||||
// Initialize this application processor
|
||||
Core::init();
|
||||
|
||||
// And call the AP main
|
||||
main_ap();
|
||||
|
||||
// Exit CPU
|
||||
DBG_VERBOSE << "CPU core " << Core::getID() << " (AP) shutdown." << endl;
|
||||
Core::exit();
|
||||
}
|
||||
|
||||
// Only on last core
|
||||
if (Core::countOnline() == 1) {
|
||||
// Call global destructors
|
||||
CSU::finalizer();
|
||||
}
|
||||
|
||||
// wait forever
|
||||
while (true) {
|
||||
Core::die();
|
||||
}
|
||||
}
|
||||
50
boot/startup.h
Normal file
50
boot/startup.h
Normal file
@@ -0,0 +1,50 @@
|
||||
/*! \file
|
||||
* \brief Startup of the first core, also known as bootstrap processor (BSP)
|
||||
*/
|
||||
#pragma once
|
||||
#include "../compiler/fix.h"
|
||||
#include "../types.h"
|
||||
|
||||
/*! \brief Entry point of your kernel
|
||||
*
|
||||
* \ingroup Startup
|
||||
*
|
||||
* Executed by boot loader.
|
||||
* Stores Pointer to \ref Multiboot information structure,
|
||||
* initializes stack pointer,
|
||||
* switches to long mode
|
||||
* and finally calls the C++ \ref kernel_init function
|
||||
*/
|
||||
extern "C" void startup_bsp() ERROR_ON_CALL(
|
||||
"The kernel entry point shall never be called from your code!");
|
||||
|
||||
/*! \brief Initializes the C++ environment and detects system components
|
||||
*
|
||||
* \ingroup Startup
|
||||
*
|
||||
* The startup code(both for \ref startup_bsp "bootstrap" and \ref startup_ap
|
||||
* "application processor") jumps to this high level function. After
|
||||
* initialization it will call \ref main()
|
||||
*/
|
||||
/*! or \ref main_ap() respectively
|
||||
*/
|
||||
extern "C" [[noreturn]] void kernel_init() ERROR_ON_CALL(
|
||||
"The kernel init function shall never be called from your code!");
|
||||
|
||||
/*! \brief Kernels main function
|
||||
*
|
||||
* Called after initialization of the system by \ref kernel_init()
|
||||
*/
|
||||
/*! \note This code will only be executed on the booting CPU (i.e., the one with
|
||||
* ID 0).
|
||||
*/
|
||||
extern "C" int main();
|
||||
|
||||
/*! \brief Entry point for application processors
|
||||
*
|
||||
* Called after initialization of the system by \ref kernel_init()
|
||||
*
|
||||
* \note Code in this function will be executed on all APs (i.e., all CPUs
|
||||
* except ID 0)
|
||||
*/
|
||||
extern "C" int main_ap();
|
||||
68
boot/startup_ap.asm
Normal file
68
boot/startup_ap.asm
Normal file
@@ -0,0 +1,68 @@
|
||||
|
||||
; Startup of the remaining application processors (in real mode)
|
||||
; and switching to 'Protected Mode' with a temporary GDT.
|
||||
; This code is relocated by ApplicationProcessor::relocateSetupCode()
|
||||
|
||||
[SECTION .setup_ap_seg]
|
||||
[GLOBAL setup_ap_gdt]
|
||||
[GLOBAL setup_ap_gdtd]
|
||||
|
||||
; Unlike the bootstrap processor, the application processors have not been
|
||||
; set up by the boot loader -- they start in real mode (16 bit) and have to be
|
||||
; switched manually to protected mode (32 bit)
|
||||
[BITS 16]
|
||||
|
||||
setup_ap:
|
||||
; Initialize segment register
|
||||
mov ax, cs ; Code segment and...
|
||||
mov ds, ax ; .. data segment should point to the same segment
|
||||
; (we don't use stack / stack segment)
|
||||
|
||||
; Disable interrupts
|
||||
cli
|
||||
; Disable non maskable interrupts (NMI)
|
||||
mov al, 0x80
|
||||
out 0x70, al
|
||||
|
||||
; load temporary real mode Global Descriptor Table (GDT)
|
||||
lgdt [setup_ap_gdtd - setup_ap]
|
||||
|
||||
; Switch to protected mode:
|
||||
; enable protected mode bit (1 << 0) in control register 0
|
||||
mov eax, cr0
|
||||
or eax, 1
|
||||
mov cr0, eax
|
||||
; Far jump to 32 bit `startup_ap` function
|
||||
jmp dword 0x08:startup_ap
|
||||
|
||||
; memory reserved for temporary real mode GDT
|
||||
; initialized by ApplicationProcessor::relocateSetupCode()
|
||||
align 4
|
||||
setup_ap_gdt:
|
||||
dq 0,0,0,0,0 ; reserve memory for at least 5 GDT entries
|
||||
|
||||
; memory reserved for temporary real mode GDT descriptor
|
||||
; initialized by ApplicationProcessor::relocateSetupCode()
|
||||
setup_ap_gdtd:
|
||||
dw 0,0,0,0,0 ; reserve memory for GDT descriptor
|
||||
|
||||
[SECTION .text]
|
||||
|
||||
[BITS 32]
|
||||
|
||||
; Segment initialization defined in `boot/startup.asm`
|
||||
[EXTERN segment_init]
|
||||
|
||||
; protected mode (32 bit) startup code for application processor
|
||||
startup_ap:
|
||||
; reload all segment selectors (since they still point to the real mode GDT)
|
||||
mov ax, 0x10
|
||||
mov ds, ax
|
||||
mov es, ax
|
||||
mov fs, ax
|
||||
mov gs, ax
|
||||
mov ss, ax
|
||||
|
||||
; Use same segment initialization function as bootstrap processor
|
||||
jmp segment_init
|
||||
|
||||
82
boot/startup_ap.cc
Normal file
82
boot/startup_ap.cc
Normal file
@@ -0,0 +1,82 @@
|
||||
#include "startup_ap.h"
|
||||
|
||||
#include "../arch/core_interrupt.h"
|
||||
#include "../arch/gdt.h"
|
||||
#include "../arch/lapic.h"
|
||||
#include "../arch/pit.h"
|
||||
#include "../debug/assert.h"
|
||||
#include "../debug/output.h"
|
||||
#include "../utils/size.h"
|
||||
#include "../utils/string.h"
|
||||
|
||||
namespace ApplicationProcessor {
|
||||
|
||||
// Make sure that the RELOCATED_SETUP is in low memory (< 1 MiB)
|
||||
static_assert((RELOCATED_SETUP & ~0x000ff000) == 0,
|
||||
"Not a valid 1 MB address for RELOCATED_SETUP!");
|
||||
|
||||
/*! \brief Temporary Global Descriptor Table
|
||||
*
|
||||
* Blue print, to be copied into real mode code
|
||||
*/
|
||||
constinit GDT::SegmentDescriptor ap_gdt[] = {
|
||||
// nullptr-Deskriptor
|
||||
{},
|
||||
|
||||
// XXX: Can't we just use GDT::protected_mode?
|
||||
// code segment
|
||||
GDT::SegmentDescriptor::Segment(0, UINT32_MAX, true, 0, GDT::SIZE_32BIT),
|
||||
|
||||
// data segment
|
||||
GDT::SegmentDescriptor::Segment(0, UINT32_MAX, false, 0, GDT::SIZE_32BIT),
|
||||
};
|
||||
|
||||
void relocateSetupCode() {
|
||||
// Relocated setup code
|
||||
memcpy(reinterpret_cast<void*>(RELOCATED_SETUP), &___SETUP_AP_START__,
|
||||
&___SETUP_AP_END__ - &___SETUP_AP_START__);
|
||||
|
||||
// Adjust GDT:
|
||||
// Calculate offset for real mode GDT and GDT descriptor
|
||||
uintptr_t ap_gdt_offset = reinterpret_cast<uintptr_t>(&setup_ap_gdt) -
|
||||
reinterpret_cast<uintptr_t>(&___SETUP_AP_START__);
|
||||
uintptr_t ap_gdtd_offset = reinterpret_cast<uintptr_t>(&setup_ap_gdtd) -
|
||||
reinterpret_cast<uintptr_t>(&___SETUP_AP_START__);
|
||||
|
||||
// Copy blue print of real mode GDT to the relocated memory
|
||||
void* relocated_ap_gdt =
|
||||
reinterpret_cast<void*>(RELOCATED_SETUP + ap_gdt_offset);
|
||||
memcpy(relocated_ap_gdt, &ap_gdt, sizeof(ap_gdt));
|
||||
|
||||
// Calculate GDT descriptor for relocated address
|
||||
GDT::Pointer* relocated_ap_gdtd =
|
||||
reinterpret_cast<GDT::Pointer*>(RELOCATED_SETUP + ap_gdtd_offset);
|
||||
relocated_ap_gdtd->set(relocated_ap_gdt, size(ap_gdt));
|
||||
}
|
||||
|
||||
void boot(void) {
|
||||
assert(!Core::Interrupt::isEnabled() &&
|
||||
"Interrupts should not be enabled before APs have booted!");
|
||||
|
||||
// Relocate setup code
|
||||
relocateSetupCode();
|
||||
|
||||
// Calculate Init-IPI vector based on address of relocated setup_ap()
|
||||
uint8_t vector = RELOCATED_SETUP >> 12;
|
||||
|
||||
// Send Init-IPI to all APs
|
||||
LAPIC::IPI::sendInit();
|
||||
|
||||
// wait at least 10ms
|
||||
PIT::delay(10000);
|
||||
|
||||
// Send Startup-IPI twice
|
||||
DBG_VERBOSE << "Sending STARTUP IPI #1" << endl;
|
||||
LAPIC::IPI::sendStartup(vector);
|
||||
// wait at least 200us
|
||||
PIT::delay(200);
|
||||
|
||||
DBG_VERBOSE << "Sending STARTUP IPI #2" << endl;
|
||||
LAPIC::IPI::sendStartup(vector);
|
||||
}
|
||||
} // namespace ApplicationProcessor
|
||||
114
boot/startup_ap.h
Normal file
114
boot/startup_ap.h
Normal file
@@ -0,0 +1,114 @@
|
||||
/*! \file
|
||||
* \brief Startup of additional cores, the application processors (APs)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "../compiler/fix.h"
|
||||
#include "../types.h"
|
||||
|
||||
/*! \brief Application Processor Boot
|
||||
*
|
||||
* Interface to boot the APs
|
||||
*/
|
||||
namespace ApplicationProcessor {
|
||||
/*! \brief Address (below 1 MiB) to which the setup code gets relocated
|
||||
*/
|
||||
constexpr uintptr_t RELOCATED_SETUP = 0x40000;
|
||||
|
||||
/*! \brief Relocate the real mode setup code
|
||||
*
|
||||
* The application processors (APs) start in real mode, which means that your
|
||||
* setup code must be placed within the first megabyte -- your operating system
|
||||
* resides currently at a much higher address (16 MiB), so the code has to be
|
||||
* copied down there first.
|
||||
*
|
||||
* Luckily, the code in `setup_ap()` can be relocated by copying -- because it
|
||||
* does not use any absolute addressing (except when jumping to the protected
|
||||
* mode function `startup_ap()`).
|
||||
* The function must be copied to the address of \ref RELOCATED_SETUP (0x40000),
|
||||
* so that the APs can start there.
|
||||
*
|
||||
* The memory section contains a reserved area for the \ref GDT and its
|
||||
* descriptor, which has to be assigned first with the contents of \ref ap_gdt.
|
||||
*
|
||||
* \note You could also tell the linker script to put the code directly
|
||||
* at the appropriate place, but unfortunately the Qemu multiboot
|
||||
* implementation (via `-kernel` parameter) can't handle it properly.
|
||||
*/
|
||||
void relocateSetupCode();
|
||||
|
||||
/*! \brief Boot all application processors
|
||||
*
|
||||
* Performs relocation by calling \ref relocateSetupCode()
|
||||
*
|
||||
* \see [ISDMv3, 8.4.4.2 Typical AP Initialization
|
||||
* Sequence](intel_manual_vol3.pdf#page=276)
|
||||
*/
|
||||
void boot();
|
||||
} // namespace ApplicationProcessor
|
||||
|
||||
/*! \brief Begin of setup code for application processors
|
||||
*
|
||||
* The setup code has to switch from real mode (16 bit) to protected mode (32
|
||||
* bit), hence it is written in assembly and must be executed in low memory (< 1
|
||||
* MiB).
|
||||
*
|
||||
* After kernel start the code is somewhere above 16 MiB (the bootstrap
|
||||
* processor was already launched in protected mode by the boot loader).
|
||||
* Therefore this symbol is required for relocate the code to the position
|
||||
* specified by \ref ApplicationProcessor::RELOCATED_SETUP.
|
||||
*
|
||||
* Luckily, the `setup_ap` code in `boot/startup_ap.asm` is rather simple and
|
||||
* doesn't depend on absolute addressing -- and is therefore relocatable.
|
||||
*
|
||||
* Relocation is done by the function \ref
|
||||
* ApplicationProcessor::relocateSetupCode()
|
||||
*
|
||||
* The `___SETUP_AP_START__` symbol is defined in the linker script
|
||||
* (`compiler/section.ld`)
|
||||
*/
|
||||
extern char ___SETUP_AP_START__;
|
||||
|
||||
/*! \brief End of startup code for application processors
|
||||
*
|
||||
* This Symbol is defined in the linker script (`compiler/section.ld`)
|
||||
*/
|
||||
extern char ___SETUP_AP_END__;
|
||||
|
||||
/*! \brief Memory reserved for a temporary real mode GDT
|
||||
* within the relocatable memory area of the setup code
|
||||
*/
|
||||
extern char setup_ap_gdt;
|
||||
|
||||
/*! \brief Memory reserved for a temporary real mode GDT descriptor
|
||||
* within the relocatable memory area of the setup code
|
||||
*/
|
||||
extern char setup_ap_gdtd;
|
||||
|
||||
/*! \brief Entry point for application processors
|
||||
*
|
||||
* Unlike the bootstrap processor, the application processors have not been
|
||||
* setup by the boot loader -- they start in `Real Mode` (16 bit) and have to be
|
||||
* switched manually to `Protected Mode` (32 bit).
|
||||
* This is exactly what this real mode function does, handing over control
|
||||
* to the (32 bit) function \ref startup_ap()
|
||||
*
|
||||
* This code is written is assembly (`boot/startup_ap.asm`) and relocated by
|
||||
* \ref ApplicationProcessor::relocateSetupCode() during
|
||||
* \ref ApplicationProcessor::boot()
|
||||
*/
|
||||
extern "C" void setup_ap() ERROR_ON_CALL(
|
||||
"The setup function for application processors shall never be called from "
|
||||
"your code!");
|
||||
|
||||
/*! \brief Startup for application processors
|
||||
* \ingroup Startup
|
||||
*
|
||||
* This function behaves similar to \ref startup_bsp():
|
||||
* Initializes stack pointer,
|
||||
* switches to long mode
|
||||
* and calls the C++ \ref kernel_init function
|
||||
*/
|
||||
extern "C" void startup_ap() ERROR_ON_CALL(
|
||||
"The startup function for application processors shall never be called "
|
||||
"from your code!");
|
||||
Reference in New Issue
Block a user