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

This commit is contained in:
Alexander Krause 2023-10-11 13:00:04 +02:00
parent 5bfe56aafd
commit 3e8f31ac80
93 changed files with 14746 additions and 0 deletions

60
Makefile Normal file
View File

@ -0,0 +1,60 @@
# 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/*')
ASM_SOURCES = $(shell find * -name "*.asm" -a ! -name '.*')
# Target files
KERNEL = $(BUILDDIR)/system
KERNEL64 = $(KERNEL)64
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)
$(KERNEL64): $(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 $@ $(CRTI_OBJECT) $(CRTBEGIN_OBJECT) $(ASM_OBJECTS) $(CC_OBJECTS) $(LDFLAGS) $(LIBGCC) $(CRTEND_OBJECT) $(CRTN_OBJECT)
# The kernel must be a 32bit elf for multiboot compliance
$(KERNEL): $(KERNEL64)
$(VERBOSE) $(OBJCOPY) -I elf64-x86-64 -O elf32-i386 $< $@
# 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 $@"
$(VERBOSE) MAKEFLAGS="" $(MAKE) -C $(dir $(INITRD_TOOL))
# Initial Ramdisk with Minix v3 file system
$(INITRD): $(INITRD_TOOL) $(INITRD_DEP)
@echo "INITRD $@"
$(VERBOSE) dd if=/dev/zero of=$@ bs=$(shell du -s $(INITRD_DIR) | cut -f1 | xargs expr $(INITRD_FREE) + ) count=1
$(VERBOSE) /sbin/mkfs.minix -3 $@ # optional --inodes <number>
$(VERBOSE) ./$(INITRD_TOOL) put "$(INITRD_DIR)" $@

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, machine/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 the 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:
; Set data segment registers to SEGMENT_KERNEL_DATA (machine/gdt.h)
mov ax, 0x10
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)

165
boot/multiboot/data.cc Normal file
View File

