Commit 7866ccc5 authored by Christian Dietrich's avatar Christian Dietrich
Browse files

Handout for Assignment 1: In- & Output (text mode, keyboard)

parent 88d9a29e
# Kernel Makefile
# try `make help` for more information
# Default target
.DEFAULT_GOAL = all
# Path to the files for the initial ramdisk (for Assignment 7)
INITRD_DIR ?= initrd/
INITRD_TOOL ?= fs/tool/fstool
INITRD_DEP =
# 1MB free space
INITRD_FREE ?= 1048576
# Kernel source files
LINKER_SCRIPT = compiler/sections.ld
CRTI_SOURCE = compiler/crti.asm
CRTN_SOURCE = compiler/crtn.asm
CC_SOURCES = $(shell find * -name "*.cc" -a ! -name '.*' -a ! -path 'test*' -a ! -path 'fs/tool/*' -a ! -path 'assets/*')
ASM_SOURCES = $(shell find * -name "*.asm" -a ! -name '.*')
# Target files
KERNEL = $(BUILDDIR)/system
ISOFILE = $(BUILDDIR)/stubs.iso
# Include global variables and standard recipes
include tools/common.mk
# Initial Ramdisk
ifneq ($(wildcard $(INITRD_DIR)*),)
INITRD = $(BUILDDIR)/initrd.img
INITRD_DEP += $(shell find $(INITRD_DIR) -type f )
# Additional dependency for kernel
$(KERNEL): $(INITRD)
endif
all: $(KERNEL)
# Linking the system image
# We use the C++ compiler (which calls the actual linker)
$(KERNEL): $(CRTI_OBJECT) $(CRTN_OBJECT) $(ASM_OBJECTS) $(CC_OBJECTS) $(LINKER_SCRIPT) $(MAKEFILE_LIST)
@echo "LD $@"
@mkdir -p $(@D)
$(VERBOSE) $(CXX) $(CXXFLAGS) -Wl,-T $(LINKER_SCRIPT) -o $@ $(LDFLAGS) $(CRTI_OBJECT) $(CRTBEGIN_OBJECT) $(ASM_OBJECTS) $(CC_OBJECTS) $(CRTEND_OBJECT) $(CRTN_OBJECT) $(LIBGCC)
# Tool for editing a Minix v3 file system image (Assignment 7)
$(INITRD_TOOL): $(shell test -d $(dir $(INITRD_TOOL)) && find $(dir $(INITRD_TOOL)) -name "*.cc" -or -name '*.h')
@echo "Make $@"
@make -C $(dir $(INITRD_TOOL))
# Initial Ramdisk with Minix v3 file system
$(INITRD): $(INITRD_TOOL) $(INITRD_DEP)
@echo "INITRD $@"
@dd if=/dev/zero of=$@ bs=$(shell du -s $(INITRD_DIR) | cut -f1 | xargs expr $(INITRD_FREE) + ) count=1
@/sbin/mkfs.minix -3 $@ # optional --inodes <number>
@./$(INITRD_TOOL) put "$(INITRD_DIR)" $@
; 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)
#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
/*! \file
* \brief \ref Multiboot Interface
*/
#pragma once
#include "types.h"
#include "compiler/fix.h"
#include "debug/assert.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 UNUSED_STRUCT_FIELD; ///< 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
; 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
; 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 `machine/core.cc`
[EXTERN cpu_core_stack_pointer] ; Pointer to reserved memory for CPU core stacks, see `machine/core.cc`
[EXTERN kernel_init] ; C++ entry function, see `boot/startup.h`
[EXTERN gdt_protected_mode_pointer] ; Pointer to 32 Bit Global Descriptor Table (located in `machine/gdt.cc`)
[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
; 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
; Call high-level (C++) kernel initialization function
call kernel_init
; Print `STOP` to screen and stop
mov dword [0xb8000], 0x2f544f53
mov dword [0xb8004], 0x2f502f4f
hlt
#include "startup.h"
#include "types.h"
#include "compiler/libc.h"
#include "debug/output.h"
#include "debug/kernelpanic.h"
#include "interrupt/handler.h"
#include "machine/acpi.h"
#include "machine/apic.h"
#include "machine/core.h"
#include "machine/idt.h"
#include "machine/pic.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)
*
* On the first call to \ref kernel_init(), we have to assign the
* addresses of the entry functions for each interrupt
* (called 'interrupt_entry_VECTOR', defined in `interrupt/handler.asm`)
* to the IDT. These entry functions save the context and call the C++
* \ref interrupt_handler(). As the \ref IDT is used by all CPUs,
* it is sufficient to do this initialization on only the BSP (first core).
*/
for (int i = 0; i < Core::Interrupt::VECTORS ; i++) {