This commit is contained in:
Niklas Gollenstede
2025-04-14 11:20:52 +02:00
commit 5a2e32aaeb
126 changed files with 16742 additions and 0 deletions

245
boot/longmode.asm Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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!");