@ -0,0 +1,165 @@
#include "boot/multiboot/data.h"
/*! \brief Multiboot Information Structure according to Specification
* \see [Multiboot Specification]{#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 = 1U << 0, ///< is there basic lower/upper memory information?
BootDev = 1U << 1, ///< is there a boot device set?
CmdLine = 1U << 2, ///< is the command-line defined?
Modules = 1U << 3, ///< are there modules to do something with?
/* These next two are mutually exclusive */
SymbolTable = 1U << 4, ///< is there an a.out symbol table loaded?
SectionHeader = 1U << 5, ///< is there an ELF section header table?
MemoryMap = 1U << 6, ///< is there a full memory map?
DriveInfo = 1U << 7, ///< Is there drive info?
ConfigTable = 1U << 8, ///< Is there a config table?
BootLoaderName = 1U << 9, ///< Is there a boot loader name?
ApmTable = 1U << 10, ///< Is there a APM table?
// Is there video information?
VbeInfo = 1U << 11, ///< Vesa bios extension
FramebufferInfo = 1U << 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 (boot/startup.asm)
*/
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*>(end & 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

232
boot/multiboot/data.h Normal file
View File

@ -0,0 +1,232 @@
/*! \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 \ref multiboot "Multiboot Specification".
*
* 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 \ref multiboot-boot-modules "1.7 Boot modules"
* \see \ref multiboot-boot-info "3.3 Boot information format"
*/
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 or `nullptr` if last area
*/
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 `machine/core.cc`
[EXTERN cpu_core_stack_pointer] ; Pointer to reserved memory for CPU core stacks, see `machine/core.cc`
[EXTERN gdt_protected_mode_pointer] ; Pointer to 32 Bit Global Descriptor Table (located in `machine/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

86
boot/startup.cc Normal file
View File

@ -0,0 +1,86 @@
#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 is_bootstrap_processor = true;
extern "C" [[noreturn]] void kernel_init() {
if (is_bootstrap_processor) {
is_bootstrap_processor = false;
/* Setup and load Interrupt Descriptor 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++) {
IDT::handle(i, interrupt_entry[i]);
}
IDT::load();
// 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 Descriptor 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();
}
}

46
boot/startup.h Normal file
View File

@ -0,0 +1,46 @@
/*! \file
* \brief Startup of the first core, also known as bootstrap processor (BSP)
*/
#pragma once
#include "compiler/fix.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();

66
boot/startup_ap.asm Normal file
View File

@ -0,0 +1,66 @@
; 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
; setup 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

83
boot/startup_ap.cc Normal file
View File

@ -0,0 +1,83 @@
#include "startup_ap.h"
#include "utils/string.h"
#include "utils/size.h"
#include "debug/output.h"
#include "debug/assert.h"
#include "machine/lapic.h"
#include "machine/core_interrupt.h"
#include "machine/gdt.h"
#include "machine/pit.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
*/
constexpr GDT::SegmentDescriptor ap_gdt[] = {
// nullptr-Deskriptor
{},
// code segment
{ /* base = */ 0x0,
/* limit = */ 0xFFFFFFFF,
/* code = */ true,
/* ring = */ 0,
/* size = */ GDT::SIZE_32BIT },
// data segment
{ /* base = */ 0x0,
/* limit = */ 0xFFFFFFFF,
/* code = */ false,
/* ring = */ 0,
/* size = */ 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

109
boot/startup_ap.h Normal file
View File

@ -0,0 +1,109 @@
/*! \file
* \brief Startup of additional cores, the application processors (APs)
*/
#pragma once
#include "types.h"
#include "compiler/fix.h"
/*! \brief Application Processor Boot
*
* Interface to boot the APs
*/
namespace ApplicationProcessor {
/*! \brief Address (below 1 MiB) to which the setup code gets relocated
*/
const 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!");

18
compiler/crti.asm Normal file
View File

@ -0,0 +1,18 @@
; C Runtime Objects - Function prologues for the initialization (.init) and termination routines (.fini) required by the runtime libs.
; When developing on Linux, these files are automatically added by the compiler/linker; we, however, are writing an operating system
; and therefore need to add them ourselves.
; C Runtime - beginning (needs to be passed as first element to the linker)
[SECTION .init]
[GLOBAL _init]
_init:
push rbp
mov rbp, rsp
; The linker will inject the contents of the .init from crtbegin.o here
[SECTION .fini]
[GLOBAL _fini]
_fini:
push rbp
mov rbp, rsp
; The linker will inject the contents of the .fini from crtbegin.o here

12
compiler/crtn.asm Normal file
View File

@ -0,0 +1,12 @@
; C Runtime Objects - Function prologues for the initialization (.init) and termination routines (.fini) required by the runtime libs.
; C Runtime - end (needs to be passed as last element to the linker)
[SECTION .init]
; The linker will inject the contents of the .init from crtend.o here
pop rbp
ret
[SECTION .fini]
; The linker will inject the contents of the .fini from crtend.o here
pop rbp
ret

21
compiler/fix.h Normal file
View File

@ -0,0 +1,21 @@
/*! \file
* \brief Compiler-dependent fixes & idiosyncrasies
*/
#pragma once
#ifdef __clang__
# define UNUSED_STRUCT_FIELD __attribute__((unused))
#else
// GCC does not understand this attribute correctly for structures
# define UNUSED_STRUCT_FIELD
#endif
#if defined(__GNUC__) && !defined(__clang__)
// Only GCC understands the error attribute
# define ERROR_ON_CALL(MSG) __attribute__((error(MSG)));
#else
# define ERROR_ON_CALL(MSG)
#endif
#define MAYBE_UNUSED __attribute__((unused))

44
compiler/libc.cc Normal file
View File

@ -0,0 +1,44 @@
#include "compiler/libc.h"
#include "types.h"
/*! \brief Function pointer for initialization/finalization functions for global objects
* required since GCC 4.7 and later.
*
* These symbols appear kind of magically due to the compiler
*/
extern void(*__preinit_array_start[]) ();
extern void(*__preinit_array_end[]) ();
extern void(*__init_array_start[]) ();
extern void(*__init_array_end[]) ();
extern void(*__fini_array_start[]) ();
extern void(*__fini_array_end[]) ();
extern "C" void _init();
extern "C" void _fini();
namespace CSU {
void initializer() {
const unsigned int preinit_size = __preinit_array_end - __preinit_array_start;
for (unsigned int i = 0; i != preinit_size; ++i) {
(*__preinit_array_start[i])();
}
_init();
const size_t size = __init_array_end - __init_array_start;
for (size_t i = 0; i < size; i++) {
(*__init_array_start[i])();
}
}
void finalizer() {
const unsigned int fini_size = __fini_array_end - __fini_array_start;
for (unsigned int i = 0; i != fini_size; ++i) {
(*__fini_array_start[i])();
}
_fini();
}
} // namespace CSU

22
compiler/libc.h Normal file
View File

@ -0,0 +1,22 @@
/*! \file
* \brief Initialization functions for global objects required by the compiler
*/
#pragma once
/*! \brief C StartUp (CSU)
* required by the compiler and provided by the c standard library
*/
namespace CSU {
/*! \brief Call global constructors and initialization functions
* (this is usually done by __libc_csu_init)
*/
void initializer();
/*! \brief Call global destructors and finalizer functions
* (this is usually done by __libc_csu_fini)
*/
void finalizer();
} // namespace CSU

31
compiler/libcxx.cc Normal file
View File

@ -0,0 +1,31 @@
/*! \file
* \brief C++ runtime support functions
*/
#include "types.h"
void operator delete(void *ptr) {
(void) ptr;
}
void operator delete(void *ptr, size_t size) {
(void) ptr;
(void) size;
}
extern "C" int __cxa_atexit(void (*func)(void *), void * arg, void * dso_handle) {
// Registers a function that will be executed on exit.
// We simply ignore those functions, as we don't need them for our operating systems.
(void) func;
(void) arg;
(void) dso_handle;
return 0;
}
extern "C" [[noreturn]] void __cxa_pure_virtual() {
// Pure virtual function was called -- this if obviously not valid,
// therefore we wait infinitely.
while (true) {}
}

119
compiler/sections.ld Normal file
View File

@ -0,0 +1,119 @@
/* Entry in our OS -- label 'startup_bsp' in file boot/startup.asm */
ENTRY(startup_bsp)
SECTIONS
{
/* start address of our kernel */
. = 16M;
/* This is a linker script defined "variable" (without value -- therefore
* better to be considered as a symbol), which can be referenced in the C++
* source code using `&___KERNEL_START___` (mind the reference operator!)
* to get a pointer to the current (virtual) memory position.
* However, a previous declaration with C linkage is required, e.g.
* extern "C" void * ___KERNEL_START___;
* For more information have a look at
* https://sourceware.org/binutils/docs/ld/Source-Code-Reference.html
*/
___KERNEL_START___ = .;
.boot :
{
/* Multiboot Header should be at the very beginning of the binary */
*(.multiboot_header)
}
/* The (executable) machine code */
.text :
{
*(.text .text.* .gnu.linkonce.t.*)
*(.init)
*(.fini)
}
/* Align to the next page border
* allowing the linker to mark the section above as [read and] executable
* while the following sections is just read-only. */
. = ALIGN(0x1000);
/* Start for application processors, relocated by APIC::init()
* to a below 1 MB address to boot from real mode.
*
* Please note:
* It is possible to let the linker place it at a below 1 MB address,
* while all the rest starts at 16 MB. This will work for multiboot
* compliant boot loader like GRUB and PXELINUX, however,
* the qemu boot loader cannot handle such ELF files (yet)...
* That's why we have to copy it ourself in the kernel */
.setup_ap_seg ALIGN(0x10) :
{
___SETUP_AP_START__ = .;
*(.setup_ap_seg)
}
___SETUP_AP_END__ = .;
/* Read-only data */
.rodata :
{
*(.rodata .rodata.* .gnu.linkonce.r.*)
KEEP(*(.note.gnu.build-id))
}
/* Align to the next page border
* allowing the linker to mark the section below as [read and] writeable */
. = ALIGN(0x1000);
/* lists containing the start address of global constructors and destructors (generated by the compiler) */
.preinit_array :
{
PROVIDE_HIDDEN (__preinit_array_start = .);
KEEP (*(.preinit_array))
PROVIDE_HIDDEN (__preinit_array_end = .);
}
.init_array :
{
PROVIDE_HIDDEN (__init_array_start = .);
KEEP (*(SORT(.init_array.*)))
KEEP (*(.init_array))
PROVIDE_HIDDEN (__init_array_end = .);
}
.fini_array :
{
PROVIDE_HIDDEN (__fini_array_start = .);
KEEP (*(SORT(.fini_array.*)))
KEEP (*(.fini_array))
PROVIDE_HIDDEN (__fini_array_end = .);
}
/* the data section containing initialized static variables
* (writeable with a value not zero) */
.data :
{
*(.data .data.* .gnu.linkonce.d.*)
/* Global offset table */
*(.got*)
/* Exception Handling */
*(.eh_frame*)
}
/* the bss (block starting symbol) section containing uninitialized
* static variables (writeable with an initial value of zero);
* this section does not consume any memory in the binary */
.bss :
{
*(.bss .bss.* .gnu.linkonce.b.*)
*(COMMON)
}
___KERNEL_END___ = .;
/* Sections which should not be included in the binary */
/DISCARD/ :
{
*(.note)
*(.comment)
/* Debug information (commented out to keep it)
*(.debug*)
*/
}
}

12
debug/assert.cc Normal file
View File

@ -0,0 +1,12 @@
#include "assert.h"
[[noreturn]] void assertion_failed(const char * exp, const char * func, const char * file, int line) {
(void) exp;
(void) func;
(void) file;
(void) line;
// TODO: Print error message (in debug window)
// TODO: Then stop the current core permanently
// Use appropriate method from class Core to do so.
while(true) {} // wait forever
}

63
debug/assert.h Normal file
View File

@ -0,0 +1,63 @@
/*! \file
* \brief Contains several macros usable for making assertions
*
* Depending on the type of assertion (either static or at runtime), a failing assertion will trigger an error.
* For static assertion, this error will be shown at compile time and abort compilation.
* Runtime assertions will trigger a message containing details about the error occurred and will make the CPU die.
*/
#pragma once
#ifndef STRINGIFY
/*! \def STRINGIFY(S)
* \brief Converts a macro parameter into a string
* \ingroup debug
* \param S Expression to be converted
* \return stringified version of S
*/
#define STRINGIFY(S) #S
#endif
/*! \def assert_size(TYPE, SIZE)
* \brief Statically ensure (at compile time) that a data type (or variable) has the expected size.
* \ingroup debug
* \param TYPE The type to be checked
* \param SIZE Expected size in bytes
*/
#define assert_size(TYPE, SIZE) \
static_assert(sizeof(TYPE) == (SIZE), "Wrong size for " STRINGIFY(TYPE))
/*! \def assert(EXP)
* \brief Ensure (at execution time) an expression evaluates to `true`, print an error message and stop the CPU otherwise.
* \ingroup debug
* \param EXP The expression to be checked
*/
#ifdef NDEBUG
#define assert(EXP) \
do { \
(void)sizeof(EXP); \
} while (false)
#else
#define assert(EXP) \
do { \
if (__builtin_expect(!(EXP), 0)) { \
assertion_failed(STRINGIFY(EXP), __func__, __FILE__, __LINE__); \
} \
} while (false)
/*! \brief Handles a failed assertion
*
* This function will print a message containing further information about the
* failed assertion and stops the current CPU permanently.
*
* \note This function should never be called directly, but only via the macro `assert`.
*
* \todo Implement Remainder of Method (output & CPU stopping)
*
* \param exp Expression that did not hold
* \param func Name of the function in which the assertion failed
* \param file Name of the file in which the assertion failed
* \param line Line in which the assertion failed
*/
[[noreturn]] void assertion_failed(const char * exp, const char * func, const char * file, int line);
#endif

22
debug/kernelpanic.h Normal file
View File

@ -0,0 +1,22 @@
/*! \file
* \brief Macro to print an error message and stop the current core.
*/
#pragma once
/*! \def kernelpanic
* \brief Print an error message in the debug window and \ref Core::die "stop the current core"
* \ingroup debug
* \param MSG error message
*/
#define kernelpanic(MSG) \
do { \
DBG << "PANIC: '" << (MSG) << "' in " << __func__ \
<< " @ " << __FILE__ << ":" << __LINE__ \
<< flush; \
Core::die(); \
} while (0)
// The includes are intentionally placed at the end, so the macro can be used inside those included files as well.
#include "debug/output.h"
#include "machine/core.h"

4
debug/nullstream.cc Normal file
View File

@ -0,0 +1,4 @@
#include "debug/nullstream.h"
// Instance
NullStream nullstream;

39
debug/nullstream.h Normal file
View File

@ -0,0 +1,39 @@
/*! \file
* \brief \ref NullStream is a stream discarding everything
*/
#pragma once
#include "object/outputstream.h"
/*! \brief Ignore all data passed by the stream operator
* \ingroup io
*
* Can be used instead of the \ref OutputStream if (for debugging reasons) all
* output should be ignored, e.g. for \ref DBG_VERBOSE
*
* By using template programming, a single generic methods is sufficient
* (which simply discard everything).
*/
class NullStream {
public:
/*! \brief Empty default constructor
*/
NullStream() {}
/*! \brief Generic stream operator for any data type
*
* Uses template meta programming for a generic & short solution
*
* \tparam T Type of data to ignore
* \param value data to be ignore
* \return Reference to the \ref NullStream object allowing concatenation of operators
*/
template <typename T>
NullStream& operator << (T value) {
(void) value;
return *this;
}
};
extern NullStream nullstream;

65
debug/output.h Normal file
View File

@ -0,0 +1,65 @@
/*! \file
* \brief Debug macros enabling debug output on a separate window for each core.
*/
#pragma once
/*! \def DBG_VERBOSE
* \brief An output stream, which is only displayed in the debug window in verbose mode
*
* \note If a serial console has been implemented, the output can be redirected
* to the serial stream instead (by changing the macro) -- this makes the
* (usually) very large output more readable (since it allows scrolling back)
*/
#ifdef VERBOSE
// If VERBOSE is defined, forward everything to \ref DBG
#define DBG_VERBOSE DBG
#else
// Otherwise sent everything to the NullStream (which will simply discard everything)
#define DBG_VERBOSE nullstream
// in this case we have to include the null stream
#include "debug/nullstream.h"
#endif
/*! \def DBG
* \brief An output stream, which is displayed in the debug window of the core it was executed on
*
* In single core (\OOStuBS) this is just an alias to the debug window object
* `dout`.
* However, on a multi core system a debug window for each core is
* required, therefore `dout` has to be an \ref TextStream object array with the
* core ID as array index -- the selection is done via Core::getID()
*
*/
#define DBG dout[Core::getID()]
#include "device/textstream.h"
#include "machine/core.h"
/*! \brief Debug window
*
* Debug output using \ref DBG like
* `DBG << "var = " << var << endl`
* should be displayed in window dedicated to the core it is executed on.
*
* While this is quite easy on single core systems like \OOStuBS -- they only
* require a single \ref TextStream object called `dout` -- multi core systems
* like \MPStuBS need an object array with one window per core.
* In the latter case direct list initialization can be used:
*
* \code{.cpp}
* TextStream dout[Core::MAX]{
* {0, 40, 17, 21}, // Debug window for core 0, like TextStream(0, 40, 17, 21)
* {40, 80, 17, 21}, // Debug window for core 1, like TextStream(40, 80, 17, 21)
* //...
* };
* \endcode
*
* The debug windows in should be located right below the normal output window
* without any overlap and should be able to display at least 3 lines.
* In \MPStuBS, two windows can be placed side-by-side, having 40 columns each.
*
* \todo Define `dout`
*/
extern TextStream dout[Core::MAX];

40
device/serialstream.cc Normal file
View File

@ -0,0 +1,40 @@
#include "device/serialstream.h"
#include "debug/output.h"
SerialStream::SerialStream(ComPort port, BaudRate baud_rate, DataBits data_bits, StopBits stop_bits, Parity parity)
: Serial(port, baud_rate, data_bits, stop_bits, parity) {}
void SerialStream::flush() {
}
void SerialStream::setForeground(Color c) {
(void) c;
}
void SerialStream::setBackground(Color c) {
(void) c;
}
void SerialStream::setAttribute(Attrib a) {
(void) a;
}
void SerialStream::reset() {
}
void SerialStream::setPos(int x, int y) {
(void) x;
(void) y;
}
bool SerialStream::getPos(int &x, int &y) {
(void) x;
(void) y;
return false;
}
void SerialStream::print(char* str, int length) {
(void) str;
(void) length;
}

163
device/serialstream.h Normal file
View File

@ -0,0 +1,163 @@
/*! \file
* \brief \ref Serial \ref SerialStream "output stream"
*/
#pragma once
#include "object/outputstream.h"
#include "machine/serial.h"
/*! \brief Console (VT100 compatible) via \ref Serial interface.
* \ingroup io
*
* This class allows to connect a VT100-compatible display terminal via
* the serial interface.
*
* The utility 'screen' can be used to attach a terminal to an interface
* at a specified connection speed: `screen /dev/ttyS0 115200`
*
* Color and position can be adjusted with the help of
* [escape codes](https://web.archive.org/web/20210126100003/https://ascii-table.com/ansi-escape-sequences-vt-100.php).
*/
class SerialStream : public OutputStream, public Serial {
/*! \brief Helper to send a multi-digit number as human readable ASCII characters
* \param num Number to send
*/
void write_number(int num);
public:
/*! \brief Attributes
* can be used to influence the display of the output.
*
* \note The attributes might not be supported or have a different effect
* depending on the terminal emulator!
*/
enum Attrib {
RESET = 0, ///< Turn off character attributes
BRIGHT = 1, ///< Bold
DIM = 2, ///< Low intensity (dimmed)
UNDERSCORE = 4, ///< Underline
BLINK = 5, ///< Blink (slow)
REVERSE = 7, ///< Swap fore & background
HIDDEN = 8, ///< Concealed
};
/*! \brief Color codes
*
* Default VT100 supports eight colors for both foreground and background
* (later versions 256 [8 bit] and even true color [32 bit]).
* The actual color is affected by the attributes and can look significantly
* different depending on the terminal emulator.
*/
enum Color {
BLACK = 0,
RED = 1,
GREEN = 2,
YELLOW = 3,
BLUE = 4,
MAGENTA = 5,
CYAN = 6,
WHITE = 7
};
/*! \brief Constructor for the VT100-compatible console
*
* Sets up the serial connection as well
*
* \todo Implement Method
*/
explicit SerialStream(ComPort port = COM1, BaudRate baud_rate = BAUD_115200, DataBits data_bits = DATA_8BIT,
StopBits stop_bits = STOP_1BIT, Parity parity = PARITY_NONE);
/*! \brief Method to output the buffer contents of the base class \ref Stringbuffer
*
* The method is automatically called when the buffer is full,
* but can also be called explicitly to force output of the current buffer.
*
* \todo Implement Method
*/
void flush();
/*! \brief Change foreground color (for subsequent output)
*
* \todo Implement Method
*
* \param c Color
*/
void setForeground(Color c);
/*! \brief Change background color (for subsequent output)
*
* \todo Implement Method
*
* \param c Color
*/
void setBackground(Color c);
/*! \brief Change text attribute (for subsequent output)
*
* \todo Implement Method
*
* \param a Attribute
*/
void setAttribute(Attrib a);
/*! \brief Reset terminal
*
* Clear screen, place cursor at the beginning and reset colors
* and attributes to the default value.
*
* \opt Implement Method
*/
void reset();
/*! \brief Set the cursor position
*
* \param x Column in window
* \param y Row in window
*
* \opt Implement Method
*/
void setPos(int x, int y);
/*! \brief Read the current cursor position
*
* It is possible to receive the current cursor position via a special
* escape code: Request by sending `\e[6n`, answer will be `\e[y;xR` with
* `y` (row) and `x` (column) as human readable ASCII character number.
*
* However, depending on the implementation, it may be possible that the
* system waits endlessly due to an disconnected terminal or data
* transmission error.
*
* \param x Column in window
* \param y Row in window
* \return `true` if position was successfully received
*
* \opt Implement Method
*/
bool getPos(int &x, int &y);
/*! \brief Display multiple characters in the window starting at the current cursor position
*
* This method can be used to output a string, starting at the current cursor
* position. Since the string does not need to contain a '\0' termination
* (as it is usually the case in C), the parameter `length` is required to
* specify the number of characters in the string.
*
* The text is displayed using the previously configured
* \ref setAttribute() "attributes", \ref setForeground() "fore-"
* and \ref setBackground "background" color.
*
* A line break will occur wherever the character `\n` is inserted
* in the text to be output (for compatibility reasons a `\r` is
* automatically appended).
*
* \todo Implement Method
* \param str String to output
* \param length length of string
*/
void print(char* str, int length);
};

2
device/textstream.cc Normal file
View File

@ -0,0 +1,2 @@
#include "device/textstream.h"

46
device/textstream.h Normal file
View File

@ -0,0 +1,46 @@
/*! \file
* \brief \ref TextStream outputs text onto the screen in \ref TextMode
*/
/*! \defgroup io I/O subsystem
* \brief The input/output subsystem
*/
#pragma once
#include "object/outputstream.h"
#include "machine/textwindow.h"
/*! \brief Output text (form different data type sources) on screen in text mode
* \ingroup io
*
* Allows the output of different data types as strings on the \ref TextMode
* screen of a PC.
* To achieve this, \ref TextStream is derived from both \ref OutputStream and
* \ref TextWindow and only implements the method \ref TextStream::flush().
* Further formatting or special effects are implemented in \ref TextWindow.
*/
class TextStream {
// Prevent copies and assignments
TextStream(const TextStream&) = delete;
TextStream& operator=(const TextStream&) = delete;
public:
/// \copydoc TextWindow::TextWindow(unsigned,unsigned,unsigned,unsigned,bool)
TextStream(unsigned from_col, unsigned to_col, unsigned from_row, unsigned to_row, bool use_cursor = false) {
(void) from_col;
(void) to_col;
(void) from_row;
(void) to_row;
(void) use_cursor;
}
/*! \brief Output the buffer contents of the base class \ref Stringbuffer
*
* The method is automatically called when the buffer is full,
* but can also be called explicitly to force output of the current buffer.
*
*
* \todo Implement method
*/
void flush();
};

120
interrupt/handler.asm Normal file
View File

@ -0,0 +1,120 @@
; Low-level stuff required for interrupt handling
; The "actual" code to be executed is implemented in the C function "interrupt_handler"
[EXTERN interrupt_handler]
[SECTION .text]
; Entry function for interrupt handling (of any vector)
; The interrupt handling of each vector has to be started in assembler to store
; the scratch registers (see SystemV calling conventions) before the actual
; high-level (C++) interrupt_handler function can be executed.
;
; For this purpose we use a macro to generate a customized entry function for
; each interrupt vector (0-255), wheres the vector itself is the first parameter.
; The second parameter is a boolean flag indicating whether an error code is
; placed on the stack for the corresponding trap (by the CPU).
;
; Usage: IRQ <vector> <error-code?>
%macro IRQ 2
ALIGN 16
interrupt_entry_%1:
%if %2 == 0
; If the vector is not a trap with an error code automatically pushed
; on the stack, the entry function pushes a zero instead
; to retain an identical stack layout in each case.
push 0
%endif
; The interrupt may be triggered asynchronously, therefore the whole context
; has to be saved and restored, or the interrupted code might not be able to
; continue. The C++ compiler will only generates code to preserve
; non-scratch registers in the high-level interrupt handler -- the scratch
; registers have to be saved (and restored later) manually!
push rax
push rcx
push rdx
push rsi
push rdi
push r8
push r9
push r10
push r11
; Clear direction flag for string operations
cld
; Assign vector as first parameter for the high-level interrupt handler
mov rdi, %1
; Assign pointer to the context (= interrupt stack) as second parameter
mov rsi, rsp
; Call the high-level interrupt handler routine
call interrupt_handler
; Restore scratch registers
pop r11
pop r10
pop r9
pop r8
pop rdi
pop rsi
pop rdx
pop rcx
pop rax
; Drop error code (or the fake zero value)
add rsp, 8
; Return from interrupt
iretq
%endmacro
; For traps the CPU sometimes pushes an error code onto the stack.
; These vectors are documented in the manual (or instead use the
; [osdev wiki](https://wiki.osdev.org/Exceptions) for a handy list).
; Therefore we manually call the macro for the corresponding traps.
IRQ 0, 0
IRQ 1, 0
IRQ 2, 0
IRQ 3, 0
IRQ 4, 0
IRQ 5, 0
IRQ 6, 0
IRQ 7, 0
IRQ 8, 1
IRQ 9, 0
IRQ 10, 1
IRQ 11, 1
IRQ 12, 1
IRQ 13, 1
IRQ 14, 1
IRQ 15, 0
IRQ 16, 0
IRQ 17, 1
; All subsequent interrupts (18 - 255) have no error code,
; therefore we use a loop calling the macro.
%assign i 18
%rep 238
IRQ i, 0
%assign i i+1
%endrep
[SECTION .data]
; Create a function pointer array for each interrupt entry
; (to be used in C++ for IDT::handle)
[GLOBAL interrupt_entry]
interrupt_entry:
%macro interrupt_vector 1
dq interrupt_entry_%1
%endmacro
%assign i 0
%rep 256
interrupt_vector i
%assign i i+1
%endrep

7
interrupt/handler.cc Normal file
View File

@ -0,0 +1,7 @@
#include "interrupt/handler.h"
extern "C" void interrupt_handler(Core::Interrupt::Vector vector, InterruptContext *context) {
(void) vector;
(void) context;
}

81
interrupt/handler.h Normal file
View File

@ -0,0 +1,81 @@
/*! \file
* \brief \ref interrupt_handler() Interrupt handler
*/
#pragma once
#include "types.h"
#include "machine/core_interrupt.h"
/*! \defgroup interrupts Interrupt Handling
* \brief The interrupt subsystem
*
* The interrupt subsystem of StubBS contains all functionality to accept
* interrupts from the hardware and process them.
* In later exercises the interrupts will enable applications to
* execute core functionality (system calls).
* The entry point for the interrupt subsystem is the function
* 'interrupt_entry_VECTOR' (in `interrupt/handler.asm`).
*/
/*! \brief Preserved interrupt context
*
* After an interrupt was triggered, the core first saves the basic context
* (current code- & stack segment, instruction & stack pointer and the status
* flags register) and looks up the handling function for the vector using the
* \ref IDT. In StuBS for each vector an own `interrupt_entry_VECTOR` function
* (written in assembly in `interrupt/handler.asm`) was registered during boot
* by \ref kernel_init(), which all save the scratch registers on the stack
* before calling the C++ function \ref interrupt_handler().
* The high-level handler gets a pointer to the part of the stack which
* corresponds to the \ref InterruptContext structure as second parameter.
* After returning from the high-level handler, the previous state is restored
* from this context (scratch register in assembly and basic context while
* executing `iret`) so it can continue transparently at the previous position.
*/
struct InterruptContext {
// Scratch register (stored by `interrupt/handler.asm`)
uintptr_t r11; ///< scratch register R11
uintptr_t r10; ///< scratch register R10
uintptr_t r9; ///< scratch register R9
uintptr_t r8; ///< scratch register R8
uintptr_t rdi; ///< scratch register RDI
uintptr_t rsi; ///< scratch register RSI
uintptr_t rdx; ///< scratch register RDX
uintptr_t rcx; ///< scratch register RCX
uintptr_t rax; ///< scratch register RAX
// Context saved by CPU
uintptr_t error_code; ///< Error Code
uintptr_t ip; ///< Instruction Pointer (at interrupt)
uintptr_t cs : 16; ///< Code segment (in case of a ring switch it is the segment of the user mode)
uintptr_t : 0; ///< Alignment (due to 16 bit code segment)
uintptr_t flags; ///< Status flags register
uintptr_t sp; ///< Stack pointer (at interrupt)
uintptr_t ss : 16; ///< Stack segment (in case of a ring switch it is the segment of the user mode)
uintptr_t : 0; ///< Alignment (due to 16 bit stack segment)
} __attribute__((packed));
/*! \brief High-Level Interrupt Handling.
* \ingroup interrupts
*
* Main interrupt handling routine of the system.
* This function is called by the corresponding `interrupt_entry_VECTOR`
* function (located in `interrupt/handler.asm`) with disabled interrupts.
*
* \param vector number of the interrupt
* \param context Pointer to interrupt context (on stack).
*
*/
extern "C" void interrupt_handler(Core::Interrupt::Vector vector, InterruptContext *context);
/*! \brief Array of function pointer to the default low-level interrupt handlers
*
* The index corresponds to the vectors entry function, e.g. `interrupt_entry[6]`
* points to `interrupt_entry_6`, handling the trap for
* \ref Core::Interrupt::INVALID_OPCODE "invalid opcode".
*
* The entry functions and this array are defined in assembly in
* `interrupt/handler.asm` and used in \ref kernel_init() to
* initialize the \ref IDT "Interrupt Descriptor Table (IDT)".
*/
extern "C" void * const interrupt_entry[];

139
machine/acpi.cc Normal file
View File

@ -0,0 +1,139 @@
#include "machine/acpi.h"
#include "debug/output.h"
namespace ACPI {
static RSDP* rsdp = 0;
static RSDT* rsdt = 0;
static XSDT* xsdt = 0;
const char * RSDP_SIGNATURE = "RSD PTR ";
static int checksum(const void *pos, unsigned len) {
const uint8_t *mem = reinterpret_cast<const uint8_t*>(pos);
uint8_t sum = 0;
for (unsigned i = 0; i < len; i++) {
sum += mem[i];
}
return sum;
}
static const RSDP* findRSDP(const void *pos, unsigned len) {
/* since the RSDP is 16-Byte aligned, we only need to check
every second 64bit memory block */
for (unsigned block = 0; block < len / 8; block += 2) {
const uint64_t *mem = reinterpret_cast<const uint64_t*>(pos) + block;
if (*mem == *reinterpret_cast<const uint64_t *>(RSDP_SIGNATURE)) {
const RSDP *rsdp = reinterpret_cast<const RSDP*>(mem);
/* ACPI Specification Revision 4.0a: 5.2.5.3*/
if ((rsdp->revision == 0 && checksum(mem, 20) == 0) ||
(rsdp->length > 20 && checksum(mem, rsdp->length) == 0)) {
return rsdp;
}
}
}
return 0;
}
bool init() {
/* ACPI Specification Revision 4.0a:
* 5.2.5.1 Finding the RSDP on IA-PC Systems
* OSPM finds the Root System Description Pointer (RSDP) structure by
* searching physical memory ranges on 16-byte boundaries for a valid
* Root System Description Pointer structure signature and checksum
* match as follows:
* * The first 1 KB of the Extended BIOS Data Area (EBDA). For EISA or
* MCA systems, the EBDA can be found in the two-byte location 40:0Eh
* on the BIOS data area.
* * The BIOS read-only memory space between 0E0000h and 0FFFFFh.
*/
volatile uintptr_t ebda_base_address = 0x40e;
const uintptr_t ebda = static_cast<uintptr_t>(*reinterpret_cast<uint32_t *>(ebda_base_address));
const RSDP *rsdp = findRSDP(reinterpret_cast<void*>(ebda), 1024);
if (rsdp == nullptr) {
rsdp = findRSDP(reinterpret_cast<void*>(0xe0000), 0xfffff-0xe0000);
}
if (rsdp == nullptr) {
DBG_VERBOSE << "No ACPI!" << endl;
return false;
}
rsdt = reinterpret_cast<RSDT*>(static_cast<uintptr_t>(rsdp->rsdtaddress));
/* If the XSDT is present we must use it; see:
* ACPI Specification Revision 4.0a:
* "An ACPI-compatible OS must use the XSDT if present."
*/
if (rsdp->revision != 0 && rsdp->length >= 36) {
xsdt = reinterpret_cast<XSDT*>(rsdp->xsdtaddress);
}
DBG_VERBOSE << "ACPI revision " << rsdp->revision << endl;
for (unsigned i = 0; i != count(); ++i) {
SDTH *sdt = get(i);
if (sdt != nullptr) {
char *c = reinterpret_cast<char*>(&sdt->signature);
DBG_VERBOSE << i << ". " << c[0] << c[1] << c[2] << c[3] << " @ " << reinterpret_cast<void*>(sdt) << endl;
}
}
return true;
}
unsigned count() {
if (xsdt != nullptr) {
return (xsdt->length-36)/8;
} else if (rsdt != nullptr) {
return (rsdt->length-36)/4;
} else {
return 0;
}
}
SDTH *get(unsigned num) {
if (xsdt != nullptr) {
SDTH *entry = reinterpret_cast<SDTH*>(xsdt->entries[num]);
if (checksum(entry, entry->length) == 0) {
return entry;
}
} else if (rsdt != nullptr) {
SDTH *entry = reinterpret_cast<SDTH*>(static_cast<uintptr_t>(rsdt->entries[num]));
if (checksum(entry, entry->length) == 0) {
return entry;
}
}
return 0;
}
SDTH *get(char a, char b, char c, char d) {
union {
char signature[4];
uint32_t value;
};
signature[0] = a;
signature[1] = b;
signature[2] = c;
signature[3] = d;
if (xsdt != nullptr) {
for (unsigned i = 0; i < count(); i++) {
SDTH *entry = reinterpret_cast<SDTH*>(xsdt->entries[i]);
if (entry->signature == value && checksum(entry, entry->length) == 0) {
return entry;
}
}
} else if (rsdt != nullptr) {
for (unsigned i = 0; i < count(); i++) {
SDTH *entry = reinterpret_cast<SDTH*>(static_cast<uintptr_t>(rsdt->entries[i]));
if (entry->signature == value && checksum(entry, entry->length) == 0) {
return entry;
}
}
}
return 0;
}
int revision() {
return rsdp != nullptr ? rsdp->revision : -1;
}
} // namespace ACPI

257
machine/acpi.h Normal file
View File

@ -0,0 +1,257 @@
/*! \file
* \brief Structs and methods related to the \ref ACPI "Advanced Configuration and Power Interface (ACPI)"
*/
#pragma once
#include "types.h"
/*! \brief Abstracts the ACPI standard that provides interfaces for hardware detection, device configuration,
* and energy management.
* \ingroup io
*
* ACPI is the successor to APM (Advanced Power Management), aiming to give the operating system more control
* over the hardware. This extended control, for instance, enables the operating system to assign a particular amount
* of energy to every device (e.g., by disabling a device or changing to standby mode).
* For this purpose, BIOS and chipset provide a set of tables that describe the system and its components and provide
* routines the OS can call.
* These tables contain details about the system, such as the number of CPU cores and the LAPIC/IOAPIC, which are
* determined during system boot.
*/
namespace ACPI {
/*! \brief Root System Description Pointer (RSDP)
*
* The first step to using ACPI is finding the RSDP that is used to find the RSDT / XSDT, which themselves
* contain pointers to even more tables.
*
* On UEFI systems, the RSDP can be found in the EFI_SYSTEM_TABLE; for non-UEFI systems we have to search for the
* signature 'RSD PTR ' in the EBDA (Extended Bios Data Area) or in the memory area up to `FFFFFh`.
*
* \see [ACPI-Specification 5.2.5.3; Root System Description Pointer (RSDP) Structure](acpi.pdf#page=161)
*/
struct RSDP {
char signature[8]; /* must exactly be equal to 'RSD PTR ' */
uint8_t checksum;
char oemid[6];
uint8_t revision; /* specifies the ACPI version */
uint32_t rsdtaddress; /* physical address of the RSDT */
uint32_t length;
uint64_t xsdtaddress; /* physical address of the XSDT */
uint8_t extended_checksum;
uint8_t reserved[3];
} __attribute__((packed));
/*! \brief System Description Table Header (SDTH)
*
* All System Description Tables (e.g., the RSDT) contain the same entries at the very beginning of
* the structure, which are abstracted in the SDTH.
*
* \see [ACPI-Specification 5.2.6; System Description Table Header](acpi.pdf#page=162)
*/
struct SDTH {
uint32_t signature; /* table id */
uint32_t length;
uint8_t revision;
uint8_t checksum;
char oemid[6];
char oem_table_id[8];
uint32_t oem_revision;
uint32_t creator_id;
uint32_t creator_revision;
/* \brief Helper method
* \return Pointer to the end of the table
*/
void *end() {
return reinterpret_cast<uint8_t*>(this)+length;
}
} __attribute__((packed));
/*! \brief Root System Description Table (RSDT)
*
* The RSDT can be found in the RSDP. The RSDT contains physical addresses of all other System Description Tables,
* for example the MADT.
*
* \see [ACPI-Specification 5.2.7; Root System Description Table (RSDT)](acpi.pdf#page=167)
*/
struct RSDT : SDTH {
uint32_t entries[];
} __attribute__((packed));
/*! \brief Extended System Description Table (XSDT)
*
* Like RSDT, but contains 64-bit instead of 32-bit addresses.
*
* \see [ACPI-Specification 5.2.8; Extended System Description Table (XSDT)](acpi.pdf#page=168)
*/
struct XSDT : SDTH {
uint64_t entries[];
} __attribute__((packed));
/*! \brief Helper structure
*
* Is used for accessing the substructures present in SRAT / MADT.
*
*/
struct SubHeader {
uint8_t type;
uint8_t length;
/* Method to traverse multiple substructures */
SubHeader *next() {
return reinterpret_cast<SubHeader*>(reinterpret_cast<uint8_t*>(this)+length);
}
} __attribute__((packed));
/*! \brief Multiple APIC Description Table (MADT)
*
* Describes all interrupt controllers present within the system. Is used to obtain the IDs of the APICs, along with
* the number of available processor cores.
*
* \see [ACPI-Specification 5.2.12; Multiple APIC Description Table (MADT)](acpi.pdf#page=193)
*/
struct MADT : SDTH {
uint32_t local_apic_address;
uint32_t flags_pcat_compat:1,
flags_reserved:31;
/* method to access the first subheader */
SubHeader *first() {
return reinterpret_cast<SubHeader*>(reinterpret_cast<uint8_t*>(this)+sizeof(MADT));
}
} __attribute__((packed));
enum class AddressSpace : uint8_t {
MEMORY = 0x0,
IO = 0x1,
};
/*! \brief ACPI address format
*
* The ACPI standard defines its own address format that is able to handle addresses both in memory address space,
* as well as IO-port address space.
*/
struct Address {
AddressSpace address_space;
uint8_t register_bit_width;
uint8_t register_bit_offset;
uint8_t reserved;
uint64_t address;
} __attribute__((packed));
// Multiple APIC Definition Structure
namespace MADS {
enum Type {
Type_LAPIC = 0,
Type_IOAPIC = 1,
Type_Interrupt_Source_Override = 2,
Type_LAPIC_Address_Override = 5,
};
/*! \brief Processor Local APIC (LAPIC) Structure
*
* Represents a physical processor along with its local interrupt controller.
* The MADT contains a LAPIC structure for every processor available in the system.
*
* \see [ACPI-Specification 5.2.12.2; Processor Local APIC Structure](acpi.pdf#page=195)
*/
struct LAPIC : SubHeader {
uint8_t acpi_processor_id;
uint8_t apic_id;
uint32_t flags_enabled : 1,
flags_reserved : 31; /* must be 0 */
} __attribute__((packed));
/*! \brief I/O APIC Structure
*
* Represents an I/O-APIC.
* The MADT contains an IOAPIC structure for every I/O APIC present in the system.
*
* \see [ACPI-Specification 5.2.12.3; I/O APIC Structure](acpi.pdf#page=196)
*/
struct IOAPIC : SubHeader {
uint8_t ioapic_id;
uint8_t reserved;
uint32_t ioapic_address;
uint32_t global_system_interrupt_base;
} __attribute__((packed));
/*! \brief Interrupt Source Override Structure
*
* Is required to describe differences between the IA-PC standard interrupt definition and the actual
* hardware implementation.
*
* \see [ACPI-Specification 5.2.12.5; Interrupt Source Override Structure](acpi.pdf#page=197)
*/
struct Interrupt_Source_Override : SubHeader {
uint8_t bus;
uint8_t source;
uint32_t global_system_interrupt;
uint16_t flags_polarity : 2,
flags_trigger_mode : 2,
flags_reserved : 12; /* must be 0 */
} __attribute__((packed));
/*! \brief Local APIC Address Override Structure
*
* Support for 64-bit systems is achieved by replacing the 32-bit physical LAPIC address stored in the MADT
* with the corresponding 64-bit address.
*
* \see [ACPI-Specification 5.2.12.8; Local APIC Address Override Structure](acpi.pdf#page=199)
*/
struct LAPIC_Address_Override : SubHeader {
uint16_t reserved;
union {
uint64_t lapic_address;
struct {
uint32_t lapic_address_low;
uint32_t lapic_address_high;
} __attribute__((packed));
};
} __attribute__((packed));
} // namespace MADS
/*! \brief Initialize the ACPI description table
*
* Searches physical memory ranges o 16-byte boundaries for a valid Root System Description Pointer (RSDP)
* structure signature and checksum.
* If present, the superseding Extended System Description Table (XSDT) is used.
*
* \see [ACPI-Specification 5.2.5 Root System Description Pointer (RSDP)](acpi.pdf#page=160)
* \see [ACPI-Specification 5.2.8 Extended System Description Table (XSDT)](acpi.pdf#page=168)
*/
bool init();
/*! \brief Number of entries in the description table
*/
unsigned count();
/*! \brief Get entry of description table by index
*
* \param num index in description table
* \return Pointer to corresponding entry or `nullptr` if not available
*/
SDTH *get(unsigned num);
/*! \brief Get entry of description table by four character identifier
*
* \param a first character of identifier
* \param b second character of identifier
* \param c third character of identifier
* \param d forth and last character of identifier
* \return Pointer to corresponding entry or `nullptr` if not available
*/
SDTH *get(char a, char b, char c, char d);
/*! \brief Retrieve the revision from the Root System Description Pointer (RSDP)
*/
int revision();
} // namespace ACPI

143
machine/apic.cc Normal file
View File

@ -0,0 +1,143 @@
#include "machine/apic.h"
#include "machine/acpi.h"
#include "machine/core.h"
#include "machine/cmos.h"
#include "machine/ioport.h"
#include "machine/lapic.h"
#include "machine/lapic_registers.h"
#include "utils/string.h"
#include "debug/assert.h"
#include "debug/output.h"
namespace APIC {
static struct {
uint32_t id;
uintptr_t address;
uint32_t interrupt_base;
} ioapic;
static uint8_t slot_map[16];
static uint8_t lapic_id[Core::MAX];
static unsigned lapics = 0;
bool init() {
// get Multiple APIC Definition Table (MADT) from ACPI
ACPI::MADT *madt = static_cast<ACPI::MADT*>(ACPI::get('A', 'P', 'I', 'C'));
if(madt == 0) {
DBG_VERBOSE << "ERROR: no MADT found in ACPI" << endl;
return false;
}
// read the local APIC address
LAPIC::base_address = static_cast<uintptr_t>(madt->local_apic_address);
DBG_VERBOSE << "LAPIC Address "
<< reinterpret_cast<void*>(static_cast<uintptr_t>(madt->local_apic_address)) << endl;
// PC/AT compatibility mode
if (madt->flags_pcat_compat != 0) {
// The APIC operating mode is set to compatible PIC mode - we have to change it.
DBG_VERBOSE << "PIC comp mode, disabling PICs." << endl;
// Select Interrupt Mode Control Register (IMCR)
// (this register will only exist if hardware supports the PIC mode)
IOPort reg(0x22);
reg.outb(0x70);
// disable PIC mode, use APIC
IOPort imcr(0x23);
imcr.outb(0x01);
}
// Set default mapping of external interrupt slots (might be overwritten below)
for (unsigned i = 0; i < sizeof(slot_map)/sizeof(slot_map[0]); i++) {
slot_map[i] = i;
}
// Initialize invalid lapic_ids
for (unsigned i = 0; i < Core::MAX; i++) {
lapic_id[i] = INVALID_ID;
}
// reset numbers, store apic data into arrays
for (ACPI::SubHeader *mads = madt->first(); mads < madt->end(); mads = mads->next()) {
switch(mads->type) {
case ACPI::MADS::Type_LAPIC:
{
ACPI::MADS::LAPIC* mads_lapic = static_cast<ACPI::MADS::LAPIC*>(mads);
if (mads_lapic->flags_enabled == 0) {
DBG_VERBOSE << "Detected disabled LAPIC with ID " << static_cast<unsigned>(mads_lapic->apic_id) << endl;
} else if (lapics >= Core::MAX) {
DBG_VERBOSE << "Got more LAPICs than Core::MAX" << endl;
} else if (mads_lapic->apic_id == INVALID_ID) {
DBG_VERBOSE << "Got invalid APIC ID" << endl;
} else {
DBG_VERBOSE << "Detected LAPIC with ID " << static_cast<unsigned>(mads_lapic->apic_id) << endl;
lapic_id[lapics++] = mads_lapic->apic_id;
}
break;
}
case ACPI::MADS::Type_IOAPIC:
{
ACPI::MADS::IOAPIC* mads_ioapic = static_cast<ACPI::MADS::IOAPIC*>(mads);
DBG_VERBOSE << "Detected IO APIC with ID " << static_cast<unsigned>(mads_ioapic->ioapic_id) << " / Base "
<< reinterpret_cast<void*>(static_cast<uintptr_t>(mads_ioapic->global_system_interrupt_base))
<< endl;
if (mads_ioapic->global_system_interrupt_base > 23) {
DBG_VERBOSE << "Ignoring IOAPIC since we currently only support one." << endl;
} else {
ioapic.id = mads_ioapic->ioapic_id;
ioapic.address = static_cast<uintptr_t>(mads_ioapic->ioapic_address);
ioapic.interrupt_base = mads_ioapic->global_system_interrupt_base;
}
break;
}
case ACPI::MADS::Type_Interrupt_Source_Override:
{
ACPI::MADS::Interrupt_Source_Override* mads_iso = static_cast<ACPI::MADS::Interrupt_Source_Override*>(mads);
if (mads_iso->bus == 0) {
DBG_VERBOSE << "Overriding Interrupt Source " << static_cast<unsigned>(mads_iso->source)
<< " with " << mads_iso->global_system_interrupt << endl;
if (mads_iso->source < sizeof(slot_map)/sizeof(slot_map[0])) {
slot_map[mads_iso->source] = mads_iso->global_system_interrupt;
}
} else {
DBG_VERBOSE << "Override for bus " << mads_iso->bus << " != ISA. Does not conform to ACPI." << endl;
}
break;
}
case ACPI::MADS::Type_LAPIC_Address_Override:
{
ACPI::MADS::LAPIC_Address_Override* mads_lao = static_cast<ACPI::MADS::LAPIC_Address_Override*>(mads);
LAPIC::base_address = static_cast<uintptr_t>(mads_lao->lapic_address_low);
DBG_VERBOSE << "Overriding LAPIC address with "
<< reinterpret_cast<void*>(static_cast<uintptr_t>(mads_lao->lapic_address)) << endl;
break;
}
}
}
return true;
}
uint8_t getIOAPICSlot(APIC::Device device) {
return slot_map[device];
}
uintptr_t getIOAPICAddress() {
return ioapic.address;
}
uint8_t getIOAPICID() {
return ioapic.id;
}
uint8_t getLogicalAPICID(uint8_t core) {
return core < Core::MAX ? (1 << core) : 0;
}
uint8_t getLAPICID(uint8_t core) {
assert(core < Core::MAX);
return lapic_id[core];
}
} // namespace APIC

80
machine/apic.h Normal file
View File

@ -0,0 +1,80 @@
/*! \file
* \brief Gather system information from the \ref ACPI about the \ref APIC "Advanced Programmable Interrupt Controller (APIC)"
*/
#pragma once
#include "types.h"
/*! \brief Information about the (extended) Advanced Programmable Interrupt Controller
*/
namespace APIC {
/*! \brief Historic order of interrupt lines (PIC)
*/
enum Device {
TIMER = 0, ///< Programmable Interrupt Timer (\ref PIT)
KEYBOARD = 1, ///< Keyboard
COM1 = 4, ///< First serial interface
COM2 = 3, ///< Second serial interface
COM3 = 4, ///< Third serial interface (shared with COM1)
COM4 = 3, ///< Forth serial interface (shared with COM2)
FLOPPY = 6, ///< Floppy device
LPT1 = 7, ///< Printer
REALTIMECLOCK = 8, ///< Real time clock
PS2MOUSE = 12, ///< Mouse
IDE1 = 14, ///< First hard disk
IDE2 = 15 ///< Second hard disk
};
/*! \brief Invalid APIC ID
*
* The highest address is reserved according to xAPIC specification
*/
const uint8_t INVALID_ID = 0xff;
/*! \brief Executes system detection
*
* Searches and evaluates the APIC entries in the \ref ACPI table.
* This function recognizes a possibly existing multicore system.
* After successful detection, the number of available CPUs (which is equal
* to the number of \ref LAPIC "local APICs") ) can be queried
* using the method \ref Core::count().
*
* \note Called by \ref kernel_init() on BSP
*
* \return `true` if detection of the APIC entries was successful
*/
bool init();
/*! \brief Queries the physical I/O-APIC address determined during system boot
*
* \return Base address of the (first & only supported) I/O APIC
*/
uintptr_t getIOAPICAddress();
/*! \brief Queries of ID of the I/O-APIC determined during system boot
*
* \return Identification of the (first & only supported) I/O APIC
*/
uint8_t getIOAPICID();
/*! \brief Returns the pin number the \p device is connected to.
*/
uint8_t getIOAPICSlot(APIC::Device device);
/*! \brief Returns the logical ID of the Local APIC passed for \a core.
*
* The LAPIC's logical ID is set (by StuBS) during boot such that exactly one bit is set per CPU core.
* For core 0, bit 0 is set in its ID, while core 1 has bit 1 set, etc.
*
* \param core The queried CPU core
*/
uint8_t getLogicalAPICID(uint8_t core);
/*! \brief Get the Local APIC ID of a CPU
* \param core Query CPU core number
* \return LAPIC ID of CPU or INVALID_ID if invalid CPU ID
*/
uint8_t getLAPICID(uint8_t core);
} // namespace APIC

61
machine/cmos.cc Normal file
View File

@ -0,0 +1,61 @@
#include "machine/cmos.h"
#include "machine/core.h"
#include "machine/ioport.h"
namespace CMOS {
static IOPort address(0x70);
static IOPort data(0x71);
namespace NMI {
static const uint8_t mask = 0x80;
// Cache NMI to speed things up
static bool disabled = false;
void enable() {
bool status = Core::Interrupt::disable();
uint8_t value = address.inb();
value &= ~mask;
address.outb(value);
Core::Interrupt::restore(status);
disabled = false;
}
void disable() {
bool status = Core::Interrupt::disable();
uint8_t value = address.inb();
value |= mask;
address.outb(value);
Core::Interrupt::restore(status);
disabled = true;
}
bool isEnabled() {
disabled = (address.inb() & mask) != 0;
return !disabled;
}
} // namespace NMI
static void setAddress(enum Register reg) {
uint8_t value = reg;
// The highest bit controls the Non Maskable Interrupt
// so we don't want to accidentally change it.
if (NMI::disabled) {
value |= NMI::mask;
} else {
value &= ~NMI::mask;
}
address.outb(value);
}
uint8_t read(enum Register reg) {
setAddress(reg);
return data.inb();
}
void write(enum Register reg, uint8_t value) {
setAddress(reg);
data.outb(value);
}
} // namespace CMOS

41
machine/cmos.h Normal file
View File

@ -0,0 +1,41 @@
/*! \file
* \brief Controlling the \ref CMOS "complementary metal oxide semiconductor (CMOS)"
*/
#pragma once
#include "types.h"
/*! \brief CMOS
* \ingroup CMOS
*/
namespace CMOS {
enum Register {
REG_SECOND = 0x0, ///< RTC
REG_ALARM_SECOND = 0x1, ///< RTC
REG_MINUTE = 0x2, ///< RTC
REG_ALARM_MINUTE = 0x3, ///< RTC
REG_HOUR = 0x4, ///< RTC
REG_ALARM_HOUR = 0x5, ///< RTC
REG_WEEKDAY = 0x6, ///< RTC
REG_DAYOFMONTH = 0x7, ///< RTC
REG_MONTH = 0x8, ///< RTC
REG_YEAR = 0x9, ///< RTC
REG_STATUS_A = 0xa, ///< RTC
REG_STATUS_B = 0xb, ///< RTC
REG_STATUS_C = 0xc, ///< RTC
REG_STATUS_D = 0xd, ///< RTC
REG_STATUS_DIAGNOSE = 0xe,
REG_STATUS_SHUTDOWN = 0xf
};
uint8_t read(enum Register reg);
void write(enum Register reg, uint8_t value);
namespace NMI {
void enable();
void disable();
bool isEnabled();
} // namespace NMI
} // namespace CMOS

75
machine/core.cc Normal file
View File

@ -0,0 +1,75 @@
#include "machine/core.h"
#include "machine/apic.h"
#include "machine/lapic.h"
/*! \brief Initial size of CPU core stacks
*
* Used during startup in `boot/startup.asm`
*/
extern "C" const unsigned long CPU_CORE_STACK_SIZE = 4096;
/*! \brief Reserved memory for CPU core stacks
*/
alignas(16) static unsigned char cpu_core_stack[Core::MAX * CPU_CORE_STACK_SIZE];
/*! \brief Pointer to stack memory
*
* Incremented during startup of each core (bootstrap and application processors) in `boot/startup.asm`
*/
unsigned char * cpu_core_stack_pointer = cpu_core_stack;
namespace Core {
static unsigned cores = 0; ///< Number of available CPU cores
static unsigned core_id[255]; ///< Lookup table for CPU core IDs with LAPIC ID as index
static unsigned online_cores = 0; ///< Number of currently online CPU cores
static bool online_core[Core::MAX]; ///< Lookup table for online CPU cores with CPU core ID as index
void init() {
// Increment number of online CPU cores
if (__atomic_fetch_add(&online_cores, 1, __ATOMIC_RELAXED) == 0) {
// Fill Lookup table
for (unsigned i = 0; i < Core::MAX; i++) {
uint8_t lapic_id = APIC::getLAPICID(i);
if (lapic_id < APIC::INVALID_ID) { // ignore invalid LAPICs
core_id[lapic_id] = i;
cores++;
}
}
}
// Get CPU ID
uint8_t cpu = getID();
// initialize local APIC with logical APIC ID
LAPIC::init(APIC::getLogicalAPICID(cpu));
// set current CPU online
online_core[cpu] = true;
}
void exit() {
// CPU core offline
online_core[getID()] = false;
__atomic_fetch_sub(&online_cores, 1, __ATOMIC_RELAXED);
}
unsigned getID() {
return core_id[LAPIC::getID()];
}
unsigned count() {
return cores;
}
unsigned countOnline() {
return online_cores;
}
bool isOnline(uint8_t core_id) {
return core_id > Core::MAX ? false : online_core[core_id];
}
} // namespace Core

112
machine/core.h Normal file
View File

@ -0,0 +1,112 @@
/*! \file
* \brief Access to internals of a CPU \ref Core
*/
/*! \defgroup sync CPU Synchronization
*
* The synchronization module houses functions useful for orchestrating multiple processors and their activities.
* Synchronisation, in this case, means handling the resource contention between multiple participants, running on
* either the same or different cores.
*/
#pragma once
#include "types.h"
#include "machine/core_cr.h"
#include "machine/core_interrupt.h"
#include "machine/core_msr.h"
/*! \brief Implements an abstraction for CPU internals.
*
* These internals include functions to \ref Core::Interrupt "allow or deny interrupts",
* access \ref Core::CR "control registers".
*/
namespace Core {
/*! \brief Maximum number of supported CPUs
*/
const unsigned MAX = 8;
/*! \brief Get the ID of the current CPU core
* using \ref LAPIC::getID() with an internal lookup table.
*
* \return ID of current Core (a number between 0 and \ref Core::MAX)
*/
unsigned getID();
/*! \brief Initialize this CPU core
*
* Mark this core as *online* and setup the cores \ref LAPIC by assigning it a
* unique \ref APIC::getLogicalAPICID() "logical APIC ID"
*
* \note Should only be called from \ref kernel_init() during startup.
*/
void init();
/*! \brief Deinitialize this CPU core
*
* Mark this Core as *offline*
*
* \note Should only be called from \ref kernel_init() after returning from `main()` or `main_ap()`.
*/
void exit();
/*! \brief Get number of available CPU cores
*
* \return total number of cores
*/
unsigned count();
/*! \brief Get number of successfully started (and currently active) CPU cores
*
* \return total number of online cores
*/
unsigned countOnline();
/*! \brief Check if CPU core is currently active
* \param core_id ID of the CPU core
* \return `true` if successfully started and is currently active
*/
bool isOnline(uint8_t core_id);
/*! \brief Gives the core a hint that it is executing a spinloop and should sleep "shortly"
*
* Improves the over-all performance when executing a spinloop by waiting a short moment reduce
* the load on the memory.
*
* \see [ISDMv2, Chapter 4. PAUSE - Spin Loop Hint](intel_manual_vol2.pdf#page=887)
*/
inline void pause() {
asm volatile("pause\n\t" : : : "memory");
}
/*! \brief Halt the CPU core until the next interrupt.
*
* Halts the current CPU core such that it will wake up on the next interrupt. Internally, this function first enables
* the interrupts via `sti` and then halts the core using `hlt`. Halted cores can only be woken by interrupts.
* The effect of `sti` is delayed by one instruction, making the sequence `sti hlt` atomic (if interrupts were
* disabled previously).
*
* \see [ISDMv2, Chapter 4. STI - Set Interrupt Flag](intel_manual_vol2.pdf#page=1297)
* \see [ISDMv2, Chapter 3. HLT - Halt](intel_manual_vol2.pdf#page=539)
*/
inline void idle() {
asm volatile("sti\n\t hlt\n\t" : : : "memory");
}
/*! \brief Permanently halts the core.
*
* Permanently halts the current CPU core. Internally, this function first disables the interrupts via `cli` and
* then halts the CPU core using `hlt`. As halted CPU cores can only be woken by interrupts, it is guaranteed that
* this core will be halted until the next reboot. The execution of die never returns.
* On multicore systems, only the executing CPU core will be halted permanently, other cores will continue execution.
*
* \see [ISDMv2, Chapter 3. CLI - Clear Interrupt Flag](intel_manual_vol2.pdf#page=245)
* \see [ISDMv2, Chapter 3. HLT - Halt](intel_manual_vol2.pdf#page=539)
*/
[[noreturn]] inline void die() {
while (true) {
asm volatile("cli\n\t hlt\n\t" : : : "memory");
}
}
} // namespace Core

81
machine/core_cr.h Normal file
View File

@ -0,0 +1,81 @@
/*! \file
* \brief Access to \ref Core::CR "Control Register" of a \ref Core "CPU core"
*/
#pragma once
#include "types.h"
namespace Core {
/*! \brief Control Register 0
*
* \see [ISDMv3, 2.5 Control Registers](intel_manual_vol3.pdf#page=74)
*/
enum CR0 : uintptr_t {
CR0_PE = 1U << 0, ///< Protected Mode enabled
CR0_MP = 1U << 1, ///< Monitor co-processor
CR0_EM = 1U << 2, ///< Emulation (no x87 floating-point unit present)
CR0_TS = 1U << 3, ///< Task switched
CR0_ET = 1U << 4, ///< Extension type
CR0_NE = 1U << 5, ///< Numeric error
CR0_WP = 1U << 16, ///< Write protect
CR0_AM = 1U << 18, ///< Alignment mask
CR0_NW = 1U << 29, ///< Not-write through caching
CR0_CD = 1U << 30, ///< Cache disable
CR0_PG = 1U << 31, ///< Paging
};
/*! \brief Control Register 4
*
* \see [ISDMv3, 2.5 Control Registers](intel_manual_vol3.pdf#page=77)
*/
enum CR4 : uintptr_t {
CR4_VME = 1U << 0, ///< Virtual 8086 Mode Extensions
CR4_PVI = 1U << 1, ///< Protected-mode Virtual Interrupts
CR4_TSD = 1U << 2, ///< Time Stamp Disable
CR4_DE = 1U << 3, ///< Debugging Extensions
CR4_PSE = 1U << 4, ///< Page Size Extension
CR4_PAE = 1U << 5, ///< Physical Address Extension
CR4_MCE = 1U << 6, ///< Machine Check Exception
CR4_PGE = 1U << 7, ///< Page Global Enabled
CR4_PCE = 1U << 8, ///< Performance-Monitoring Counter enable
CR4_OSFXSR = 1U << 9, ///< Operating system support for FXSAVE and FXRSTOR instructions
CR4_OSXMMEXCPT = 1U << 10, ///< Operating System Support for Unmasked SIMD Floating-Point Exceptions
CR4_UMIP = 1U << 11, ///< User-Mode Instruction Prevention
CR4_VMXE = 1U << 13, ///< Virtual Machine Extensions Enable
CR4_SMXE = 1U << 14, ///< Safer Mode Extensions Enable
CR4_FSGSBASE = 1U << 16, ///< Enables the instructions RDFSBASE, RDGSBASE, WRFSBASE, and WRGSBASE.
CR4_PCIDE = 1U << 17, ///< PCID Enable
CR4_OSXSAVE = 1U << 18, ///< XSAVE and Processor Extended States Enable
CR4_SMEP = 1U << 20, ///< Supervisor Mode Execution Protection Enable
CR4_SMAP = 1U << 21, ///< Supervisor Mode Access Prevention Enable
CR4_PKE = 1U << 22, ///< Protection Key Enable
};
/*! \brief Access to the Control Register
*
* \see [ISDMv3, 2.5 Control Registers](intel_manual_vol3.pdf#page=73)
* \tparam id Control Register to access
*/
template<uint8_t id>
class CR {
public:
/*! \brief Read the value of the current Control Register
*
* \return Value stored in the CR
*/
inline static uintptr_t read(void) {
uintptr_t val;
asm volatile("mov %%cr%c1, %0" : "=r"(val) : "n"(id) : "memory");
return val;
}
/*! \brief Write a value into the current Control Register
*
* \param value Value to write into the CR
*/
inline static void write(uintptr_t value) {
asm volatile("mov %0, %%cr%c1" : : "r"(value), "n"(id) : "memory");
}
};
} // namespace Core

130
machine/core_interrupt.h Normal file
View File

@ -0,0 +1,130 @@
/*! \file
* \brief \ref Core::Interrupt "Interrupt control" and \ref Core::Interrupt::Vector "interrupt vector list"
*/
#pragma once
#include "types.h"
namespace Core {
/*! \brief Exception and Interrupt control
*
* \see [ISDMv3, Chapter 6 Interrupt and Exception Handling](intel_manual_vol3.pdf#page=185)
*/
namespace Interrupt {
/*! \brief Bit in `FLAGS` register corresponding to the current interrupt state
*/
const uintptr_t FLAG_ENABLE = 1 << 9;
/*! \brief List of used interrupt vectors.
*
* The exception vectors from `0` to `31` are reserved for traps, faults and aborts.
* Their behavior is different for each exception, some push an *error code*,
* some are not recoverable.
*
* The vectors from `32` to `255` are user defined interrupts.
*
* \see [ISDMv3, 6.15 Exception and Interrupt Reference](intel_manual_vol3.pdf#page=203)
*/
enum Vector {
// Predefined Exceptions
DIVISON_BY_ZERO = 0, ///< Divide-by-zero Error (at a `DIV`/`IDIV` instruction)
DEBUG = 1, ///< Debug exception
NON_MASKABLE_INTERRUPT = 2, ///< Non Maskable Interrupt
BREAKPOINT = 3, ///< Breakpoint exception (used for debugging)
OVERFLOW = 4, ///< Overflow exception (at `INTO` instruction)
BOUND_RANGE_EXCEEDED = 5, ///< Bound Range Exceeded (at `BOUND` instruction)
INVALID_OPCODE = 6, ///< Opcode at Instruction Pointer is invalid (you probably shouldn't be here)
DEVICE_NOT_AVAILABLE = 7, ///< FPU "FPU/MMX/SSE" instruction but corresponding extension not activate
DOUBLE_FAULT = 8, ///< Exception occurred while trying to call exception/interrupt handler
// Coprocessor Segment Overrun (Legacy)
INVALID_TSS = 10, ///< Invalid Task State Segment selector (see error code for index)
SEGMENT_NOT_PRESENT = 11, ///< Segment not available (see error code for selector index)
STACK_SEGMENT_FAULT = 12, ///< Stack segment not available or invalid (see error code for selector index)
GENERAL_PROTECTION_FAULT = 13, ///< Operation not allowed (see error code for selector index)
PAGE_FAULT = 14, ///< Operation on Page (r/w/x) not allowed for current privilege (error code + `cr2`)
// reserved (15)
FLOATING_POINT_EXCEPTION = 16, ///< x87 FPU error (at `WAIT`/`FWAIT`), accidentally \ref Core::CR0_NE set?
ALIGNMENT_CHECK = 17, ///< Unaligned memory access in userspace (Exception activated by \ref Core::CR0_AM)
MACHINE_CHECK = 18, ///< Model specific exception
SIMD_FP_EXCEPTION = 19, ///< SSE/MMX error (if \ref Core::CR4_OSXMMEXCPT activated)
// reserved (20 - 31)
EXCEPTIONS = 32, ///< Number of exceptions
// Interrupts
TIMER = 32, ///< Periodic CPU local \ref LAPIC::Timer interrupt
KEYBOARD = 33, ///< Keyboard interrupt (key press / release)
GDB = 35, ///< Inter-processor interrupt to stop other CPUs for debugging in \ref GDB
ASSASSIN = 100, ///< Inter-processor interrupt to immediately stop threads running on other CPUs
WAKEUP = 101, ///< Inter-processor interrupt to WakeUp sleeping CPUs
VECTORS = 256 ///< Number of interrupt vectors
};
/*! \brief Check if interrupts are enabled on this CPU
*
* This is done by pushing the `FLAGS` register onto stack,
* reading it into a register and checking the corresponding bit.
*
* \return `true` if enabled, `false` if disabled
*/
inline bool isEnabled() {
uintptr_t out;
asm volatile (
"pushf\n\t"
"pop %0\n\t"
: "=r"(out)
:
: "memory"
);
return (out & FLAG_ENABLE) != 0;
}
/*! \brief Allow interrupts
*
* Enables interrupt handling by executing the instruction `sti`.
* Since this instruction is delayed by one cycle, an subsequent `nop` is executed
* (to ensure deterministic behavior, independent from the compiler generated code)
*
* A pending interrupt (i.e., those arriving while interrupts were disabled) will
* be delivered after re-enabling interrupts.
*
* \see [ISDMv2, Chapter 4. STI - Set Interrupt Flag](intel_manual_vol2.pdf#page=1297)
*/
inline void enable() {
asm volatile("sti\n\t nop\n\t" : : : "memory");
}
/*! \brief Forbid interrupts
*
* Prevents interrupt handling by executing the instruction `cli`.
* Will return the previous interrupt state.
* \return `true` if interrupts were enabled at the time of executing this function,
* `false` if they were already disabled.
*
* \see [ISDMv2, Chapter 3. CLI - Clear Interrupt Flag](intel_manual_vol2.pdf#page=245)
*/
inline bool disable() {
bool enabled = isEnabled();
asm volatile ("cli\n\t" : : : "memory");
return enabled;
}
/*! \brief Restore interrupt
*
* Restore the interrupt state to the state prior to calling \ref disable() by using its return value.
*
* \note This function will never disable interrupts, even if val is false!
* This function is designed to allow nested disabling and restoring of the interrupt state.
*
* \param val if set to `true`, interrupts will be enabled; nothing will happen on false.
*/
inline void restore(bool val) {
if (val) {
enable();
}
}
} // namespace Interrupt
} // namespace Core

91
machine/core_msr.h Normal file
View File

@ -0,0 +1,91 @@
/*! \file
* \brief \ref Core::MSRs "Identifiers" for \ref Core::MSR "Model-Specific Register"
*/
#pragma once
namespace Core {
/*! \brief Model-Specific Register Identifiers
*
* Selection of useful identifiers.
*
* \see [ISDMv4](intel_manual_vol4.pdf)
*/
enum MSRs : uint32_t {
MSR_PLATFORM_INFO = 0xceU, ///< Platform information including bus frequency (Intel)
MSR_TSC_DEADLINE = 0x6e0U, ///< Register for \ref LAPIC::Timer Deadline mode
// Fast system calls
MSR_EFER = 0xC0000080U, ///< Extended Feature Enable Register, \see Core::MSR_EFER
MSR_STAR = 0xC0000081U, ///< eip (protected mode), ring 0 and 3 segment bases
MSR_LSTAR = 0xC0000082U, ///< rip (long mode)
MSR_SFMASK = 0xC0000084U, ///< lower 32 bit: flag mask, if bit is set corresponding rflag is cleared through syscall
// Core local variables
MSR_FS_BASE = 0xC0000100U,
MSR_GS_BASE = 0xC0000101U, ///< Current GS base pointer
MSR_SHADOW_GS_BASE = 0xC0000102U, ///< Usually called `MSR_KERNEL_GS_BASE` but this is misleading
};
/* \brief Important bits in Extended Feature Enable Register (EFER)
*
* \see [ISDMv3, 2.2.1 Extended Feature Enable Register](intel_manual_vol3.pdf#page=69)
* \see [AAPMv2, 3.1.7 Extended Feature Enable Register](amd64_manual_vol2.pdf#page=107)
*/
enum MSR_EFER : uintptr_t {
MSR_EFER_SCE = 1U << 0, ///< System Call Extensions
MSR_EFER_LME = 1U << 8, ///< Long mode enable
MSR_EFER_LMA = 1U << 10, ///< Long mode active
MSR_EFER_NXE = 1U << 11, ///< No-Execute Enable
MSR_EFER_SVME = 1U << 12, ///< Secure Virtual Machine Enable
MSR_EFER_LMSLE = 1U << 13, ///< Long Mode Segment Limit Enable
MSR_EFER_FFXSR = 1U << 14, ///< Fast `FXSAVE`/`FXRSTOR` instruction
MSR_EFER_TCE = 1U << 15, ///< Translation Cache Extension
};
/*! \brief Access to the Model-Specific Register (MSR)
*
* \see [ISDMv3, 9.4 Model-Specific Registers (MSRs)](intel_manual_vol3.pdf#page=319)
* \see [ISDMv4](intel_manual_vol4.pdf)
* \tparam id ID of the Model-Specific Register to access
*/
template<enum MSRs id>
class MSR {
/*! \brief Helper to access low and high bits of a 64 bit value
* \internal
*/
union uint64_parts {
struct {
uint32_t low;
uint32_t high;
} __attribute__((packed));
uint64_t value;
explicit uint64_parts(uint64_t value = 0) : value(value) {}
};
public:
/*! \brief Read the value of the current MSR
*
* \return Value stored in the MSR
*
* \see [ISDMv2, Chapter 4. RDMSR - Read from Model Specific Register](intel_manual_vol2.pdf#page=1186)
*/
static inline uint64_t read() {
uint64_parts p;
asm volatile ("rdmsr \n\t" : "=a"(p.low), "=d"(p.high) : "c"(id));
return p.value;
}
/*! \brief Write a value into the current MSR
*
* \param value Value to write into the MSR
*
* \see [ISDMv2, Chapter 5. WRMSR - Write to Model Specific Register](intel_manual_vol2.pdf#page=1912)
*/
static inline void write(uint64_t value) {
uint64_parts p(value);
asm volatile ("wrmsr \n\t" : : "c"(id), "a"(p.low), "d"(p.high));
}
};
} // namespace Core

53
machine/gdt.cc Normal file
View File

@ -0,0 +1,53 @@
#include "machine/gdt.h"
#include "machine/core.h"
#include "debug/assert.h"
#include "debug/output.h"
namespace GDT {
// The static 32-bit Global Descriptor Table (GDT)
alignas(16) static SegmentDescriptor protected_mode[] = {
// NULL descriptor
{},
// Global code segment von 0-4GB
{ /* base = */ 0x0,
/* limit = */ 0xFFFFFFFF,
/* code = */ true,
/* ring = */ 0,
/* size = */ SIZE_32BIT },
// Global data segment von 0-4GB
{ /* base = */ 0x0,
/* limit = */ 0xFFFFFFFF,
/* code = */ false,
/* ring = */ 0,
/* size = */ SIZE_32BIT },
};
extern "C" constexpr Pointer gdt_protected_mode_pointer(protected_mode);
// The static 64-bit Global Descriptor Table (GDT)
// \see [ISDMv3 3.2.4 Segmentation in IA-32e Mode](intel_manual_vol3.pdf#page=91)
alignas(16) static SegmentDescriptor long_mode[] = {
// Null segment
{},
// Global code segment
{ /* base = */ 0x0,
/* limit = */ 0x0, // ignored
/* code = */ true,
/* ring = */ 0,
/* size = */ SIZE_64BIT_CODE },
// Global data segment
{ /* base = */ 0x0,
/* limit = */ 0x0, // ignored
/* code = */ false,
/* ring = */ 0,
/* size = */ SIZE_64BIT_DATA },
};
extern "C" constexpr Pointer gdt_long_mode_pointer(long_mode);
} // namespace GDT

203
machine/gdt.h Normal file
View File

@ -0,0 +1,203 @@
/*! \file
* \brief The \ref GDT "Global Descriptor Table (GDT)".
*/
#pragma once
#include "types.h"
/*! \brief Abstracts the GDT that, primarily, contains descriptors to memory segments.
* \ingroup memory
*
* The GDT is a table that primarily contains segment descriptors. Segment descriptors has a size of 8 Bytes and
* contains the size, position, access rights, and purpose of such a segment.
* Unlike the LDT, the GDT is shared between all processes and may contain TSS and LDT descriptors.
* For the kernel, the first entry is required to be a null descriptor and the code and data segments.
* To support user-mode processes, additional TSS, code, and data segments for ring 3 must be added.
*
* The base address and size of the GDT are written to the GDTR register during boot (via. `lgdt`).
*
* \see [ISDMv3, 2.4.1; Global Descriptor Table Register (GDTR)](intel_manual_vol3.pdf#page=72)
* \see [ISDMv3, 3.5.1; Segment Descriptor Tables](intel_manual_vol3.pdf#page=99)
*/
namespace GDT {
enum Segments {
SEGMENT_NULL = 0,
SEGMENT_KERNEL_CODE,
SEGMENT_KERNEL_DATA,
};
/*! \brief Unit of the segment limit
*/
enum Granularity {
GRANULARITY_BYTES = 0, ///< Segment limit in Bytes
GRANULARITY_4KBLOCK = 1 ///< Segment limit in blocks of 4 Kilobytes
};
/*! \brief Descriptor type */
enum DescriptorType {
DESCRIPTOR_SYSTEM = 0, ///< entry is a system segment
DESCRIPTOR_CODEDATA = 1, ///< entry is a code/data segment
};
/*! \brief Address width
*/
enum Size {
SIZE_16BIT = 0, ///< 16-bit (D/B = 0, L = 0)
SIZE_32BIT = 2, ///< 32-bit (D/B = 1, L = 0)
SIZE_64BIT_CODE = 1, ///< 64-bit (D/B = 0, L = 1)
SIZE_64BIT_DATA = 0, ///< 64-bit (D/B = 0, L = 0)
};
/*! \brief Describes the structure of segment descriptors
*
* A data structure that contains size, position, access rights, and purpose of any segment.
* Segment descriptors are used in both the GDT, as well as in LDTs.
*
* \see [ISDMv3, 3.4.5; Segment Descriptors](intel_manual_vol3.pdf#page=95)
* \see [AAPMv2, 4.7 Legacy Segment Descriptors](amd64_manual_vol2.pdf#page=132)
* \see [AAPMv2, 4.8 Long-Mode Segment Descriptors](amd64_manual_vol2.pdf#page=140)
*/
union SegmentDescriptor {
// Universally valid values (shared across all segment types)
struct {
uint64_t limit_low : 16; ///< Least-significant bits of segment size (influenced by granularity!)
uint64_t base_low : 24; ///< Least-significant bits of base address
uint64_t type : 4; ///< Meaning of those 4 bits depends on descriptor_type below
DescriptorType descriptor_type : 1; ///< Descriptor type (influences the meaning of the 3 bits above)
uint64_t privilege_level : 2; ///< Ring for this segment
bool present : 1; ///< Entry is valid iff set to `true`
uint64_t limit_high : 4; ///< Most-significant bits of segment size
bool available : 1; ///< Bit which can be used for other purposes (in software)
uint64_t custom : 2; ///< Meaning of those 2 bits relate to descriptor_type and type
Granularity granularity : 1; ///< Unit used as granularity for the segment limit
uint64_t base_high : 8; ///< most-significant bits of base address
} __attribute__((packed));
/*! \brief Fields specific for Code Segment
* (for debugging purposes)
* \see [ISDMv3, 3.4.5.1; Code- and Data-Segment Descriptor Types](intel_manual_vol3.pdf#page=98)
*/
struct {
uint64_t : 40; ///< Ignored (set via `limit_low` and `base_low` )
/* `type` field bits */
bool code_accessed : 1; ///< If set, the code segment was used since the last reset of this value
bool readable : 1; ///< If set, the code is readable (otherwise only executable)
/*! \brief If set, the execution of code from this segment is only allowed when running at a privilege of
* numerically less than or equal to privilege_level (i.e. the executor has the same or higher
* privileges). However, the executor's privileges remain unchanged.
* For nonconforming code segments (i.e., conforming is set to `0`), execution is allowed only if
* the privileges are equal.
* Execution will cause a GPF in case of privilege violation.
*/
bool conforming : 1;
bool code : 1; ///< Has to be set to `true`
uint64_t : 9; ///< Ignored (set via `privilege_level` ... `available`)
Size operation_size : 2; ///< Default address width (`custom` field bit)
uint64_t : 0; ///< Remainder ignored (set via `base_high`)
} __attribute__((packed));
/*! \brief Fields specific for Data Segment
* (for debugging purposes)
* \see [ISDMv3, 3.4.5.1; Code- and Data-Segment Descriptor Types](intel_manual_vol3.pdf#page=98)
*/
struct {
uint64_t : 40; ///< Ignored (set via `limit_low` and `base_low`)
bool data_accessed : 1; ///< If set, the data segment was used since the last reset of this value
bool writeable : 1; ///< If set, data is writable (otherwise read only)
bool expand_down : 1; ///< Growing direction for dynamically growing segments
bool notData : 1; ///< Has to be cleared (`false`)
uint64_t : 9; ///< Ignored (set via `privilege_level` ... `available`)
uint64_t reserved : 1; ///< Reserved, always set to `0`!
/*! \brief Size of the stack pointer (`false` = 16 bit, `true` = 32 bit)
* \warning Has a different meaning in case expand_down is set to `1`.
*/
bool big : 1;
uint64_t : 0; ///< Remainder ignored
} __attribute__((packed));
uint64_t value; ///!< Merged value; useful for debugging
/*! \brief Constructor for a specific value */
constexpr SegmentDescriptor(uint64_t val = 0) : value(val) {} //NOLINT due to copy-initialization
/*! \brief Constructor for a code/data GDT entry.
* \param base Base Address of segment
* \param limit Size of segment
* \param code Code or data segment
* \param ring Privilege level
* \param size Address width
*/
constexpr SegmentDescriptor(uintptr_t base, uint32_t limit, bool code, int ring, Size size) :
limit_low(limit >> (limit > 0xFFFFF ? 12 : 0) & 0xFFFF),
base_low(base & 0xFFFFFF),
type(code ? 0xA : 0x2), // code readable / non-conforming, data writeable and not expanding down
descriptor_type(DESCRIPTOR_CODEDATA),
privilege_level(ring),
present(true),
limit_high((limit > 0xFFFFF ? (limit >> 28) : (limit >> 16)) & 0xF),
available(false),
custom(size),
granularity(limit > 0xFFFFF ? GRANULARITY_4KBLOCK : GRANULARITY_BYTES),
base_high((base >> 24) & 0xFF) {}
} __attribute__((packed));
static_assert(sizeof(SegmentDescriptor) == 8, "GDT::SegmentDescriptor has wrong size");
/*! \brief Structure that describes a GDT Pointer (aka GDT Descriptor)
*
* It contains both the length (in bytes) of the GDT (minus 1 byte) and the pointer to the GDT.
* The pointer to the GDT can be loaded using the instruction `lgdt`.
*
* \note As Intel uses little endian for representing multi-byte values, the GDT::Pointer structure can be used for
* 16, 32, and 64 bit descriptor tables:
* \verbatim
* | 16 bit | 16 bit | 16 bit | 16 bit | 16 bit |
* +--------+---------------------------------------+
* Pointer | limit | base (up to 64 bit) |
* +--------+---------+---------+---------+---------+
* | used for 16 bit | ignored... |
* | used for 32 bit | ignored... |
* | used for 64 bit |
* \endverbatim
*
* \see [ISDMv3, Figure 2-6; Memory Management Registers](intel_manual_vol3.pdf#page=72)
*/
struct Pointer {
uint16_t limit; //!< GDT size in bytes (minus 1 byte)
void * base; //!< GDT base address
/*! \brief Constructor (automatic length)
* \param desc Array of GDT segment descriptors -- must be defined in the same module!
*/
template<typename T, size_t LEN>
explicit constexpr Pointer(const T (&desc)[LEN]) : limit(LEN * sizeof(T) - 1), base(const_cast<T*>(desc)) {}
/*! \brief Constructor
* \param desc Address of the GDT segment descriptors
* \param len Number of entries
*/
constexpr Pointer(void * desc, size_t len) : limit(len * sizeof(SegmentDescriptor) - 1), base(desc) {}
/*! \brief Set an address
* \note On change, `lgdt` must be executed again
* \param desc Address of the GDT segment descriptors
* \param len Number of entries
*/
void set(void * desc, size_t len) {
limit = len * sizeof(SegmentDescriptor) - 1;
base = desc;
}
} __attribute__((packed));
static_assert(sizeof(Pointer) == 10, "GDT::Pointer has wrong size");
} // namespace GDT

63
machine/idt.cc Normal file
View File

@ -0,0 +1,63 @@
#include "machine/idt.h"
#include "machine/gdt.h"
#include "machine/core_interrupt.h"
namespace IDT {
// Interrupt Descriptor stored in the Interrupt-Descriptor Table (IDT)
struct alignas(8) InterruptDescriptor {
uint16_t address_low; ///< lower interrupt function offset
uint16_t selector; ///< code segment selector in GDT or LDT
union {
struct {
uint8_t ist : 3; ///< IST Index (64 bit)
uint8_t : 5; ///< unused, has to be 0
Gate type : 3; ///< gate type
GateSize size : 1; ///< gate size
uint8_t : 1; ///< unused, has to be 0
DPL dpl : 2; ///< descriptor privilege level
uint8_t present : 1; ///< present: 1 for interrupts
} __attribute__((packed));
uint16_t flags;
};
uint64_t address_high : 48; ///< higher interrupt function offset
uint64_t : 0; ///< fill until aligned with 64 bit
} __attribute__((packed));
// Interrupt Descriptor Table, 8 Byte aligned
static struct InterruptDescriptor idt[256];
// Struct used for loading (the address of) the Interrupt Descriptor Table into the IDT-Register
struct Register {
uint16_t limit; // Address of the last valid byte (relative to base)
struct InterruptDescriptor * base;
explicit Register(uint8_t max = 255) {
limit = (max + static_cast<uint16_t>(1)) * sizeof(InterruptDescriptor) - 1;
base = idt;
}
} __attribute__((packed));
static_assert(sizeof(InterruptDescriptor) == 16, "IDT::InterruptDescriptor has wrong size");
static_assert(sizeof(Register) == 10, "IDT::Register has wrong size");
static_assert(alignof(decltype(idt)) % 8 == 0, "IDT must be 8 byte aligned!");
void load() {
// Create structure required for writing to idtr and load via lidt
Register idtr(Core::Interrupt::VECTORS - 1);
asm volatile("lidt %0\n\t" :: "m"(idtr) );
}
void handle(uint8_t vector, void * handler, enum Gate type, enum GateSize size, enum DPL dpl, bool present) {
struct InterruptDescriptor &item = idt[vector];
item.selector = GDT::SEGMENT_KERNEL_CODE * sizeof(GDT::SegmentDescriptor);
item.type = type;
item.size = size;
item.dpl = dpl;
item.present = present ? 1 : 0;
uintptr_t address = reinterpret_cast<uintptr_t>(handler);
item.address_low = address & 0xffff;
item.address_high = (address >> 16) & 0xffffffffffff;
}
} // namespace IDT

60
machine/idt.h Normal file
View File

@ -0,0 +1,60 @@
/*! \file
* \brief \ref IDT "Interrupt Descriptor Table (IDT)" containing the entry points for interrupt handling.
*/
#pragma once
#include "types.h"
/*! \brief "Interrupt Descriptor Table (IDT)
* \ingroup interrupt
*
* \see [ISDMv3 6.14 Exception and Interrupt Handling in 64-bit Mode](intel_manual_vol3.pdf#page=200)
*/
namespace IDT {
/*! \brief Gate types
*
* \see [ISDMv3 3.5 System Descriptor Types](intel_manual_vol3.pdf#page=99)
*/
enum Gate {
GATE_TASK = 0x5, ///< Task Gate
GATE_INT = 0x6, ///< Interrupt Gate
GATE_TRAP = 0x7, ///< Trap Gate
};
/*! \brief Segment type
*
* \see [ISDMv3 3.5 System Descriptor Types](intel_manual_vol3.pdf#page=99)
*/
enum GateSize {
GATE_SIZE_16 = 0, ///< 16 bit
GATE_SIZE_32 = 1, ///< 32 / 64 bit
};
/*! \brief Descriptor Privilege Level
*/
enum DPL {
DPL_KERNEL = 0, ///< Ring 0 / Kernel mode
/* DPLs 1 and 2 are unused */
DPL_USER = 3, ///< Ring 3 / User mode
};
/*! \brief Load the IDT's address and size into the IDT-Register via `idtr`.
*/
void load();
/*! \brief Configure entry point for interrupt handling
*
* The provided entry function ("handler") is required to, as first step, save the registers.
*
* \param vector Interrupt vector number for which the handler is to be set/changed
* \param handler Low-level entry point for interrupt handling
* \param type Gate type (Interrupt, Trap, or Task)
* \param size 16- or 32-bit
* \param dpl Permissions required for enter this interrupt handler (kernel- or user space)
* \param present Denotes whether the IDT descriptor is marked as available
*/
void handle(uint8_t vector, void * handler, enum Gate type = Gate::GATE_INT,
enum GateSize size = GateSize::GATE_SIZE_32, enum DPL dpl = DPL::DPL_KERNEL, bool present = true);
} // namespace IDT

83
machine/ioport.h Normal file
View File

@ -0,0 +1,83 @@
/*! \file
* \brief \ref IOPort provides access to the x86 IO address space
*/
#pragma once
#include "types.h"
/*! \brief Abstracts access to the I/O address space
*
* x86 PCs have a separated I/O address space that is accessible only via the machine instructions `in` and `out`.
* An IOPort object encapsulates the corresponding address in the I/O address space and can be used for byte or
* word-wise reading or writing.
*/
class IOPort {
/*! \brief Address in I/O address space
*
*/
uint16_t address;
public:
/*! \brief Constructor
* \param addr Address from the I/O address space
*/
explicit IOPort(uint16_t addr) : address(addr) {}
/*! \brief Write one byte to the I/O port
* \param val The value to be written
*/
void outb(uint8_t val) const {
asm volatile(
"out %%al, %%dx\n\t"
:
: "a"(val), "d"(address)
:
);
}
/*! \brief Write one word (2 bytes) to the I/O port
* \param val The value to be written
*/
void outw(uint16_t val) const {
asm volatile(
"out %%ax, %%dx\n\t"
:
:"a"(val), "d"(address)
:
);
}
/*! \brief Read one byte from the I/O port
* \return Read byte
*/
uint8_t inb() const {
uint8_t out = 0;
asm volatile(
"in %%dx, %%al\n\t"
:"=a"(out)
:"d"(address)
:
);
return out;
}
/*! \brief Read one word (2 bytes) from the I/O port
* \return Read word (2 bytes)
*/
uint16_t inw() const {
uint16_t out = 0;
asm volatile(
"inw %%dx, %%ax\n\t"
:"=a"(out)
:"d"(address)
:
);
return out;
}
};

118
machine/keydecoder.cc Normal file
View File

@ -0,0 +1,118 @@
#include "machine/keydecoder.h"
#include "machine/ps2controller.h"
// Constants used for key decoding
const unsigned char BREAK_BIT = 0x80;
const unsigned char PREFIX_1 = 0xe0;
const unsigned char PREFIX_2 = 0xe1;
Key KeyDecoder::decode(unsigned char code) {
Key key = modifier;
// All keys that are introduced by the MF II keyboard (compared to the older AT keyboard)
// always send a prefix value as first byte.
if (code == PREFIX_1 || code == PREFIX_2) {
prefix = code;
} else {
// Releasing a key is, for us, only important for the modifier keys such as SHIFT, CTRL and ALT,
// For other, non-modifier keys, we ignore the break code.
bool pressed = (code & BREAK_BIT) == 0;
// A key's break code is identical to its make code with an additionally set BREAK_BIT
Key::Scancode scancode = static_cast<Key::Scancode>(code & (~BREAK_BIT));
// We ignore "new" special keys, such as the Windows key
if (scancode < Key::Scancode::KEYS) {
// save state
status[scancode] = pressed;
// Take a closer look at modifier make and break events
bool isModifier = true;
switch (scancode) {
// both shifts are handled equally
case Key::Scancode::KEY_LEFT_SHIFT:
case Key::Scancode::KEY_RIGHT_SHIFT:
modifier.shift = pressed;
break;
case Key::Scancode::KEY_LEFT_ALT:
if (prefix == PREFIX_1) {
modifier.alt_right = pressed;
} else {
modifier.alt_left = pressed;
}
break;
case Key::Scancode::KEY_LEFT_CTRL:
if (prefix == PREFIX_1) {
modifier.ctrl_right = pressed;
} else {
modifier.ctrl_left = pressed;
}
break;
default:
isModifier = false;
}
// For keys other than modifiers, we only care about the make code
if (pressed && !isModifier) {
switch (scancode) {
case Key::Scancode::KEY_CAPS_LOCK:
modifier.caps_lock ^= 1;
setLed(PS2Controller::LED_CAPS_LOCK, modifier.caps_lock);
break;
case Key::Scancode::KEY_SCROLL_LOCK:
modifier.scroll_lock ^= 1;
setLed(PS2Controller::LED_SCROLL_LOCK, modifier.scroll_lock);
break;
case Key::Scancode::KEY_NUM_LOCK: // Can be both NumLock and pause
// On old keyboards, the pause functionality was only accessible by
// pressing Ctrl+NumLock. Modern MF-II keyboards therefore send exactly
// this code combination when the pause key was pressed.
// Normally, the pause key does not provide an ASCII code, but we check
// that anyway. In either case, we're now done decoding.
if (modifier.ctrl_left) { // pause key
key.scancode = scancode;
} else { // NumLock
modifier.num_lock ^= 1;
setLed(PS2Controller::LED_NUM_LOCK, modifier.num_lock);
}
break;
// Special case scan code 53: This code is used by both the minus key on the main
// keyboard and the division key on the number block.
// When the division key was pressed, we adjust the scancode accordingly.
case Key::Scancode::KEY_SLASH:
if (prefix == PREFIX_1) {
key.scancode = Key::Scancode::KEY_DIV;
key.shift = true;
} else {
key.scancode = scancode;
}
break;
default:
key.scancode = scancode;
// When NumLock is enabled and a key on the keypad was pressed, we
// want return the ASCII and scan codes of the corresponding numerical
// key instead of the arrow keys.
// The keys on the cursor block (prefix == PREFIX_1), however, should
// remain usable. Therefore, as a little hack, we deactivate the NumLock
// for these keys.
if (modifier.num_lock && prefix == PREFIX_1) {
key.num_lock = false;
}
}
}
}
// The prefix is only valid for the immediately following code, which was just handled.
prefix = 0;
}
return key;
}

35
machine/keydecoder.h Normal file
View File

@ -0,0 +1,35 @@
/*! \file
* \brief \ref KeyDecoder decodes a keystroke to the corresponding \ref Key object
*/
#pragma once
#include "object/key.h"
/*! \brief Decoder for \ref ps2keyboardset1 "keyboard codes" received from the \ref PS2Controller
* \ingroup io
*
* Extracts the make and break codes, modifier and scan codes from the pressed key.
*/
class KeyDecoder {
unsigned char prefix; ///< Prefix byte for keys
Key modifier; ///< activated modifier keys (e.g., caps lock)
public:
/*! \brief Current state (pressed or released) of all keys.
*/
bool status[Key::Scancode::KEYS];
/*! \brief Default constructor
*/
KeyDecoder() {}
/*! \brief Interprets the \ref ps2keyboardset1 "make and break codes" received from the
* keyboard and derives the corresponding scan code and further information about
* other pressed keys, such as \key{shift} and \key{ctrl}.
*
* \param code Byte from Keyboard to decode
* \return Pressed key (\ref Key::valid returns `false` if the key is not yet complete)
*/
Key decode(unsigned char code);
};

191
machine/lapic.cc Normal file
View File

@ -0,0 +1,191 @@
#include "machine/lapic.h"
#include "machine/lapic_registers.h"
namespace LAPIC {
/*! \brief Base Address
* used with offset to access memory mapped registers
*/
volatile uintptr_t base_address = 0xfee00000;
Register read(Index idx) {
return *reinterpret_cast<volatile Register *>(base_address + idx);
}
void write(Index idx, Register value) {
*reinterpret_cast<volatile Register *>(base_address + idx) = value;
}
/*! \brief Local APIC ID (for Pentium 4 and newer)
*
* Is assigned automatically during boot and should not be changed.
*
* \see [ISDMv3, 10.4.6 Local APIC ID](intel_manual_vol3.pdf#page=371)
*/
union IdentificationRegister {
struct {
uint32_t : 24, ///< (reserved)
apic_id : 8; ///< APIC ID
};
Register value;
IdentificationRegister() : value(read(Index::IDENTIFICATION)) {}
} __attribute__((packed));
/*! \brief Local APIC Version
*
* \see [ISDMv3 10.4.8 Local APIC Version Register](intel_manual_vol3.pdf#page=373)
*/
union VersionRegister {
struct {
uint32_t version : 8, ///< 0x14 for P4 and Xeon, 0x15 for more recent hardware
: 8, ///< (reserved)
max_lvt_entry : 8, ///< Maximum number of local vector entries
suppress_eoi_broadcast : 1, ///< Support for suppressing EOI broadcasts
: 7; ///< (reserved)
};
Register value;
VersionRegister() : value(read(Index::VERSION)) {}
} __attribute__((packed));
/*! \brief Logical Destination Register
* \see [ISDMv3 10.6.2.2 Logical Destination Mode](intel_manual_vol3.pdf#page=385)
*/
union LogicalDestinationRegister {
struct {
uint32_t : 24, ///< (reserved)
lapic_id : 8; ///< Logical APIC ID
};
Register value;
LogicalDestinationRegister() : value(read(Index::LOGICAL_DESTINATION)) {}
~LogicalDestinationRegister() {
write(Index::LOGICAL_DESTINATION, value);
}
} __attribute__((packed));
enum Model {
CLUSTER = 0x0,
FLAT = 0xf
};
/*! \brief Destination Format Register
*
* \see [ISDMv3 10.6.2.2 Logical Destination Mode](intel_manual_vol3.pdf#page=385)
*/
union DestinationFormatRegister {
struct {
uint32_t : 28; ///< (reserved)
Model model : 4; ///< Model (Flat vs. Cluster)
};
Register value;
DestinationFormatRegister() : value(read(Index::DESTINATION_FORMAT)) {}
~DestinationFormatRegister() {
write(Index::DESTINATION_FORMAT, value);
}
} __attribute__((packed));
/*! \brief Task Priority Register
*
* \see [ISDMv3 10.8.3.1 Task and Processor Priorities](intel_manual_vol3.pdf#page=391)
*/
union TaskPriorityRegister {
struct {
uint32_t task_prio_sub : 4, ///< Task Priority Sub-Class
task_prio : 4, ///< Task Priority
: 24; ///< (reserved)
};
Register value;
TaskPriorityRegister() : value(read(Index::TASK_PRIORITY)) {}
~TaskPriorityRegister() {
write(Index::TASK_PRIORITY, value);
}
} __attribute__((packed));
/*! \brief APIC Software Status for Spurious Interrupt Vector */
enum APICSoftware {
APIC_DISABLED = 0,
APIC_ENABLED = 1,
};
/*! \brief Focus Processor Checking for Spurious Interrupt Vector */
enum FocusProcessorChecking {
CHECKING_ENABLED = 0,
CHECKING_DISABLED = 1,
};
/*! \brief Suppress End-Of-Interrupt-Broadcast for Spurious Interrupt Vector */
enum SuppressEOIBroadcast {
BROADCAST = 0,
SUPPRESS_BROADCAST = 1,
};
/*! \brief Spurious Interrupt Vector Register
*
* \see [ISDMv3 10.9 Spurious Interrupt](intel_manual_vol3.pdf#page=394)
*/
union SpuriousInterruptVectorRegister {
struct {
uint32_t spurious_vector : 8; ///< Spurious Vector
APICSoftware apic_software : 1; ///< APIC Software Enable/Disable
FocusProcessorChecking focus_processor_checking : 1; ///< Focus Processor Checking
uint32_t reserved_1 : 2;
SuppressEOIBroadcast eoi_broadcast_suppression : 1;
uint32_t reserved:19;
};
Register value;
SpuriousInterruptVectorRegister() : value(read(Index::SPURIOUS_INTERRUPT_VECTOR)) {}
~SpuriousInterruptVectorRegister() {
write(Index::SPURIOUS_INTERRUPT_VECTOR, value);
}
} __attribute__((packed));
static_assert(sizeof(SpuriousInterruptVectorRegister) == 4, "LAPIC Spurious Interrupt Vector has wrong size");
uint8_t getID() {
IdentificationRegister ir;
return ir.apic_id;
}
uint8_t getLogicalID() {
LogicalDestinationRegister ldr;
return ldr.lapic_id;
}
uint8_t getVersion() {
VersionRegister vr;
return vr.version;
}
void init(uint8_t logical_id) {
// reset logical destination ID
// can be set using setLogicalLAPICID()
LogicalDestinationRegister ldr;
ldr.lapic_id = logical_id;
// set task priority to 0 -> accept all interrupts
TaskPriorityRegister tpr;
tpr.task_prio = 0;
tpr.task_prio_sub = 0;
// set flat delivery mode
DestinationFormatRegister dfr;
dfr.model = Model::FLAT;
// use 255 as spurious vector, enable APIC and disable focus processor
SpuriousInterruptVectorRegister sivr;
sivr.spurious_vector = 0xff;
sivr.apic_software = APICSoftware::APIC_ENABLED;
sivr.focus_processor_checking = FocusProcessorChecking::CHECKING_DISABLED;
}
void endOfInterrupt() {
// dummy read
read(SPURIOUS_INTERRUPT_VECTOR);
// signal end of interrupt
write(EOI, 0);
}
} // namespace LAPIC

142
machine/lapic.h Normal file
View File

@ -0,0 +1,142 @@
/*! \file
* \brief \ref LAPIC abstracts access to the Local \ref APIC
*/
#pragma once
#include "types.h"
/*! \brief Abstracts the local APIC (which is integrated into every CPU core)
* \ingroup interrupts
*
* In modern (x86) PCs, every CPU core has its own Local APIC (LAPIC). The LAPIC is the link between the
* local CPU core and the I/O APIC (that takes care about external interrupt sources.
* Interrupt messages received by the LAPIC will be passed to the corresponding CPU core and trigger the
* interrupt handler on this core.
*
* \see [ISDMv3 10.4 Local APIC](intel_manual_vol3.pdf#page=366)
*/
namespace LAPIC {
/*! \brief Initialized the local APIC of the calling CPU core and sets the logical LAPIC ID in the LDR register
* \param logical_id APIC ID to be set
*/
void init(uint8_t logical_id);
/*! \brief Signalize EOI (End of interrupt)
*
* Signalizes the LAPIC that the handling of the current interrupt finished. This function must be called at
* the end of interrupt handling before ireting.
*/
void endOfInterrupt();
/*! \brief Get the ID of the current core's LAPIC
* \return LAPIC ID
*/
uint8_t getID();
/*! \brief Get the Logical ID of the current core's LAPIC
* \return Logical ID
*/
uint8_t getLogicalID();
/*! \brief Set the Logical ID of the current core's LAPIC
* \param id new Logical ID
*/
void setLogicalID(uint8_t id);
/*! \brief Get version number of local APIC
* \return version number
*/
uint8_t getVersion();
/*! \brief Inter-Processor Interrupts
*
* For multi-core systems, the LAPIC enables sending messages (Inter-Processor Interrupts, IPIs) to
* other CPU cores and receiving those sent from other cores.
*
* \see [ISDMv3 10.6 Issuing Interprocessor Interrupts](intel_manual_vol3.pdf#page=380)
*/
namespace IPI {
/*! \brief Check if the previously sent IPI has reached its destination.
*
* \return `true` if the previous IPI was accepted from its target processor, otherwise `false`
*/
bool isDelivered();
/*! \brief Send an Inter-Processor Interrupt (IPI)
* \param destination ID of the target processor (use APIC::getLAPICID(core) )
* \param vector Interrupt vector number to be triggered
*/
void send(uint8_t destination, uint8_t vector);
/*! \brief Send an Inter-Processor Interrupt (IPI) to a group of processors
* \param logical_destination Mask containing the logical APIC IDs of the target processors (use APIC::getLogicalLAPICID())
* \param vector Interrupt vector number to be triggered
*/
void sendGroup(uint8_t logical_destination, uint8_t vector);
/*! \brief Send an Inter-Processor Interrupt (IPI) to all processors (including self)
* \param vector Interrupt vector number to be triggered
*/
void sendAll(uint8_t vector);
/*! \brief Send an Inter-Processor Interrupt (IPI) to all other processors (all but self)
* \param vector Interrupt vector number to be triggered
*/
void sendOthers(uint8_t vector);
/*! \brief Send an INIT request IPI to all other processors
*
* \note Only required for startup
*
* \param assert if `true` send an INIT,
* on `false` send an INIT Level De-assert
*/
void sendInit(bool assert = true);
/*! \brief Send an Startup IPI to all other processors
*
* \note Only required for startup
*
* \param vector Pointer to a startup routine
*/
void sendStartup(uint8_t vector);
} // namespace IPI
/*! \brief Local Timer (for each LAPIC / CPU)
*
* \see [ISDMv3 10.5.4 APIC Timer](intel_manual_vol3.pdf#page=378)
*/
namespace Timer {
/*! \brief Determines the LAPIC timer frequency.
*
* This function will calculate the number of LAPIC-timer ticks passing in the course of one millisecond.
* To do so, this function will rely on PIT timer functionality and measure the tick delta between start
* and end of waiting for a predefined period.
*
* For measurement, the LAPIC-timer single-shot mode (without interrupts) is used; after measurement, the
* timer is disabled again.
*
* \note The timer is counting towards zero.
*
* \return Number of LAPIC-timer ticks per millisecond
*/
uint32_t ticks(void);
/*! \brief Set the LAPIC timer.
* \param counter Initial counter value; decremented on every LAPIC timer tick
* \param divide Divider (power of 2, i.e., 1 2 4 8 16 32...) used as prescaler between bus frequency
* and LAPIC timer frequency: `LAPIC timer frequency = divide * bus frequency`.
* `divide` is a numerical parameter, the conversion to the corresponding bit mask is
* done internally by calling getClockDiv().
* \param vector Interrupt vector number to be triggered on counter expiry
* \param periodic If set, the interrupt will be issued periodically
* \param masked If set, interrupts on counter expiry are suppressed
*/
void set(uint32_t counter, uint8_t divide, uint8_t vector, bool periodic, bool masked = false);
} // namespace Timer
} // namespace LAPIC

228
machine/lapic_ipi.cc Normal file
View File

@ -0,0 +1,228 @@
#include "machine/lapic_registers.h"
namespace LAPIC {
namespace IPI {
/*! \brief Delivery mode specifies the type of interrupt sent to the CPU. */
enum DeliveryMode {
FIXED = 0, ///< "ordinary" interrupt; send to ALL cores listed in the destination bit mask
LOWEST_PRIORITY = 1, ///< "ordinary" interrupt; send to the lowest priority core from destination mask
SMI = 2, ///< System Management Interrupt; vector number required to be 0
// Reserved
NMI = 4, ///< Non-Maskable Interrupt, vector number ignored, only edge triggered
INIT = 5, ///< Initialization interrupt (always treated as edge triggered)
INIT_LEVEL_DEASSERT = 5, ///< Synchronization interrupt
STARTUP = 6, ///< Dedicated Startup-Interrupt (SIPI)
// Reserved
};
/*! \brief Way of interpreting the value written to the destination field. */
enum DestinationMode {
PHYSICAL = 0, ///< Destination contains the physical destination APIC ID
LOGICAL = 1 ///< Destination contains a mask of logical APIC IDs
};
/*! \brief Interrupt state */
enum DeliveryStatus {
IDLE = 0, ///< No activity for this interrupt
SEND_PENDING = 1 ///< Interrupt will be sent as soon as the bus / LAPIC is ready
};
/*! \brief Interrupt level */
enum Level {
DEASSERT = 0, ///< Must be zero when DeliveryMode::INIT_LEVEL_DEASSERT
ASSERT = 1 ///< Must be one for all other delivery modes
};
/*! \brief Trigger mode for DeliveryMode::INIT_LEVEL_DEASSERT */
enum TriggerMode {
EDGE_TRIGGERED = 0, ///< edge triggered
LEVEL_TRIGGERED = 1 ///< level triggered
};
/*! \brief Shorthand for commonly used destinations */
enum DestinationShorthand {
NO_SHORTHAND = 0, ///< Use destination field instead of shorthand
SELF = 1, ///< Send IPI to self
ALL_INCLUDING_SELF = 2, ///< Send IPI to all including self
ALL_EXCLUDING_SELF = 3 ///< Send IPI to all except self
};
/*! \brief Interrupt mask */
enum InterruptMask {
UNMASKED = 0, ///< Interrupt entry is active (non-masked)
MASKED = 1 ///< Interrupt entry is deactivated (masked)
};
/*! \brief Interrupt Command
*
* \see [ISDMv3 10.6.1 Interrupt Command Register (ICR)](intel_manual_vol3.pdf#page=381)
*/
union InterruptCommand {
struct {
/*! \brief Interrupt vector in the \ref IDT "Interrupt Descriptor Table (IDT)" will be
* activated when the corresponding external interrupt triggers.
*//*! \brief Interrupt vector in the \ref IDT "Interrupt Descriptor Table (IDT)" will be
* activated when the corresponding external interrupt triggers.
*/
uint64_t vector : 8;
/*! \brief The delivery mode denotes the way the interrupts will be delivered to the local CPU
* cores, respectively to their local APICs.
*
* For StuBS, we use `DeliveryMode::LowestPriority`, as all CPU cores have the same
* priority and we want to distribute interrupts evenly among them.
* It, however, is not guaranteed that this method of load balancing will work on every system.
*/
enum DeliveryMode delivery_mode : 3;
/*! \brief The destination mode defines how the value stored in `destination` will be
* interpreted.
*
* For StuBS, we use `DestinationMode::Logical`.
*/
enum DestinationMode destination_mode : 1;
/*! \brief Delivery status holds the current status of interrupt delivery.
*
* \note This field is read only; write accesses to this field will be ignored.
*/
enum DeliveryStatus delivery_status : 1;
uint64_t : 1; ///< reserved
/*! \brief The polarity denotes when an interrupt should be issued.
*
* For StuBS, we use `Polarity::High` (i.e., when the interrupt line is, logically, 1).
*/
enum Level level : 1;
/*! \brief The trigger mode states whether the interrupt signaling is level or edge triggered.
*
* StuBS uses `TriggerMode::Edge` for Keyboard and Timer, the (optional) serial interface,
* however, needs `TriggerMode::Level`.
*/
enum TriggerMode trigger_mode : 1;
uint64_t : 2; ///< reserved
enum DestinationShorthand destination_shorthand : 2;
uint64_t : 36; ///< Reserved, do not modify
/*! \brief Interrupt destination.
*
* The meaning of destination depends on the destination mode:
* For the logical destination mode, destination holds a bit mask made up of the cores that
* are candidates for receiving the interrupt.
* In the single-core case, this value is `1`, in the multi-core case, the `n` low-order bits
* needs to be set (with `n` being the number of CPU cores, see \ref Core::count() ).
* Setting the `n` low-order bits marks all available cores as candidates for receiving
* interrupts and thereby balancing the number of interrupts between the cores.
*
* \note This form of load balancing depends on the hardware's behavior and may not work on all
* systems in the same fashion. Most notably, in QEMU all interrupts are sent to the BSP
* (core 0).
*/
uint64_t destination : 8;
} __attribute__((packed));
/*! \brief I/O redirection-table entry
*
* Every entry in the redirection table represents an external source of interrupts and has a size
* of 64 bits. Due to the I/O APIC registers being only 32 bits wide, the 64-bit value is split in two
* 32 bit values.
*/
struct {
Register value_low; ///< First, low-order register
Register value_high; ///< Second, high-order register
} __attribute__((packed));
/*! \brief Default constructor */
InterruptCommand() = default;
explicit InterruptCommand(uint8_t destination, uint8_t vector = 0,
DestinationMode destination_mode = DestinationMode::PHYSICAL,
DeliveryMode delivery_mode = DeliveryMode::FIXED,
TriggerMode trigger_mode = TriggerMode::EDGE_TRIGGERED,
Level level = Level::ASSERT) {
readRegister();
this->vector = vector;
this->delivery_mode = delivery_mode;
this->destination_mode = destination_mode;
this->level = level;
this->trigger_mode = trigger_mode;
this->destination_shorthand = DestinationShorthand::NO_SHORTHAND;
this->destination = destination;
}
InterruptCommand(DestinationShorthand destination_shorthand, uint8_t vector,
DeliveryMode delivery_mode = DeliveryMode::FIXED,
TriggerMode trigger_mode = TriggerMode::EDGE_TRIGGERED,
Level level = Level::ASSERT) {
readRegister();
this->vector = vector;
this->delivery_mode = delivery_mode;
this->level = level;
this->trigger_mode = trigger_mode;
this->destination_shorthand = destination_shorthand;
this->destination = destination;
}
void send() const {
write(INTERRUPT_COMMAND_REGISTER_HIGH, value_high);
write(INTERRUPT_COMMAND_REGISTER_LOW, value_low);
}
bool isSendPending() {
value_low = read(INTERRUPT_COMMAND_REGISTER_LOW);
return delivery_status == DeliveryStatus::SEND_PENDING;
}
private:
void readRegister() {
while (isSendPending()) {}
value_high = read(INTERRUPT_COMMAND_REGISTER_HIGH);
}
};
static_assert(sizeof(InterruptCommand) == 8, "LAPIC Interrupt Command has wrong size");
bool isDelivered() {
InterruptCommand ic;
return !ic.isSendPending();
}
void send(uint8_t destination, uint8_t vector) {
InterruptCommand ic(destination, vector);
ic.send();
}
void sendGroup(uint8_t logical_destination, uint8_t vector) {
InterruptCommand ic(logical_destination, vector, DestinationMode::LOGICAL);
ic.send();
}
void sendAll(uint8_t vector) {
InterruptCommand ic(DestinationShorthand::ALL_INCLUDING_SELF, vector);
ic.send();
}
void sendOthers(uint8_t vector) {
InterruptCommand ic(DestinationShorthand::ALL_EXCLUDING_SELF, vector);
ic.send();
}
void sendInit(bool assert) {
LAPIC::IPI::InterruptCommand ic(DestinationShorthand::ALL_EXCLUDING_SELF, 0, DeliveryMode::INIT,
assert ? TriggerMode::EDGE_TRIGGERED : TriggerMode::LEVEL_TRIGGERED,
assert ? Level::ASSERT : Level::DEASSERT);
ic.send();
}
void sendStartup(uint8_t vector) {
InterruptCommand ic(DestinationShorthand::ALL_EXCLUDING_SELF, vector, DeliveryMode::STARTUP);
ic.send();
}
} // namespace IPI
} // namespace LAPIC

48
machine/lapic_registers.h Normal file
View File

@ -0,0 +1,48 @@
/*! \file
* \brief Structures and macros for accessing \ref LAPIC "the local APIC".
*/
#pragma once
#include "types.h"
namespace LAPIC {
// Memory Mapped Base Address
extern volatile uintptr_t base_address;
typedef uint32_t Register;
/*! \brief Register Offset Index
*
* \see [ISDMv3 10.4.1 The Local APIC Block Diagram](intel_manual_vol3.pdf#page=368)
*/
enum Index : uint16_t {
IDENTIFICATION = 0x020, ///< Local APIC ID Register, RO (sometimes R/W). Do not change!
VERSION = 0x030, ///< Local APIC Version Register, RO
TASK_PRIORITY = 0x080, ///< Task Priority Register, R/W
EOI = 0x0b0, ///< EOI Register, WO
LOGICAL_DESTINATION = 0x0d0, ///< Logical Destination Register, R/W
DESTINATION_FORMAT = 0x0e0, ///< Destination Format Register, bits 0-27 RO, bits 28-31 R/W
SPURIOUS_INTERRUPT_VECTOR = 0x0f0, ///< Spurious Interrupt Vector Register, bits 0-8 R/W, bits 9-1 R/W
INTERRUPT_COMMAND_REGISTER_LOW = 0x300, ///< Interrupt Command Register 1, R/W
INTERRUPT_COMMAND_REGISTER_HIGH = 0x310, ///< Interrupt Command Register 2, R/W
TIMER_CONTROL = 0x320, ///< LAPIC timer control register, R/W
TIMER_INITIAL_COUNTER = 0x380, ///< LAPIC timer initial counter register, R/W
TIMER_CURRENT_COUNTER = 0x390, ///< LAPIC timer current counter register, RO
TIMER_DIVIDE_CONFIGURATION = 0x3e0 ///< LAPIC timer divide configuration register, RW
};
/*! \brief Get value from APIC register
*
* \param idx Register Offset Index
* \return current value of register
*/
Register read(Index idx);
/*! \brief Write value to APIC register
*
* \param idx Register Offset Index
* \param value value to be written into register
*/
void write(Index idx, Register value);
} // namespace LAPIC

97
machine/lapic_timer.cc Normal file
View File

@ -0,0 +1,97 @@
#include "types.h"
#include "machine/lapic.h"
#include "machine/lapic_registers.h"
#include "machine/core.h"
namespace LAPIC {
namespace Timer {
/*! \brief Timer Delivery Status */
enum DeliveryStatus {
IDLE = 0,
SEND_PENDING = 1
};
/*! \brief Timer Mode */
enum TimerMode {
ONE_SHOT = 0,
PERIODIC = 1,
DEADLINE = 2
// reserved
};
/*! \brief Timer Mask */
enum Mask {
NOT_MASKED = 0,
MASKED = 1
};
static const Register INVALID_DIV = 0xff;
/*! \brief LAPIC-Timer Control Register
*
* \see [ISDMv3 10.5.1 Local Vector Table](intel_manual_vol3.pdf#page=375)
*/
union ControlRegister {
struct {
uint32_t vector : 8; ///< Vector
uint32_t : 4;
DeliveryStatus delivery_status : 1; ///< Delivery Status
uint32_t : 3;
Mask masked : 1; ///< Interrupt Mask (if set, interrupt will not trigger)
TimerMode timer_mode : 2; ///< Timer Mode
uint32_t : 13;
};
Register value;
} __attribute__((packed));
/*! \brief LAPIC timer divider table
*
* \see [ISDMv3 10.5.4 APIC Timer](intel_manual_vol3.pdf#page=378)
*/
static const Register div_masks[] = {
0xb, ///< divides by 1
0x0, ///< divides by 2
0x1, ///< divides by 4
0x2, ///< divides by 8
0x3, ///< divides by 16
0x8, ///< divides by 32
0x9, ///< divides by 64
0xa ///< divides by 128
};
/*! \brief Calculate the bit mask for the LAPIC-timer divider.
* \param div Divider, must be power of two: 1, 2, 4, 8, 16, 32, 64, 128
* \return Bit mask for LAPIC::Timer::set() or `0xff` if `div` is invalid.
*/
Register getClockDiv(uint8_t div) {
// div is zero or not a power of two?
if (div == 0 || (div & (div - 1)) != 0) {
return INVALID_DIV;
}
int trail = __builtin_ctz(div); // count trailing 0-bits
if (trail > 7) {
return INVALID_DIV;
}
return div_masks[trail];
}
uint32_t ticks(void) {
uint32_t ticks = 0; // ticks per millisecond
// Calculation (Assignment 5)
return ticks;
}
void set(uint32_t counter, uint8_t divide, uint8_t vector, bool periodic, bool masked) {
(void) counter;
(void) divide;
(void) vector;
(void) periodic;
(void) masked;
}
} // namespace Timer
} // namespace LAPIC

61
machine/pic.cc Normal file
View File

@ -0,0 +1,61 @@
#include "machine/pic.h"
#include "machine/ioport.h"
#include "types.h"
namespace PIC {
void initialize() {
// Access primary & secondary PIC via two ports each
IOPort primary_port_a(0x20);
IOPort primary_port_b(0x21);
IOPort secondary_port_a(0xa0);
IOPort secondary_port_b(0xa1);
// Initialization Command Word 1 (ICW1)
// Basic PIC configuration, starting initialization
enum InitializationCommandWord1 {
ICW4_NEEDED = 1 << 0, // use Initialization Command Word 4
SINGLE_MODE = 1 << 1, // Single or multiple (cascade mode) 8259A
ADDRESS_INTERVAL_HALF = 1 << 2, // 4 or 8 bit interval between the interrupt vector locations
LEVEL_TRIGGERED = 1 << 3, // Level or edge triggered
ALWAYS_1 = 1 << 4,
};
const uint8_t icw1 = InitializationCommandWord1::ICW4_NEEDED
| InitializationCommandWord1::ALWAYS_1;
// ICW1 in port A (each)
primary_port_a.outb(icw1);
secondary_port_a.outb(icw1);
// Initialization Command Word 2 (ICW2):
// Configure interrupt vector base offset in port B
primary_port_b.outb(0x20); // Primary: IRQ Offset 32
secondary_port_b.outb(0x28); // Secondary: IRQ Offset 40
// Initialization Command Word 3 (ICW3):
// Configure pin on primary PIC connected to secondary PIC
const uint8_t pin = 2; // Secondary connected on primary pin 2
primary_port_b.outb(1 << pin); // Pin as bit mask for primary
secondary_port_b.outb(pin); // Pin as value (ID) for secondary
// Initialization Command Word 4 (ICW4)
// Basic PIC configuration, starting initialization
enum InitializationCommandWord4 {
MODE_8086 = 1 << 0, // 8086/8088 or 8085 mode
AUTO_EOI = 1 << 1, // Single or multiple (cascade mode) 8259A
BUFFER_PRIMARY = 1 << 2, // Primary or secondary buffering
BUFFERED_MODE = 1 << 3, // Enable or disable buffering (for primary or secondary above)
SPECIAL_FULLY_NESTED = 1 << 4 // Special or non special fully nested
};
const uint8_t icw4 = InitializationCommandWord4::MODE_8086
| InitializationCommandWord4::AUTO_EOI;
// ICW3 in port B (each)
primary_port_b.outb(icw4);
secondary_port_b.outb(icw4);
// Operation Control Word 1 (OCW1):
// Disable (mask) all hardware interrupts on both legacy PICs (we'll use APIC)
secondary_port_b.outb(0xff);
primary_port_b.outb(0xff);
}
} // namespace PIC

17
machine/pic.h Normal file
View File

@ -0,0 +1,17 @@
/*! \file
* \brief Handle (disable) the old Programmable Interrupt Controller (PIC)
*/
#pragma once
/*! \brief The Programmable Interrupt Controller (PIC aka 8259A)
*/
namespace PIC {
/*! \brief Initialize the PICs (Programmable Interrupt Controller, 8259A),
* such that all 15 hardware interrupts are stored sequentially in the \ref IDT
* and the hardware interrupts are disabled (in favor of \ref APIC).
*/
void initialize();
} // namespace PIC

216
machine/pit.cc Normal file
View File

@ -0,0 +1,216 @@
#include "machine/pit.h"
#include "machine/ioport.h"
#include "machine/core.h"
namespace PIT {
// we only use PIT channel 2
const uint8_t CHANNEL = 2;
static IOPort data(0x40 + CHANNEL);
/*! \brief Access mode
*/
enum AccessMode {
LATCH_COUNT_VALUE = 0,
LOW_BYTE_ONLY = 1,
HIGH_BYTE_ONLY = 2,
LOW_AND_HIGH_BYTE = 3
};
/*! \brief Operating Mode
*
* \warning Channel 2 is not able to send interrupts, however, the status bit will be set
*/
enum OperatingMode {
INTERRUPT_ON_TERMINAL_COUNT = 0,
PROGRAMMABLE_ONE_SHOT = 1,
RATE_GENERATOR = 2,
SQUARE_WAVE_GENERATOR = 3, ///< useful for the PC speaker
SOFTWARE_TRIGGERED_STROBE = 4,
HARDWARE_TRIGGERED_STROBE = 5
};
/*! \brief data format
*/
enum Format {
BINARY = 0,
BCD = 1 ///< Binary Coded Decimals
};
// Mode register (only writable)
static IOPort mode_register(0x43);
union Mode {
struct {
Format format : 1;
OperatingMode operating : 3;
AccessMode access : 2;
uint8_t channel : 2;
};
uint8_t value;
/*! \brief Constructor for mode, takes the numeric value */
explicit Mode(uint8_t value) : value(value) {}
/*! \brief Constructor for counting mode
* \param access Access mode to the 16-bit counter value
* \param operating Operating mode for the counter
* \param format Number format for the 16-bit counter values (binary or BCD)
*/
Mode(AccessMode access, OperatingMode operating, Format format) :
format(format), operating(operating), access(access), channel(PIT::CHANNEL) {}
/*! \brief (Default) constructor for reading the counter value
*/
Mode() : value(0) {
this->channel = PIT::CHANNEL;
}
/*! \brief Write the value to the mode register
*/
void write() const {
mode_register.outb(value);
}
};
// The NMI Status and Control Register contains details about PIT counter 2
static IOPort controlRegister(0x61);
union Control {
/*! \brief I/O-port bitmap for the NMI Status and Control Register
* \note Over time, the meaning of the bits stored at I/O port 0x61 changed; don't get the structure confused
* with old documentation on the IBM PC XT platform.
* \see [Intel® I/O Controller Hub 7 (ICH7) Family](i-o-controller-hub-7-datasheet.pdf#page=415), page 415
*/
struct {
//! If enabled, the interrupt state will be visible at status_timer_counter2
uint8_t enable_timer_counter2 : 1;
uint8_t enable_speaker_data : 1; ///< If set, speaker output is equal to status_timer_counter2
uint8_t enable_pci_serr : 1; ///< not important, do not modify
uint8_t enable_nmi_iochk : 1; ///< not important, do not modify
const uint8_t refresh_cycle_toggle : 1; ///< not important, must be 0 on write
const uint8_t status_timer_counter2 : 1; ///< will be set on timer expiration; must be 0 on write
const uint8_t status_iochk_nmi_source : 1; ///< not important, must be 0 on write
const uint8_t status_serr_nmi_source : 1; ///< not important, must be 0 on write
};
uint8_t value;
/*! \brief Constructor
* \param value Numeric value for the control register
*/
explicit Control(uint8_t value) : value(value) {}
/*! \brief Default constructor
* Automatically reads the current contents from the control register.
*/
Control() : value(controlRegister.inb()) {}
/*! \brief Write the current state to the control register.
*/
void write() const {
controlRegister.outb(value);
}
};
// The base frequency is, due to historic reasons, 1.193182 MHz.
const uint64_t BASE_FREQUENCY = 1193182ULL;
bool set(uint16_t us) {
// Counter ticks for us
uint64_t counter = BASE_FREQUENCY * us / 1000000ULL;
// As the hardware counter has a size of 16 bit, we want to check whether the
// calculated counter value is too large ( > 54.9ms )
if (counter > 0xffff) {
return false;
}
// Interrupt state should be readable in status register, but PC speaker should remain off
Control c;
c.enable_speaker_data = 0;
c.enable_timer_counter2 = 1;
c.write();
// Channel 2, 16-bit divisor, with mode 0 (interrupt) in binary format
Mode m(AccessMode::LOW_AND_HIGH_BYTE, OperatingMode::INTERRUPT_ON_TERMINAL_COUNT, Format::BINARY);
m.write();
// Set the counter's start value
data.outb(counter & 0xff); // low
data.outb((counter >> 8) & 0xff); // high
return true;
}
uint16_t get(void) {
// Set mode to reading
Mode m;
m.write();
// Read low and high
uint16_t value = data.inb();
value |= data.inb() << 8;
return value;
}
bool isActive(void) {
Control c; // reads the current value from the control register
return c.enable_timer_counter2 == 1 && c.status_timer_counter2 == 0;
}
bool waitForTimeout(void) {
while(true) {
Control c; // reads the current value from the control register
if (c.enable_timer_counter2 == 0) {
return false;
} else if (c.status_timer_counter2 == 1) {
return true;
} else {
Core::pause();
}
}
}
bool delay(uint16_t us) {
return set(us) && waitForTimeout();
}
void pcspeaker(uint32_t freq) {
Control c;
if (freq == 0) {
disable();
} else {
// calculate frequency divider
uint64_t div = BASE_FREQUENCY / freq;
if (div > 0xffff) {
div = 0xffff;
}
// check if already configured
if (c.enable_speaker_data == 0) {
// if not, set mode
Mode m(AccessMode::LOW_AND_HIGH_BYTE, OperatingMode::SQUARE_WAVE_GENERATOR, Format::BINARY);
m.write();
}
// write frequency divider
data.outb(div & 0xff);
data.outb((div >> 8) & 0xff);
// already configured? (second part to prevent playing a wrong sound)
if (c.enable_speaker_data == 0) {
// activate PC speaker
c.enable_speaker_data = 1;
c.enable_timer_counter2 = 1;
c.write();
}
}
}
void disable(void) {
Control c;
c.enable_speaker_data = 0;
c.enable_timer_counter2 = 0;
c.write();
}
} // namespace PIT

74
machine/pit.h Normal file
View File

@ -0,0 +1,74 @@
/*! \file
* \brief The old/historical \ref PIT "Programmable Interval Timer (PIT)"
*/
#pragma once
#include "types.h"
/*! \brief Abstraction of the historical Programmable Interval Timer (PIT).
*
* Historically, PCs had a Timer component of type 8253 or 8254, modern systems come with a compatible chip.
* Each of these chips provides three 16-bit wide counters ("channel"), each running at a frequency of 1.19318 MHz.
* The timer's counting speed is thereby independent from the CPU frequency.
*
* Traditionally, the first counter (channel 0) was used for triggering interrupts, the second one (channel 1) controlled
* the memory refresh, and the third counter (channel 2) was assigned to the PC speaker.
*
* As the PIT's frequency is fixed to a constant value of 1.19318 MHz, the PIT can be used for calibration.
* For this purpose, we use channel 2 only.
*
* \note Interrupts should be disabled while configuring the timer.
*/
namespace PIT {
/*! \brief Start timer
*
* Sets the channel 2 timer to the provided value and starts counting.
*
* \note The maximum waiting time is approx. 55 000 us due to the timers being limited to 16 bit.
* \param us Waiting time in us
* \return `true` if the counter is running; `false` if the waiting time exceeds the limits.
*/
bool set(uint16_t us);
/*! \brief Reads the current timer value
* \return Current timer value
*/
uint16_t get(void);
/*! \brief Check if the timer is running
* \return `true` if running, `false` otherwise
*/
bool isActive(void);
/*! \brief (Active) waiting for timeout
* \return `true` when timeout was successfully hit, `false` if the timer was not active prior to calling.
*/
bool waitForTimeout(void);
/*! \brief Set the timer and wait for timeout
* \note The maximum waiting time is approx. 55 000 us due to the timers being limited to 16 bit.
* \param us Waiting time in us
* \return `true` when waiting successfully terminated; `false` on error (e.g., waiting time exceeds its limits)
*/
bool delay(uint16_t us);
/*! \brief Play a given frequency on the PC speaker.
*
* As the PC speaker is connected to PIT channel 2, the PIT can be used to play an acoustic signal.
* Playing sounds occupies the PIT, so it cannot be used for other purposes while playback.
*
* \note Not every PC has an activated PC speaker
* \note Qemu & KVM have to be launched with `-audiodev`
* If you still cannot hear anything, try to set `QEMU_AUDIO_DRV` to `alsa`
* (by launching \StuBS with `QEMU_AUDIO_DRV=alsa make kvm`)
* \param freq Frequency (in Hz) of the sound to be played, or 0 to deactivate playback.
*/
void pcspeaker(uint32_t freq);
/*! \brief Deactivate the timer
*/
void disable(void);
} // namespace PIT

118
machine/ps2controller.cc Normal file
View File

@ -0,0 +1,118 @@
#include "machine/ps2controller.h"
#include "machine/keydecoder.h"
#include "machine/ioport.h"
#include "debug/output.h"
#include "compiler/fix.h"
namespace PS2Controller {
// I/O Ports of the PS2 Controller
static const IOPort ctrl_port(0x64); ///< Access status- (read) and command (write) register
static const IOPort data_port(0x60); ///< Access PS/2 device [keyboard] output- (read) and input (write) buffer
/* The buffers are used to communicate with the controller or the connected
* PS/2 devices alike:
* - For the output buffer, the controller decides to which PS/2 device the
* data gets forwarded to -- by default it is the primary PS/2 device (keyboard).
* - The source device from which the data was gathered can be determined using
* the status flag (\ref IS_MOUSE).
*
* Please also note, that the naming of the buffer may be a bit contra-intuitive
* since it is the perspective of the PS/2 controller due to historical reasons.
*/
// Key decoder (stores the state of the modifier keys)
static KeyDecoder key_decoder;
// To store the current state of the Keyboard LEDs
static uint8_t MAYBE_UNUSED leds = 0;
/*! \brief Flags in the PS/2 controller status register
*/
enum Status {
HAS_OUTPUT = 1 << 0, ///< Output buffer non-empty?
INPUT_PENDING = 1 << 1, ///< Is input buffer full?
SYSTEM_FLAG = 1 << 2, ///< set on soft reset, cleared on power up
IS_COMMAND = 1 << 3, ///< Is command Byte? (otherwise data)
IS_MOUSE = 1 << 5, ///< Mouse output has data
TIMEOUT_ERROR = 1 << 6, ///< Timeout error
PARITY_ERROR = 1 << 7 ///< Parity error
};
/*! \brief Commands to be send to the Keyboard
*/
enum KeyboardCommand : uint8_t {
KEYBOARD_SET_LED = 0xed, ///< Set the LED (according to the following parameter byte)
KEYBOARD_SEND_ECHO = 0xee, ///< Send an echo packet
KEYBOARD_SET_SPEED = 0xf3, ///< Set the repeat rate (according to the following parameter byte)
KEYBOARD_ENABLE = 0xf4, ///< Enable Keyboard
KEYBOARD_DISABLE = 0xf5, ///< Disable Keyboard
KEYBOARD_SET_DEFAULT = 0xf6, ///< Load defaults
};
/*! \brief Replies
*/
enum Reply {
ACK = 0xfa, ///< Acknowledgement
RESEND = 0xfe, ///< Request to resend (not required to implement)
ECHO = 0xee ///< Echo answer
};
/*! \brief Commands for the PS/2 Controller
*
* These commands are processed by the controller and *not* send to keyboard/mouse.
* They have to be written into the command register.
*/
enum ControllerCommand {
CONTROLLER_GET_COMMAND_BYTE = 0x20, ///< Read Command Byte of PS/2 Controller
CONTROLLER_SET_COMMAND_BYTE = 0x60, ///< Write Command Byte of PS/2 Controller
CONTROLLER_MOUSE_DISABLE = 0xa7, ///< Disable mouse interface
CONTROLLER_MOUSE_ENABLE = 0xa8, ///< Enable mouse interface
CONTROLLER_KEYBOARD_DISABLE = 0xad, ///< Disable keyboard interface
CONTROLLER_KEYBOARD_ENABLE = 0xae, ///< Enable keyboard interface
CONTROLLER_SEND_TO_MOUSE = 0xd4, ///< Send parameter to mouse device
};
/*! \brief Send a command or data to a connected PS/2 device
*
* The value must only be written into the input buffer after the previously
* written values have been fetched (\ref INPUT_PENDING in the status register).
*
* \todo Implement method
*
* \param value data to be sent
*/
static void MAYBE_UNUSED sendData(uint8_t value) {
// TODO: You have to implement this method
(void) value;
}
void init() {
// Switch all LEDs off (on many PCs NumLock is turned on after power up)
setLed(LED_CAPS_LOCK, false);
setLed(LED_SCROLL_LOCK, false);
setLed(LED_NUM_LOCK, false);
// Set to maximum speed & minimum delay
setRepeatRate(SPEED_30_0CPS, DELAY_250MS);
}
bool fetch(Key &pressed) {
// TODO: You have to implement this method
(void) pressed;
return false;
}
void setRepeatRate(Speed speed, Delay delay) {
// TODO: You have to implement this method. Use sendData()
(void) speed;
(void) delay;
}
void setLed(enum LED led, bool on) {
// TODO: You have to implement this method. Use sendData()
(void) led;
(void) on;
}
} // namespace PS2Controller

127
machine/ps2controller.h Normal file
View File

@ -0,0 +1,127 @@
/*! \file
* \brief \ref PS2Controller "PS/2 Controller" (Intel 8042, also known as Keyboard Controller)
*/
#pragma once
#include "types.h"
#include "object/key.h"
/*! \brief PS/2 Controller
* \ingroup io
*
* Initializes the PS/2 devices (Keyboard and optional Mouse), and
* determines both the scan code and ASCII character of a pressed key from the
* transmitted make and break codes using the \ref KeyDecoder.
*
* \note This controller is also known as Intel 8042 (nowadays integrated in
* the mainboard) or *Keyboard Controller*.
* But to avoid confusion with the actual Keyboard and since we use the
* PS/2-compatible mode to support the Mouse as well, the name
* PS/2 Controller was chosen for the sake of simplicity.
*
* \note Since modern PCs sometimes don't have an PS/2 connector, USB keyboards
* and mice are emulated as PS/2 device with USB Legacy Support.
*/
namespace PS2Controller {
/*! \brief Initialization of connected devices
*
* All status LEDs of the keyboard are switched off and the repetition rate is
* set to maximum speed.
*/
void init();
/*! \brief Retrieve the keyboard event
*
* Retrieves make and brake events from the keyboard.
* If a valid (non special) key was pressed, the scan code is determined
* using \ref KeyDecoder::decode into a \ref Key object.
* Events on special keys like \key{Shift}, \key{Alt}, \key{CapsLock} etc. are stored
* (in \ref KeyDecoder) and applied on subsequent keystrokes,
* while no valid key is retrieved.
*
* Mouse events are ignored.
*
* \todo Implement Method
* \param pressed Reference to an object which will contain the pressed \ref Key on success
* \return `true` if a valid key was decoded
*/
bool fetch(Key &pressed);
/*! \brief Delay before the keyboard starts repeating sending a pressed key
*/
enum Delay {
DELAY_250MS = 0, ///< Delay of 0.25s
DELAY_500MS = 1, ///< Delay of 0.5s
DELAY_750MS = 2, ///< Delay of 0.75s
DELAY_1000MS = 3 ///< Delay of 1s
};
/*! \brief Repeat Rate of Characters
*
* \see \ref ps2keyboard
*/
enum Speed {
SPEED_30_0CPS = 0x00, ///< 30 characters per second
SPEED_26_7CPS = 0x01, ///< 26.7 characters per second
SPEED_24_0CPS = 0x02, ///< 24 characters per second
SPEED_21_8CPS = 0x03, ///< 12.8 characters per second
SPEED_20_7CPS = 0x04, ///< 20.7 characters per second
SPEED_18_5CPS = 0x05, ///< 18.5 characters per second
SPEED_17_1CPS = 0x06, ///< 17.1 characters per second
SPEED_16_0CPS = 0x07, ///< 16 characters per second
SPEED_15_0CPS = 0x08, ///< 15 characters per second
SPEED_13_3CPS = 0x09, ///< 13.3 characters per second
SPEED_12_0CPS = 0x0a, ///< 12 characters per second
SPEED_10_9CPS = 0x0b, ///< 10.9 characters per second
SPEED_10_0CPS = 0x0c, ///< 10 characters per second
SPEED_09_2CPS = 0x0d, ///< 9.2 characters per second
SPEED_08_6CPS = 0x0e, ///< 8.6 characters per second
SPEED_08_0CPS = 0x0f, ///< 8 characters per second
SPEED_07_5CPS = 0x10, ///< 7.5 characters per second
SPEED_06_7CPS = 0x11, ///< 6.7 characters per second
SPEED_06_0CPS = 0x12, ///< 6 characters per second
SPEED_05_5CPS = 0x13, ///< 5.5 characters per second
SPEED_05_0CPS = 0x14, ///< 5 characters per second
SPEED_04_6CPS = 0x15, ///< 4.6 characters per second
SPEED_04_3CPS = 0x16, ///< 4.3 characters per second
SPEED_04_0CPS = 0x17, ///< 4 characters per second
SPEED_03_7CPS = 0x18, ///< 3.7 characters per second
SPEED_03_3CPS = 0x19, ///< 3.3 characters per second
SPEED_03_0CPS = 0x1a, ///< 3 characters per second
SPEED_02_7CPS = 0x1b, ///< 2.7 characters per second
SPEED_02_5CPS = 0x1c, ///< 2.5 characters per second
SPEED_02_3CPS = 0x1d, ///< 2.3 characters per second
SPEED_02_1CPS = 0x1e, ///< 2.1 characters per second
SPEED_02_0CPS = 0x1f, ///< 2 characters per second
};
/*! \brief Configure the repeat rate of the keyboard
*
* \param delay configures how long a key must be pressed before the repetition begins.
* \param speed determines how fast the key codes should follow each other.
* Valid values are between `0` (30 characters per second) and
* `31` (2 characters per second).
*
* \todo Implement method
*/
void setRepeatRate(Speed speed, Delay delay);
/*! \brief Keyboard LEDs
*/
enum LED {
LED_SCROLL_LOCK = 1 << 0, ///< Scroll Lock
LED_NUM_LOCK = 1 << 1, ///< Num Lock
LED_CAPS_LOCK = 1 << 2, ///< Caps Lock
};
/*! \brief Enable or disable a keyboard LED
*
* \param led LED to enable or disable
* \param on `true` will enable the specified LED, `false` disable
*
* \todo Implement method
*/
void setLed(enum LED led, bool on);
} // namespace PS2Controller

35
machine/serial.cc Normal file
View File

@ -0,0 +1,35 @@
#include "machine/serial.h"
Serial::Serial(ComPort port, BaudRate baud_rate, DataBits data_bits, StopBits stop_bits, Parity parity) : port(port) {
// TODO: Implement
(void) baud_rate;
(void) data_bits;
(void) stop_bits;
(void) parity;
}
void Serial::writeReg(RegisterIndex reg, char out) {
// TODO: Implement (if you want, optional exercise)
(void) reg;
(void) out;
}
char Serial::readReg(RegisterIndex reg) {
// TODO: Implement (if you want, optional exercise)
(void) reg;
return '\0';
}
int Serial::write(char out, bool blocking) {
// TODO: Implement (if you want, optional exercise)
(void) out;
(void) blocking;
return 0;
}
int Serial::read(bool blocking) {
// TODO: Implement (if you want, optional exercise)
(void) blocking;
return 0;
}

209
machine/serial.h Normal file
View File

@ -0,0 +1,209 @@
/*! \file
* \brief Communication via the \ref Serial interface (RS-232)
*/
#pragma once
#include "types.h"
/*! \brief Serial interface.
* \ingroup io
*
* This class provides a serial interface (COM1 - COM4) for communication with the outside world.
*
* The first IBM PC used the external chip [8250 UART](https://de.wikipedia.org/wiki/NSC_8250), whereas, in today's
* systems, this functionality is commonly integrated into the southbridge, but remained compatible.
*
* \see [PC8250A Data Sheet](uart-8250a.pdf#page=11) (Registers on page 11)
* \see [PC16550D Data Sheet](uart-16550d.pdf#page=16) (Successor, for optional FIFO buffer, page 16)
*/
class Serial {
public:
/*! \brief COM-Port
*
* The serial interface and its hardware addresses. Modern desktop PCs have, at most,
* a single, physical COM-port (`COM1`)
*/
enum ComPort{
COM1 = 0x3f8,
COM2 = 0x2f8,
COM3 = 0x3e8,
COM4 = 0x2e8,
};
/*! \brief Transmission speed
*
* The unit Baud describes the transmission speed in number of symbols per seconds.
* 1 Baud therefore equals the transmission of 1 symbol per second.
* The possible Baud rates are whole-number dividers of the clock frequency of 115200 Hz..
*/
enum BaudRate {
BAUD_300 = 384,
BAUD_600 = 192,
BAUD_1200 = 96,
BAUD_2400 = 48,
BAUD_4800 = 24,
BAUD_9600 = 12,
BAUD_19200 = 6,
BAUD_38400 = 3,
BAUD_57600 = 2,
BAUD_115200 = 1,
};
/*! \brief Number of data bits per character */
enum DataBits {
DATA_5BIT = 0,
DATA_6BIT = 1,
DATA_7BIT = 2,
DATA_8BIT = 3,
};
/*! \brief Number of stop bits per character */
enum StopBits {
STOP_1BIT = 0,
STOP_1_5BIT = 4,
STOP_2BIT = 4,
};
/*! \brief parity bit */
enum Parity {
PARITY_NONE = 0,
PARITY_ODD = 8,
PARITY_EVEN = 24,
PARITY_MARK = 40,
PARITY_SPACE = 56,
};
private:
/*! \brief register index */
enum RegisterIndex {
// if Divisor Latch Access Bit [DLAB] = 0
RECEIVE_BUFFER_REGISTER = 0, ///< read only
TRANSMIT_BUFFER_REGISTER = 0, ///< write only
INTERRUPT_ENABLE_REGISTER = 1,
// if Divisor Latch Access Bit [DLAB] = 1
DIVISOR_LOW_REGISTER = 0,
DIVISOR_HIGH_REGISTER = 1,
// (irrespective from DLAB)
INTERRUPT_IDENT_REGISTER = 2, ///< read only
FIFO_CONTROL_REGISTER = 2, ///< write only -- 16550 and newer (esp. not 8250a)
LINE_CONTROL_REGISTER = 3, ///< highest-order bit is DLAB (see above)
MODEM_CONTROL_REGISTER = 4,
LINE_STATUS_REGISTER = 5,
MODEM_STATUS_REGISTER = 6
};
/*! \brief Mask for the respective register */
enum RegisterMask : uint8_t {
// Interrupt Enable Register
RECEIVED_DATA_AVAILABLE = 1 << 0,
TRANSMITTER_HOLDING_REGISTER_EMPTY = 1 << 1,
RECEIVER_LINE_STATUS = 1 << 2,
MODEM_STATUS = 1 << 3,
// Interrupt Ident Register
INTERRUPT_PENDING = 1 << 0, ///< 0 means interrupt pending
INTERRUPT_ID_0 = 1 << 1,
INTERRUPT_ID_1 = 1 << 2,
// FIFO Control Register
ENABLE_FIFO = 1 << 0, ///< 0 means disabled ^= conforming to 8250a
CLEAR_RECEIVE_FIFO = 1 << 1,
CLEAR_TRANSMIT_FIFO = 1 << 2,
DMA_MODE_SELECT = 1 << 3,
TRIGGER_RECEIVE = 1 << 6,
// Line Control Register
// bits per character: 5 6 7 8
WORD_LENGTH_SELECT_0 = 1 << 0, // Setting Select0: 0 1 0 1
WORD_LENGTH_SELECT_1 = 1 << 1, // Setting Select1: 0 0 1 1
NUMBER_OF_STOP_BITS = 1 << 2, // 0 ≙ one stop bit, 1 ≙ 1.5/2 stop bits
PARITY_ENABLE = 1 << 3,
EVEN_PARITY_SELECT = 1 << 4,
STICK_PARITY = 1 << 5,
SET_BREAK = 1 << 6,
DIVISOR_LATCH_ACCESS_BIT = 1 << 7, // DLAB
// Modem Control Register
DATA_TERMINAL_READY = 1 << 0,
REQUEST_TO_SEND = 1 << 1,
OUT_1 = 1 << 2,
OUT_2 = 1 << 3, // must be set for interrupts!
LOOP = 1 << 4,
// Line Status Register
DATA_READY = 1 << 0, // Set when there is a value in the receive buffer
OVERRUN_ERROR = 1 << 1,
PARITY_ERROR = 1 << 2,
FRAMING_ERROR = 1 << 3,
BREAK_INTERRUPT = 1 << 4,
TRANSMITTER_HOLDING_REGISTER = 1 << 5,
TRANSMITTER_EMPTY = 1 << 6, // Send buffer empty (ready to send)
// Modem Status Register
DELTA_CLEAR_TO_SEND = 1 << 0,
DELTA_DATA_SET_READY = 1 << 1,
TRAILING_EDGE_RING_INDICATOR = 1 << 2,
DELTA_DATA_CARRIER_DETECT = 1 << 3,
CLEAR_TO_SEND = 1 << 4,
DATA_SET_READY = 1 << 5,
RING_INDICATOR = 1 << 6,
DATA_CARRIER_DETECT = 1 << 7
};
/*! \brief Read value from register
*
* \todo Implement Method
*
* \param reg Register index
* \return The value read from register
*/
char readReg(RegisterIndex reg);
/*! \brief Write value to register
*
* \todo Implement Method
*
* \param reg Register index
* \param out value to be written
*/
void writeReg(RegisterIndex reg, char out);
protected:
/*! \brief Selected COM port */
const ComPort port;
public:
/*! \brief Constructor
*
* Creates a Serial object that encapsulates the used COM port, as well as the parameters used for the
* serial connection. Default values are `8N1` (8 bit, no parity bit, one stop bit) with 115200 Baud using COM1.
*
* \todo Implement Constructor
*/
explicit Serial(ComPort port = COM1, BaudRate baud_rate = BAUD_115200, DataBits data_bits = DATA_8BIT,
StopBits stop_bits = STOP_1BIT, Parity parity = PARITY_NONE);
/*! \brief Read one byte from the serial interface
*
* \todo Implement Method
*
* \param blocking If set, \ref read() blocks until one byte was read
* \return Value read from serial interface (or `-1 ` if non-blocking and no data ready)
*/
int read(bool blocking = true);
/*! \brief Write one byte to the serial interface
*
* \todo Implement Method
*
* \param out Byte to be written
* \param blocking If set, \ref write() blocks until the byte was written
* \return Byte written (or `-1` if writing byte failed)
*/
int write(char out, bool blocking = true);
};

15
machine/system.cc Normal file
View File

@ -0,0 +1,15 @@
#include "machine/system.h"
#include "machine/cmos.h"
#include "machine/ioport.h"
#include "debug/output.h"
namespace System {
void reboot() {
const IOPort system_control_port_a(0x92);
DBG_VERBOSE << "rebooting smp" << endl;
CMOS::write(CMOS::REG_STATUS_SHUTDOWN, 0);
system_control_port_a.outb(0x3);
}
} // namespace System

14
machine/system.h Normal file
View File

@ -0,0 +1,14 @@
/*! \file
* \brief General \ref System functionality (\ref System::reboot "reboot")
*/
#pragma once
/*! \brief General System functions
*/
namespace System {
/*! \brief Perform a reboot
*/
void reboot();
} // namespace System

119
machine/textmode.h Normal file
View File

@ -0,0 +1,119 @@
/*! \file
* \brief \ref TextMode provides a basic interface to display a character in VGA-compatible text mode
*/
#pragma once
#include "types.h"
/*! \brief Basic operations in the VGA-compatible text mode
* \ingroup io
*
* This class provides an interface to access the screen in text mode, with
* access directly on the hardware level, i.e. the video memory and the
* I/O ports of the graphics card.
*/
class TextMode {
public:
static const unsigned ROWS = 25; ///< Visible rows in text mode
static const unsigned COLUMNS = 80; ///< Visible columns in text mode
/*! \brief CGA color palette
*
* Colors for the attribute byte.
* All 16 colors can be used for the foreground while the background colors
* are limited to the first eight (from`BLACK` to `LIGHT_GREY`)
*/
enum Color {
BLACK, ///< Black (fore- and background)
BLUE, ///< Blue (fore- and background)
GREEN, ///< Green (fore- and background)
CYAN, ///< Cyan (fore- and background)
RED, ///< Red (fore- and background)
MAGENTA, ///< Magenta (fore- and background)
BROWN, ///< Brown (fore- and background)
LIGHT_GREY, ///< Light grey (fore- and background)
DARK_GREY, ///< Dark grey (foreground only)
LIGHT_BLUE, ///< Light blue (foreground only)
LIGHT_GREEN, ///< Light green (foreground only)
LIGHT_CYAN, ///< Light cyan (foreground only)
LIGHT_RED, ///< Light red (foreground only)
LIGHT_MAGENTA, ///< Light magenta (foreground only)
YELLOW, ///< Yellow (foreground only)
WHITE ///< White (foreground only)
};
/*! \brief Structure of a character attribute
* consists of 4 bit fore- and 3 bit background color, and a single blink bit.
*
* [Bit fields](https://en.cppreference.com/w/cpp/language/bit_field) can
* notably simplify the access and code readability.
*
* \note [Type punning](https://en.wikipedia.org/wiki/Type_punning#Use_of_union)
* is indeed undefined behavior in C++. However, *gcc* explicitly allows this construct as a
* [language extension](https://gcc.gnu.org/bugs/#nonbugs).
* Some compilers ([other than gcc](https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html#Type%2Dpunning)
* might allow this feature only by disabling strict aliasing (`-fno-strict-aliasing`).
* In \StuBS we use this feature extensively due to the improved code readability.
*/
union Attribute {
struct {
uint8_t foreground : 4; ///< `.... XXXX` Foreground color
uint8_t background : 3; ///< `.XXX ....` Background color
uint8_t blink : 1; ///< `X... ....` Blink
} __attribute__((packed));
uint8_t value; ///< combined value
/*! \brief Attribute constructor (with default values)
*
* \todo Complete constructor
*
* \param foreground Foreground color (Default: \ref LIGHT_GREY)
* \param background Background color (Default: \ref BLACK)
* \param blink Blink if `true` (default: no blinking)
*/
explicit Attribute(Color foreground = LIGHT_GREY, Color background = BLACK, bool blink = false)
{ //NOLINT
(void) foreground;
(void) background;
(void) blink;
}
} __attribute__((packed)); // prevent padding by the compiler
/*! \brief Set the keyboard hardware cursor to absolute screen position
*
* \todo Implement the method using \ref IOPort
*
* \param abs_x absolute column of the keyboard hardware cursor
* \param abs_y absolute row of the keyboard hardware cursor
*/
static void setCursor(unsigned abs_x, unsigned abs_y);
/*! \brief Retrieve the keyboard hardware cursor position on screen
*
* \todo Implement the method using the \ref IOPort
*
* \param abs_x absolute column of the keyboard hardware cursor
* \param abs_y absolute row of the keyboard hardware cursor
*/
static void getCursor(unsigned& abs_x, unsigned& abs_y);
/*! \brief Basic output of a character at a specific position on the screen.
*
* This method outputs the given character at the absolute screen position
* (`x`, `y`) with the specified color attribute.
*
* The position (`0`,`0`) indicates the upper left corner of the screen.
* The attribute defines characteristics such as background color,
* foreground color and blinking.
*
* \param abs_x Column (`abs_x` < \ref COLUMNS) in which the character should be displayed
* \param abs_y Row (`abs_y` < \ref ROWS) in which the character should be displayed
* \param character Character to be displayed
* \param attrib Attribute with color settings
* \todo Implement the method
*/
static void show(unsigned abs_x, unsigned abs_y, char character, Attribute attrib = Attribute());
};

112
machine/textwindow.h Normal file
View File

@ -0,0 +1,112 @@
/*! \file
* \brief \ref TextWindow provides virtual output windows in text mode
*/
#pragma once
#include "types.h"
#include "machine/textmode.h"
/*! \brief Virtual windows in text mode
* \ingroup io
*
* Outputs text on a part of the screen in \ref TextMode "text mode",
* a window defined in its position and size (with its own cursor).
*
* This allows to separate the output of the application from the debug output
* on the screen without having to synchronize.
*/
class TextWindow : public TextMode {
// Prevent copies and assignments
TextWindow(const TextWindow&) = delete;
TextWindow& operator=(const TextWindow&) = delete;
public:
/*! \brief Constructor of a text window
*
* Creates a virtual, rectangular text window on the screen.
* The coordinates to construct the window are absolute positions in the
* \ref TextMode screen.
*
* \note Overlapping windows are neither supported nor prevented -- better
* just try to avoid construction windows with overlapping coordinates!
*
* \warning Don't use the hardware cursor in more than one window!
*
* \param from_col Text Window starts in column `from_col`,
* the first (leftmost) possible column is `0`
* \param to_col Text Window extends to the right to column `to_col` (exclusive).
* This column has to be strictly greater than `from_col`,
* the maximum allowed value is \ref TextMode::COLUMNS (rightmost)
* \param from_row Text Window starts in row `from_row`,
* the first possible (uppermost) row is `0`
* \param to_row Text Window extends down to row `to_row` (exclusive).
* This row has to be strictly greater than `from_row`,
* the maximum allowed value is \ref TextMode::ROWS (bottom-most)
* \param use_cursor Specifies whether the hardware cursor (`true`) or a
* software cursor/variable (`false`) should be used to
* store the current position
*
* \todo Implement constructor
*/
TextWindow(unsigned from_col, unsigned to_col, unsigned from_row, unsigned to_row, bool use_cursor = false);
/*! \brief Set the cursor position in the window
*
* Depending on the constructor parameter `use_cursor` either the
* hardware cursor (and only the hardware cursor!) is used or the position
* is stored internally in the object.
*
* The coordinates are relative to the upper left starting position of
* the window.
*
* \param rel_x Column in window
* \param rel_y Row in window
* \todo Implement method, use \ref TextMode::setCursor() for the hardware cursor
*/
void setPos(unsigned rel_x, unsigned rel_y);
/*! \brief Get the current cursor position in the window
*
* Depending on the constructor parameter `use_cursor` either the
* hardware cursor (and only the hardware cursor!) is used or the position
* is retrieved from the internally stored object.
*
* \param rel_x Column in window
* \param rel_y Row in window
* \todo Implement Method, use \ref TextMode::getCursor() for the hardware cursor
*/
void getPos(unsigned& rel_x, unsigned& rel_y) const;
/*! \brief Display multiple characters in the window
*
* Output a character string, starting at the current cursor position.
* Since the string does not need to contain a `\0` termination (unlike the
* common C string), a length parameter is required to specify the number
* of characters in the string.
* When the output is complete, the cursor is positioned after the last
* printed character.
* The same attributes (colors) are used for the entire text.
*
* If there is not enough space left at the end of the line,
* the output continues on the following line.
* As soon as the last window line is filled, the entire window area is
* moved up one line: The first line disappears, the bottom line is cleared.
*
* A line break also occurs whenever the character `\n` appears in the text.
*
* \param string Text to be printed
* \param length Length of text
* \param attrib Attribute for text
* \todo Implement Method
*/
void print(const char* string, size_t length, Attribute attrib = TextMode::Attribute()); //NOLINT
/*! \brief Delete all contents in the window and reset the cursor.
*
* \param character Fill character
* \param attrib Attribute for fill character
* \todo Implement Method
*/
void reset(char character = ' ', Attribute attrib = TextMode::Attribute());
};

25
main.cc Normal file
View File

@ -0,0 +1,25 @@
#include "boot/startup_ap.h"
#include "machine/lapic.h"
#include "debug/output.h"
const char * os_name = "MP" "StuBS";
// Main function (the bootstrap processor starts here)
extern "C" int main() {
unsigned int num_cpus = Core::count();
DBG_VERBOSE << "Number of CPUs: " << num_cpus << endl;
// Start application processors
ApplicationProcessor::boot();
return 0;
}
// Main function for application processors
extern "C" int main_ap() {
DBG_VERBOSE << "CPU core " << static_cast<int>(Core::getID())
<< " / LAPIC " << static_cast<int>(LAPIC::getID()) << " in main_ap()" << endl;
return 0;
}

123
object/key.cc Normal file
View File

@ -0,0 +1,123 @@
#include "object/key.h"
// Character table for scan codes for US keyboards
static struct {
const unsigned char normal, // Character without modifiers
shift, // Character with pressed Shift, Capslock, or in Numpad
alt; // Character with pressed Alt key
} ascii_tab[Key::Scancode::KEYS] = {
{ 0, 0, 0 }, // KEY_INVALID
{ 0, 0, 0 }, // KEY_ESCAPE
{ '1', '!', 0 }, // KEY_1
{ '2', '"', 253 }, // KEY_2
{ '3', 21, 0 }, // KEY_3
{ '4', '$', 0 }, // KEY_4
{ '5', '%', 0 }, // KEY_5
{ '6', '&', 0 }, // KEY_6
{ '7', '/', '{' }, // KEY_7
{ '8', '(', '[' }, // KEY_8
{ '9', ')', ']' }, // KEY_9
{ '0', '=', '}' }, // KEY_0
{ 225, '?', '\\'}, // KEY_DASH
{ 39, 96, 0 }, // KEY_EQUAL
{'\b', 0, 0 }, // KEY_BACKSPACE
{ 0, 0, 0 }, // KEY_TAB
{ 'q', 'Q', '@' }, // KEY_Q
{ 'w', 'W', 0 }, // KEY_W
{ 'e', 'E', 0 }, // KEY_E
{ 'r', 'R', 0 }, // KEY_R
{ 't', 'T', 0 }, // KEY_T
{ 'z', 'Z', 0 }, // KEY_Y
{ 'u', 'U', 0 }, // KEY_U
{ 'i', 'I', 0 }, // KEY_I
{ 'o', 'O', 0 }, // KEY_O
{ 'p', 'P', 0 }, // KEY_P
{ 129, 154, 0 }, // KEY_OPEN_BRACKET
{ '+', '*', '~' }, // KEY_CLOSE_BRACKET
{'\n', 0, 0 }, // KEY_ENTER
{ 0, 0, 0 }, // KEY_LEFT_CTRL
{ 'a', 'A', 0 }, // KEY_A
{ 's', 'S', 0 }, // KEY_S
{ 'd', 'D', 0 }, // KEY_D
{ 'f', 'F', 0 }, // KEY_F
{ 'g', 'G', 0 }, // KEY_G
{ 'h', 'H', 0 }, // KEY_H
{ 'j', 'J', 0 }, // KEY_J
{ 'k', 'K', 0 }, // KEY_K
{ 'l', 'L', 0 }, // KEY_L
{ 148, 153, 0 }, // KEY_SEMICOLON
{ 132, 142, 0 }, // KEY_APOSTROPH
{ '^', 248, 0 }, // KEY_GRAVE_ACCENT
{ 0, 0, 0 }, // KEY_LEFT_SHIFT
{ '#', 39, 0 }, // KEY_BACKSLASH
{ 'y', 'Y', 0 }, // KEY_Z
{ 'x', 'X', 0 }, // KEY_X
{ 'c', 'C', 0 }, // KEY_C
{ 'v', 'V', 0 }, // KEY_V
{ 'b', 'B', 0 }, // KEY_B
{ 'n', 'N', 0 }, // KEY_N
{ 'm', 'M', 230 }, // KEY_M
{ ',', ';', 0 }, // KEY_COMMA
{ '.', ':', 0 }, // KEY_PERIOD
{ '-', '_', 0 }, // KEY_SLASH
{ 0, 0, 0 }, // KEY_RIGHT_SHIFT
{ '*', '*', 0 }, // KEY_KP_STAR
{ 0, 0, 0 }, // KEY_LEFT_ALT
{ ' ', ' ', 0 }, // KEY_SPACEBAR
{ 0, 0, 0 }, // KEY_CAPS_LOCK
{ 0, 0, 0 }, // KEY_F1
{ 0, 0, 0 }, // KEY_F2
{ 0, 0, 0 }, // KEY_F3
{ 0, 0, 0 }, // KEY_F4
{ 0, 0, 0 }, // KEY_F5
{ 0, 0, 0 }, // KEY_F6
{ 0, 0, 0 }, // KEY_F7
{ 0, 0, 0 }, // KEY_F8
{ 0, 0, 0 }, // KEY_F9
{ 0, 0, 0 }, // KEY_F10
{ 0, 0, 0 }, // KEY_NUM_LOCK
{ 0, 0, 0 }, // KEY_SCROLL_LOCK
{ 0, '7', 0 }, // KEY_KP_7
{ 0, '8', 0 }, // KEY_KP_8
{ 0, '9', 0 }, // KEY_KP_9
{ '-', '-', 0 }, // KEY_KP_DASH
{ 0, '4', 0 }, // KEY_KP_4
{ 0, '5', 0 }, // KEY_KP_5
{ 0, '6', 0 }, // KEY_KP_6
{ '+', '+', 0 }, // KEY_KP_PLUS
{ 0, '1', 0 }, // KEY_KP_1
{ 0, '2', 0 }, // KEY_KP_2
{ 0, '3', 0 }, // KEY_KP_3
{ 0, '0', 0 }, // KEY_KP_0
{ 127, ',', 0 }, // KEY_KP_PERIOD
{ 0, 0, 0 }, // KEY_SYSREQ
{ 0, 0, 0 }, // KEY_EUROPE_2
{ '<', '>', '|' }, // KEY_F11
{ 0, 0, 0 }, // KEY_F12
{ 0, 0, 0 }, // KEY_KP_EQUAL
};
unsigned char Key::ascii() const {
// Select the correct table depending on the modifier bits.
// For the sake of simplicity, Shift and NumLock have precedence over Alt.
// The Ctrl modifier does not have a distinct table.
if (!valid()) {
return '\0';
} else if (shift
|| (caps_lock
&& (
(scancode >= KEY_Q && scancode <= KEY_P)
|| (scancode >= KEY_A && scancode <= KEY_L)
|| (scancode >= KEY_Z && scancode <= KEY_M)
)
)
|| (num_lock && scancode >= KEY_KP_7 && scancode <= KEY_KP_PERIOD)
) {
return ascii_tab[scancode].shift;
} else if (alt()) {
return ascii_tab[scancode].alt;
} else {
return ascii_tab[scancode].normal;
}
}

177
object/key.h Normal file
View File

@ -0,0 +1,177 @@
/*! \file
* \brief \ref Key, an abstraction for handling pressed keys and their modifiers
*/
#pragma once
#include "types.h"
/*! \brief Class that abstracts a key, made up of the scan code and the modifier bits.
*/
struct Key {
/*! \brief The keys' scan codes (code 1)
*/
enum Scancode : uint8_t {
// Invalid scan code
KEY_INVALID = 0,
// "real" valid scan codes
KEY_ESCAPE,
KEY_1,
KEY_2,
KEY_3,
KEY_4,
KEY_5,
KEY_6,
KEY_7,
KEY_8,
KEY_9,
KEY_0,
KEY_DASH,
KEY_EQUAL,
KEY_BACKSPACE,
KEY_TAB,
KEY_Q,
KEY_W,
KEY_E,
KEY_R,
KEY_T,
KEY_Y,
KEY_U,
KEY_I,
KEY_O,
KEY_P,
KEY_OPEN_BRACKET,
KEY_CLOSE_BRACKET,
KEY_ENTER,
KEY_LEFT_CTRL,
KEY_A,
KEY_S,
KEY_D,
KEY_F,
KEY_G,
KEY_H,
KEY_J,
KEY_K,
KEY_L,
KEY_SEMICOLON,
KEY_APOSTROPH,
KEY_GRAVE_ACCENT,
KEY_LEFT_SHIFT,
KEY_BACKSLASH,
KEY_Z,
KEY_X,
KEY_C,
KEY_V,
KEY_B,
KEY_N,
KEY_M,
KEY_COMMA,
KEY_PERIOD,
KEY_SLASH,
KEY_RIGHT_SHIFT,
KEY_KP_STAR,
KEY_LEFT_ALT,
KEY_SPACEBAR,
KEY_CAPS_LOCK,
KEY_F1,
KEY_F2,
KEY_F3,
KEY_F4,
KEY_F5,
KEY_F6,
KEY_F7,
KEY_F8,
KEY_F9,
KEY_F10,
KEY_NUM_LOCK,
KEY_SCROLL_LOCK,
KEY_KP_7,
KEY_KP_8,
KEY_KP_9,
KEY_KP_DASH,
KEY_KP_4,
KEY_KP_5,
KEY_KP_6,
KEY_KP_PLUS,
KEY_KP_1,
KEY_KP_2,
KEY_KP_3,
KEY_KP_0,
KEY_KP_PERIOD,
KEY_SYSREQ,
KEY_EUROPE_2,
KEY_F11,
KEY_F12,
KEY_KP_EQUAL,
// Number of keys (excluding aliases below)
KEYS,
// aliases
KEY_DIV = KEY_7,
KEY_DEL = KEY_KP_PERIOD,
KEY_UP = KEY_KP_8,
KEY_DOWN = KEY_KP_2,
KEY_LEFT = KEY_KP_4,
KEY_RIGHT = KEY_KP_6,
};
Scancode scancode;
// bit masks for the modifier keys
bool shift : 1,
alt_left : 1,
alt_right : 1,
ctrl_left : 1,
ctrl_right : 1,
caps_lock : 1,
num_lock : 1,
scroll_lock : 1;
/*! \brief Default constructor: Instantiates an invalid key by setting ASCII, scan code, and modifier bits to 0
*/
Key() : scancode(KEY_INVALID), shift(false), alt_left(false), alt_right(false),
ctrl_left(false), ctrl_right(false),
caps_lock(false), num_lock(false), scroll_lock(false) {}
/*! \brief Invalid keys have a scancode = 0
* \return Checks whether a key is valid.
*/
bool valid() const {
return scancode != KEY_INVALID && scancode < KEYS;
}
/*! \brief Marks the key as invalid by setting the scan code to 0.
*
*/
void invalidate() {
scancode = KEY_INVALID;
}
/*! \brief Get the key's ASCII value
* \return the key's ASCII value
*/
unsigned char ascii() const;
/*! \brief Indicates whether the ALT modifier is set
* \return `true` if ALT key was pressed during key press
*/
bool alt() const {
return alt_left || alt_right;
}
/*! \brief Indicates whether the CTRL modifier is set
* \return `true` if CTRL key was pressed during key press
*/
bool ctrl() const {
return ctrl_left || ctrl_right;
}
/*! \brief Conversion to char (ASCII code)
*
*/
operator char() const { //NOLINT since we want implicit conversions
return static_cast<char>(ascii());
}
};

143
tools/build.mk Normal file
View File

@ -0,0 +1,143 @@
# Build the kernel
# Folder the generated files will be placed in.
BUILDDIR ?= .build
# Build folder suffixes
OPTTAG = -opt
NOOPTTAG = -noopt
DBGTAG = -dbg
VERBOSETAG = -verbose
# C++
CXX = $(PREFIX)g++
CXXFLAGS_ARCH = -m64
CXXFLAGS_DEFAULT = -std=c++14 -ffreestanding -fno-pic -nodefaultlibs -nostdlib -nostdinc -I. -fno-rtti -fno-exceptions -Wno-write-strings -fno-stack-protector -mno-red-zone -g -gdwarf-2
CXXFLAGS_OPT = -O3 -fomit-frame-pointer
CXXFLAGS_WARNING = -Wall -Wextra -Werror -Wno-error=unused-parameter -Wno-non-virtual-dtor
CXXFLAGS_CLANG = -no-pie -Wno-error=unused-private-field -Wno-implicit-exception-spec-mismatch -Wno-error=unused-const-variable -Wno-unused-command-line-argument -Wno-unused-const-variable -fno-strict-aliasing
CXXFLAGS_GCC = -fno-tree-loop-distribute-patterns -no-pie -nostartfiles -Wstack-usage=1024 -Wno-error=stack-usage= -fno-threadsafe-statics
CXXFLAGS_NOFPU = -mno-mmx -mno-sse -mgeneral-regs-only
CXXFLAGS = $(CXXFLAGS_ARCH) $(CXXFLAGS_DEFAULT) $(CXXFLAGS_OPT) $(CXXFLAGS_NOFPU) $(CXXFLAGS_WARNING)
# Compiler specific flags
ifneq (,$(findstring clang,$(CXX)))
COMPILER := CLANG
CXXFLAGS += $(CXXFLAGS_CLANG)
else ifneq (,$(findstring g++,$(CXX)))
COMPILER := GCC
# g++ 6 does not support general-regs-only flag
ifeq "$(shell expr `$(CXX) -dumpversion | cut -f1 -d.` \<= 6)" "1"
CXXFLAGS := $(filter-out -mgeneral-regs-only,$(CXXFLAGS))
endif
CXXFLAGS += $(CXXFLAGS_GCC)
else
COMPILER :=
endif
# Assembly
ASM = nasm
ASMFLAGS = -f elf64
# Additional build utilities
OBJCOPY = $(PREFIX)objcopy
STRIP = $(PREFIX)strip
AR = $(PREFIX)ar
# C Runtime objects
CRTBEGIN_OBJECT = $(shell $(CXX) $(CXXFLAGS) --print-file-name=crtbegin.o)
CRTEND_OBJECT = $(shell $(CXX) $(CXXFLAGS) --print-file-name=crtend.o)
# GCC library
# Attention: libgcc.a must not use red-zone!
LIBGCC = $(shell $(CXX) $(CXXFLAGS) -print-libgcc-file-name )
# Subdirectories with sources
VPATH = $(sort $(dir $(CC_SOURCES) $(ASM_SOURCES)))
# Lists of object files that are generated by compilation:
# Note that the variables containing the input files are to be defined by
# the Makefiles prior to including this common.mk.
ifdef CRTI_SOURCE
CRTI_OBJECT = $(addprefix $(BUILDDIR)/,$(addsuffix .o,$(CRTI_SOURCE)))
else
CRTI_OBJECT = $(shell $(CXX) $(CXXFLAGS) --print-file-name=crti.o)
endif
ifdef CRTN_SOURCE
CRTN_OBJECT = $(addprefix $(BUILDDIR)/,$(addsuffix .o,$(CRTN_SOURCE)))
else
CRTN_OBJECT = $(shell $(CXX) $(CXXFLAGS) --print-file-name=crtn.o)
endif
CC_OBJECTS = $(addprefix $(BUILDDIR)/,$(CC_SOURCES:.cc=.o))
DEP_FILES = $(addprefix $(BUILDDIR)/,$(CC_SOURCES:.cc=.d) $(addsuffix .d,$(ASM_SOURCES)))
ASM_OBJECTS = $(addprefix $(BUILDDIR)/,$(addsuffix .o,$(filter-out $(CRTI_SOURCE) $(CRTN_SOURCE),$(ASM_SOURCES))))
# Dependency files
$(BUILDDIR)/%.d : %.cc $(MAKEFILE_LIST)
@echo "DEP $<"
@mkdir -p $(@D)
$(VERBOSE) $(CXX) $(CXXFLAGS) -MM -MT $(BUILDDIR)/$*.o -MF $@ $<
$(BUILDDIR)/%.asm.d : %.asm $(MAKEFILE_LIST)
@echo "DEP $<"
@mkdir -p $(@D)
$(VERBOSE) $(ASM) $(ASMFLAGS) -M -MT $(BUILDDIR)/$*.asm.o -MF $@ $<
# Object files
$(BUILDDIR)/%.o : %.cc $(MAKEFILE_LIST)
@echo "CXX $<"
@mkdir -p $(@D)
$(VERBOSE) $(CXX) -c $(CXXFLAGS) -o $@ $<
$(BUILDDIR)/%.asm.o : %.asm $(MAKEFILE_LIST)
@echo "ASM $<"
@mkdir -p $(@D)
$(VERBOSE) $(ASM) $(ASMFLAGS) -o $@ $<
# The standard target 'clean' removes the whole generated system, the object files, and the dependency files.
clean::
@echo "RM $(BUILDDIR)"
$(VERBOSE) rm -rf "$(BUILDDIR)" "$(BUILDDIR)$(OPTTAG)" "$(BUILDDIR)$(NOOPTTAG)" "$(BUILDDIR)$(DBGTAG)" "$(BUILDDIR)$(VERBOSETAG)"
# Target issuing a nested call to make generating a fully optimized systems without assertions.
%$(OPTTAG):
$(VERBOSE) $(MAKE) BUILDDIR="$(BUILDDIR)$(OPTTAG)" ISODIR="$(ISODIR)$(OPTTAG)" CXXFLAGS_OPT="-Ofast -fomit-frame-pointer -flto -march=westmere -DNDEBUG" $*
# Target issuing a nested call to make generating a non-optimized system.
%$(NOOPTTAG):
$(VERBOSE) $(MAKE) BUILDDIR="$(BUILDDIR)$(NOOPTTAG)" ISODIR="$(ISODIR)$(NOOPTTAG)" CXXFLAGS_OPT="-O0" $*
# Target issuing a nested call to make generating a system optimized for debugging.
%$(DBGTAG):
$(VERBOSE) $(MAKE) BUILDDIR="$(BUILDDIR)$(DBGTAG)" ISODIR="$(ISODIR)$(DBGTAG)" CXXFLAGS_OPT="-Og -fno-omit-frame-pointer" $*
# Target issuing a nested call to make generating a system with verbose output.
%$(VERBOSETAG):
$(VERBOSE) $(MAKE) BUILDDIR="$(BUILDDIR)$(VERBOSETAG)" ISODIR="$(ISODIR)$(VERBOSETAG)" CXXFLAGS_OPT="-DVERBOSE" $*
# Documentation
help::
@/bin/echo -e "" \
"All targets exist in different flavours in addition to \e[2;3m<name>\e[0m:\n" \
"\e[2;3m<name>\e[0;3m-noopt\e[0m, \e[2;3m<name>\e[0;3m-opt\e[0m, \e[2;3m<name>\e[0;3m-dbg\e[0m, and \e[2;3m<name>\e[0;3m-verbose\e[0m.\n" \
"Targets suffixed with \e[3m-noopt\e[0m are compiled without optimizations,\n" \
"\e[3m-opt\e[0m targets produce a highly optimized binary, while\n" \
"\e[3m-dbg\e[0m targets only use optimizations not hindering debugging.\n" \
"Targets suffixed with \e[3m-verbose\e[0m generate binaries including\n" \
"verbose output (via \e[3mDBG_VERBOSE\e[0m), making such targets useful for debugging.\n" \
"To get a verbose make output, clear VERBOSE, e.g. \e[3mmake VERBOSE=\e[0m.\n" \
"The following targets are available (each target can be suffixed by \e[3m-noopt\e[0m\n" \
"and \e[3m-verbose\e[0m):\n\n" \
" \e[3mall\e[0m Builds $(PROJECT), generating an ELF binary\n\n"
# Print warnings, if appropriate
ifeq (,$(COMPILER))
$(warning Unknown (and potentially unsupported) compiler "$(CXX)"!)
endif
# Include dependency files (generated via gcc flag -MM)
ifneq ($(MAKECMDGOALS),clean)
-include $(DEP_FILES)
endif
# Phony targets
.PHONY: clean help

31
tools/common.mk Normal file
View File

@ -0,0 +1,31 @@
# Common include Makefile
# Hide commands
VERBOSE = @
# Prefix for toolchain binaries
PREFIX ?=
# Project name
PROJECT ?= "MPStuBS"
help::
@/bin/echo -e "\n" \
"\e[1mMAKEFILE for the teaching operating system $(PROJECT)\e[0m\n" \
"--------------------------------------------------\n\n" \
"Executing '\e[4mmake\e[0m' will compile the operating system from source.\n"
# Get current directory path
CURRENT_DIR := $(dir $(lastword $(MAKEFILE_LIST)))
# Include Makefile scripts
include $(CURRENT_DIR)/build.mk
include $(CURRENT_DIR)/qemu.mk
include $(CURRENT_DIR)/image.mk
include $(CURRENT_DIR)/linter.mk
include $(CURRENT_DIR)/remote.mk
# Disable buitlin rules
MAKEFLAGS += --no-builtin-rules
MAKEFLAGS += --no-builtin-variables
# Disable buitlin suffixes
.SUFFIXES:

6771
tools/cpplint.py vendored Normal file

File diff suppressed because it is too large Load Diff

11
tools/gdb/stubs.py Normal file
View File

@ -0,0 +1,11 @@
import sys
# path to local stubs package
sys.path.insert(1, './tools/gdb/')
# path to checkout of qmp package 'https://pypi.org/project/qemu.qmp/'
sys.path.insert(1, '/proj/i4stubs/tools/python/')
try:
import stubs
except Exception as e:
print(f"could not load gdb stubs plugin: {str(e)}", file=sys.stderr)
pass

176
tools/gdb/stubs/__init__.py Normal file
View File

@ -0,0 +1,176 @@
from . import monitor
from . import idt
from . import gdt
from . import paging
import gdb
import traceback
qemu = monitor.Monitor('qmp.sock')
def _active_cr3():
i = gdb.selected_inferior()
cr3_desc = i.architecture().registers().find('cr3')
cr3 = gdb.selected_frame().read_register(cr3_desc)
val = cr3.cast(gdb.lookup_type('unsigned long long'))
return val
class PageVisualizer(gdb.Command):
"""resolves a virtual adress: vaview [<cr3>] <virtual address>"""
def __init__(self, monitor):
super(PageVisualizer, self).__init__("vaview", gdb.COMMAND_SUPPORT)
self.monitor = monitor
pass
def invoke(self, arg, from_tty):
args = gdb.string_to_argv(arg)
base = None
va = None
if len(args) == 1:
base = _active_cr3()
va = gdb.parse_and_eval(args[0])
pass
elif len(args) == 2:
base = gdb.parse_and_eval(args[0])
va = gdb.parse_and_eval(args[1])
else:
raise gdb.GdbError("vaview [<cr3>] <virtual address>")
try:
base = int(base)
if va.type.code == gdb.TYPE_CODE_FUNC:
va = int(va.address)
else:
va = int(va)
pass
mmu = paging.MMU(self.monitor, paging.Arch.X86_64)
page, size, offset, entries = mmu.resolve(base, va)
parts = mmu.split_addr(va)
print(
f"cr3: 0x{base:x}; vaddr: 0x{va:x} = "
f"({ '|'.join([hex(int(p)) for p in parts[0]]) })"
)
for e in entries:
print(e)
if page is not None and offset is not None:
print(f"0x{va:x} -> 0x{page:x}:{offset:x}")
else:
print(f"0x{va:x} -> <unmapped>")
except Exception as e:
traceback.print_exc()
raise e
pass
def _gdtidtargs(arg, kind):
args = gdb.string_to_argv(arg)
if len(args) == 0:
mapping = _active_cr3()
cpuid = current_cpuid()
regs = qemu.registers()[cpuid]
base, limit = regs[kind]
pass
elif len(args) == 2:
# base, limit
mapping = _active_cr3()
base = gdb.parse_and_eval(args[0])
limit = gdb.parse_and_eval(args[1])
try:
limit = int(limit)
if base.type.code == gdb.TYPE_CODE_FUNC:
base = int(base.address)
else:
base = int(base)
except Exception as e:
traceback.print_exc()
raise e
pass
elif len(args) == 3:
# mapping, cr3, limit
mapping = gdb.parse_and_eval(args[0])
base = gdb.parse_and_eval(args[1])
limit = gdb.parse_and_eval(args[2])
try:
limit = int(limit)
if base.type.code == gdb.TYPE_CODE_FUNC:
base = int(base.address)
else:
base = int(base)
except Exception as e:
traceback.print_exc()
raise e
pass
else:
raise gdb.GdbError("invalid args")
mapping = int(mapping)
return mapping, base, limit
class GDTVisualizer(gdb.Command):
"""print the GDT: gdtview [[<mapping>] <base> <limit>]"""
def __init__(self, monitor):
super(GDTVisualizer, self).__init__("gdtview", gdb.COMMAND_SUPPORT)
self.monitor = monitor
pass
def invoke(self, arg, from_tty):
mapping, base, limit = _gdtidtargs(arg, 'gdt')
mmu = paging.MMU(self.monitor, paging.Arch.X86_64)
gdt.GDT(mmu, mapping, base, limit).print()
class InterruptGateVisualizer(gdb.Command):
"""print the IDT: idtview [[<mapping>] <base> <limit>]"""
def __init__(self, monitor):
super(InterruptGateVisualizer, self).__init__(
"idtview", gdb.COMMAND_USER)
self.monitor = monitor
pass
def invoke(self, args, from_tty):
mapping, base, limit = _gdtidtargs(args, 'idt')
mmu = paging.MMU(self.monitor, paging.Arch.X86_64)
try:
idt.IDT(mmu, mapping, base, limit).print()
except Exception as e:
traceback.print_exc()
raise e
class CurrentThread(gdb.Function):
"""Fetch the current thread `Dispatcher::life` """
def __init__(self):
super(CurrentThread, self).__init__("current")
pass
def invoke(self):
if not gdb.selected_thread():
return None
cpuid = current_cpuid()
sym, field = gdb.lookup_symbol('Dispatcher::life')
instances = sym.value()['instances']
thread_ptr = instances[cpuid]['value']
return thread_ptr
def current_cpuid():
inferior = gdb.selected_inferior()
max_threadid = len(inferior.threads())
assert (max_threadid > 0)
_, threadid, _ = gdb.selected_thread().ptid
assert (threadid > 0 and threadid <= max_threadid)
cpuid = threadid - 1
return cpuid
pass
InterruptGateVisualizer(qemu)
PageVisualizer(qemu)
GDTVisualizer(qemu)
CurrentThread()

209
tools/gdb/stubs/gdt.py Normal file
View File

@ -0,0 +1,209 @@
from . import helper
class SegmentDescriptor(object):
pass
def __init__(self, memview):
assert (len(memview) > 7)
self.kind = '<generic descriptor>'
raw = int.from_bytes(memview[0:8], byteorder='little')
self.base = helper.bits(raw, 16, 24) \
| helper.bits(raw, 32 + 24, 8) << 24
self.limit = helper.bits(raw, 0, 16) \
| helper.bits(raw, 32 + 16, 4) << 16
self.type = helper.bits(raw, 32 + 8, 4)
self.g = helper.bits(raw, 32 + 23, 1)
self.p = helper.bits(raw, 32 + 15, 1)
self.l = helper.bits(raw, 32 + 21, 1)
self.dpl = helper.bits(raw, 32 + 13, 2)
pass
def create(memview):
raw = int.from_bytes(memview[0:8], byteorder='little')
feature = helper.bits(raw, 32 + 11, 2)
if feature == 2:
return DataSegmentDescriptor(memview)
elif feature == 3:
return CodeSegmentDescriptor(memview)
else:
feature = helper.bits(raw, 32 + 8, 5)
if feature == 0x00:
return NullDescriptor(memview)
elif feature == 0x0f:
return CallGateDescriptor(memview)
else:
return SystemSegmentDescriptor.create(memview)
pass
def __str__(self):
verbose = self.str_verbose()
return f"<{self.kind}> 0x{self.base:x}:{self.limit:x} [{verbose}]"
if verbose:
return f"<{self.kind}> 0x{self.base:x}:{self.limit:x} [{verbose}]"
else:
return f"<{self.kind}> 0x{self.base:x}:{self.limit:x}"
def size(self):
return None
class DataSegmentDescriptor(SegmentDescriptor):
def __init__(self, memview):
super(DataSegmentDescriptor, self).__init__(memview)
self.kind = 'data'
self.raw = int.from_bytes(memview[0:8], byteorder='little')
self.b = helper.bits(self.raw, 32 + 22, 1)
self.avl = helper.bits(self.raw, 32 + 20, 1)
self.e = helper.bits(self.raw, 32 + 10, 1)
self.w = helper.bits(self.raw, 32 + 9, 1)
self.a = helper.bits(self.raw, 32 + 8, 1)
def str_verbose(self):
msg = "|".join([
f"g{self.g}", f"b{self.b}", f"l{self.l}",
f"avl{self.avl}", f"p{self.p}", f"dpl{self.dpl}",
f"e{self.e}", f"w{self.w}", f"a{self.a} {self.raw:x}"
])
if self.l:
msg = msg + '"invalid l"'
pass
return msg
def size(self):
return 8
class CodeSegmentDescriptor(SegmentDescriptor):
def __init__(self, memview):
super(CodeSegmentDescriptor, self).__init__(memview)
self.kind = 'code'
self.raw = int.from_bytes(memview[0:8], byteorder='little')
self.d = helper.bits(self.raw, 32 + 22, 1)
self.avl = helper.bits(self.raw, 32 + 20, 1)
self.c = helper.bits(self.raw, 32 + 10, 1)
self.r = helper.bits(self.raw, 32 + 9, 1)
self.a = helper.bits(self.raw, 32 + 8, 1)
def size(self):
return 8
def str_verbose(self):
return "|".join([
f"g{self.g}", f"d{self.d}", f"l{self.l}",
f"avl{self.avl}", f"p{self.p}", f"dpl{self.dpl}",
f"c{self.c}", f"r{self.r}", f"a{self.a}", f"{self.raw:x}"
])
class SystemSegmentDescriptor(SegmentDescriptor):
def __init__(self, memview):
super(SystemSegmentDescriptor, self).__init__(memview)
self.kind = 'system'
self.raw = int.from_bytes(memview[0:16], byteorder='little')
pass
def create(memview):
raw = int.from_bytes(memview[0:8], byteorder='little')
type = helper.bits(raw, 32 + 8, 4)
masks = [(TSSDescriptor, 0x9)]
for ctor, mask in masks:
if type & mask == mask:
return ctor(memview)
pass
else:
raise Exception("no matching Descriptor")
pass
def size(self):
return 16
def str_verbose(self):
return f"raw: {self.raw}"
class TSSDescriptor(SystemSegmentDescriptor):
def __init__(self, memview):
super(SystemSegmentDescriptor, self).__init__(memview)
self.kind = 'tss'
self.raw = int.from_bytes(memview[0:16], byteorder='little')
self.type = helper.bits(self.raw, 32 + 8, 4)
self.base = helper.bits(self.raw, 16, 24) \
| helper.bits(self.raw, 32 + 24, 8) << 24 \
| helper.bits(self.raw, 64, 32) << 32
self.avl = helper.bits(self.raw, 32 + 20, 1)
self.null = helper.bits(self.raw, 3 * 32 + 8, 5)
self.reserved_a = helper.bits(self.raw, 3 * 32 + 13, 32 - 13)
self.reserved_b = helper.bits(self.raw, 3 * 32, 8)
assert (self.null == 0)
def str_verbose(self):
b = helper.bits(self.raw, 32 + 22, 1)
e = helper.bits(self.raw, 32 + 12, 1)
msg = "|".join([
f"rsvd: {self.reserved_a}", f"0: {self.null}",
f"rsvd: {self.reserved_b}" f"g: {self.g}",
f"0{b}", f"0{self.l}", f"avl{self.avl}",
f"p{self.p}", f"dpl{self.dpl}", f"0{e}",
f"type: {self.type:x}", f"{self.raw:x}"
])
return msg
class NullDescriptor(SystemSegmentDescriptor):
def __init__(self, memview):
super(NullDescriptor, self).__init__(memview)
self.kind = 'call gate'
self.kind = 'null'
self.raw = int.from_bytes(memview[0:8], byteorder='little')
pass
def size(self):
return 8
def str_verbose(self):
return f"{self.raw:x}"
class CallGateDescriptor(SystemSegmentDescriptor):
def __init__(self, mems):
super(CallGateDescriptor, self).__init__(mems)
self.kind = 'call gate'
pass
pass
class GDT:
def __init__(self, mmu, mapping, base, limit):
self.mmu = mmu
self.mapping = mapping
self.limit = limit
self.base = base
pass
def print(self):
offset = 0
print(f"base: 0x{self.base:x}, limit: 0x{self.limit:x}")
while offset < self.limit + 1:
desc_bytes = self.mmu.linear_bytes(
self.mapping,
self.base + offset,
16
)
segment = SegmentDescriptor.create(desc_bytes)
print(f"[0x{offset:x}]: {str(segment)}")
offset += segment.size()
pass
pass

33
tools/gdb/stubs/helper.py Normal file
View File

@ -0,0 +1,33 @@
def compact_entries(entries, cmp_eq):
l = []
for idx, entry in entries:
# prepare first item in list
if len(l) == 0:
l.append((idx, idx, entry))
continue
# check for duplicate entry
if cmp_eq(l[-1][2], entry):
old = l.pop()
l.append((old[0], idx, old[2]))
continue
l.append((idx, idx, entry))
return l
def bits(value, start, nbits):
# drop any bits positioned higher than (start + nbits)
mask = 1 << (start + nbits) # mind the overflow in any other language without bigints!
mask = mask - 1
value = value & mask
# select only nbits
mask = (1 << nbits) - 1
mask = mask << start
value = value & mask
# shift result accordingly
value = value >> start
return value

68
tools/gdb/stubs/idt.py Normal file
View File

@ -0,0 +1,68 @@
import gdb
from . import helper
class IDT:
class InterruptTrapGate:
def __init__(self, raw):
self.raw = raw # 128-bit type
self.offset = helper.bits(self.raw, 0, 16) \
| helper.bits(self.raw, 32 + 16, 16) << 16\
| helper.bits(self.raw, 64, 32) << 32
self.segment = helper.bits(self.raw, 16, 16)
self.ist = helper.bits(self.raw, 32, 4)
self.type = helper.bits(self.raw, 32 + 8, 4)
self.dpl = helper.bits(self.raw, 32 + 13, 2)
self.p = helper.bits(self.raw, 32 + 15, 1)
def _get_symbol(self):
block = gdb.block_for_pc(self.offset)
if block is None:
return "?"
if block.function is None:
return "?"
return block.function
def __str__(self):
symbol = self._get_symbol()
addr = f"0x{self.segment:x}:0x{self.offset:x}"
bits = "|".join([
f"off:{self.offset:x}", f"p:{self.p}",
f"dpl:{self.dpl}", f"type:{self.type:x}",
f"ist:{self.ist:x}", f"ss:{self.segment:x}"
])
return f"{addr} <{symbol}> [{bits}] raw={self.raw:032x}"
def __init__(self, mmu, mapping, base, limit):
self.mmu = mmu
self.mapping = mapping
self.base = base
self.limit = limit
self.desc_size = 16
self.nentry = (limit + 1) // self.desc_size
pass
def print(self):
entries = []
for i in range(0, self.nentry):
offset = i * self.desc_size
desc_bytes = self.mmu.linear_bytes(
self.mapping,
self.base + offset,
self.desc_size
)
desc = int.from_bytes(desc_bytes, 'little')
gate = self.InterruptTrapGate(desc)
entries.append((i, gate))
def cmp_eq(a, b):
return a.offset == b.offset and a.segment == b.segment
compact = helper.compact_entries(entries, cmp_eq)
for start, stop, gate in compact:
if start == stop:
print(f"[{start}]:\t{str(gate)}")
else:
print(f"[{start}-{stop}]:\t{str(gate)}")
pass
pass

131
tools/gdb/stubs/monitor.py Normal file
View File

@ -0,0 +1,131 @@
import asyncio
import re
from qemu.qmp import QMPClient
class Monitor:
def __init__(self, socket):
self.socket = socket
self.qmp = QMPClient(f'Monitor: {socket}')
pass
async def _connect(self):
await self.qmp.connect(self.socket)
async def _disconnect(self):
await self.qmp.disconnect()
async def _hmc(self, cmd):
raw = await self.qmp.execute(
'human-monitor-command', {'command-line': cmd})
return raw
def registers(self):
registers = dict()
async def query_registers():
await self._connect()
raw = await self._hmc('info registers -a')
await self._disconnect()
return raw
raw = asyncio.run(query_registers())
# each paragraph of `raw` contains the registers for a logical CPU
cpu_split = raw.split('\r\n\r\n')
cpu_split_stripped = (x.strip() for x in cpu_split)
cpu_split = [s for s in cpu_split_stripped if s]
# general purpose registers
def fetch_gpr(input):
registers = dict()
gprs = ['RAX', 'RBX', 'RCX', 'RDX', 'RSI', 'RDI',
'RBP', 'RSP', 'R8', 'R9', 'R10', 'R11',
'R12', 'R13', 'R14', 'R15', 'RIP']
for gpr in gprs:
pattern = rf"{gpr}\s?=(?P<{gpr}>\w{{16}})"
match = re.search(pattern, input)
value_raw = match.group(gpr)
value = int(value_raw, 16)
registers[gpr.lower()] = value
return registers
# control registers
def fetch_cr(input):
registers = dict()
for cr in ['CR0', 'CR2', 'CR3', 'CR4']:
pattern = rf"{cr}=(?P<{cr}>\w{{8,16}})"
match = re.search(pattern, input)
value_raw = match.group(cr)
value = int(value_raw, 16)
registers[cr.lower()] = value
return registers
# desriptor tables
def fetch_dt(input):
registers = dict()
for tbl in ['GDT', 'IDT']:
pattern = rf"{tbl}\s*=\s*(?P<{tbl}_base>\w{{16}})" \
rf"\s+(?P<{tbl}_limit>\w{{8}})"
match = re.search(pattern, input)
base_raw = match.group(f"{tbl}_base")
limit_raw = match.group(f"{tbl}_limit")
base = int(base_raw, 16)
limit = int(limit_raw, 16)
registers[tbl.lower()] = (base, limit)
return registers
registers = dict()
for cpuid, regstr in enumerate(cpu_split):
assert (regstr is not None and len(regstr) > 0)
registers[cpuid] = dict()
registers[cpuid].update(fetch_gpr(regstr))
registers[cpuid].update(fetch_cr(regstr))
registers[cpuid].update(fetch_dt(regstr))
return registers
def virtual_memory(self, addr, size):
# byte, word, double word, giant
types = {1: 'b', 2: 'w', 4: 'd', 8: 'g'}
assert (size in types)
async def query_virtual_memory():
await self._connect()
res = await self._hmc(f"x/x{types[size]} {addr}")
await self._disconnect()
return res
res = asyncio.run(query_virtual_memory())
match = re.match(r"[a-f\d]+:\s*(0x[a-f\d]+)", res)
assert (match)
return int(match.group(1), 16)
def physical_memory(self, addr, size):
# byte, word, double word, giant
types = {1: 'b', 2: 'w', 4: 'd', 8: 'g'}
assert (size in types)
async def query_physical_memory():
await self._connect()
res = await self._hmc(f"xp/x{types[size]} {addr}")
await self._disconnect()
return res
res = asyncio.run(query_physical_memory())
match = re.match(r"[a-f\d]+:\s*(0x[a-f\d]+)", res)
assert (match)
return int(match.group(1), 16)
def gva2gpa(self, addr):
async def query_gva2gpa():
await self._connect()
res = await self._hmc(f"gva2gpa {addr}")
await self._disconnect()
return res
res = asyncio.run(query_gva2gpa())
if res == 'Unmapped\r\n':
return None
match = re.match(r"gpa:\s*0x([\da-f]+)", res)
assert (match)
return int(match.group(1), 16)

333
tools/gdb/stubs/paging.py Normal file
View File

@ -0,0 +1,333 @@
from enum import Enum
from . import helper
class Arch(Enum):
X86 = 0
X86_64 = 1
class PageLevel(Enum):
PML4 = 0
DirectoryPtr = 1
Directory = 2
Table = 3
class PageTableEntry:
def __init__(self, base, idx, mmu, value):
self.base = base
self.idx = idx
self.raw = value
self.faulty = False
self.present = helper.bits(value, 0, 1)
self.rw = helper.bits(value, 1, 1)
self.us = helper.bits(value, 2, 1)
self.pwt = helper.bits(value, 3, 1)
self.pcd = helper.bits(value, 4, 1)
self.a = helper.bits(value, 5, 1)
self.ps = helper.bits(value, 7, 1)
self.r = helper.bits(value, 11, 1)
self.xd = helper.bits(value, 63, 1)
self.reference = helper.bits(value, 12, mmu.M)
self.rsvd = helper.bits(value, mmu.M, 64 - mmu.M)
pass
def addr(self):
return self.reference << 12
def name(self):
raise Exception("must be implemented by inheriting class")
def description(self):
raise Exception("must be implemented by inheriting class")
def __str__(self):
return f"<{self.name()}> {self.description()}"
class PML4Entry(PageTableEntry):
def __init__(self, base, idx, mmu, value):
super().__init__(base, idx, mmu, value)
if (self.ps != 0):
self.faulty = True
pass
def name(self):
return f"PML4 (0x{self.base:x}[0x{self.idx:x}])"
def description(self):
if not self.present:
return f"[p:0|raw: {self.raw:x}]"
return "[" + str.join('|', [
"p:1", f"rw:{self.rw}", f"us:{self.us}", f"pwt:{self.pwt}",
f"pcd:{self.pcd}", f"a:{self.a}", f"rsvd:{self.ps}",
f"r:{self.r}", f"addr:0x{self.reference:x}",
f"rsvd:{self.rsvd}"
]) + f"] = {self.raw:x}"
pass
class PDPEntry(PageTableEntry):
def __init__(self, base, idx, mmu, value):
super().__init__(base, idx, mmu, value)
self.d = helper.bits(value, 6, 1)
self.g = helper.bits(value, 8, 1)
self.pat = helper.bits(value, 12, 1)
self.xd = helper.bits(value, 63, 1)
if self.ps == 1:
self.reference = helper.bits(value, 30, mmu.M - (30 - 12))
def name(self):
return f"PDP (0x{self.base:x}[0x{self.idx:x}])"
def addr(self):
if self.ps == 1:
return self.reference << (12 + 18)
return self.reference << 12
def description(self):
if not self.present:
return f"[p:0|raw: {self.raw:x}]"
return "[" + str.join('|', [
"p:1", f"rw:{self.rw}", f"us:{self.us}", f"pwt:{self.pwt}",
f"pcd:{self.pcd}", f"a:{self.a}", f"d:{self.d}", f"ps:{self.ps}",
f"g:{self.g}", f"r:{self.r}", f"addr:0x{self.reference:x}",
f"rsvd:{self.rsvd}"
]) + f"] = {self.raw:x}"
pass
class PDEntry(PageTableEntry):
def __init__(self, base, idx, mmu, value):
super().__init__(base, idx, mmu, value)
self.d = helper.bits(value, 6, 1)
self.g = helper.bits(value, 8, 1)
self.pat = helper.bits(value, 12, 1)
self.xd = helper.bits(value, 63, 1)
if self.ps == 1:
self.reference = helper.bits(value, 21, mmu.M - (21 - 12))
def addr(self):
if self.ps == 1:
return self.reference << (12 + 9)
return self.reference << 12
def name(self):
return f"PD (0x{self.base:x}[0x{self.idx:x}])"
def description(self):
if not self.present:
return f"[p:0|raw: {self.raw:x}]"
desc = [
"p:1", f"rw:{self.rw}", f"us:{self.us}", f"pwt:{self.pwt}",
f"pcd:{self.pcd}", f"a:{self.a}", f"d:{self.d}", f"ps:{self.ps}",
f"g:{self.g}", f"r:{self.r}", f"r:{self.r}"
]
if self.ps == 1:
desc.append(f"pat:{self.pat}")
desc = desc + [
f"addr:0x{self.reference:x}", f"rsvd:{self.rsvd}", f"xd:{self.xd}"
]
return "[" + str.join('|', desc) + f"] = {self.raw:x}"
pass
class PTEntry(PageTableEntry):
def __init__(self, base, idx, mmu, value):
super().__init__(base, idx, mmu, value)
self.d = helper.bits(value, 6, 1)
self.g = helper.bits(value, 8, 1)
self.xd = helper.bits(value, 63, 1)
def name(self):
return f"PT (0x{self.base:x}[0x{self.idx:x}])"
def description(self):
if not self.present:
return f"[p:0|raw: {self.raw:x}]"
desc = [
"p:1", f"rw:{self.rw}", f"us:{self.us}", f"pwt:{self.pwt}",
f"pcd:{self.pcd}", f"a:{self.a}", f"d:{self.d}", f"ps:{self.ps}",
f"g:{self.g}", f"r:{self.r}", f"r:{self.r}",
f"addr:0x{self.reference:x}", f"rsvd:{self.rsvd}", f"xd:{self.xd}"
]
return "[" + str.join('|', desc) + f"] = {self.raw:x}"
class FourLevelPagingTable:
entries = 512
pass
def __init__(self, mmu, base):
self.mmu = mmu
self.base = base
self.descriptor_size = 8
def _create(self, base, idx, mmu, val):
pass
def __iter__(self):
table = self
class TableIterator:
def __init__(self):
self.idx = 0
def __next__(self):
if self.idx < FourLevelPagingTable.entries:
idx = self.idx
e = table.entry(self.idx)
self.idx = self.idx + 1
return idx, e
raise StopIteration
return TableIterator()
def name(self):
return f"table_{hex(self.base)}"
def entry(self, idx):
offset = idx * self.descriptor_size
val = self.mmu.monitor.physical_memory(
self.base + offset, self.descriptor_size)
return self._create(self.base, idx, self.mmu, val)
pass
class PML4Table(FourLevelPagingTable):
def _create(self, base, idx, mmu, val):
return PML4Entry(base, idx, self.mmu, val)
pass
class PageDirectoryPointerTable(FourLevelPagingTable):
def _create(self, base, idx, mmu, val):
return PDPEntry(base, idx, self.mmu, val)
pass
class PageDirectory(FourLevelPagingTable):
def _create(self, base, idx, mmu, val):
return PDEntry(base, idx, self.mmu, val)
pass
pass
class PageTable(FourLevelPagingTable):
def _create(self, base, idx, mmu, val):
return PTEntry(base, idx, self.mmu, val)
pass
pass
class MMU:
def __init__(self, monitor, arch):
self.monitor = monitor
if arch == Arch.X86:
self.bits = 32
self.M = 20
pass
else:
self.bits = 64
self.M = 48 # todo check
pass
assert (self.bits == 64) # TODO x86
pass
def assert_width(self, addr):
assert ((1 << self.bits) > addr)
pass
def split_addr(self, addr: int) -> list[int]:
parts = []
# 4Ki pages
parts.append([
(addr >> 39) & 0x1ff,
(addr >> 30) & 0x1ff,
(addr >> 21) & 0x1ff,
(addr >> 12) & 0x1ff,
addr & 0xfff
])
# 2Mi pages
parts.append([
(addr >> 39) & 0x1ff,
(addr >> 30) & 0x1ff,
(addr >> 21) & 0x1ff,
addr & 0x1fffff
])
# 1Gi pages
parts.append([
(addr >> 39) & 0x1ff,
(addr >> 30) & 0x1ff,
addr & 0x3fffffff
])
return parts
def resolve(self, base: int, addr: int):
self.assert_width(addr)
entries = list()
parts = self.split_addr(addr)
pml4_tbl = PML4Table(self, base)
pml4_idx = parts[0][0]
pml4_entry = pml4_tbl.entry(pml4_idx)
entries.append(pml4_entry)
if not pml4_entry.present:
return (None, None, None, entries)
pdp_tbl = PageDirectoryPointerTable(self, pml4_entry.addr())
pdp_idx = parts[0][1]
pdp_entry = pdp_tbl.entry(pdp_idx)
entries.append(pdp_entry)
if not pdp_entry.present:
return (None, None, None, entries)
if (pdp_entry.ps):
return pdp_entry.addr(), (4096 << 18), parts[2][2], entries
pd_tbl = PageDirectory(self, pdp_entry.addr())
pd_idx = parts[0][2]
pd_entry = pd_tbl.entry(pd_idx)
entries.append(pd_entry)
if not pd_entry.present:
return (None, None, None, entries)
if (pd_entry.ps):
return pd_entry.addr(), (4096 << 9), parts[1][3], entries
pt_tbl = PageTable(self, pd_entry.addr())
pt_idx = parts[0][3]
pt_entry = pt_tbl.entry(pt_idx)
entries.append(pt_entry)
if not pt_entry.present:
return (None, None, None, entries)
physical = pt_entry.addr()
return physical, 4096, parts[0][4], entries
def linear_bytes(self, mapping, addr, len):
chunks = []
while len > 0:
page, size, offset, _ = self.resolve(mapping, addr)
# read larger 8 byte blocks
if offset % 8 == 0:
while offset + 8 < size and len >= 8:
b = self.monitor.physical_memory(page + offset, 8)
chunks.append(int.to_bytes(b, 8, 'little'))
len = len - 8
offset = offset + 8
pass
# read single bytes
while offset < size and len > 0:
b = self.monitor.physical_memory(page + offset, 1)
chunks.append(int.to_bytes(b, 1, 'little'))
len = len - 1
offset = offset + 1
pass
addr = page + size
pass
return bytes().join(chunks)

101
tools/image.mk Normal file
View File

@ -0,0 +1,101 @@
# Generates a bootable ISO image that can be transferred to external media, such as CDs or USB sticks.
# This will install, in addition to your kernel, the bootloader GRUB (https://www.gnu.org/software/grub/).
#
# The target 'gemu-iso' is used to test the image generated with 'iso'.
#
# Assuming that a USB mass-storage devices is connected as, for instance /dev/sdc, the target 'usb-sdc'
# can be used to make your device bootable (requires root access, substitute sdc with the matching device).
# Alternatively, you can burn the .iso file directly to CD.
DD = dd
XORRISO = xorriso
MKISO = grub-mkrescue
ISODIR = $(BUILDDIR)-iso
ISOGRUBCFG = boot/grub/grub.cfg
ISOKERNEL = boot/kernel
ISOINITRD = initrd
GRUBTITLE = $(shell id -un)s $(PROJECT)
GRUBTIMEOUT = 2
GRUBBIN = /usr/lib/grub/i386-pc
# Default ISO target
iso: $(ISOFILE)
# Create Grub config
$(ISODIR)/$(ISOGRUBCFG):
@echo "GEN $@"
@mkdir -p $(dir $@)
@/bin/echo -e "set timeout=$(GRUBTIMEOUT)\nset default=0\n\nmenuentry \"$(GRUBTITLE)\" {\n\tmultiboot /$(ISOKERNEL)\n\tmodule /$(ISOINITRD)\n\tboot\n}" > $@
# Strip debug symbols from kernel binary
$(ISODIR)/$(ISOKERNEL): all
@echo "STRIP $@"
@mkdir -p $(dir $@)
$(VERBOSE) $(STRIP) --strip-debug --strip-unneeded -p -o $@ $(KERNEL)
# copy inital ramdisk
$(ISODIR)/$(ISOINITRD): all
@echo "CPY $@"
@mkdir -p $(dir $@)
@if [ -s $(INITRD) ] ; then cp -a $(INITRD) $@ ; else touch $@ ; fi
# Pack to ISO
$(ISOFILE): $(ISODIR)/$(ISOKERNEL) $(ISODIR)/$(ISOINITRD) $(ISODIR)/$(ISOGRUBCFG)
@echo "ISO $@"
@which $(XORRISO) >/dev/null || echo "Xorriso cannot be found - if building the ISO fails, this may be the reason!" >&2
$(VERBOSE) $(MKISO) -d $(GRUBBIN) -o $@ $(ISODIR)
# Run ISO in Qemu
qemu-iso: $(ISOFILE)
$(QEMU) -cdrom $< -smp $(QEMUCPUS) $(QEMUFLAGS)
# Run ISO in KVM
kvm-iso: $(ISOFILE)
$(QEMU) -cdrom $< -smp $(QEMUCPUS) $(KVMFLAGS)
# Copy ISO to USB device
usb: $(ISOFILE)
ifeq (,$(USBDEV))
@echo "The environment variable USBDEV must contain the path to the USB mass-storage device:" >&2
@lsblk -o TYPE,KNAME,SIZE,MODEL -a -p | grep "^disk" | cut -b 6-
@exit 1
else
$(VERBOSE) $(DD) if=$< of=$(USBDEV) bs=4M status=progress && sync
endif
# Shorthand to copy ISO to a specific USB device
usb-%:
@$(MAKE) USBDEV=/dev/$* usb
# Burn ISO to CD
cd: $(ISOFILE)
ifeq (,$(CDRWDEV))
@echo "The environment variable CDRWDEV must contain the path to the CD/DVD writer" >&2
@exit 1
else
$(VERBOSE) $(XORRISO) -as cdrecord -v dev=$(CDRWDEV) -dao $<
endif
# Shorthand to nurn ISO to specific CD device
cd-%:
@$(MAKE) CDRWDEV=/dev/$* cd
# The standard target 'clean' removes the whole generated system, the object files, and the dependency files.
clean::
@echo "RM $(ISODIR)"
$(VERBOSE) rm -rf "$(ISODIR)" "$(ISODIR)$(OPTTAG)" "$(ISODIR)$(NOOPTTAG)" "$(ISODIR)$(DBGTAG)" "$(ISODIR)$(VERBOSETAG)"
# Documentation
help::
@/bin/echo -e "" \
" \e[3miso\e[0m Generates a bootable system image (File: $(ISOFILE))\n\n" \
" \e[3mqemu-iso\e[0m Starts the system in QEMU by booting from the virtual CD drive\n\n" \
" \e[3mkvm-iso\e[0m Same as \e[3mqemu-iso\e[0m, but with hardware acceleration\n\n" \
" \e[3musb\e[0m Generates a bootable USB mass-storage device; the environment\n" \
" variable \e[4mUSBDEV\e[0m should point to the USB device\n\n" \
" \e[3mcd\e[0m Generates a bootable CD; the environment variable \e[4mCDRWDEV\e[0m\n" \
" should point to the CD writer\n\n"
# Phony targets
.PHONY: iso qemu-iso kvm-iso cd usb help

30
tools/linter.mk Normal file
View File

@ -0,0 +1,30 @@
# Perform static code checks
TIDY ?= clang-tidy
CPPLINT ?= /usr/bin/env python3 "$(CURRENT_DIR)/cpplint.py"
# Check sources with Clang Tidy
tidy::
ifeq (,$(CC_SOURCES))
@echo "(nothing to tidy)"
else
$(VERBOSE) $(TIDY) --format-style=google -header-filter=.* -warnings-as-errors="readability*" -checks="readability*,google-readability-casting,google-explicit-constructor,bugprone*,-bugprone-easily-swappable-parameters,-bugprone-implicit-widening-of-multiplication-result,-bugprone-narrowing-conversions,-bugprone-reserved-identifier,-readability-else-after-return,-readability-identifier-length,-readability-magic-numbers,-readability-use-anyofallof,-readability-function-cognitive-complexity" $(filter-out utils/png.cc,$(CC_SOURCES)) -- $(CXXFLAGS_ARCH) $(CXXFLAGS_DEFAULT) $(CXXFLAGS_OPT)
endif
# Check sources with cpplint
lint::
@if $(CPPLINT) --quiet --recursive . ; then \
echo "Congratulations, coding style obeyed!" ; \
else \
echo "Coding style violated -- see CPPLINT.cfg for details" ; \
exit 1 ; \
fi
# Documentation
help::
@/bin/echo -e "" \
" \e[3mlint\e[0m Checks the coding style using \e[4mCPPLINT\e[0m\n\n" \
" \e[3mtidy\e[0m Uses \e[4mClang Tidy\e[0m for a static code analysis\n\n"
# Phony targets
.PHONY: tidy lint help

65
tools/qemu.mk Normal file
View File

@ -0,0 +1,65 @@
# Targets for running and debugging in Qemu/KVM
QEMUCPUS ?= 4
INITRD ?= /dev/null
QEMUSERIAL ?= pty
QEMUFLAGS = -k en-us -serial $(QEMUSERIAL) -d guest_errors -m 2048
# According to qemu(1): "Creates a backend using PulseAudio. This backend is
# available on most systems." So we use pa as audiodev.
QEMUFLAGS += -audiodev pa,id=stubsad -machine pcspk-audiodev=stubsad
# Switch to curses if no graphical output is available
ifeq ($(DISPLAY),)
QEMUFLAGS += -display curses
endif
KVMFLAGS = -enable-kvm -cpu host $(QEMUFLAGS)
DBGFLAGS = -no-shutdown -no-reboot -qmp unix:qmp.sock,server=on,wait=off -monitor vc
DBGKERNEL ?= $(KERNEL64)
DBGARCH ?= i386:x86-64
QEMU ?= qemu-system-x86_64
QEMUKERNEL := -kernel $(KERNEL) -initrd $(INITRD)
GDB = $(PREFIX)gdb
GDBFLAG = --eval-command="source tools/gdb/stubs.py"
# Run the kernel in Qemu
qemu: all
$(QEMU) $(QEMUKERNEL) -smp $(QEMUCPUS) $(QEMUFLAGS)
# Execute Qemu with activated GDB stub and directly connect GDB to the spawned Qemu.
qemu-gdb: all
$(GDB) $(GDBFLAG) $(DBGKERNEL) \
-ex "set arch $(DBGARCH)" \
-ex "target remote | exec $(QEMU) -gdb stdio $(QEMUKERNEL) -smp $(QEMUCPUS) -S $(QEMUFLAGS) $(DBGFLAGS)"
qemu-gdb-tmux: all
tmux new-session -s StuBS -n QEMU bash -c "tmux new-window -n gdb $(GDB) $(DBGKERNEL) -ex 'set arch $(DBGARCH)' -ex 'target remote localhost:1234'; $(QEMU) -s -S $(QEMUKERNEL) -initrd /dev/null -smp $(QEMUCPUS) $(QEMUFLAGS) -display curses $(DBGFLAGS)"
# Runs StuBS in Qemu with with hardware accelerations (KVM support) enabled
# The started emulator provides several virtual CPUs that execute in parallel.
kvm: all
$(QEMU) $(QEMUKERNEL) -smp $(QEMUCPUS) $(KVMFLAGS)
# Executes Qemu with KVM suppot with activated GDB stub
# and directly connect GDB to the spawned Qemu.
# Please note: Software breakpoints may not work before the stubs kernel
# has switched to long mode -- so we use a hardware breakpoint to stop
# at `kernel_init` (the C++ entry point)
kvm-gdb: all
$(GDB) $(GDBFLAG) $(DBGKERNEL) \
-ex "set arch $(DBGARCH)" \
-ex "target remote | exec $(QEMU) -gdb stdio $(QEMUKERNEL) -smp $(QEMUCPUS) -S $(KVMFLAGS) $(DBGFLAGS)" \
-ex "hbreak kernel_init" \
-ex "continue"
# Help for Qemu targets
help::
@/bin/echo -e "" \
" \e[3mqemu\e[0m Starts $(PROJECT) in QEMU\n" \
" Due to the internal design of QEMU, some things (especially\n" \
" race conditions) might behave different compared to hardware!\n\n" \
" \e[3mqemu-gdb\e[0m Starts $(PROJECT) in QEMU with internal GDB stub and attaches\n" \
" it to a GDB session allowing step-by-step debugging\n\n" \
" \e[3mkvm\e[0m Starts $(PROJECT) in KVM, a hardware-accelerated virtual machine\n\n" \
" \e[3mkvm-gdb\e[0m Same as \e[3mqemu-gdb\e[0m, but with hardware acceleration\n\n"
# Phony targets
.PHONY: qemu kvm qemu-gdb kvm-gdb help

91
tools/remote.mk Normal file
View File

@ -0,0 +1,91 @@
# use the sys Infrastructure
SOLUTIONDIR = /fs/stubs/solutions
SOLUTIONPREFIX = musterloesung-m
SOLUTIONTMPDIR = .solution
NETBOOTDIR = /fs/stubs/students
# Will use either the username from the ssh configuration or, by default, `whoami`@faui06.cs.fau.de
NETBOOTSSH ?= mars.cs.tu-dortmund.de
USERNAME ?= $(USER)
##HALLOFFAMESRC = /proj/i4stubs/halloffame/halloffame.iso
#HALLOFFAMEISO = $(SOLUTIONTMPDIR)/halloffame.iso
# Fetch an ISO including all hall of fame images
#$(HALLOFFAMEISO):
# $(VERBOSE) echo "Get Hall-Of-Fame ISO" ; \
# mkdir -p $(SOLUTIONTMPDIR) ; \
# if [ -f "$(HALLOFFAMESRC)" ] ; then \
# cp $(HALLOFFAMESRC) $@ ; \
# else \
# echo "via SSH $(USERNAME)@$(NETBOOTSSH)" ; \
# scp $(USERNAME)@$(NETBOOTSSH):$(HALLOFFAMESRC) $@ ; \
# fi
# 'halloffame' starts an ISO including all hall of fame images
# with 4 cores and KVM virtualization
#halloffame: $(HALLOFFAMEISO)
# $(QEMU) -cdrom $< -smp $(QEMUCPUS) $(KVMFLAGS) ; \
# 'halloffame-old' starts an ISO including all hall of fame images
# in compatibility mode (single core, software emulation)
#halloffame-old: $(HALLOFFAMEISO)
# $(QEMU) -cdrom $< ; \
#
# The target 'netboot' copies the resulting StuBS kernel to the base directory
# of our tftp server. The tftp server enables the text systems to boot the image
# via pxelinux.
netboot: all
$(VERBOSE) initrd="$(INITRD)" ; \
if [ ! -s "$$initrd" ] ; then \
initrd="$(BUILDDIR)/fake-initrd" ; \
echo "(none)" > "$$initrd" ; \
fi ; \
if [ -d "$(NETBOOTDIR)" ] ; then \
echo "CPY $(NETBOOTDIR)/$(USERNAME)/kernel" ; \
install -m 644 $(KERNEL) $(NETBOOTDIR)/$(USERNAME)/kernel ; \
echo "CPY $(NETBOOTDIR)/$(USERNAME)/initrd.img" ; \
install -m 644 "$$initrd" $(NETBOOTDIR)/$(USERNAME)/initrd.img ; \
else \
echo "via SSH $(USERNAME)@$(NETBOOTSSH)" ; \
echo "SSH $(USERNAME)@$(NETBOOTSSH)@$(NETBOOTDIR)/$(USERNAME)/kernel" ; \
echo "SSH $(USERNAME)@$(NETBOOTSSH)@$(NETBOOTDIR)/$(USERNAME)/initrd.img" ; \
tar --mode="u=rw,og=r" --transform='flags=r;s|$(KERNEL)|kernel|' --transform="flags=r;s|$$initrd|initrd.img|" -cz $(KERNEL) "$$initrd" | \
ssh "$(USERNAME)@$(NETBOOTSSH)" "cat - | tar -xvzp -C $(NETBOOTDIR)/\`id -run\`/" ; \
fi
# 'solution-1' starts our solution for exercise 1 using KVM.
# Of course, other exercise numbers can be supplied, too.
solution-%:
$(VERBOSE) echo "Solution for $(PROJECT) assignment $*" ; \
if [ -d "$(SOLUTIONDIR)" ] ; then \
$(QEMU) -kernel $(SOLUTIONDIR)/$(SOLUTIONPREFIX)$*.elf -initrd $(SOLUTIONDIR)/$(SOLUTIONPREFIX)$*.rd -smp $(QEMUCPUS) $(KVMFLAGS) ; \
else \
echo "via SSH $(USERNAME)@$(NETBOOTSSH)" ; \
mkdir -p $(SOLUTIONTMPDIR) ; \
bash -c "scp $(USERNAME)@$(NETBOOTSSH):$(SOLUTIONDIR)/$(SOLUTIONPREFIX)$*.{elf,rd} $(SOLUTIONTMPDIR)" && \
$(QEMU) -kernel $(SOLUTIONTMPDIR)/$(SOLUTIONPREFIX)$*.elf -initrd $(SOLUTIONTMPDIR)/$(SOLUTIONPREFIX)$*.rd -smp $(QEMUCPUS) $(KVMFLAGS) ; \
fi
# The standard target 'clean' removes the temporary solution directory
clean::
@echo "RM $(SOLUTIONTMPDIR)"
$(VERBOSE) rm -rf "$(SOLUTIONTMPDIR)"
# Documentation
help::
@/bin/echo -e "" \
" \e[3mnetboot\e[0m Copies $(PROJECT) to the network share, allowing the test systems\n" \
" to boot your system. Define the shell variable USERNAME to use a specific user.\n" \
" This is useful if your local username is different from the one on mars.\n\n\n" \
"Apart from the above targets that run your implementation, our solution can\n" \
"be run in KVM (at least when called in the SysLab or IRB pool) using the target\n\n" \
" \e[3msolution-\e[2;3mexercise\e[0m\n\n" \
"where \e[2;3mexercise\e[0m is the number of the exercise whose solution should be executed.\n\n"
# Phony targets
.PHONY: netboot help

65
types.h Normal file
View File

@ -0,0 +1,65 @@
/*! \file
* \brief Definition of standard integer types with specified widths and their limits
*/
#pragma once
// Standard Integer Types
typedef unsigned char uint8_t;
typedef unsigned short uint16_t;
typedef unsigned int uint32_t;
typedef unsigned long long uint64_t;
typedef unsigned long long uintptr_t;
typedef __SIZE_TYPE__ size_t;
typedef char int8_t;
typedef short int16_t;
typedef int int32_t;
typedef long long int64_t;
typedef long long intptr_t;
typedef long int ssize_t;
typedef __PTRDIFF_TYPE__ ptrdiff_t;
// validate typedef size
static_assert(sizeof(int8_t) == (1), "Wrong size for 'int8_t'");
static_assert(sizeof(int16_t) == (2), "Wrong size for 'int16_t'");
static_assert(sizeof(int32_t) == (4), "Wrong size for 'int32_t'");
static_assert(sizeof(int64_t) == (8), "Wrong size for 'int64_t'");
static_assert(sizeof(intptr_t) == sizeof(void*), "Wrong size for 'intptr_t'");
static_assert(sizeof(uint8_t) == (1), "Wrong size for 'uint8_t'");
static_assert(sizeof(uint16_t) == (2), "Wrong size for 'uint16_t'");
static_assert(sizeof(uint32_t) == (4), "Wrong size for 'uint32_t'");
static_assert(sizeof(uint64_t) == (8), "Wrong size for 'uint64_t'");
static_assert(sizeof(uintptr_t) == sizeof(void*), "Wrong size for 'uintptr_t'");
#ifndef NULL
#define NULL ((uintptr_t)0)
#endif
// Limits
#define INT8_MIN (-__INT8_MAX__-1)
#define INT8_MAX (__INT8_MAX__)
#define INT16_MIN (-__INT16_MAX__-1)
#define INT16_MAX (__INT16_MAX__)
#define INT32_MIN (-__INT32_MAX__-1)
#define INT32_MAX (__INT32_MAX__)
#define INT64_MIN (-__INT64_MAX__-1)
#define INT64_MAX (__INT64_MAX__)
#define INTPTR_MIN (-__INTPTR_MAX__-1)
#define INTPTR_MAX (__INTPTR_MAX__)
#define UINT8_MAX (__UINT8_MAX__)
#define UINT16_MAX (__UINT16_MAX__)
#define UINT32_MAX (__UINT32_MAX__)
#define UINT64_MAX (__UINT64_MAX__)
#define UINTPTR_MAX (__UINTPTR_MAX__)
#define PTRDIFF_MIN (-__PTRDIFF_MAX__-1)
#define PTRDIFF_MAX (__PTRDIFF_MAX__)
#define SIZE_MAX (__SIZE_MAX__)
#define SSIZE_MIN (-__INT64_MAX__-1)
#define SSIZE_MAX (__INT64_MAX__)

5
user/app1/appl.cc Normal file
View File

@ -0,0 +1,5 @@
#include "user/app1/appl.h"
void Application::action() { //NOLINT
}

20
user/app1/appl.h Normal file
View File

@ -0,0 +1,20 @@
#pragma once
/*! \brief Test application
*
*
*/
class Application {
// Prevent copies and assignments
Application(const Application&) = delete;
Application& operator=(const Application&) = delete;
public:
/*! \brief Constructor
*/
/*! \brief Contains the application code.
*
*/
void action();
};

4
user/app2/kappl.cc Normal file
View File

@ -0,0 +1,4 @@
#include "user/app2/kappl.h"
void KeyboardApplication::action() { //NOLINT
}

22
user/app2/kappl.h Normal file
View File

@ -0,0 +1,22 @@
/*! \file
* \brief \ref KeyboardApplication to test the input
*/
#pragma once
/*! \brief Keyboard Application
*/
class KeyboardApplication {
// Prevent copies and assignments
KeyboardApplication(const KeyboardApplication&) = delete;
KeyboardApplication& operator=(const KeyboardApplication&) = delete;
public:
/*! \brief Constructor
*/
/*! \brief Contains the application code.
*
*/
void action();
};

26
utils/math.h Normal file
View File

@ -0,0 +1,26 @@
/*! \file
* \brief General purpose \ref Math "math functions"
*/
#pragma once
#include "types.h"
/*! \brief Basic math helper functions
*/
namespace Math {
template <typename T>
T abs(T a) {
return (a >= 0 ? a : -a);
}
template <typename T>
T min(T a, T b) {
return a > b ? b : a;
}
template <typename T>
T max(T a, T b) {
return a > b ? a : b;
}
} // namespace Math

17
utils/size.h Normal file
View File

@ -0,0 +1,17 @@
/*! \file
* \brief Template function to determine the length of an array
*/
#pragma once
#include "types.h"
/* \brief Helper to retrieve the number of elements in an array
* (Warning: template magic)
* \param Array
* \return Number of elements
*/
template<class T, size_t N>
constexpr size_t size(T (&/*unused*/)[N]) {
return N;
}

144
utils/string.cc Normal file
View File

@ -0,0 +1,144 @@
#include "string.h"
#include "types.h"
extern "C" char *strchrnul(const char *s, int c) {
if (s != nullptr) {
while(*s != '\0') {
if (*s == c) {
break;
}
s++;
}
}
return const_cast<char *>(s);
}
extern "C" char *strchr(const char *s, int c) {
if (s != nullptr) {
s = strchrnul(s, c);
if (*s == c) {
return const_cast<char *>(s);
}
}
return nullptr;
}
extern "C" int strcmp(const char *s1, const char *s2) {
if (s1 == nullptr || s2 == nullptr) {
return 0;
}
while(*s1 == *s2++) {
if (*s1++ == '\0') {
return 0;
}
}
return static_cast<int>(*s1) - static_cast<int>(*(s2-1));
}
extern "C" int strncmp(const char *s1, const char *s2, size_t n) {
if (s1 != nullptr && s2 != nullptr) {
for (size_t i = 0; i < n; i++) {
if (s1[i] != s2[i]) {
return static_cast<int>(s1[i]) - static_cast<int>(s2[i]);
} else if (s1[i] == '\0') {
break;
}
}
}
return 0;
}
extern "C" size_t strlen(const char *s) {
size_t len = 0;
if (s != nullptr) {
while (*s++ != '\0') {
len++;
}
}
return len;
}
extern "C" size_t strnlen(const char *s, size_t maxlen) {
size_t len = 0;
if (s != nullptr) {
while (maxlen-- > 0 && *s++ != '\0') {
len++;
}
}
return len;
}
extern "C" char * strcpy(char *dest, const char *src) { //NOLINT
char *r = dest;
if (dest != nullptr && src != nullptr) {
while ((*dest++ = *src++) != '\0') {}
}
return r;
}
extern "C" char * strncpy(char *dest, const char *src, size_t n) {
char *r = dest;
if (dest != nullptr && src != nullptr) {
while (*src != '\0' && n-- != 0) {
*dest++ = *src++;
}
while (n-- != 0) {
*dest++ = '\0';
}
}
return r;
}
extern "C" void* memcpy(void * __restrict__ dest, void const * __restrict__ src, size_t size) {
uint8_t *destination = reinterpret_cast<uint8_t*>(dest);
uint8_t const *source = (uint8_t const*)src;
for(size_t i = 0; i != size; ++i) {
destination[i] = source[i];
}
return dest;
}
extern "C" void* memmove(void * dest, void const * src, size_t size) {
uint8_t *destination = reinterpret_cast<uint8_t*>(dest);
uint8_t const *source = reinterpret_cast<uint8_t const*>(src);
if(source > destination) {
for(size_t i = 0; i != size; ++i) {
destination[i] = source[i];
}
} else {
for(size_t i = size; i != 0; --i) {
destination[i-1] = source[i-1];
}
}
return dest;
}
extern "C" void* memset(void *dest, int pattern, size_t size) {
uint8_t *destination = reinterpret_cast<uint8_t*>(dest);
for(size_t i = 0; i != size; ++i) {
destination[i] = static_cast<uint8_t>(pattern);
}
return dest;
}
extern "C" int memcmp(const void * s1, const void * s2, size_t n) {
const unsigned char * c1 = reinterpret_cast<const unsigned char*>(s1);
const unsigned char * c2 = reinterpret_cast<const unsigned char*>(s2);
for(size_t i = 0; i != n; ++i) {
if (c1[i] != c2[i]) {
return static_cast<int>(c1[i]) - static_cast<int>(c2[i]);
}
}
return 0;
}

122
utils/string.h Normal file
View File

@ -0,0 +1,122 @@
/*! \file
* \brief General purpose \ref string "String functions"
*/
#pragma once
#include "types.h"
/*! \defgroup string String function
* \brief String functions as provided by `%string.h` in the C standard library
*/
/*! \brief Find the first occurrence of a character in a string
* \ingroup string
* \param s string to
* \param c character to find
* \return Pointer to first occurrence of the character
* or to null byte at the end of the string if not found
*/
extern "C" char *strchrnul(const char *s, int c);
/*! \brief Find the first occurrence of a character in a string
* \ingroup string
* \param s string to
* \param c character to find
* \return Pointer to first occurrence of the character
* or to nullptr if not found
*/
extern "C" char *strchr(const char *s, int c);
/*! \brief Compare two strings
* \ingroup string
* \param s1 first string
* \param s2 second string
* \return an integer less than, equal to, or greater than zero if first string is found, respectively,
* to be less than, to match, or be greater than second string
*/
extern "C" int strcmp(const char *s1, const char *s2);
/*! \brief Compare two strings
* \ingroup string
* \param s1 first string
* \param s2 second string
* \param n number of bytes to compare
* \return an integer less than, equal to, or greater than zero if the given number of bytes of the first string are
* found, respectively, to be less than, to match, or be greater than second string
*/
extern "C" int strncmp(const char *s1, const char *s2, size_t n);
/*! \brief Calculate the length of a string
* \ingroup string
* \param s pointer to a string
* \return number of bytes in the string
*/
extern "C" size_t strlen(const char *s);
/*! \brief Calculate the length of a string, limited by maxlen
* \ingroup string
* \param s pointer to a string
* \param maxlen upper limit of length to be returned
* \return number of bytes in the string, or maxlen -- whichever is smaller
*/
extern "C" size_t strnlen(const char *s, size_t maxlen);
/*! \brief Copy the contents of a string
* including the terminating null byte (`\0`)
* \ingroup string
* \param dest destination string buffer
* \param src source string buffer
* \return a pointer to the destination string buffer
* \note Beware of buffer overruns!
*/
extern "C" char * strcpy(char * dest, const char * src); //NOLINT
/*! \brief Copy the contents of a string up to a maximum length
* or the terminating null byte (`\0`), whatever comes first.
* \ingroup string
* \param dest destination string buffer
* \param src source string buffer
* \param n maximum number of bytes to copy
* \return a pointer to the destination string buffer
* \note If there is no null byte (`\0`) among the first `n` bytes, the destination will not be null-terminated!
*/
extern "C" char * strncpy(char * dest, const char * src, size_t n);
/*! \brief Copy a memory area
* \ingroup string
* \param dest destination buffer
* \param src source buffer
* \param size number of bytes to copy
* \return pointer to destination
* \note The memory must not overlap!
*/
extern "C" void* memcpy(void * __restrict__ dest, void const * __restrict__ src, size_t size);
/*! \brief Copy a memory area
* while the source may overlap with the destination
* \ingroup string
* \param dest destination buffer
* \param src source buffer
* \param size number of bytes to copy
* \return pointer to destination
*/
extern "C" void* memmove(void * dest, void const * src, size_t size);
/*! \brief Fill a memory area with a pattern
* \ingroup string
* \param dest destination buffer
* \param pattern single byte pattern
* \param size number of bytes to fill with pattern
* \return pointer to destination
*/
extern "C" void* memset(void *dest, int pattern, size_t size);
/*! \brief Compare a memory area
* \ingroup string
* \param s1 first memory buffer
* \param s2 second memory buffer
* \param n number of bytes to compare
* \return an integer less than, equal to, or greater than zero if the first n bytes of s1 is found, respectively,
* to be less than, to match, or be greater than the first n bytes of s2.
*/
extern "C" int memcmp(const void * s1, const void * s2, size_t n);