main
Niklas Gollenstede 7 months ago
commit 5a2e32aaeb

2
.gitignore vendored

@ -0,0 +1,2 @@
.build*
/build*

@ -0,0 +1,16 @@
# StuBS Coding Style Checker
#
# Wir orientieren uns grob an den Google C++ Style Guide ( http://google.github.io/styleguide/cppguide.html )
# mit primär folgenden Änderungen/Anpassungen:
#
# - Tabs statt Leerzeichen. Spart Bytes ;)
# - Zeilenlänge ist 120
# - Keine Angaben zum Copyright
# - Aufgrund des Aufgabenbuildsystems sind neue / leere Zeilen leider nicht immer vermeidbar
#
# Zum Prüfen empfiehlt sich beispielsweise das Pythonscript CPPLINT.py ( https://github.com/cpplint/cpplint )
# welches mit dieser Konfigurationsdatei arbeiten kann.
#
set noparent
filter=-whitespace/tab,-legal/copyright,-runtime/int,-runtime/threadsafe_fn,-readability/todo,-build/include_subdir,-runtime/references,-build/include_what_you_use,-whitespace/blank_line,-build/include,-whitespace/end_of_line,-whitespace/indent
linelength=120

@ -0,0 +1,13 @@
Copyright 1998-2002 Institut für Verteilte Systeme (IVS), Otto-von-Guericke-Universität Magdeburg
Copyright 2002-2019 Lehrstuhl für Informatik 4, Friedrich-Alexander-Universität Erlangen-Nürnberg
Copyright 2017- System- und Rechnerarchitektur (SRA), Leibniz Universität Hannover
Copyright 2021-2023 Operating System Group (OSG), Technische Universität Hamburg
Copyright 2024- Verlässliche Systemsoftware (VSS), Technische Universität Braunschweig
Diese Vorlage dient als Grundlage für Lehrveranstaltungen und darf nicht ohne vorherige, schriftliche Erlaubnis der Urheberrechtsinhaber veröffentlicht oder weitergegeben werden.
Es ist erlaubt und wünschenswert, diese Vorlage als Inspiration für eigene Projekte zu verwenden, es wird allerdings erbeten, dass die Vorgabe nicht mit deiner Lösung veröffentlicht wird.
Wir, als Lehrende, möchten alle teilnehmenden Studierenden dazu ermutigen eine eigene Lösung zu erstellen; eine veröffentlichte Lösung ist ein Anreiz zum Abschreiben, den wir gerne vermeiden möchten.
This skeleton is provided as a foundation for educational purposes and therefore MUST NOT BE DISTRIBUTED OR PUBLISHED without prior, written consent of the copyright holders.
You are free to use this skeleton as inspiration for your projects, but, please, do not publish it along with your solution.
We, as lecturers, want to encourage every participating student to write a solution themself; a public solution is an allurement to copying we want to avoid.

@ -0,0 +1,62 @@
# Kernel Makefile
# try `make help` for more information
echo=$(shell which echo) -e
# 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
CC_SOURCES = $(shell find * -name "*.cc" -a ! -name '.*' -a ! -path 'test*' -a ! -path 'fs/tool/*' -a ! -path 'assets/*' -a ! -path 'tools/*')
ASM_SOURCES = $(shell find * -name "*.asm" -a ! -name '.*')
# Target files
KERNEL = $(BUILDDIR)/system
KERNEL64 = $(KERNEL)64
ISOFILE = $(BUILDDIR)/stubs.iso
KERNEL_LINK = $(ROOTBUILDDIR)/system.img
# 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): $(ASM_OBJECTS) $(CC_OBJECTS) $(LINKER_SCRIPT) $(MAKEFILE_LIST)
@echo "LD $@"
@mkdir -p $(@D)
$(VERBOSE) $(CXX) $(CXXFLAGS) -Wl,-T $(LINKER_SCRIPT) -o $@ $(LDFLAGS) $(ASM_OBJECTS) $(CC_OBJECTS)
@echo "LN $(KERNEL_LINK)"
$(VERBOSE) ln -sf $(@:$(ROOTBUILDDIR)/%=%) "$(KERNEL_LINK)"
# 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 $@"
@make -C $(dir $(INITRD_TOOL))
# Initial Ramdisk with Minix v3 file system
$(INITRD): $(INITRD_TOOL) $(INITRD_DEP)
@echo "INITRD $@"
@dd if=/dev/zero of=$@ bs=$(shell du -s $(INITRD_DIR) | cut -f1 | xargs expr $(INITRD_FREE) + ) count=1
@mkfs.minix -3 $@ # optional --inodes <number>
@./$(INITRD_TOOL) put "$(INITRD_DIR)" $@

@ -0,0 +1,42 @@
MPStuBS - Multiprozessor Studenten Betriebssystem
=================================================
Coding Guidelines
-----------------
Similar to [Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html) but with following exceptions:
- No license boilerplate
- *Tabs* instead of *Spaces*
- Line length of 120 characters
- `#pragma once` instead of `#include` guards
The code should be *self-documenting*, don't state the obvious!
However, this does not make comments superfluous:
Since good naming is sometimes not enough, more advanced parts need to be documented,
so any operating system developer should be able to easily understand your code.
### Naming Convention
- **Variables**: lowercase with underscore
char* variable_name;
- **Constants** (and **enum** values): uppercase with underscore
const int CONST_VALUE = 42;
- **Type Names** (`class`/`struct`/`namespace`/`enum`): Capital letter, camel case
class SomeClassName;
- **Methods/Functions** (C++): start with lowercase letter, then camel case
void someFunctionName();
- **extern "C" Functions**: lowercase with underscore (like variables).
void interrupt_handler(int vector);
- **File Names**: lowercase, main type name, underscores only if is a sub type
folder/classname.cc

@ -0,0 +1,144 @@
#include "acpi.h"
#include "../debug/output.h"
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Warray-bounds"
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.
*/
const uintptr_t ebda =
static_cast<uintptr_t>(*reinterpret_cast<uint32_t *>(0x40e));
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

@ -0,0 +1,270 @@
/*! \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

@ -0,0 +1,150 @@
#include "apic.h"
#include "../debug/assert.h"
#include "../debug/output.h"
#include "acpi.h"
#include "core.h"
#include "ioport.h"
#include "lapic_registers.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

@ -0,0 +1,82 @@
/*! \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 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

@ -0,0 +1,22 @@
/*! \file
* \brief Helper for cache alignment
*/
#pragma once
#include "../debug/assert.h"
// Helper for aligning to cache line (to prevent false sharing)
#ifndef CACHE_LINE_SIZE
#define CACHE_LINE_SIZE 64
#endif
#define cache_aligned alignas(CACHE_LINE_SIZE)
/*!
* \def assert_cache_aligned(TYPE)
* \brief Compile time check of cache alignment
* \param TYPE data type to check
*/
#define assert_cache_aligned(TYPE) \
static_assert(sizeof(TYPE) % CACHE_LINE_SIZE == 0, \
STRINGIFY(TYPE) "Not aligned on cache boundary")

@ -0,0 +1,21 @@
#include "cga.h"
namespace CGA {
void setCursor(unsigned abs_x, unsigned abs_y) {
(void)abs_x;
(void)abs_y;
}
void getCursor(unsigned& abs_x, unsigned& abs_y) {
(void)abs_x;
(void)abs_y;
}
void show(unsigned abs_x, unsigned abs_y, char character, Attribute attrib) {
(void)abs_x;
(void)abs_y;
(void)character;
(void)attrib;
}
}; // namespace CGA

@ -0,0 +1,138 @@
/*! \file
* \brief \ref CGA 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 namespace provides an interface to access the screen in text mode
* (also known as CGA mode), with access directly on the hardware
* level, i.e. the video memory and the I/O ports of the graphics
* card.
*/
namespace CGA {
constexpr unsigned ROWS = 25; ///< Visible rows in text mode
constexpr 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.
*
* \todo(11) Fill in the bitfield
*/
union Attribute {
struct {
uint8_t todo : 8;
} __attribute__((packed));
uint8_t value; ///< combined value
/*! \brief Attribute constructor (with default values)
*
* \todo(11) 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(11) 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
*/
void setCursor(unsigned abs_x, unsigned abs_y);
/*! \brief Retrieve the keyboard hardware cursor position on screen
*
* \todo(11) 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
*/
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(11) Implement the method
*/
void show(unsigned abs_x, unsigned abs_y, char character,
Attribute attrib = Attribute());
/*! \brief Structure for a cell in text mode
*
* Consisting of two bytes, character and attribute
*/
struct Cell {
char character;
Attribute attribute;
Cell(char character, Attribute attribute)
: character(character), attribute(attribute) {}
} __attribute__((packed));
/*! \brief Base address for linear text buffer in video memory
*/
Cell* const TEXT_BUFFER_BASE = nullptr;
}; // namespace CGA

@ -0,0 +1,61 @@
#include "cmos.h"
#include "core_interrupt.h"
#include "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

@ -0,0 +1,46 @@
/*! \file
* \brief Controlling the \ref CMOS "complementary metal oxide semiconductor
* (CMOS)"
*/
#pragma once
#include "../types.h"
/*!
* \defgroup CMOS CMOS
* \brief complementary metal oxide semiconductor (CMOS)
*/
/*! \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

@ -0,0 +1,20 @@
[SECTION .text]
[GLOBAL context_switch]
[GLOBAL context_launch]
[GLOBAL fake_systemv_abi]
; context_switch saves the registers in the current context structure
; and populates the registers from the the next context.
align 16
context_switch:
; context_launch populates the register set from the next context structure.
; It does not save the current registers.
align 16 ; When only one parameter is used for `align`, it will use NOP
context_launch:
; fake_systemv_abi is used to populate the volatile argument registers used by the systemv abi (rdi, rsi, ...)
; with values from the non-volatile registers saved within the thread context (r15, r14, ...)
align 16
fake_systemv_abi:

@ -0,0 +1,9 @@
#include "context.h"
void prepareContext(void* tos, Context& context, void (*kickoff)(void*),
void* param1) {
(void)tos;
(void)context;
(void)kickoff;
(void)param1;
}

@ -0,0 +1,118 @@
/*! \file
* \brief Functionality required for \ref context_switch "context switching"
*/
/*! \defgroup context Context Switch
* \brief Low-Level functionality required for context switching
*/
#pragma once
#include "../types.h"
/*! \brief Structure for saving the CPU context when switching coroutines.
* \ingroup context
*/
struct Context {
uintptr_t rbx; ///< RBX of the thread
uintptr_t rbp; ///< RBP of the thread
uintptr_t r12; ///< R12 of the thread
uintptr_t r13; ///< R13 of the thread
uintptr_t r14; ///< R14 of the thread
uintptr_t r15; ///< R15 of the thread
void* rsp; ///< Current stack pointer of the thread
} __attribute__((packed));
/*! \brief Prepares a context for its first activation.
*
* \ingroup context
*
* On first activation (during *some* context switch), the execution of a
* thread should start at its entry point (typically an implementation of \ref
* Thread::kickoff).
*
* For this, we have to prepare the thread context such that \ref
* context_switch and \ref context_launch can work with it.
*
* Just pushing the entry point onto the stack as a return address is not
* sufficient, however.
* \ref Thread::kickoff requires a pointer to the current thread as a
* parameter, which we also have to transfer. According to the 64 bit systemv
* calling convention, parameters are passed via the volatile registers `rdi,
* rsi, rcx, rdx, r8, r9`. But theses are never set during the intial context
* switch (why?). Therefore we pass the parameter using the non-volatile
* register `r15` and use a trampoline function as the actual entry point. See
* \ref fake_systemv_abi for details.
*
* `prepareContext()` can be implemented in the high-level programming language
* C++ (in file `context.cc`).
*
* \param tos Pointer to the top of stack (= address of first byte beyond
* the memory reserved for the stack)
* \param context Reference to the Context structure to be filled
* \param kickoff Pointer to the \ref Thread::kickoff function
* \param param1 first parameter for \ref Thread::kickoff function
*/
/*!
* \todo(14) Implement Function (and helper functions, if required)
*/
void prepareContext(void* tos, Context& context, void (*kickoff)(void*),
void* param1 = nullptr);
/*! \brief Executes the context switch.
*
* \ingroup context
*
* For a clean context switch, the current register values must be stored in
* the given context struct. Subsequently, these values must be restored
* accordingly from the `next` context struct.
*
* This function must be implemented in assembler in the file `context.asm`
* (why?). It must be declared as `extern "C"`, so assembler functions are not
* C++ name mangled.
*
* \param next Pointer to the structure that the next context will be read
* from
* \param current Pointer to the structure that the current context will be
* stored in
*
* \todo(14) Implement Method
*/
extern "C" void context_switch(Context* next, Context* current);
/*! \brief Launch context switching.
*
* To start context switching, the current context (from the boot-routines) is
* thrown away and the prepared register values within the given `next` context
* replace it.
*
* This function must be implemented in assembler in the file `context.asm`
* (why?). It must be declared as `extern "C"`, so assembler functions are not
* C++ name mangled.
*
* \ingroup context
*
* \param next Pointer to the structure that the next context will be read
* from
*
* \todo(14) Implement Method
*/
extern "C" void context_launch(Context* next);
/*! \brief Fakes a systemv abi call.
*
* When a thread is first started, only non-volatile registers are "restored"
* from our prepared context (which is where we stored our \ref Thread::kickoff
* parameters). However, the 64 bit calling convention (systemv) dictates that
* parameters are passed via the volatile registers `rdi, rsi, rcx, rdx, r8,
* r9`. In order to call a C++ function, we have to transfer our parameters from
* the non-volatile registers (e.g. `r15, ...`) to the correct volatile ones
* (`rdi, ...`).
*
* This function must be implemented in assembler in the file `context.asm`
* (why?). It must be declared as `extern "C"`, so assembler functions are not
* C++ name mangled.
* \ingroup context
*
* \todo(14) Implement Method
*/
extern "C" void fake_systemv_abi();

@ -0,0 +1,73 @@
#include "core.h"
#include "apic.h"
#include "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 volatile 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

@ -0,0 +1,117 @@
/*! \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 "core_cr.h"
#include "core_interrupt.h"
#include "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
*/
constexpr 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

@ -0,0 +1,83 @@
/*! \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 {
CR0_PE = 1 << 0, ///< Protected Mode enabled
CR0_MP = 1 << 1, ///< Monitor co-processor
CR0_EM = 1 << 2, ///< Emulation (no x87 floating-point unit present)
CR0_TS = 1 << 3, ///< Task switched
CR0_ET = 1 << 4, ///< Extension type
CR0_NE = 1 << 15, ///< Numeric error
CR0_WP = 1 << 16, ///< Write protect
CR0_AM = 1 << 18, ///< Alignment mask
CR0_NW = 1 << 29, ///< Not-write through caching
CR0_CD = 1 << 30, ///< Cache disable
CR0_PG = 1 << 31, ///< Paging
};
/*! \brief Control Register 4
*
* \see [ISDMv3, 2.5 Control Registers](intel_manual_vol3.pdf#page=77)
*/
enum CR4 {
CR4_VME = 1 << 0, ///< Virtual 8086 Mode Extensions
CR4_PVI = 1 << 1, ///< Protected-mode Virtual Interrupts
CR4_TSD = 1 << 2, ///< Time Stamp Disable
CR4_DE = 1 << 3, ///< Debugging Extensions
CR4_PSE = 1 << 4, ///< Page Size Extension
CR4_PAE = 1 << 5, ///< Physical Address Extension
CR4_MCE = 1 << 6, ///< Machine Check Exception
CR4_PGE = 1 << 7, ///< Page Global Enabled
CR4_PCE = 1 << 8, ///< Performance-Monitoring Counter enable
CR4_OSFXSR =
1 << 9, ///< Operating system support for FXSAVE and FXRSTOR instructions
CR4_OSXMMEXCPT = 1 << 10, ///< Operating System Support for Unmasked SIMD
///< Floating-Point Exceptions
CR4_UMIP = 1 << 11, ///< User-Mode Instruction Prevention
CR4_VMXE = 1 << 13, ///< Virtual Machine Extensions Enable
CR4_SMXE = 1 << 14, ///< Safer Mode Extensions Enable
CR4_FSGSBASE = 1 << 16, ///< Enables the instructions RDFSBASE, RDGSBASE,
///< WRFSBASE, and WRGSBASE.
CR4_PCIDE = 1 << 17, ///< PCID Enable
CR4_OSXSAVE = 1 << 18, ///< XSAVE and Processor Extended States Enable
CR4_SMEP = 1 << 20, ///< Supervisor Mode Execution Protection Enable
CR4_SMAP = 1 << 21, ///< Supervisor Mode Access Prevention Enable
CR4_PKE = 1 << 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));
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));
}
};
} // namespace Core

@ -0,0 +1,141 @@
/*! \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
*/
constexpr 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)
* \todo(12) Add Keyboard and Panic vector numbers
*/
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/MMX/SSE instruction but corresponding extension not activated
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
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)
SECURITY_EXCEPTION = 31,
// Interrupts
};
constexpr size_t VECTORS = 256;
/*! \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 - Ckear 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

@ -0,0 +1,103 @@
/*! \file
* \brief \ref Core::MSRs "Identifiers" for \ref Core::MSR "Model-Specific
* Register"
*/
#pragma once
#include "../types.h"
namespace Core {
/*! \brief Model-Specific Register Identifiers
*
* Selection of useful identifiers.
*
* \see [ISDMv4](intel_manual_vol4.pdf)
*/
enum MSRs {
MSR_PLATFORM_INFO =
0xce, ///< Platform information including bus frequency (Intel)
MSR_TSC_DEADLINE = 0x6e0, ///< Register for \ref LAPIC::Timer Deadline mode
// Fast system calls
// XXX: Remove if we don't do fast syscalls
MSR_EFER =
0xC0000080, ///< Extended Feature Enable Register, \see Core::MSR_EFER
MSR_STAR = 0xC0000081, ///< eip (protected mode), ring 0 and 3 segment bases
MSR_LSTAR = 0xC0000082, ///< rip (long mode)
MSR_SFMASK = 0xC0000084, ///< lower 32 bit: flag mask, if bit is set
///< corresponding rflag is cleared through syscall
// CPU local variables
MSR_FS_BASE = 0xC0000100,
MSR_GS_BASE = 0xC0000101, ///< Current GS base pointer
MSR_SHADOW_GS_BASE = 0xC0000102, ///< 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 {
MSR_EFER_SCE = 1 << 0, ///< System Call Extensions
MSR_EFER_LME = 1 << 8, ///< Long mode enable
MSR_EFER_LMA = 1 << 10, ///< Long mode active
MSR_EFER_NXE = 1 << 11, ///< No-Execute Enable
MSR_EFER_SVME = 1 << 12, ///< Secure Virtual Machine Enable
MSR_EFER_LMSLE = 1 << 13, ///< Long Mode Segment Limit Enable
MSR_EFER_FFXSR = 1 << 14, ///< Fast `FXSAVE`/`FXRSTOR` instruction
MSR_EFER_TCE = 1 << 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

@ -0,0 +1,201 @@
/*! \file
* \brief \ref CPUID queries information about the processor
*/
#pragma once
#include "../types.h"
/*! \brief Query information about the processor
*
* \note This is an interface to the `cpuid` instruction, which can return
* information about the processor. It should therefor **not** be confused with
* functionality to
* \ref Core::getID() "retrieve the ID of the current CPU (core)"!
*/
namespace CPUID {
/*! \brief Structure for register values returned by `cpuid` instruction
*/
union Reg {
struct {
uint32_t ebx, edx, ecx, eax;
};
char value[16];
};
enum Function {
HIGHEST_FUNCTION_PARAMETER = 0x0, ///< Maximum Input Value for Basic CPUID
///< Information (in register `eax`)
MANUFACTURER_ID = 0x0, ///< CPU String (in register `ebx`, `ecx` and `edx`)
PROCESSOR_INFO = 0x1, ///< Version Information like Type, Family, Model (in
///< register `eax`)
FEATURE_BITS = 0x1, ///< Feature Information (in register `ecx` and `edx`)
CACHE_INFORMATION = 0x2, ///< Cache and TLB Information
PROCESSOR_SERIAL_NUMBER = 0x3, ///< deprecated
HIGHEST_EXTENDED_FUNCTION =
0x80000000, ///< Maximum Input Value for Extended Function CPUID (in
///< register `eax`)
EXTENDED_PROCESSOR_INFO = 0x80000001, ///< Extended Processor Signature and
///< Feature Bits (in register `eax`)
EXTENDED_FEATURE_BITS = 0x80000001, ///< Extended Feature Information (in
///< register `ecx` and `edx`)
PROCESSOR_BRAND_STRING_1 = 0x80000002, ///< Processor Brand String (1/3)
PROCESSOR_BRAND_STRING_2 = 0x80000003, ///< Processor Brand String (2/3)
PROCESSOR_BRAND_STRING_3 = 0x80000004, ///< Processor Brand String (3/3)
ADVANCED_POWER_MANAGEMENT = 0x80000007, ///< Advanced Power Management (with
///< Invariant TSC in register `edx`)
ADDRESS_SIZES =
0x80000008, ///< Linear/Physical Address size (in register `eax`)
};
/*! \brief Get CPU identification and feature information
*
* \param eax Requested feature
* \return Register values filled by instruction `cpuid` for the requested
* feature
*
* \see [ISDMv2, Chapter 3. CPUID - CPU
* Identification](intel_manual_vol2.pdf#page=292)
*/
inline Reg get(Function eax) {
Reg r;
asm volatile("cpuid \n\t"
: "=a"(r.eax), "=b"(r.ebx), "=c"(r.ecx), "=d"(r.edx)
: "0"(eax));
return r;
}
enum FeatureECX {
FEATURE_SSE3 = 1 << 0, ///< Prescott New Instructions-SSE3 (PNI)
FEATURE_PCLMUL = 1 << 1, ///< Carry-less Multiplication
FEATURE_DTES64 = 1 << 2, ///< 64-bit debug store (edx bit 21)
FEATURE_MONITOR = 1 << 3, ///< MONITOR and MWAIT instructions (SSE3)
FEATURE_DS_CPL = 1 << 4, ///< CPL qualified debug store
FEATURE_VMX = 1 << 5, ///< Virtual Machine eXtensions
FEATURE_SMX = 1 << 6, ///< Safer Mode Extensions (LaGrande)
FEATURE_EST = 1 << 7, ///< Enhanced SpeedStep
FEATURE_TM2 = 1 << 8, ///< Thermal Monitor 2
FEATURE_SSSE3 = 1 << 9, ///< Supplemental SSE3 instructions
FEATURE_CID = 1 << 10, ///< L1 Context ID
FEATURE_SDBG = 1 << 11, ///< Silicon Debug interface
FEATURE_FMA = 1 << 12, ///< Fused multiply-add (FMA3)
FEATURE_CX16 = 1 << 13, ///< CMPXCHG16B instruction
FEATURE_ETPRD = 1 << 14, ///< Can disable sending task priority messages
FEATURE_PDCM = 1 << 15, ///< Perfmon & debug capability
FEATURE_PCIDE = 1 << 17, ///< Process context identifiers (CR4 bit 17)
FEATURE_DCA = 1 << 18, ///< Direct cache access for DMA writes
FEATURE_SSE4_1 = 1 << 19, ///< SSE4.1 instructions
FEATURE_SSE4_2 = 1 << 20, ///< SSE4.2 instructions
FEATURE_X2APIC = 1 << 21, ///< x2APIC
FEATURE_MOVBE = 1 << 22, ///< MOVBE instruction (big-endian)
FEATURE_POPCNT = 1 << 23, ///< POPCNT instruction
FEATURE_TSC_DEADLINE =
1
<< 24, ///< APIC implements one-shot operation using a TSC deadline value
FEATURE_AES = 1 << 25, ///< AES instruction set
FEATURE_XSAVE = 1 << 26, ///< XSAVE, XRESTOR, XSETBV, XGETBV
FEATURE_OSXSAVE = 1 << 27, ///< XSAVE enabled by OS
FEATURE_AVX = 1 << 28, ///< Advanced Vector Extensions
FEATURE_F16C = 1 << 29, ///< F16C (half-precision) FP feature
FEATURE_RDRND =
1 << 30, ///< RDRAND (on-chip random number generator) feature
FEATURE_HYPERVISOR =
1 << 31 ///< Hypervisor present (always zero on physical CPUs)
};
enum FeatureEDX {
FEATURE_FPU = 1 << 0, ///< Onboard x87 FPU
FEATURE_VME =
1 << 1, ///< Virtual 8086 mode extensions (such as VIF, VIP, PIV)
FEATURE_DE = 1 << 2, ///< Debugging extensions (CR4 bit 3)
FEATURE_PSE = 1 << 3, ///< Page Size Extension
FEATURE_TSC = 1 << 4, ///< Time Stamp Counter
FEATURE_MSR = 1 << 5, ///< Model-specific registers
FEATURE_PAE = 1 << 6, ///< Physical Address Extension
FEATURE_MCE = 1 << 7, ///< Machine Check Exception
FEATURE_CX8 = 1 << 8, ///< CMPXCHG8 (compare-and-swap) instruction
FEATURE_APIC =
1 << 9, ///< Onboard Advanced Programmable Interrupt Controller
FEATURE_SEP = 1 << 11, ///< SYSENTER and SYSEXIT instructions
FEATURE_MTRR = 1 << 12, ///< Memory Type Range Registers
FEATURE_PGE = 1 << 13, ///< Page Global Enable bit in CR4
FEATURE_MCA = 1 << 14, ///< Machine check architecture
FEATURE_CMOV = 1 << 15, ///< Conditional move and FCMOV instructions
FEATURE_PAT = 1 << 16, ///< Page Attribute Table
FEATURE_PSE36 = 1 << 17, ///< 36-bit page size extension
FEATURE_PSN = 1 << 18, ///< Processor Serial Number
FEATURE_CLF = 1 << 19, ///< CLFLUSH instruction (SSE2)
FEATURE_DTES = 1 << 21, ///< Debug store: save trace of executed jumps
FEATURE_ACPI = 1 << 22, ///< Onboard thermal control MSRs for ACPI
FEATURE_MMX = 1 << 23, ///< MMX instructions
FEATURE_FXSR = 1 << 24, ///< FXSAVE, FXRESTOR instructions, CR4 bit 9
FEATURE_SSE = 1 << 25, ///< SSE instructions (a.k.a. Katmai New Instructions)
FEATURE_SSE2 = 1 << 26, ///< SSE2 instructions
FEATURE_SS = 1 << 27, ///< CPU cache implements self-snoop
FEATURE_HTT = 1 << 28, ///< Hyper-threading
FEATURE_TM1 = 1 << 29, ///< Thermal monitor automatically limits temperature
FEATURE_IA64 = 1 << 30, ///< IA64 processor emulating x86
FEATURE_PBE = 1 << 31 ///< Pending Break Enable (PBE# pin) wakeup capability
};
enum ExtendedFeatureEDX {
EXTENDED_FEATURE_FPU = 1 << 0, ///< Onboard x87 FPU
EXTENDED_FEATURE_VME =
1 << 1, ///< Virtual 8086 mode extensions (such as VIF, VIP, PIV)
EXTENDED_FEATURE_DE = 1 << 2, ///< Debugging extensions (CR4 bit 3)
EXTENDED_FEATURE_PSE = 1 << 3, ///< Page Size Extension
EXTENDED_FEATURE_TSC = 1 << 4, ///< Time Stamp Counter
EXTENDED_FEATURE_MSR = 1 << 5, ///< Model-specific registers
EXTENDED_FEATURE_PAE = 1 << 6, ///< Physical Address Extension
EXTENDED_FEATURE_MCE = 1 << 7, ///< Machine Check Exception
EXTENDED_FEATURE_CX8 = 1 << 8, ///< CMPXCHG8 (compare-and-swap) instruction
EXTENDED_FEATURE_APIC =
1 << 9, ///< Onboard Advanced Programmable Interrupt Controller
EXTENDED_FEATURE_SYSCALL = 1 << 11, ///< SYSCALL and SYSRET instructions
EXTENDED_FEATURE_MTRR = 1 << 12, ///< Memory Type Range Registers
EXTENDED_FEATURE_PGE = 1 << 13, ///< Page Global Enable bit in CR4
EXTENDED_FEATURE_MCA = 1 << 14, ///< Machine check architecture
EXTENDED_FEATURE_CMOV = 1 << 15, ///< Conditional move and FCMOV instructions
EXTENDED_FEATURE_PAT = 1 << 16, ///< Page Attribute Table
EXTENDED_FEATURE_PSE36 = 1 << 17, ///< 36-bit page size extension
EXTENDED_FEATURE_MP = 1 << 19, ///< Multiprocessor Capable
EXTENDED_FEATURE_NX = 1 << 20, ///< Non-executable bit
EXTENDED_FEATURE_MMXEXT = 1 << 22, ///< extended MMX instructions
EXTENDED_FEATURE_MMX = 1 << 23, ///< MMX instructions
EXTENDED_FEATURE_FXSR = 1
<< 24, ///< FXSAVE, FXRESTOR instructions, CR4 bit 9
EXTENDED_FEATURE_FXSR_OPT = 1 << 25, ///< FXSAVE, FXRESTOR optimizations
EXTENDED_FEATURE_PDPE1GB = 1 << 26, ///< Gibibyte Pages
EXTENDED_FEATURE_RDTSCP = 1 << 27, ///< CPU cache implements self-snoop
EXTENDED_FEATURE_LM = 1 << 29, ///< Long Mode (x64)
EXTENDED_FEATURE_3DNOWEXT = 1 << 30, ///< Extended 3DNow! instructions
EXTENDED_FEATURE_3DNOW = 1 << 31 ///< 3DNow! instructions
};
/*! \brief Check if feature is provided by this system
*
* \param feature Feature to test
* \return `true` if available, `false` otherwise
*/
inline bool has(enum FeatureECX feature) {
return (get(FEATURE_BITS).ecx & feature) != 0;
}
/*! \brief Check if feature is provided by this system
*
* \param feature Feature to test
* \return `true` if available, `false` otherwise
*/
inline bool has(enum FeatureEDX feature) {
return (get(FEATURE_BITS).edx & feature) != 0;
}
/*! \brief Check if feature is provided by this system
*
* \param feature Extended feature to test
* \return `true` if available, `false` if either feature or extended features
* are unavailable
*/
inline bool has(enum ExtendedFeatureEDX feature) {
return (get(EXTENDED_FEATURE_BITS).edx & feature) != 0;
}
} // namespace CPUID

@ -0,0 +1,36 @@
#include "gdt.h"
#include "core.h"
namespace GDT {
// The static 32-bit Global Descriptor Table (GDT)
alignas(16) constinit SegmentDescriptor protected_mode[] = {
// Null descriptor
{},
// Global code segment von 0-4GB
SegmentDescriptor::Segment(0, UINT32_MAX, true, 0, SIZE_32BIT),
// Global data segment von 0-4GB
SegmentDescriptor::Segment(0, UINT32_MAX, false, 0, 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) constinit SegmentDescriptor long_mode[] = {
// Null descriptor
SegmentDescriptor::Null(),
// Global code segment
SegmentDescriptor::Segment64(true, 0),
// Global data segment
SegmentDescriptor::Segment64(false, 0),
};
extern "C" constexpr Pointer gdt_long_mode_pointer(long_mode);
} // namespace GDT

@ -0,0 +1,199 @@
/*! \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 = 1, ///< 64-bit (D/B = 0, L = 1)
};
/*! \brief Type flags for used descriptor types
*/
enum TypeFlags {
TYPE_DATA_RW = 0b0010ull, ///< Data rw, not expanding down
TYPE_CODE_RX = 0b1010ull, ///< Code rx, non-conforming
};
/*! \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));
uint64_t value; ///!< Merged value
/*! \brief Explicitly constructs a null descriptor.
*/
consteval static SegmentDescriptor Null() {
return SegmentDescriptor{
.value = 0,
};
}
/*! \brief Constructs a code/data segment descriptor.
* \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
*/
consteval static SegmentDescriptor Segment(uintptr_t base, uint32_t limit,
bool code, uint64_t ring,
Size size) {
return SegmentDescriptor{
.limit_low = limit >> (limit > 0xFFFFF ? 12 : 0) & 0xFFFF,
.base_low = base & 0xFFFFFF,
.type = code ? TYPE_CODE_RX : TYPE_DATA_RW,
.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,
};
}
/*! \brief Constructs a 64bit code/data segment descriptor.
* \param code Code or data segment
* \param ring Privilege level
*/
consteval static SegmentDescriptor Segment64(bool code, int ring) {
return SegmentDescriptor::Segment(0, 0, code, ring, SIZE_64BIT);
}
} __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
*/
consteval 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
*/
constexpr 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

@ -0,0 +1,35 @@
#include "idt.h"
#include "core_interrupt.h"
namespace IDT {
// Interrupt Descriptor Table, 8 Byte aligned
constinit 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 set(Core::Interrupt::Vector vector, InterruptDescriptor descriptor) {
idt[(uint8_t)vector] = descriptor;
}
} // namespace IDT

@ -0,0 +1,210 @@
/*! \file
* \brief \ref IDT "Interrupt Descriptor Table (IDT)" containing the entry
* points for interrupt handling.
*/
#pragma once
#include "../types.h"
#include "core_interrupt.h"
/*! \brief "Interrupt Descriptor Table (IDT)
* \ingroup interrupts
*
* \see [ISDMv3 6.14 Exception and Interrupt Handling in 64-bit
* Mode](intel_manual_vol3.pdf#page=200)
*/
/*! \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. No other registers are saved or restored automatically. It is the
* handlers (our) job to save and restore all modified registers. However, most
* handlers in StuBS are implemented directly in C++ utilizing the `interrupt`
* attribute: The compiler treats all modified registers as callee-saved, which
* saves us a lot of work, but prevents us from knowing the exact contents of
* each regiser (we don't know if/when the compiler modified it). `interrupt`
* functions receive up to two parameters: A pointer to this /ref
* InterruptContext and, depending on the interrupt, an error code, which is
* also pushed onto the stack by the CPU. Contrary to "normal" functions, the
* compiler will return using the `iret` instruction.
*/
struct InterruptContext {
// 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));
static_assert(sizeof(InterruptContext) == 5 * 8,
"InterruptContext has wrong size");
namespace IDT {
/*! \brief Gate types
*
* \see [ISDMv3 3.5 System Descriptor Types](intel_manual_vol3.pdf#page=99)
*/
enum Gate {
GATE_INT = 0x6, ///< Interrupt Gate (CPU disables interrupts unpon entry)
GATE_TRAP = 0x7, ///< Trap Gate (interrupts remain enabled unpon entry)
};
/*! \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 bit
GATE_SIZE_64 = 1, ///< 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 Interrupt handler that returns after execution (trap/fault).
*/
using ReturningHandler = void (*)(InterruptContext*);
/*! \brief Interrupt handler that returns after execution (trap/fault) and
* receives an error code.
*/
using ReturningHandlerWithError = void (*)(InterruptContext*, uint64_t);
/*! \brief Interrupt handler that does **not** return after execution (abort).
*/
using DivergingHandler = void (*)(InterruptContext*);
/*! \brief Interrupt handler that does **not** return after execution (abort)
* and receives an error code.
*/
using DivergingHandlerWithError = void (*)(InterruptContext*, uint64_t);
/*! \brief 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; ///< Interrupt Stack Index
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
bool 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
/*! \brief Create a non-present interrupt descriptor.
*/
InterruptDescriptor() = default;
/*! \brief Create an interrupt descriptor.
*
*
* \param handler Entry point for interrupt handling
* \param ist Stack index from the \ref TaskStateSegment for the interrupt
* handler. Set to 0 to use current stack.
* \param dpl Permissions required for enter this interrupt handler
* (kernel- or user space)
*/
InterruptDescriptor(uintptr_t handler, uint8_t ist, DPL dpl)
: address_low(handler & 0xffff),
selector(8), // XXX: This should come from `Segments`
ist(ist),
type(GATE_INT),
size(GATE_SIZE_64),
dpl(dpl),
present(true),
address_high((handler >> 16) & 0xffffffffffff) {}
/*! \brief Create an interrupt descriptor for a handler that does return
* (trap/fault).
*
* \param handler Entry point for interrupt handling
* \param ist Stack index from the \ref TaskStateSegment for the interrupt
* handler. Set to 0 to use current stack.
* \param dpl Permissions required for enter this interrupt handler
* (kernel- or user space)
*/
static InterruptDescriptor Returning(ReturningHandler handler,
uint8_t ist = 0, DPL dpl = DPL_KERNEL) {
return {reinterpret_cast<uintptr_t>(handler), ist, dpl};
}
/*! \brief Create an interrupt descriptor for a handler that does return
* (traps/fault) and receives an error code.
*
*
* \param handler Entry point for interrupt handling
* \param ist Stack index from the \ref TaskStateSegment for the interrupt
* handler. Set to 0 to use current stack.
* \param dpl Permissions required for enter this interrupt handler
* (kernel- or user space)
*/
static InterruptDescriptor ReturningWithError(
ReturningHandlerWithError handler, uint8_t ist = 0,
DPL dpl = DPL_KERNEL) {
return {reinterpret_cast<uintptr_t>(handler), ist, dpl};
}
/*! \brief Create an interrupt descriptor for a handler that does **not**
* return (abort).
*
*
* \param handler Entry point for interrupt handling
* \param ist Stack index from the \ref TaskStateSegment for the interrupt
* handler. Set to 0 to use current stack.
* \param dpl Permissions required for enter this interrupt handler
* (kernel- or user space)
*/
static InterruptDescriptor Diverging(DivergingHandler handler,
uint8_t ist = 0, DPL dpl = DPL_KERNEL) {
return {reinterpret_cast<uintptr_t>(handler), ist, dpl};
}
/*! \brief Create an interrupt descriptor for a handler that does **not**
* return (abort) and receives an error code.
*
*
* \param handler Entry point for interrupt handling
* \param ist Stack index from the \ref TaskStateSegment for the interrupt
* handler. Set to 0 to use current stack.
* \param dpl Permissions required for enter this interrupt handler
* (kernel- or user space)
*/
static InterruptDescriptor DivergingWithError(
DivergingHandlerWithError handler, uint8_t ist = 0,
DPL dpl = DPL_KERNEL) {
return {reinterpret_cast<uintptr_t>(handler), ist, dpl};
}
} __attribute__((packed));
static_assert(sizeof(InterruptDescriptor) == 16,
"IDT::InterruptDescriptor has wrong size");
/*! \brief Load the IDT's address and size into the IDT-Register via `idtr`.
*/
void load();
/*! \brief Set the idt entry for the given interrupt vector.
*/
void set(Core::Interrupt::Vector vector, InterruptDescriptor descriptor);
} // namespace IDT

@ -0,0 +1,44 @@
#include "ioapic.h"
namespace IOAPIC {
/*! \brief IOAPIC registers memory mapped into the CPU's address space.
*
* Access to the actual IOAPIC registers can be obtained by performing the
* following steps:
* 1. Write the number of the IOAPIC register to the address stored in
* `IOREGSEL_REG`
* 2. Read the value from / write the value to the address referred to by
* `IOWIN_REG`.
*
* \see [IO-APIC manual](intel_ioapic.pdf#page=8)
*/
volatile Index *IOREGSEL_REG = reinterpret_cast<volatile Index *>(0xfec00000);
/// \copydoc IOREGSEL_REG
volatile Register *IOWIN_REG =
reinterpret_cast<volatile Register *>(0xfec00010);
// IOAPIC manual, p. 8
const Index IOAPICID_IDX = 0x00;
const Index IOREDTBL_IDX = 0x10;
const uint8_t slot_max = 24;
void init() {}
void config(uint8_t slot, Core::Interrupt::Vector vector,
TriggerMode trigger_mode, Polarity polarity) {
(void)slot;
(void)vector;
(void)trigger_mode;
(void)polarity;
}
void allow(uint8_t slot) { (void)slot; }
void forbid(uint8_t slot) { (void)slot; }
bool status(uint8_t slot) {
(void)slot;
return false;
}
} // namespace IOAPIC

@ -0,0 +1,82 @@
/*! \file
* \brief \ref IOAPIC abstracts the access to the I/O \ref APIC
*/
#pragma once
#include "../types.h"
#include "core_interrupt.h"
#include "ioapic_registers.h"
/*! \brief Abstraction of the I/O APIC that is used for management of external
* interrupts.
* \ingroup interrupts
*
* The I/O APIC's Core component is the IO-redirection table. This table is
* used to configure a flexible mapping between the interrupt number and the
* external interruption. Entries within this table have a width of 64 bit. For
* convenience, the union \ref IOAPIC::RedirectionTableEntry should be used for
* modifying these tables (see file `ioapic_registers.h` for details).
*/
namespace IOAPIC {
/*! \brief Initializes the I/O APIC.
*
* This function will initialize the I/O APIC by initializing the
* IO-redirection table with sane default values. The default interrupt-vector
* number is chosen such that, in case the interrupt is issued, the panic
* handler is executed. In the beginning, all external interrupts are disabled
* within the I/O APIC. Apart from the redirection table, the `APICID` (read
* from the system description tables during boot) needs to be written to the
* `IOAPICID` register (see \ref APIC::getIOAPICID() )
*
* \todo(12) Implement Function
*/
void init();
/*! \brief Creates a mapping between an interrupt vector and an external
interrupt.
*
* \param slot Number of the slot (i.e., the external interrupt) to
configure.
* \param vector Number of the interrupt vector that will be issued for
the external interrupt.
* \param trigger_mode Edge or level triggered interrupt signaling
(level-triggered interrupts required for the optional serial interface)
* \param polarity Polarity of the interrupt signaling (active high or
active low)
*
* \todo(12) Implement Function
*/
void config(uint8_t slot, Core::Interrupt::Vector vector,
TriggerMode trigger_mode = TriggerMode::EDGE,
Polarity polarity = Polarity::HIGH);
/*! \brief Enables the redirection of particular external interrupts to the
* CPU(s).
*
* To fully enable interrupt handling, the interrupts must be enabled for every
* CPU (e.g., by calling
* \ref Core::Interrupt::enable() in main).
* \todo(12) Do that somewhere appropriate.
*
* \param slot Number of the external interrupt that should be enabled.
*
* \todo(12) Implement Function
*/
void allow(uint8_t slot);
/*! \brief Selectively masks external interrupts by slot number.
* \param slot Slot number of the interrupt to be disabled.
*
* \todo(12) Implement Function
*/
void forbid(uint8_t slot);
/*! \brief Check whether an external interrupt source is masked.
* \param slot Slot number of the interrupt to be checked.
* \return Returns `true` iff the interrupt is unmasked, `false` otherwise
*
* \todo(12) Implement Function
*/
bool status(uint8_t slot);
} // namespace IOAPIC

@ -0,0 +1,227 @@
/*! \file
* \brief Helper structures for interacting with the \ref IOAPIC "I/O APIC".
*/
#pragma once
#include "../types.h"
namespace IOAPIC {
typedef uint32_t Index;
typedef uint32_t Register;
extern volatile Index *IOREGSEL_REG;
extern volatile Register *IOWIN_REG;
/*! \brief I/O APIC Identification
*
* The IOAPICID register is register number 0x0. The I/O APIC's ID will be read
* from the system configuration tables (provided by the BIOS) during boot. The
* number can be queried by calling \ref APIC::getIOAPICID(). During
* initialization, this number must be written to the IOAPICID register.
*
* \see [IO-APIC manual](intel_ioapic.pdf#page=9), page 9
*/
union Identification {
struct {
uint32_t : 24, ///< Reserved, do not modify
id : 4, ///< I/O APIC Identification
: 4; ///< Reserved, do not modify
};
Register value;
explicit Identification(Register value) : value(value) {}
} __attribute__((packed));
static_assert(sizeof(Identification) == 4,
"IOAPIC Identification has wrong size");
/*! \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)
// Reserved
EXTERN_INT = 7 ///< external interrupt (only edge triggered)
};
/*! \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 polarity for the redirection-table entry */
enum Polarity {
HIGH = 0, ///< active high
LOW = 1 ///< active low
};
/*! \brief Trigger mode */
enum TriggerMode {
EDGE = 0, ///< edge triggered
LEVEL = 1 ///< level triggered
};
/*! \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 masking */
enum InterruptMask {
UNMASKED = 0, ///< Redirection-table entry is active (non-masked)
MASKED = 1 ///< Redirection-table entry is inactive (masked)
};
/*! \brief Entry in the redirection table.
*
* The redirection table begins with I/O APIC register `0x10` and ends at
* `0x3f`.
*
* Each entry has a size of 64 bit, equaling two I/O APIC registers.
* For instance, entry 0 is stored in registers `0x10` and `0x11`, in which the
* low-order 32 bit (equals \ref value_low) and high-order 32 bit (equals \ref
* value_high) need to be stored.
*
* The union defined below provides an overlay allowing convenient modification
* of individual bits, while the 32-bit values \ref value_low and \ref
* value_high can be used for writing to the I/O APIC registers.
*
* \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.
*
* \see [IO-APIC manual](intel_ioapic.pdf#page=11), page 11-13
*/
union RedirectionTableEntry {
// @cond ANONYMOUS_STRUCT
struct {
// @endcond
/*! \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 \ref LOWEST_PRIORITY, 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.
*/
DeliveryMode delivery_mode : 3;
/*! \brief The destination mode defines how the value stored in \ref
* destination will be interpreted.
*
* For StuBS, we use \ref LOGICAL
*/
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.
*/
DeliveryStatus delivery_status : 1;
/*! \brief The polarity denotes when an interrupt should be issued.
*
* For StuBS, we usually use \ref HIGH (i.e., when the interrupt line is,
* logically, `1`).
*/
Polarity polarity : 1;
/*! \brief The remote IRR bit indicates whether the local APIC(s) accept the
* level interrupt.
*
* Once the LAPIC sends an \ref LAPIC::endOfInterrupt "End Of Interrupt
* (EOI)", this bit is reset to `0`.
*
* \note This field is read only and is only meaningful for level-triggered
* interrupts.
*/
uint64_t remote_irr : 1;
/*! \brief The trigger mode states whether the interrupt signaling is level
* or edge triggered.
*
* StuBS uses \ref EDGE for the Timer, the Keybaord and (optional) serial
* interface need \ref LEVEL
*/
TriggerMode trigger_mode : 1;
/*! \brief Mask or unmask interrupts for a particular, external source.
*
* The interrupt mask denotes whether interrupts should be
* accepted/unmasked (value \ref UNMASKED) or ignored/masked (value \ref
* MASKED).
*/
InterruptMask interrupt_mask : 1;
/*! \brief Reserved, do not modify. */
uint64_t : 39;
/*! \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;
// @cond ANONYMOUS_STRUCT
} __attribute__((packed));
// @endcond
// @cond ANONYMOUS_STRUCT
struct {
// @endcond
Register value_low; ///< Low-order 32 bits (for the register with the
///< smaller index)
Register value_high; ///< High-order 32 bits (for the register with the
///< higher index)
// @cond ANONYMOUS_STRUCT
} __attribute__((packed));
// @endcond
/*! \brief Constructor for an 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 constructor takes two 32 bit values.
*
* \param value_low First, low-order 32 bit value
* \param value_high Second, high-order 32 bit value
*/
RedirectionTableEntry(Register value_low, Register value_high)
: value_low(value_low), value_high(value_high) {}
};
static_assert(sizeof(RedirectionTableEntry) == 8,
"IOAPIC::RedirectionTableEntry has wrong size");
} // namespace IOAPIC

@ -0,0 +1,63 @@
/*! \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 constexpr 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;
}
};

@ -0,0 +1,190 @@
#include "lapic.h"
#include "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

@ -0,0 +1,199 @@
/*! \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)
*
* Signals to 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 \ref 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
*
* \todo(15) Implement Method
*/
uint32_t ticks(void);
/*! \brief Set the \ref 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
*
* \todo(15) Implement Method
*/
void set(uint32_t counter, uint8_t divide, uint8_t vector, bool periodic,
bool masked = false);
/*! \brief Setup the \ref LAPIC::Timer.
*
* Initializes the \ref LAPIC::Timer
* in such a way that regular interrupts are triggered approx. every `us`
* microseconds when \ref LAPIC::Timer:::activate() is called.
* For this purpose, a suitable timer divisor is determined
* based on the timer frequency determined with \ref LAPIC::Timer::ticks().
* This timer divisor has to be as small as possible, but large enough to
* prevent the 32bit counter from overflowing.
*
* \param us Desired interrupt interval in microseconds.
* \return Indicates if the interval could be set.
*
* \todo(15) Implement Method
*/
bool setup(uint32_t us);
/*! \brief Retrieve the interrupt interval set during \ref LAPIC::Timer::setup()
*
* \return Interval in microseconds
*
* \todo(15) Implement method
*/
uint32_t interval();
/*! \brief Activate the timer on this core.
*
* The core local timer starts with the interval previously configured in
* \ref LAPIC::Timer::setup(). To get timer interrupts on all cores, this method
* must be called once per core (however, it is sufficient to call \ref
* LAPIC::Timer::setup() only once since the APIC-Bus frequency is the same on
* each core).
*
* \todo(15) Implement method
*/
void activate();
/*! \brief Set the LAPIC-timer interrupt mask
* \param masked If set, interrupts are suppressed on counter expiry.
*
* \todo(16) Implement for tick-less kernel
*/
void setMasked(bool masked);
} // namespace Timer
} // namespace LAPIC

@ -0,0 +1,244 @@
#include "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

@ -0,0 +1,54 @@
/*! \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

@ -0,0 +1,91 @@
#include "lapic.h"
#include "lapic_registers.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 (readonly)
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::setTimer() or `0xff` if `div` is invalid.
*/
Register getClockDiv(uint8_t div) {
(void)div;
return 0;
}
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;
}
bool setup(uint32_t us) {
(void)us;
return false;
}
uint32_t interval() { return 0; }
void activate() {}
void setMasked(bool masked) { (void)masked; }
} // namespace Timer
} // namespace LAPIC

@ -0,0 +1,63 @@
#include "pic.h"
#include "ioport.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

@ -0,0 +1,18 @@
/*! \file
* \brief Handle (disable) the old Programmable Interrupt Controller (PIC)
*/
#pragma once
#include "../types.h"
/*! \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

@ -0,0 +1,225 @@
#include "pit.h"
#include "core.h"
#include "ioport.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

@ -0,0 +1,80 @@
/*! \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. 54,900 us (16 bit / 1.193 MHz).
* \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. 54,900 us (16 bit / 1.193 MHz).
* \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 `-soundhw pcspk`.
* 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

@ -0,0 +1,41 @@
#include "serial.h"
Serial::Serial(ComPort port, BaudRate baud_rate, DataBits data_bits,
StopBits stop_bits, Parity parity)
: port(port) {
// initialize FIFO mode, no irqs for sending, irq if first byte was received
// line control, select r/w of divisor latch register
writeReg(LINE_CONTROL_REGISTER, DIVISOR_LATCH_ACCESS_BIT);
// TODO: Implement here the correct handling of input arguments
(void)baud_rate;
(void)data_bits;
(void)stop_bits;
(void)parity;
// FIFO: Enable & clear buffers
writeReg(FIFO_CONTROL_REGISTER,
ENABLE_FIFO | CLEAR_RECEIVE_FIFO | CLEAR_TRANSMIT_FIFO);
// Modem Control: OUT2 (0000 1000) must be set for interrupt
writeReg(MODEM_CONTROL_REGISTER, OUT_2);
}
void Serial::writeReg(RegisterIndex reg, char out) {
// TODO: Implement
(void)reg;
(void)out;
}
char Serial::readReg(RegisterIndex reg) {
// TODO: Implement
(void)reg;
return '\0';
}
int Serial::write(char out) {
// TODO: Implement
(void)out;
return 0;
}

@ -0,0 +1,205 @@
/*! \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 motherboard chipset, 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 : uint8_t {
DATA_5BIT = 0,
DATA_6BIT = 1,
DATA_7BIT = 2,
DATA_8BIT = 3,
};
/*! \brief Number of stop bits per character */
enum StopBits : uint8_t {
STOP_1BIT = 0,
STOP_1_5BIT = 4,
STOP_2BIT = 4,
};
/*! \brief parity bit */
enum Parity : uint8_t {
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(11) Implement Method
*
* \param reg Register index
* \return The value read from register
*/
char readReg(RegisterIndex reg);
/*! \brief Write value to register
*
* \todo(11) 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(11) - 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 Write one byte to the serial interface
*
* \todo(11) - Implement Method
*
* \param out Byte to be written
* \return Byte written (or `-1` if writing byte failed)
*/
int write(char out);
};

@ -0,0 +1,16 @@
#include "system.h"
#include "../debug/output.h"
#include "cmos.h"
#include "ioport.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

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

@ -0,0 +1,41 @@
#include "textwindow.h"
TextWindow::TextWindow(unsigned from_col, unsigned to_col, unsigned from_row,
unsigned to_row, bool use_cursor) {
(void)from_col;
(void)to_col;
(void)from_row;
(void)to_row;
(void)use_cursor;
}
void TextWindow::setPos(unsigned rel_x, unsigned rel_y) {
(void)rel_x;
(void)rel_y;
}
void TextWindow::getPos(unsigned& rel_x, unsigned& rel_y) const {
(void)rel_x;
(void)rel_y;
}
void TextWindow::setPos(int rel_x, int rel_y) {
(void)rel_x;
(void)rel_y;
}
void TextWindow::getPos(int& rel_x, int& rel_y) const {
(void)rel_x;
(void)rel_y;
}
void TextWindow::print(const char* str, size_t length, CGA::Attribute attrib) {
(void)str;
(void)length;
(void)attrib;
}
void TextWindow::reset(char character, CGA::Attribute attrib) {
(void)character;
(void)attrib;
}

@ -0,0 +1,134 @@
/*! \file
* \brief \ref TextWindow provides virtual output windows in text mode
*/
#pragma once
#include "../types.h"
#include "cga.h"
/*! \brief Virtual windows in text mode
* \ingroup io
*
* Outputs text on a part of the screen in \ref CGA,
* a window is defined in by 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 {
// 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 CGA 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 CGA::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 CGA::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(11) 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(11) Implement method, use \ref CGA::setCursor() for the hardware
* cursor
*/
void setPos(unsigned rel_x, unsigned rel_y);
/*! \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.
* Negative coordinates are interpreted relative to the right and bottom
* border of the window.
*
* \todo(11) Implement this method (it can either use or replace
* \ref setPos(unsigned, unsigned))
*/
void setPos(int rel_x, int 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(11) Implement Method, use \ref CGA::getCursor() for the hardware
* cursor
*/
void getPos(unsigned& rel_x, unsigned& rel_y) const;
/// \copydoc TextWindow::getPos(unsigned&,unsigned&) const
void getPos(int& rel_x, int& 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(11) Implement Method
*/
void print(const char* string, size_t length,
CGA::Attribute attrib = CGA::Attribute()); // NOLINT
/*! \brief Delete all contents in the window and reset the cursor.
*
* \param character Fill character
* \param attrib Attribute for fill character
* \todo(11) Implement Method
*/
void reset(char character = ' ', CGA::Attribute attrib = CGA::Attribute());
};

@ -0,0 +1,245 @@
; The stony path to Long Mode (64-bit)...
; ... begins in 32-bit Protected Mode
[BITS 32]
; Pointer to Long Mode Global Descriptor Table (GDT, arch/gdt.cc)
[EXTERN gdt_long_mode_pointer]
[GLOBAL long_mode]
long_mode:
; You can check if the CPU supports Long Mode by using the `cpuid` command.
; Problem: You first have to figure out if the `cpuid` command itself is
; supported. Therefore, you have to try to reverse the 21st bit in the EFLAGS
; register -- if it works, then there is the 'cpuid' instruction.
CPUID_BIT_MASK equ 1 << 21
check_cpuid:
; Save EFLAGS on stack
pushfd
; Copy stored EFLAGS from stack to EAX register
mov eax, [esp]
; Flip the 21st bit (ID) in EAX
xor eax, CPUID_BIT_MASK
; Copy EAX to EFLAGS (using the stack)
push eax
popfd
; And reverse: copy EFLAGS to EAX (using the stack)
; (but the 21st bit should now still be flipped, if `cpuid` is supported)
pushfd
pop eax
; Compare the new EFLAGS copy (residing in EAX) with the EFLAGS stored at
; the beginning of this function by using an exclusive OR -- all different
; (flipped) bits will be stored in EAX.
xor eax, [esp]
; Restore original EFLAGS
popfd
; If 21st Bit in EAX is set, `cpuid` is supported -- continue at check_long_mode
and eax, CPUID_BIT_MASK
jnz check_long_mode
; Show error message "No CPUID" and stop CPU
mov dword [0xb8000], 0xcf6fcf4e
mov dword [0xb8004], 0xcf43cf20
mov dword [0xb8008], 0xcf55cf50
mov dword [0xb800c], 0xcf44cf49
hlt
; Now you are able to use the `cpuid` instruction to check if Long Mode is
; available -- after you've checked if the `cpuid` is able to perform the
; check itself (since it is an extended `cpuid` function)...
CPUID_GET_LARGEST_EXTENDED_FUNCTION_NUMBER equ 0x80000000
CPUID_GET_EXTENDED_PROCESSOR_FEATURES equ 0x80000001
CPUID_HAS_LONGMODE equ 1 << 29
check_long_mode:
; Set argument for `cpuid` to check the availability of extended functions
; and call cpuid
mov eax, CPUID_GET_LARGEST_EXTENDED_FUNCTION_NUMBER
cpuid
; The return value contains the maximum function number supported by `cpuid`,
; You'll need the function number for extended processor features
cmp eax, CPUID_GET_EXTENDED_PROCESSOR_FEATURES
; If not present, the CPU is definitely too old to support long mode
jb no_long_mode
; Finally, you are able to check the Long Mode support itself
mov eax, CPUID_GET_EXTENDED_PROCESSOR_FEATURES
cpuid
; If the return value in the EDX register has set the 29th bit,
; then long mode is supported -- continue with setup_paging
test edx, CPUID_HAS_LONGMODE
jnz setup_paging
no_long_mode:
; Show error message "No 64bit" and stop CPU
mov dword [0xb8000], 0xcf6fcf4e
mov dword [0xb8004], 0xcf36cf20
mov dword [0xb8008], 0xcf62cf34
mov dword [0xb800c], 0xcf74cf69
hlt
; Paging is required for Long Mode.
; Since an extensive page manager might be a bit of an overkill to start with,
; the following code creates an identity mapping for the first four gigabytes
; (using huge pages): each virtual address will point to the same physical one.
; This area (up to 4 GiB) is important for some memory mapped devices (APIC)
; and you don't want to remap them yet for simplicity reasons.
; In the advanced operating systems lecture, this topic is covered in detail,
; however, if you want a quick overview, have a look at
; https://wiki.osdev.org/Page_Tables#2_MiB_pages_2
PAGE_SIZE equ 4096
PAGE_FLAGS_PRESENT equ 1 << 0
PAGE_FLAGS_WRITEABLE equ 1 << 1
PAGE_FLAGS_USER equ 1 << 2
PAGE_FLAGS_HUGE equ 1 << 7
setup_paging:
; Unlike in Protected Mode, an entry in the page table has a size of 8 bytes
; (vs 4 bytes), so there are only 512 (and not 1024) entries per table.
; Structure of the 3-level PAE paging: One entry in the
; - lv2: Page-Directory-Table (PDT) covers 2 MiB (1 Huge Page)
; - lv3: Page-Directory-Pointer-Table (PDPT) covers 1 GiB (512 * 2 MiB)
; - lv4: Page-Map-Level-4-Table (PML4) covers 512 GiB (512 * 1 GiB)
; To address 4 GiB only four level-2 tables are required.
; All entries of the level-2 tables should be marked as writeable (attributes)
; and map (point to) the corresponding physical memory.
; This is done in a loop using ECX as counter
mov ecx, 0
.identitymap_level2:
; Calculate physical address in EAX (2 MiB multiplied by the counter)
mov eax, 0x200000
mul ecx
; Configure page attributes
or eax, PAGE_FLAGS_PRESENT | PAGE_FLAGS_WRITEABLE | PAGE_FLAGS_HUGE | PAGE_FLAGS_USER
; Write (8 byte) entry in the level-2 table
mov [paging_level2_tables + ecx * 8], eax
; Increment counter...
inc ecx
; ... until all four level-2 tables are filled
cmp ecx, 512 * 4
jne .identitymap_level2
; The first four entries of the level-3 table should point to the
; four level-2 tables (and be writeable as well).
; Again, ECX acts as counter for the loop
mov ecx, 0
.identitymap_level3:
; Calculate the address: ECX * PAGE_SIZE + paging_level2_tables
mov eax, ecx
; The size of a page is stored in the EDX register
mov edx, PAGE_SIZE
mul edx
add eax, paging_level2_tables
; Configure attributes
or eax, PAGE_FLAGS_PRESENT | PAGE_FLAGS_WRITEABLE | PAGE_FLAGS_USER
; Write (8 byte) entry in the level-3 table
mov [paging_level3_table + ecx * 8], eax
; Increment counter...
inc ecx
; ... until all four entries of the table are written
cmp ecx, 4
jne .identitymap_level3
mov eax, paging_level2_tables
or eax, PAGE_FLAGS_PRESENT | PAGE_FLAGS_WRITEABLE | PAGE_FLAGS_USER
mov [paging_level3_table], eax
; The first entry of the level-4 table should point to to the level-3 table
mov eax, paging_level3_table
or eax, PAGE_FLAGS_PRESENT | PAGE_FLAGS_WRITEABLE | PAGE_FLAGS_USER
mov [paging_level4_table], eax
; Time to activate paging
paging_enable:
; First setup the control registers
; Write the address of the level-4 table into the CR3 register
mov eax, paging_level4_table
mov cr3, eax
; Activate Physical Address Extension (PAE)
; by setting the 5th bits in the CR4 register
mov eax, cr4
or eax, 1 << 5
mov cr4, eax
; Set the Long Mode Enable Bit in den EFER MSR
; (Extended Feature Enable Register Model Specific Register)
mov ecx, 0xC0000080
rdmsr
or eax, 1 << 8
wrmsr
; Finally, the 31st bit in CR0 is set to enable Paging
mov eax, cr0
or eax, 1 << 31
mov cr0, eax
; Load Long Mode Global Descriptor Table
lgdt [gdt_long_mode_pointer]
; Far jump to the 64-bit start code
jmp 0x8:long_mode_start
; print `KO` to screen
mov dword [0xb8000], 0x3f4f3f4b
hlt
; Memory reserved for page tables
[SECTION .bss]
align 4096
[GLOBAL paging_level4_table]
[GLOBAL paging_level3_table]
[GLOBAL paging_level2_tables]
; 1x Level-4 Table (Page Map Level 4)
paging_level4_table:
resb PAGE_SIZE
; 1x Level-3 Table (Page Directory Pointer Table)
paging_level3_table:
resb PAGE_SIZE
; 4x Level-2 Table (Page Directory)
paging_level2_tables:
resb PAGE_SIZE * 4
[SECTION .text]
[EXTERN kernel_init] ; C++ entry function
; Continue with 64 bit code
[BITS 64]
long_mode_start:
; Zero all segment register
mov ax, 0x0
mov ss, ax
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
; Call high-level (C++) kernel initialization function
call kernel_init
; Print `STOP` to screen and stop
mov rax, 0x2f502f4f2f544f53
mov qword [0xb8000], rax
hlt

@ -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)

@ -0,0 +1,167 @@
#include "boot/multiboot/data.h"
/*! \brief Multiboot Information Structure according to Specification
* \see [Multiboot Specification]{@ref multiboot}
*/
struct multiboot_info {
/*! \brief Helper Structure
*/
struct Array {
uint32_t size; ///< Length
uint32_t addr; ///< Begin (physical address)
} __attribute__((packed));
enum Flag : uint32_t {
Memory = 1 << 0, ///< is there basic lower/upper memory information?
BootDev = 1 << 1, ///< is there a boot device set?
CmdLine = 1 << 2, ///< is the command-line defined?
Modules = 1 << 3, ///< are there modules to do something with?
/* These next two are mutually exclusive */
SymbolTable = 1 << 4, ///< is there an a.out symbol table loaded?
SectionHeader = 1 << 5, ///< is there an ELF section header table?
MemoryMap = 1 << 6, ///< is there a full memory map?
DriveInfo = 1 << 7, ///< Is there drive info?
ConfigTable = 1 << 8, ///< Is there a config table?
BootLoaderName = 1 << 9, ///< Is there a boot loader name?
ApmTable = 1 << 10, ///< Is there a APM table?
// Is there video information?
VbeInfo = 1 << 11, ///< Vesa bios extension
FramebufferInfo = 1 << 12 ///< Framebuffer
} flags;
/*! \brief Available memory retrieved from BIOS
*/
struct {
uint32_t lower; ///< Amount of memory below 1 MiB in kilobytes
uint32_t upper; ///< Amount of memory above 1 MiB in kilobytes
} mem __attribute__((packed));
uint32_t boot_device; ///< "root" partition
uint32_t cmdline; ///< Kernel command line
Array mods; ///< List of boot modules
union {
/*! \brief Symbol table for kernel in a.out format
*/
struct {
uint32_t tabsize;
uint32_t strsize;
uint32_t addr;
uint32_t reserved;
} aout_symbol_table __attribute__((packed));
/*! \brief Section header table for kernel in ELF
*/
struct {
uint32_t num; ///< Number of entries
uint32_t size; ///< Size per entry
uint32_t addr; ///< Start of the header table
uint32_t shndx; ///< String table index
} elf_section_header_table __attribute__((packed));
};
struct Array mmap; ///< Memory Map
struct Array drives; ///< Drive Information
uint32_t config_table; ///< ROM configuration table
uint32_t boot_loader_name; ///< Boot Loader Name
uint32_t apm_table; ///< APM table
struct Multiboot::VBE vbe; ///< VBE Information
struct Multiboot::Framebuffer framebuffer; ///< Framebuffer information
/*! \brief Check if setting is available
* \param flag Flag to check
* \return `true` if available
*/
bool has(enum Flag flag) const { return (flags & flag) != 0; }
} __attribute__((packed));
assert_size(multiboot_info, 116);
/*! \brief The pointer to the multiboot structures will be assigned in the
* assembler startup code (multiboot.inc)
*/
struct multiboot_info *multiboot_addr = 0;
namespace Multiboot {
Module *getModule(unsigned i) {
if (multiboot_addr != nullptr &&
multiboot_addr->has(multiboot_info::Flag::Modules) &&
i < multiboot_addr->mods.size) {
return i + reinterpret_cast<Module *>(
static_cast<uintptr_t>(multiboot_addr->mods.addr));
} else {
return nullptr;
}
}
unsigned getModuleCount() { return multiboot_addr->mods.size; }
void *Memory::getStartAddress() const {
if (sizeof(void *) == 4 && (addr >> 32) != 0) {
return reinterpret_cast<void *>(addr & 0xffffffff);
} else {
return reinterpret_cast<void *>(static_cast<uintptr_t>(addr));
}
}
void *Memory::getEndAddress() const {
uint64_t end = addr + len;
if (sizeof(void *) == 4 && (end >> 32) != 0) {
return reinterpret_cast<void *>(addr & 0xffffffff);
} else {
return reinterpret_cast<void *>(static_cast<uintptr_t>(end));
}
}
bool Memory::isAvailable() const { return type == AVAILABLE; }
Memory *Memory::getNext() const {
if (multiboot_addr != nullptr &&
multiboot_addr->has(multiboot_info::Flag::MemoryMap)) {
uintptr_t next = reinterpret_cast<uintptr_t>(this) + size + sizeof(size);
if (next < multiboot_addr->mmap.addr + multiboot_addr->mmap.size) {
return reinterpret_cast<Memory *>(next);
}
}
return nullptr;
}
Memory *getMemoryMap() {
if (multiboot_addr != nullptr &&
multiboot_addr->has(multiboot_info::Flag::MemoryMap) &&
multiboot_addr->mmap.size > 0) {
return reinterpret_cast<Memory *>(
static_cast<uintptr_t>(multiboot_addr->mmap.addr));
} else {
return nullptr;
}
}
char *getCommandLine() {
return reinterpret_cast<char *>(
static_cast<uintptr_t>(multiboot_addr->cmdline));
}
char *getBootLoader() {
return reinterpret_cast<char *>(
static_cast<uintptr_t>(multiboot_addr->boot_loader_name));
}
VBE *getVesaBiosExtensionInfo() {
if (multiboot_addr != nullptr &&
multiboot_addr->has(multiboot_info::Flag::VbeInfo)) {
return &(multiboot_addr->vbe);
} else {
return nullptr;
}
}
Framebuffer *getFramebufferInfo() {
if (multiboot_addr != nullptr &&
multiboot_addr->has(multiboot_info::Flag::FramebufferInfo)) {
return &(multiboot_addr->framebuffer);
} else {
return nullptr;
}
}
} // namespace Multiboot

@ -0,0 +1,230 @@
/*! \file
* \brief \ref Multiboot Interface
*/
#pragma once
#include "../../compiler/fix.h"
#include "../../debug/assert.h"
#include "../../types.h"
/*! \brief Interface for Multiboot
*
* Due to historical reasons, a normal BIOS allows you to do quite an egg dance
* until you finally reach the actual kernel (especially with only 512 bytes
* available in the master boot record...).
* Fortunately, there are [boot loaders](https://wiki.osdev.org/Bootloader) that
* (partly) do this ungrateful job for you:
* They load your kernel into memory, switch (the bootstrap processor) to
* protected mode (32 bit) and jump to the entry point of our kernel -- saving
* you a lot of boring (or enlightening?) work: reading ancient systems
* documentation. One of the most famous representatives is the [Grand Unified
* Bootloader (GRUB)](https://www.gnu.org/software/grub/), which is also the
* reference implementation of the [Multiboot Specification]{@ref multiboot}.
*
* A Multiboot compliant boot loader will prepare the system according to your
* needs and can hand you a lot of useful information (e.g. references to
* initial ramdisks).
*
* However, you have to inform the loader that you are also compliant to the
* specification, and (if required) instruct the loader to adjust specific
* settings (e.g. the graphics mode).
*
* For this purpose you have to configure the beginning of the kernel (the first
* 8192 bytes of the kernel binary) accordingly (see `compiler/section.ld`) --
* this is were the boot loader will search for a magic header and parse the
* subsequent entries containing the desired system configuration.
* In StuBS these flags are set in `boot/multiboot/config.inc` and the header
* structure is generated in `boot/multiboot/header.asm`.
*
* The first step in your \ref startup_bsp() "kernel entry function" is saving
* the pointer to the struct with the information from the boot loader
* (transferred via register `ebx`) -- and \ref Multiboot provides you the
* interface to comfortably access its contents!
*/
namespace Multiboot {
/*! \brief Boot Module
* (also known as `initrd` = initial Ramdisk)
*
* \see [1.7 Boot modules]{@ref multiboot}
* \see [3.3 Boot information format]{@ref multiboot}
*/
class Module {
uint32_t start; ///< Start address
uint32_t end; ///< End address (excluded)
uint32_t cmdline; ///< commandline parameter
uint32_t pad [[maybe_unused]]; ///< alignment; must be 0
public:
/*! \brief Get start of this boot module
* \return Pointer to begin of modules physical address
*/
void* getStartAddress() const {
return reinterpret_cast<void*>(static_cast<uintptr_t>(start));
}
/*! \brief Get end of this boot module
* \return Pointer beyond the modules physical address
*/
void* getEndAddress() const {
return reinterpret_cast<void*>(static_cast<uintptr_t>(end));
}
/*! \brief Get the size of this boot module
* \return Module size in bytes (difference of end and start address)
*/
size_t getSize() const { return static_cast<size_t>(end - start); }
/*! \brief Get the command line for this module
* \return pointer to zero terminated string
*/
char* getCommandLine() const {
return reinterpret_cast<char*>(static_cast<uintptr_t>(cmdline));
}
} __attribute__((packed));
assert_size(Module, 16);
/*! \brief Retrieve a certain boot module
* \param i boot module number
* \return Pointer to structure with boot module information
*/
Module* getModule(unsigned i);
/*! \brief Get the number of modules
* \return Pointer to structure with boot module information
*/
unsigned getModuleCount();
/*! \brief Get the kernel command line
* \return pointer to zero terminated string
*/
char* getCommandLine();
/*! \brief Get the name of the boot loader
* \return pointer to zero terminated string
*/
char* getBootLoader();
/*! \brief Memory Map
*
* The boot loader queries the BIOS for a memory map and stores its result in
* (something like) a linked list. However, this list may not be complete,
* can have contradictory entries and does not take the location of your kernel
* or any boot modules into account.
* (Anyways, it is still the best memory map you will have in StuBS...)
*
* \note Needs to be enabled explicitly by setting the `MULTIBOOT_MEMORY_INFO`
* flag in the multiboot header (see `boot/multiboot/config.inc`)!
*
* \see [Detecting Memory](https://wiki.osdev.org/Detecting_Memory_(x86))
*/
class Memory {
uint32_t size; ///< Size of this entry (can exceed size of the class, rest
///< will be padding bits)
uint64_t addr; ///< Begin of memory area
uint64_t len; ///< length of the memory area
/*! \brief Usage Type
*/
enum Type : uint32_t {
AVAILABLE = 1, ///< Memory is available and usable in kernel
RESERVED = 2, ///< Memory is reserved (without further explanation)
ACPI = 3, ///< Memory may be reclaimed by ACPI
NVS = 4, ///< Memory is non volatile storage for ACPI
BADRAM = 5 ///< Area contains bad memory
} type;
public:
/*! \brief Get start of this memory area
* \return Pointer to begin of the physical address of the memory area
*/
void* getStartAddress() const;
/*! \brief Get end of this memory area
* \return Pointer beyond the physical address of this memory area
*/
void* getEndAddress() const;
/*! \brief Is the memory marked as usable
* \return `true` if available, `false` if not usable.
*/
bool isAvailable() const;
/*! \brief Get the next memory area
* \return pointer to the next memory area entry
*/
Memory* getNext() const;
} __attribute__((packed));
assert_size(Memory, 24);
/*! \brief Retrieve the first entry of the memory map
*/
Memory* getMemoryMap();
/*! \brief Video mode: Vesa BIOS Extension
*
* \see [VESA BIOS Extension (VBE) Core Functions (Version 3)](vbe3.pdf)
*/
struct VBE {
uint32_t control_info; ///< Pointer to VBE control information
uint32_t mode_info; ///< Pointer to VBE mode information
uint16_t mode; ///< Selected video mode (as defined in the standard)
uint16_t interface_seg; ///< Protected mode interface (unused)
uint16_t interface_off; ///< Protected mode interface (unused)
uint16_t interface_len; ///< Protected mode interface (unused)
} __attribute__((packed));
assert_size(VBE, 16);
/*! \brief Get pointer to Vesa BIOS Extension information
*
* \note Only available if the `MULTIBOOT_VIDEO_MODE` flag was explicitly set
* in the multiboot header (see `boot/multiboot/config.inc`)!
*/
VBE* getVesaBiosExtensionInfo();
/*! \brief Video mode: Framebuffer
*
* This beautiful structure contains everything required for using the graphic
* framebuffer in a very handy manner -- however, it may not be well supported
* by current boot loaders...
* These information can be retrieved from \ref VBE as well, though you then
* have to parse these huge structures containing a lot of useless stuff.
*/
struct Framebuffer {
uint64_t address; ///< Physical address of the framebuffer
uint32_t pitch; ///< Number of bytes per row
uint32_t width; ///< Width of framebuffer
uint32_t height; ///< Height of framebuffer
uint8_t bpp; ///< Bits per pixel
enum Type : uint8_t {
INDEXED = 0, ///< Using a custom color palette
RGB = 1, ///< Standard red-green-blue
EGA_TEXT = 2 ///< Enhanced Graphics Adapter color palette
} type;
union {
/*! \brief For INDEXED type
*/
struct {
uint32_t palette_addr; ///< Address of an array with RGB values
uint16_t palette_num_colors; ///< Number of colors (in array above)
} __attribute__((packed));
/*! \brief For RGB type
*/
struct {
uint8_t offset_red; ///< Offset of red value
uint8_t bits_red; ///< Bits used in red value
uint8_t offset_green; ///< Offset of green value
uint8_t bits_green; ///< Bits used in green value
uint8_t offset_blue; ///< Offset of blue value
uint8_t bits_blue; ///< Bits used in blue value
} __attribute__((packed));
} __attribute__((packed));
} __attribute__((packed));
assert_size(Framebuffer, 28);
/*! \brief Get pointer to framebuffer information
*
* \note Only available if the `MULTIBOOT_VIDEO_MODE` flag was explicitly set
* in the multiboot header (see `boot/multiboot/config.inc`)!
*/
Framebuffer* getFramebufferInfo();
} // namespace Multiboot

@ -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

@ -0,0 +1,77 @@
; This is the actual entry point of the kernel.
; The switch into the 32-bit 'Protected Mode' has already been performed
; (by the boot loader).
; The assembly code just performs the absolute necessary steps (like setting up
; the stack) to be able to jump into the C++ code -- and continue further
; initialization in a (more) high-level language.
[BITS 32]
; External functions and variables
[EXTERN CPU_CORE_STACK_SIZE] ; Constant containing the initial stack size (per CPU core), see `arch/core.cc`
[EXTERN cpu_core_stack_pointer] ; Pointer to reserved memory for CPU core stacks, see `arch/core.cc`
[EXTERN gdt_protected_mode_pointer] ; Pointer to 32 Bit Global Descriptor Table (located in `arch/gdt.cc`)
[EXTERN long_mode] ; Low level function to jump into the 64-bit mode ('Long Mode', see `boot/longmode.asm`)
[EXTERN multiboot_addr] ; Variable, in which the Pointer to Multiboot information
; structure should be stored (`boot/multiboot/data.cc`)
; Load Multiboot settings
%include "boot/multiboot/config.inc"
[SECTION .text]
; Entry point for the bootstrap processor (CPU0)
[GLOBAL startup_bsp]
startup_bsp:
; Check if kernel was booted by a Multiboot compliant boot loader
cmp eax, MULTIBOOT_HEADER_MAGIC_LOADER
jne skip_multiboot
; Pointer to Multiboot information structure has been stored in ebx by the
; boot loader -- copy to a variable for later usage.
mov [multiboot_addr], ebx
skip_multiboot:
; Disable interrupts
cli
; Disable non maskable interrupts (NMI)
; (we are going to ignore them)
mov al, 0x80
out 0x70, al
jmp load_cs
; Segment initialization
; (code used by bootstrap and application processors as well)
[GLOBAL segment_init]
segment_init:
; Load temporary protected mode Global Descriptor Table (GDT)
lgdt [gdt_protected_mode_pointer]
; Initialize segment register
mov ax, 0x10
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
; Load code segment register
jmp 0x8:load_cs
load_cs:
; Initialize stack pointer:
; Atomic increment of `cpu_core_stack_pointer` by `CPU_CORE_STACK_SIZE`
; (to avoid race conditions at application processor boot)
mov eax, [CPU_CORE_STACK_SIZE]
lock xadd [cpu_core_stack_pointer], eax
; Since the stack grows into the opposite direction,
; Add `CPU_CORE_STACK_SIZE` again
add eax, [CPU_CORE_STACK_SIZE]
; Assign stack pointer
mov esp, eax
; Clear direction flag for string operations
cld
; Switch to long mode (64 bit)
jmp long_mode

@ -0,0 +1,73 @@
#include "startup.h"
#include "../arch/acpi.h"
#include "../arch/apic.h"
#include "../arch/core.h"
#include "../arch/idt.h"
#include "../arch/pic.h"
#include "../compiler/libc.h"
#include "../debug/output.h"
#include "../interrupt/handlers.h"
/*! \brief The first processor is the Bootstrap Processor (BSP)
*/
static bool isBootstrapProcessor = true;
extern "C" [[noreturn]] void kernel_init() {
if (isBootstrapProcessor) {
isBootstrapProcessor = false;
// Setup and load Interrupt Description Table (IDT)
initInterruptHandlers();
// Initialize PICs
PIC::initialize();
// Call global constructors
CSU::initializer();
// Initialize ACPI
if (!ACPI::init()) {
DBG_VERBOSE << "No ACPI!";
Core::die();
}
// Initialize APIC (using ACPI)
if (!APIC::init()) {
DBG_VERBOSE << "APIC Initialization failed";
Core::die();
}
// Initialize the Bootstrap Processor
Core::init();
// Go to main function
main();
// Exit CPU
DBG_VERBOSE << "CPU core " << Core::getID() << " (BSP) shutdown." << endl;
Core::exit();
} else {
// Load Interrupt Description Table (IDT)
IDT::load();
// Initialize this application processor
Core::init();
// And call the AP main
main_ap();
// Exit CPU
DBG_VERBOSE << "CPU core " << Core::getID() << " (AP) shutdown." << endl;
Core::exit();
}
// Only on last core
if (Core::countOnline() == 1) {
// Call global destructors
CSU::finalizer();
}
// wait forever
while (true) {
Core::die();
}
}

@ -0,0 +1,50 @@
/*! \file
* \brief Startup of the first core, also known as bootstrap processor (BSP)
*/
#pragma once
#include "../compiler/fix.h"
#include "../types.h"
/*! \brief Entry point of your kernel
*
* \ingroup Startup
*
* Executed by boot loader.
* Stores Pointer to \ref Multiboot information structure,
* initializes stack pointer,
* switches to long mode
* and finally calls the C++ \ref kernel_init function
*/
extern "C" void startup_bsp() ERROR_ON_CALL(
"The kernel entry point shall never be called from your code!");
/*! \brief Initializes the C++ environment and detects system components
*
* \ingroup Startup
*
* The startup code(both for \ref startup_bsp "bootstrap" and \ref startup_ap
* "application processor") jumps to this high level function. After
* initialization it will call \ref main()
*/
/*! or \ref main_ap() respectively
*/
extern "C" [[noreturn]] void kernel_init() ERROR_ON_CALL(
"The kernel init function shall never be called from your code!");
/*! \brief Kernels main function
*
* Called after initialization of the system by \ref kernel_init()
*/
/*! \note This code will only be executed on the booting CPU (i.e., the one with
* ID 0).
*/
extern "C" int main();
/*! \brief Entry point for application processors
*
* Called after initialization of the system by \ref kernel_init()
*
* \note Code in this function will be executed on all APs (i.e., all CPUs
* except ID 0)
*/
extern "C" int main_ap();

@ -0,0 +1,68 @@
; Startup of the remaining application processors (in real mode)
; and switching to 'Protected Mode' with a temporary GDT.
; This code is relocated by ApplicationProcessor::relocateSetupCode()
[SECTION .setup_ap_seg]
[GLOBAL setup_ap_gdt]
[GLOBAL setup_ap_gdtd]
; Unlike the bootstrap processor, the application processors have not been
; set up by the boot loader -- they start in real mode (16 bit) and have to be
; switched manually to protected mode (32 bit)
[BITS 16]
setup_ap:
; Initialize segment register
mov ax, cs ; Code segment and...
mov ds, ax ; .. data segment should point to the same segment
; (we don't use stack / stack segment)
; Disable interrupts
cli
; Disable non maskable interrupts (NMI)
mov al, 0x80
out 0x70, al
; load temporary real mode Global Descriptor Table (GDT)
lgdt [setup_ap_gdtd - setup_ap]
; Switch to protected mode:
; enable protected mode bit (1 << 0) in control register 0
mov eax, cr0
or eax, 1
mov cr0, eax
; Far jump to 32 bit `startup_ap` function
jmp dword 0x08:startup_ap
; memory reserved for temporary real mode GDT
; initialized by ApplicationProcessor::relocateSetupCode()
align 4
setup_ap_gdt:
dq 0,0,0,0,0 ; reserve memory for at least 5 GDT entries
; memory reserved for temporary real mode GDT descriptor
; initialized by ApplicationProcessor::relocateSetupCode()
setup_ap_gdtd:
dw 0,0,0,0,0 ; reserve memory for GDT descriptor
[SECTION .text]
[BITS 32]
; Segment initialization defined in `boot/startup.asm`
[EXTERN segment_init]
; protected mode (32 bit) startup code for application processor
startup_ap:
; reload all segment selectors (since they still point to the real mode GDT)
mov ax, 0x10
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
; Use same segment initialization function as bootstrap processor
jmp segment_init

@ -0,0 +1,82 @@
#include "startup_ap.h"
#include "../arch/core_interrupt.h"
#include "../arch/gdt.h"
#include "../arch/lapic.h"
#include "../arch/pit.h"
#include "../debug/assert.h"
#include "../debug/output.h"
#include "../utils/size.h"
#include "../utils/string.h"
namespace ApplicationProcessor {
// Make sure that the RELOCATED_SETUP is in low memory (< 1 MiB)
static_assert((RELOCATED_SETUP & ~0x000ff000) == 0,
"Not a valid 1 MB address for RELOCATED_SETUP!");
/*! \brief Temporary Global Descriptor Table
*
* Blue print, to be copied into real mode code
*/
constinit GDT::SegmentDescriptor ap_gdt[] = {
// nullptr-Deskriptor
{},
// XXX: Can't we just use GDT::protected_mode?
// code segment
GDT::SegmentDescriptor::Segment(0, UINT32_MAX, true, 0, GDT::SIZE_32BIT),
// data segment
GDT::SegmentDescriptor::Segment(0, UINT32_MAX, false, 0, GDT::SIZE_32BIT),
};
void relocateSetupCode() {
// Relocated setup code
memcpy(reinterpret_cast<void*>(RELOCATED_SETUP), &___SETUP_AP_START__,
&___SETUP_AP_END__ - &___SETUP_AP_START__);
// Adjust GDT:
// Calculate offset for real mode GDT and GDT descriptor
uintptr_t ap_gdt_offset = reinterpret_cast<uintptr_t>(&setup_ap_gdt) -
reinterpret_cast<uintptr_t>(&___SETUP_AP_START__);
uintptr_t ap_gdtd_offset = reinterpret_cast<uintptr_t>(&setup_ap_gdtd) -
reinterpret_cast<uintptr_t>(&___SETUP_AP_START__);
// Copy blue print of real mode GDT to the relocated memory
void* relocated_ap_gdt =
reinterpret_cast<void*>(RELOCATED_SETUP + ap_gdt_offset);
memcpy(relocated_ap_gdt, &ap_gdt, sizeof(ap_gdt));
// Calculate GDT descriptor for relocated address
GDT::Pointer* relocated_ap_gdtd =
reinterpret_cast<GDT::Pointer*>(RELOCATED_SETUP + ap_gdtd_offset);
relocated_ap_gdtd->set(relocated_ap_gdt, size(ap_gdt));
}
void boot(void) {
assert(!Core::Interrupt::isEnabled() &&
"Interrupts should not be enabled before APs have booted!");
// Relocate setup code
relocateSetupCode();
// Calculate Init-IPI vector based on address of relocated setup_ap()
uint8_t vector = RELOCATED_SETUP >> 12;
// Send Init-IPI to all APs
LAPIC::IPI::sendInit();
// wait at least 10ms
PIT::delay(10000);
// Send Startup-IPI twice
DBG_VERBOSE << "Sending STARTUP IPI #1" << endl;
LAPIC::IPI::sendStartup(vector);
// wait at least 200us
PIT::delay(200);
DBG_VERBOSE << "Sending STARTUP IPI #2" << endl;
LAPIC::IPI::sendStartup(vector);
}
} // namespace ApplicationProcessor

@ -0,0 +1,114 @@
/*! \file
* \brief Startup of additional cores, the application processors (APs)
*/
#pragma once
#include "../compiler/fix.h"
#include "../types.h"
/*! \brief Application Processor Boot
*
* Interface to boot the APs
*/
namespace ApplicationProcessor {
/*! \brief Address (below 1 MiB) to which the setup code gets relocated
*/
constexpr uintptr_t RELOCATED_SETUP = 0x40000;
/*! \brief Relocate the real mode setup code
*
* The application processors (APs) start in real mode, which means that your
* setup code must be placed within the first megabyte -- your operating system
* resides currently at a much higher address (16 MiB), so the code has to be
* copied down there first.
*
* Luckily, the code in `setup_ap()` can be relocated by copying -- because it
* does not use any absolute addressing (except when jumping to the protected
* mode function `startup_ap()`).
* The function must be copied to the address of \ref RELOCATED_SETUP (0x40000),
* so that the APs can start there.
*
* The memory section contains a reserved area for the \ref GDT and its
* descriptor, which has to be assigned first with the contents of \ref ap_gdt.
*
* \note You could also tell the linker script to put the code directly
* at the appropriate place, but unfortunately the Qemu multiboot
* implementation (via `-kernel` parameter) can't handle it properly.
*/
void relocateSetupCode();
/*! \brief Boot all application processors
*
* Performs relocation by calling \ref relocateSetupCode()
*
* \see [ISDMv3, 8.4.4.2 Typical AP Initialization
* Sequence](intel_manual_vol3.pdf#page=276)
*/
void boot();
} // namespace ApplicationProcessor
/*! \brief Begin of setup code for application processors
*
* The setup code has to switch from real mode (16 bit) to protected mode (32
* bit), hence it is written in assembly and must be executed in low memory (< 1
* MiB).
*
* After kernel start the code is somewhere above 16 MiB (the bootstrap
* processor was already launched in protected mode by the boot loader).
* Therefore this symbol is required for relocate the code to the position
* specified by \ref ApplicationProcessor::RELOCATED_SETUP.
*
* Luckily, the `setup_ap` code in `boot/startup_ap.asm` is rather simple and
* doesn't depend on absolute addressing -- and is therefore relocatable.
*
* Relocation is done by the function \ref
* ApplicationProcessor::relocateSetupCode()
*
* The `___SETUP_AP_START__` symbol is defined in the linker script
* (`compiler/section.ld`)
*/
extern char ___SETUP_AP_START__;
/*! \brief End of startup code for application processors
*
* This Symbol is defined in the linker script (`compiler/section.ld`)
*/
extern char ___SETUP_AP_END__;
/*! \brief Memory reserved for a temporary real mode GDT
* within the relocatable memory area of the setup code
*/
extern char setup_ap_gdt;
/*! \brief Memory reserved for a temporary real mode GDT descriptor
* within the relocatable memory area of the setup code
*/
extern char setup_ap_gdtd;
/*! \brief Entry point for application processors
*
* Unlike the bootstrap processor, the application processors have not been
* setup by the boot loader -- they start in `Real Mode` (16 bit) and have to be
* switched manually to `Protected Mode` (32 bit).
* This is exactly what this real mode function does, handing over control
* to the (32 bit) function \ref startup_ap()
*
* This code is written is assembly (`boot/startup_ap.asm`) and relocated by
* \ref ApplicationProcessor::relocateSetupCode() during
* \ref ApplicationProcessor::boot()
*/
extern "C" void setup_ap() ERROR_ON_CALL(
"The setup function for application processors shall never be called from "
"your code!");
/*! \brief Startup for application processors
* \ingroup Startup
*
* This function behaves similar to \ref startup_bsp():
* Initializes stack pointer,
* switches to long mode
* and calls the C++ \ref kernel_init function
*/
extern "C" void startup_ap() ERROR_ON_CALL(
"The startup function for application processors shall never be called "
"from your code!");

@ -0,0 +1,13 @@
/*! \file
* \brief Compiler-dependent fixes & idiosyncrasies
*/
#pragma once
#include "../types.h"
#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

@ -0,0 +1,45 @@
#include "libc.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[])();
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])();
}
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])();
}
}
} // namespace CSU
extern "C" int atexit(void (*func)(void)) {
// 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;
return 0;
}

@ -0,0 +1,23 @@
/*! \file
* \brief Initialization functions for global objects required by the compiler
*/
#pragma once
#include "../types.h"
/*! \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

@ -0,0 +1,21 @@
/*! \file
* \brief C++ runtime support functions
*/
#include "../types.h"
void* operator new(size_t, void* place) { return place; }
void operator delete(void* ptr) { (void)ptr; }
void operator delete(void* ptr, size_t size) {
(void)ptr;
(void)size;
}
extern "C" [[noreturn]] void __cxa_pure_virtual() {
// Pure virtual function was called -- this if obviously not valid,
// therefore we wait infinitely.
while (true) {
}
}

@ -0,0 +1,107 @@
/* Entry in our OS -- label 'startup_bsp' in file boot/startup.asm */
ENTRY(startup_bsp)
SECTIONS
{
/* start address of our kernel */
. = 16M;
___KERNEL_START___ = .;
.boot :
{
/* Multiboot Header should be at the very beginning */
*(.multiboot_header)
}
___KERNEL_TEXT_START___ = .;
.text :
{
*(".text")
*(".text$")
*(".init")
*(".fini")
*(".gnu.linkonce.*")
KEEP(*(.note.gnu.build-id))
}
/* 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 = .);
}
___KERNEL_TEXT_END___ = .;
.data :
{
*(".data")
*(".data$")
*(".rodata")
___CTOR_LIST__ = .;
*(".ctors")
*(".ctor")
___CTOR_LIST_END__ = .;
___DTOR_LIST__ = .;
*(".dtors")
*(".dtor")
___DTOR_LIST_END__ = .;
*(".got")
*(".got.plt")
*(".eh_frame")
*(".eh_fram")
*(".jcr")
}
/* Start for application processors, relocated by APIC::init()
* to a below 1 MB address to boot from real mode.
* 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 do it in our software */
.setup_ap_seg ALIGN(0x10) :
{
___SETUP_AP_START__ = .;
*(".setup_ap_seg")
*(".setup_ap_seg$")
}
___SETUP_AP_END__ = .;
.bss :
{
*(".bss")
*(".bss.*")
*(COMMON)
}
___KERNEL_END___ = .;
/DISCARD/ :
{
*(".note")
*(".comment")
/* Keep debug information
*(".debug_line")
*(".debug_info")
*(".debug_abbrev")
*(".debug_aranges")
*/
}
}

@ -0,0 +1,14 @@
#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 so we can mark this as [[noreturn]]
}

@ -0,0 +1,75 @@
// vim: set noet ts=4 sw=4:
/*! \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.
*/
/*!
* \defgroup debug Debugging functions
*/
#pragma once
#include "../types.h"
#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) ((void)0)
#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(11) 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

@ -0,0 +1,44 @@
/*! \file
* \brief \ref CopyStream duplicates \ref OutputStream "output streams"
*/
#pragma once
#include "../object/outputstream.h"
#include "../types.h"
/*! \brief Duplicate all data passed by the stream operator to two \ref
* OutputStream "output streams"
* \ingroup io
*
* Can be used as replacement for any \ref OutputStream -- for example,
* forwarding the \ref DBG output simultaneously to screen (\ref TextStream) and
* serial console (\ref SerialStream).
*
*/
class CopyStream : public OutputStream {
/*! \brief First recipient
*/
OutputStream* first;
/*! \brief Second recipient
*/
OutputStream* second;
public:
/*! \brief Constructor
*
* \param first First recipient for output passed to this object
* \param second Second recipient for output passed to this object
*/
CopyStream(OutputStream* first, OutputStream* second)
: first(first), second(second) {}
/*! \brief Redirect the buffer to both streams and flush them, too.
*/
void flush() override {
buffer[pos] = '\0'; // make sure buffer will only be printed until pos.
*first << buffer << ::flush;
*second << buffer << ::flush;
pos = 0;
}
};

@ -0,0 +1,27 @@
// vim: set noet ts=4 sw=4:
/*! \file
* \brief Macro to print an error message and stop the current core.
*/
#pragma once
#include "../types.h"
/*! \def kernelpanic
* \brief Print an error message in the debug window and \ref Core::die "stop
* the current core"
*
* \param MSG error message
* \ingroup debug
*/
#define kernelpanic(MSG) \
do { \
DBG << "PANIC: '" << (MSG) << "' in " << __func__ << " @ " << __FILE__ \
<< ":" << __LINE__ << ") - CPU stopped." << endl; \
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 "../arch/core.h"
#include "./output.h"

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

@ -0,0 +1,45 @@
/*! \file
* \brief \ref NullStream is a stream discarding everything
*/
#pragma once
#include "../object/outputstream.h"
#include "../types.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 {
/*! \brief Check if type is supported by output stream
*/
template <typename T>
auto check(T v, OutputStream* p = nullptr) -> decltype(*p << v, void()) {}
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) {
check(value);
return *this;
}
};
extern NullStream nullstream;

@ -0,0 +1,93 @@
// vim: set noet ts=4 sw=4:
/*! \file
* \brief Debug macros enabling debug output on a separate window for each
* core.
*/
#pragma once
#include "../types.h"
/*! \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 "./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()
*
* \warning In case of a very unfavorable scheduling, it is theoretically
* possible that the debug output in a multi core system is displayed
* on the wrong (previous) core.
*/
#define DBG nullstream
#include "../arch/core.h"
#include "../device/textstream.h"
/*! \brief Debug window for the CGA screen
*
* 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(11) Define `dout`
*/
extern TextStream dout[Core::MAX];
/*! \brief Debug window with copy function to serial
*
* Provide an additional layer to also ouput debug prints to serial.
* While this is a simple CopyStream pointer in the single core case, it is
* an array in the multi core case, which consists of three TextStreams and
* one CopyStream.
* For that, construction is done like:
*
* \code{.cpp}
* OutputStream* copyout[Core::MAX]{&dout[0], &dout[1], ...}
* \endcode
*
* \todo(11) Define `copyout`
*/
extern OutputStream* copyout[Core::MAX];

@ -0,0 +1,122 @@
#include "keydecoder.h"
#include "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;
}

@ -0,0 +1,40 @@
/*! \file
* \brief \ref KeyDecoder decodes a keystroke to the corresponding \ref Key
* object
*/
#pragma once
#include "../object/key.h"
#include "../types.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);
};

@ -0,0 +1,130 @@
#include "ps2controller.h"
#include "../arch/core_interrupt.h"
#include "../arch/ioport.h"
#include "../compiler/fix.h"
#include "../debug/output.h"
#include "keydecoder.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 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(11) Implement method
*
* \param value data to be sent
*/
[[maybe_unused]] static void 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) {
(void)speed;
(void)delay;
}
void setLed(enum LED led, bool on) {
if (on) {
leds |= led;
} else {
leds &= ~led;
}
sendData(KEYBOARD_SET_LED); // Command for the Keyboard
sendData(leds); // Parameter
}
void drainBuffer() {}
} // namespace PS2Controller

@ -0,0 +1,153 @@
/*! \file
* \brief \ref PS2Controller "PS/2 Controller" (Intel 8042, also known as
* Keyboard Controller)
*/
#pragma once
#include "../object/key.h"
#include "../types.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.
*
* Later the \ref IOAPIC is configured to receive corresponding interrupts.
*
* \note The keyboard interrupts should be configured as \ref IOAPIC::LEVEL
* "level triggered". According to the standard we would have to check the
* corresponding entry in
* \ref ACPI::MADS::Interrupt_Source_Override and use these values. Most
* likely this would suggest an \ref IOAPIC::EDGE "edge-triggered mode" -- which
* would work as well. However, using a \ref IOAPIC::LEVEL "level-triggered
* mode" is more forgiving because it resends the interrupt request even if an
* interrupt was lost (e.g. the required handling, retrieving the buffer entry,
* was not performed).
*
* \todo(12) Register with \ref IOAPIC
*/
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(11) Implement Method
*
* \todo(12) Adjust method (unless it is already non-blocking)
*
* \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).
*/
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
*/
void setLed(enum LED led, bool on);
/*! \brief Empties the keyboard buffer.
*
* The keyboard may not send any interrupts if the buffer is not empty.
* To prevent unhandled keystrokes (for example during boot) the buffer
* should be emptied once right before allowing keyboard interrupts
* (even if keystrokes might be lost).
*
* \todo(12) Implement method
*/
void drainBuffer();
} // namespace PS2Controller

@ -0,0 +1,30 @@
#include "serialstream.h"
SerialStream::SerialStream(ComPort port, BaudRate baud_rate, DataBits data_bits,
StopBits stop_bits, Parity parity) {
(void)port;
(void)baud_rate;
(void)data_bits;
(void)stop_bits;
(void)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;
}
void SerialStream::print(char* str, int length) {
(void)str;
(void)length;
}

@ -0,0 +1,143 @@
/*! \file
* \brief \ref Serial \ref SerialStream "output stream"
*/
#pragma once
#include "../arch/serial.h"
#include "../object/outputstream.h"
#include "../types.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](http://web.archive.org/web/20181008150037/http://www.termsys.demon.co.uk/vtansi.htm).
*/
class SerialStream : public OutputStream, public Serial {
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(11) 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(11) Implement Method
*/
void flush() override;
/*! \brief Change foreground color (for subsequent output)
*
* \todo(11) Implement Method
*
* \param c Color
*/
void setForeground(Color c);
/*! \brief Change background color (for subsequent output)
*
* \todo(11) Implement Method
*
* \param c Color
*/
void setBackground(Color c);
/*! \brief Change text attribute (for subsequent output)
*
* \todo(11) 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.
*
* \todo(11) Implement Method
*/
void reset();
/*! \brief Set the cursor position
*
* \param x Column in window
* \param y Row in window
*
* \todo(11) Implement Method
*/
void setPos(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).
*
* \param str String to output
* \param length length of string
*/
void print(char* str, int length);
};

@ -0,0 +1,12 @@
#include "textstream.h"
TextStream::TextStream(unsigned from_col, unsigned to_col, unsigned from_row,
unsigned to_row, bool use_cursor) {
(void)from_col;
(void)to_col;
(void)from_row;
(void)to_row;
(void)use_cursor;
}
void TextStream::flush() {}

@ -0,0 +1,41 @@
/*! \file
* \brief \ref TextStream outputs text onto the screen in \ref CGA
*/
/*! \defgroup io I/O subsystem
* \brief The input/output subsystem
*/
#pragma once
#include "../types.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 CGA
* 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);
/*! \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(11) Implement method
*/
void flush();
};

@ -0,0 +1,12 @@
#include "epilogues.h"
#include "guard.h"
namespace Epilogues {
void keyboard(Vault& g) { (void)g; }
void timer(Vault& g) { (void)g; }
void assassin(Vault& g) { (void)g; }
}; // namespace Epilogues

@ -0,0 +1,50 @@
/*! \file
* \brief XXX: Write summary
*/
#pragma once
#include "../types.h"
struct Vault;
/*! \brief A handler function for an epilogue.
* \ingroup interrupts
*
* It receives the vault directly, because it is executed on level 1/2 (by the
* \ref Guard) .
*
* \note Since it does only receive one parameter, other data must be passed
* in a different way.
*/
using Epilogue = void (*)(Vault&);
namespace Epilogues {
/*!
* @brief The keyboard epilogue.
*
* Handle the keyboard Key that has been fetched during the prologue.
*
* \todo(13) print the stored character
* \todo(15) Store the key to the keyboard buffer for user threads. Wake user
* threads waiting for a key using the key semaphore.
*
* @param g
*/
void keyboard(Vault& g);
/*!
* @brief Timer epilogue
* \todo(15) Preemptively reschedule threads
* \todo(16) Check the bellringer
* \todo(17) Refresh screen with fixed FPS rate
* @param g
*/
void timer(Vault& g);
/*! \brief Examine the `dying flag` of the current thread and reschedule if
* it is set.
*
* \todo(15) Implement the rescheduling (in \MPStuBS only)
*/
void assassin(Vault& g);
}; // namespace Epilogues

@ -0,0 +1,30 @@
#include "guard.h"
#include "../arch/core.h"
#include "../debug/output.h"
#include "../object/bbuffer.h"
#include "../sync/ticketlock.h"
#include "epilogues.h"
#define FOR_CURRENT_CORE [Core::getID()]
//! \brief The protected data for the epilogue level
static Vault global_vault;
// lists of pending epilogues
static BBuffer<Epilogue, 32> epilogue_queue[Core::MAX] = {};
// Big Kernel Lock (BKL) for the epilogue level
constinit Ticketlock global_lock;
constinit bool epi_flag[Core::MAX] = {false};
Vault::Vault() {}
Guarded::~Guarded() { Guard::leave(); }
Guarded Guard::enter() { while (true); }
void Guard::leave() {}
void Guard::relay(Epilogue handler) { (void)handler; }
const Vault &Guard::unsafeConstAccess() { return global_vault; }

@ -0,0 +1,121 @@
/*! \file
* \brief \ref Guard synchronizes access to epilogue level
*/
#pragma once
#include "../object/bbuffer.h"
#include "../object/key.h"
#include "../types.h"
#include "epilogues.h"
//! \brief The epilogue vault contains the protected data for the epilogue level
struct Vault {
Vault();
// no copy
Vault(const Vault&) = delete;
Vault& operator=(const Vault&) = delete;
};
/*! \brief Lock guard that provides access to the epilogue \ref Vault
*
* This object automatically unlocks the \ref Guard when it goes out of scope.
*/
class Guarded {
public:
//! This constructor should only be used by the \ref Guard
explicit Guarded(Vault& vault) : _vault(vault) {}
//! Leave the critical section
~Guarded();
//! Access the epilogue vault
Vault& vault() { return _vault; }
const Vault& vault() const { return _vault; }
// no copy
Guarded(const Guarded&) = delete;
Guarded& operator=(const Guarded&) = delete;
private:
Vault& _vault;
};
/*! \brief Synchronizes the kernel with interrupts using the Prologue/Epilogue
* Model \ingroup interrupts
*
* The Guard is used to synchronize between "normal" core activities (currently
* just the text output, later system calls) and interrupt handling routines.
* For this purpose, \ref Guard has to contain one ore more \ref BBuffer
* "queues", in which \ref Epilogue functions can be added. This is necessary if
* the critical section is occupied at the time when an interrupt occurs, and
* the
* \ref Epilogue cannot be executed immediately. The queued epilogues are
* processed when leaving the critical section.
*
* **Hints:**
* - The epilogue queue is a central data structure, whose consistency
* must be ensured. The implementation provided by the \ref BBuffer is not
* entirely safe against concurrency. You need to disable
* interrupts during operations on the buffer.
* - In \MPStuBS, you need a separate epilogue queue for each core,
* in which each processor serializes *its* epilogues. However, epilogues
* on different cores could then be executed in parallel, since the
* critical section is managed separately on a per-core base. This must be
* prevented by using a global \ref Ticketlock to avoid concurrent
* execution of epilogues -- there must never be more than one epilogue
* active on the whole system at the same time!<br>
* *Please note:* This [giant lock](https://en.wikipedia.org/wiki/Giant_lock)
* (synchronizing all cores) should not be confused with the (core-specific)
* flag variable that marks only the entry to the epilogue level on the
* corresponding core!
* - Interrupts should be disabled for as short as possible. Due to this
* reason, the prologue/epilogue model allows epilogues to be interrupted
* by prologues. This means that interrupts should be
* \ref Core::Interrupt::enable "enabled" again before the epilogue is
* executed (this includes notifying the APIC about the
* \ref LAPIC::endOfInterrupt() "End-Of-Interrupt")
*/
namespace Guard {
/*! \brief Entering the critical section from level 0.
*
* Entering the critical section has to be handled differently depending on
* the system: In a single-core system it is sufficient to mark the entry
* by just setting a flag variable (since only one control flow can enter
* the critical section at the same time). However, as soon as there are
* multiple cores, this is no longer the case. If a core wants to enter the
* critical section while *another* core is already in there, it should
* (actively) wait in this method until the critical area is released again.
*
* \todo(13) Implement Method
*/
Guarded enter();
/*! \brief Leaving the critical section.
*
* Leaves the critical section and processes all remaining (enqueued) epilogues.
* This may only be called while in level 1/2 after calling \ref enter().
*
* Note: Usually, this method is called by the destructor of the \ref
* Guarded.
*
* \todo(13) Implement Method
*/
void leave();
/*! \brief A prologue wants its epilogue to be processed (entering from level
* 1).
*
* This method is called by the interrupt handlers.
* Whether this is done immediately or the epilogue just enqueued to the
* epilogue queue depends on whether the critical section on *this* Core is
* accessible or not.
*
* \todo(13) Implement Method
*/
void relay(Epilogue handler);
/*! \brief Access the epilogue vault without taking the lock.
* Beware race conditions!
*/
const Vault& unsafeConstAccess();
} // namespace Guard

@ -0,0 +1,14 @@
[SECTION .text]
[EXTERN handle_keyboard]
[GLOBAL handle_keyboard_asm]
; entry point for an interrupt to trigger a kernelpanic
;
align 16
handle_keyboard_asm:
; 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!
; TODO(12): Implement the context save and restore for the keyboard interrupt

@ -0,0 +1,102 @@
#include "handlers.h"
#include "../arch/core_cr.h"
#include "../arch/idt.h"
#include "../arch/lapic.h"
#include "../arch/system.h"
#include "../debug/kernelpanic.h"
#include "../debug/output.h"
void printContext(const InterruptContext *context) {
DBG << "ip: " << hex << context->cs << ':' << context->ip
<< " sp: " << context->ss << ':' << context->sp << " flags" << bin
<< context->flags << endl;
}
[[gnu::interrupt]] void handle_invalid_opcode(InterruptContext *context) {
DBG << "Invalid opcode encoutered" << endl;
printContext(context);
kernelpanic("Invalid opcode!");
}
[[gnu::interrupt]] void handle_double_fault(InterruptContext *context,
uint64_t error) {
(void)error;
DBG << "Double fault encoutered" << endl;
printContext(context);
kernelpanic("Double fault!");
}
[[gnu::interrupt]] void handle_invalid_tss(InterruptContext *context,
uint64_t error) {
DBG << "Invalid tss encoutered. Offending selector idx: " << dec << error
<< endl;
printContext(context);
kernelpanic("Invalid TSS!");
}
[[gnu::interrupt]] void handle_general_protection_fault(
InterruptContext *context, uint64_t error) {
DBG << "General protection fault encoutered. Error code: " << dec << error
<< endl;
printContext(context);
kernelpanic("General protection fault!");
}
enum PAGE_FAULT_ERROR {
PF_ERR_PRESENT = 0x1,
PF_ERR_WRITE = 0x2,
PF_ERR_USER = 0x4,
PF_ERR_RESERVED = 0x8,
PF_ERR_IFETCH = 0x10,
};
[[gnu::interrupt]] void handle_page_fault(InterruptContext *context,
uint64_t error) {
(void)error;
DBG << "Page fault encoutered at linear address " << hex
<< Core::CR<2>::read() << endl
<< (error & PF_ERR_PRESENT ? "present" : "non-present") << " page|"
<< (error & PF_ERR_WRITE ? "write" : "read") << " access|"
<< (error & PF_ERR_USER ? "user" : "supervisor") << "|"
<< (error & PF_ERR_RESERVED ? "reserved bit int pte" : "") << "|"
<< (error & PF_ERR_IFETCH ? "instrution" : "data") << " fetch|" << endl;
printContext(context);
kernelpanic("Page fault!");
}
void handle_keyboard() {}
[[gnu::interrupt]] void handle_panic(InterruptContext *context) {
(void)context;
}
[[gnu::interrupt]] void handle_timer(InterruptContext *context) {
(void)context;
}
[[gnu::interrupt]] void handle_assassin(InterruptContext *context) {
(void)context;
}
[[gnu::interrupt]] void handle_wakeup(InterruptContext *context) {
(void)context;
}
void initInterruptHandlers() {
// Some handlers that are useful for debugging
IDT::set(Core::Interrupt::Vector::INVALID_OPCODE,
IDT::InterruptDescriptor::Returning(handle_invalid_opcode));
IDT::set(Core::Interrupt::Vector::DOUBLE_FAULT,
IDT::InterruptDescriptor::DivergingWithError(handle_double_fault));
IDT::set(Core::Interrupt::Vector::INVALID_TSS,
IDT::InterruptDescriptor::ReturningWithError(handle_invalid_tss));
IDT::set(Core::Interrupt::Vector::GENERAL_PROTECTION_FAULT,
IDT::InterruptDescriptor::ReturningWithError(
handle_general_protection_fault));
IDT::set(Core::Interrupt::Vector::PAGE_FAULT,
IDT::InterruptDescriptor::ReturningWithError(handle_page_fault));
// TODO: Add more handlers here
// Load the idt pointer
IDT::load();
}

@ -0,0 +1,109 @@
/*! \file All interrupts need to start somewhere. This file contains the entry
* points for all interrupts handled by StuBS.
* \brief The Interrupt Subsystem
* \defgroup interrupts Interrupt Handling
*/
#pragma once
#include "../types.h"
/*! \brief Initialize the IDT.
*
* 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`).
*
* \todo(12) Register your own interrupt handlers
*/
void initInterruptHandlers();
struct InterruptContext;
/*!
* @brief Helper function for printf-debugging the InterruptContext
*/
void printContext(const InterruptContext *context);
/*!
* @brief An interrupt handler for the INVALID_OPCODE trap
*/
[[gnu::interrupt]] void handle_invalid_opcode(InterruptContext *context);
/*!
* @brief A double fault occurs when another exception occurs during exception
* handling.
*
* In this case, the OS cannot recover anymore. This can happen e.g.
* during page fault handling.
*/
[[gnu::interrupt]] void handle_double_fault(InterruptContext *context,
uint64_t error);
/*!
* @brief If the task state segment is configured incorrectly, the kernel cannot
* switch the privilege levels during interrupts.
*/
[[gnu::interrupt]] void handle_invalid_tss(InterruptContext *context,
uint64_t error);
/*!
* @brief When the CPU tried to execute an unprivileged opcode or exceeds
* segmentation bounds, the GPF exception is raised.
*/
[[gnu::interrupt]] void handle_general_protection_fault(
InterruptContext *context, uint64_t error);
/*!
* @brief With paging enabled, an invalid access to a memory page causes a page
* fault.
*/
[[gnu::interrupt]] void handle_page_fault(InterruptContext *context,
uint64_t error);
extern "C" { // disable C++ name mangling for asm function
/*! \brief Assembly interrupt handler for the keyboard.
*
* On keyboard interrupt, the register state is saved to and restored from the
* stack. This function wraps the handle_keyboard C-function.
*
* \todo(12) Implement in assembly
*/
[[gnu::interrupt]] void handle_keyboard_asm(InterruptContext *context);
/*! \brief Higher-level Interrupt handler for the keyboard.
*
* On keyboard interrupt, the PS2-Controller may contain a valid Key that has to
* be fetched.
*
* \todo(12) Fetch a single key
* \todo(13) Extend to use the Prologue-Epilogue pattern
*/
void handle_keyboard();
}
/*! \brief handle_panic
*
* \todo(12) Trigger a kernel panic
*/
[[gnu::interrupt]] void handle_panic(InterruptContext *context);
/*! \brief handle_timer
*
* \todo(15) Handle the timer interrupt
*/
[[gnu::interrupt]] void handle_timer(InterruptContext *context);
/*! \brief handle_assassin
*
* Handler for the assassin IPI, i.e. a thread shall be killed.
*
* \todo(15) Handle the assassin interrupt (in \MPStuBS only)
*/
[[gnu::interrupt]] void handle_assassin(InterruptContext *context);
/*! \brief handle_wakeup
*
* In Multicore systems, an IPI is used to wake a sleeping core.
*
* \todo(16) Handle the wakeup interrupt (in \MPStuBS only)
*/
[[gnu::interrupt]] void handle_wakeup(InterruptContext *context);

@ -0,0 +1,28 @@
#include "arch/lapic.h"
#include "boot/startup_ap.h"
#include "debug/output.h"
// Main function
// (the bootstrap processor starts here)}
extern "C" int main() {
unsigned int numCPUs = Core::count();
DBG_VERBOSE << "Number of CPUs: " << numCPUs << endl;
/* Start application processors
* To avoid unexpected behaviour, make sure that interrupts are not
* enabled before the APs are booted. Otherwise it might interfere with the
* Startup IPIs or even block devices like keyboard because of a missing EOI
*/
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;
}

@ -0,0 +1,3 @@
#!/usr/bin/env sh
## This repo should not directly contain a flake.nix, to avoid it being automatically copied to the (locally) world-readable Nix store.
exec nix develop path:"$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P )"/utils "$@"

@ -0,0 +1,62 @@
// vim: set noet ts=4 sw=4:
/*! \file
* \brief Contains a \ref BBuffer "bounded buffer"
*/
#pragma once
#include "../types.h"
/*! \brief The class BBuffer implements a bounded buffer, that is a circular
* buffer with a fixed capacity.
*
* \tparam T the type of data to be stored
* \tparam CAP the buffers capacity (must be greater than 1)
*/
template <typename T, unsigned CAP>
class BBuffer {
static_assert(CAP > 1, "BBuffer of size 1 is unsupported.");
// Prevent copies and assignments
BBuffer(const BBuffer&) = delete;
BBuffer& operator=(const BBuffer&) = delete;
private:
T data[CAP];
volatile unsigned in;
volatile unsigned out;
public:
/*! \brief Constructor that initialized an empty buffer.
*/
BBuffer() : in(0), out(0) {}
/*! \brief Add an element to the buffer.
* \param val The element to be added.
* \return `false` if the buffer is full and no element can be added; `true`
* otherwise.
*/
bool produce(T val) {
unsigned nextin = (in + 1) % CAP;
if (nextin != out) {
data[in] = val;
in = nextin;
return true;
}
return false;
}
/*! \brief Remove an element from the buffer.
* \param val Output parameter that receives the next element. If there is
* (currently) no next element, `val` will not be modified.
* \return `false` if the buffer was empty; `true` if the buffer was
* not empty and an element was written to val.
*/
bool consume(T& val) {
if (in != out) {
val = data[out];
out = (out + 1) % CAP;
return true;
}
return false;
}
};

@ -0,0 +1,118 @@
#include "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;
}
}

@ -0,0 +1,165 @@
/*! \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; }
};

@ -0,0 +1,181 @@
#include "outputstream.h"
// operator <<: Converts the value in given data type to a string
// Print a single character (trivial)
OutputStream& OutputStream::operator<<(char c) {
put(c);
return *this;
}
OutputStream& OutputStream::operator<<(unsigned char c) {
return *this << static_cast<char>(c);
}
// Printing a null-terminated string
OutputStream& OutputStream::operator<<(const char* string) {
while ((*string) != '\0') {
put(*string);
string++;
}
return *this;
}
OutputStream& OutputStream::operator<<(bool b) {
return *this << (b ? "true" : "false");
}
// Print integral numbers in number system base.
// All signed types are promoted to long long,
// all unsigned types to unsigned long long.
OutputStream& OutputStream::operator<<(short ival) {
return *this << static_cast<long long>(ival);
}
OutputStream& OutputStream::operator<<(unsigned short ival) {
return *this << static_cast<unsigned long long>(ival);
}
OutputStream& OutputStream::operator<<(int ival) {
return *this << static_cast<long long>(ival);
}
OutputStream& OutputStream::operator<<(unsigned int ival) {
return *this << static_cast<unsigned long long>(ival);
}
OutputStream& OutputStream::operator<<(long ival) {
return *this << static_cast<long long>(ival);
}
OutputStream& OutputStream::operator<<(unsigned long ival) {
return *this << static_cast<unsigned long long>(ival);
}
// Print a signed , integral number.
OutputStream& OutputStream::operator<<(long long ival) {
/* Print '-' if number is negative
*
* In case ival is equal to LONG_LONG_MIN (0x8000000000000000), this
* multiplication with -1 will overflow and, as for all signed overflows,
* is not specified in C/C++. Thus, this operation will only work when
* the system uses two's complement:
* ~(0x8000000000000000) + 1 = 0x7fffffffffffffff + 1 = 0x8000000000000000
*
* When casting 0x8000000000000000 to unsigned long long, the value will
* be (correctly) interpreted as -(LONG_LONG_MIN).
*
* A solution conforming (more) to the standard could be:
* if ((ival < 0) && (base == 10)) {
* put('-');
* if (ival == LONG_LONG_MIN) {
* return *this << static_cast<unsigned long long>(LONG_LONG_MAX -
* (LONG_LONG_MAX + LONG_LONG_MIN)); } else { return *this <<
* static_cast<unsigned long long>(-ival);
* }
* (However it introduces additional overhead)
*/
if ((ival < 0) && (base == 10)) {
put('-');
ival = -ival;
}
// Print the remaining positive number using the unsigned output
return *this << static_cast<unsigned long long>(ival);
}
// Print a unsigned, integral number.
OutputStream& OutputStream::operator<<(unsigned long long ival) {
if (base == 0) {
base = 16;
}
if (base == 2) {
put('0');
put('b');
} else if (base == 8) {
put('0'); // octal numbers are prefixed with 0
} else if (base == 16) {
put('0'); // hexadecimal numbers are prefixed with 0x
put('x');
}
// Determine the largest potency in the number system used, which is
// still smaller than the number to be printed
unsigned long long div;
for (div = 1; ival / div >= static_cast<unsigned long long>(base);
div *= base) {
}
// print number char by char
for (; div > 0; div /= static_cast<unsigned long long>(base)) {
auto digit = ival / div;
if (digit < 10) {
put(static_cast<char>('0' + digit));
} else {
put(static_cast<char>('a' + digit - 10));
}
ival %= div;
}
return *this;
}
// Print a pointer as hexadecimal number
OutputStream& OutputStream::operator<<(const void* ptr) {
int oldbase = base;
base = 16;
*this << reinterpret_cast<uintptr_t>(ptr);
base = oldbase;
return *this;
}
// Calls one of the manipulator functions
OutputStream& OutputStream::operator<<(OutputStream& (*f)(OutputStream&)) {
return f(*this);
}
/* STREAM MANIPULATORS
*
* The functions below take and return a reference to an OutputStream object
* and are called by OutputStream& operator << (OutputStream& (*f)
* (OutputStream&)); The purpose of theses manipulator functions is modifying
* the behavior of the stream the are executed on, such as changing the number
* system.
*/
// flush: Explicit buffer flush
OutputStream& flush(OutputStream& os) {
os.flush();
return os;
}
// endl: Inserts a newline to the output
OutputStream& endl(OutputStream& os) {
os << '\n' << flush;
return os;
}
// bin: Selects the binary number system
OutputStream& bin(OutputStream& os) {
os.base = 2;
return os;
}
// oct: Selects the octal number system
OutputStream& oct(OutputStream& os) {
os.base = 8;
return os;
}
// dec: Selects the decimal number system
OutputStream& dec(OutputStream& os) {
os.base = 10;
return os;
}
// hex: Selects the hexadecimal number system
OutputStream& hex(OutputStream& os) {
os.base = 16;
return os;
}

@ -0,0 +1,209 @@
/*! \file
* \brief This file contains the \ref OutputStream
*
* Along with the class OutputStream itself, this file contains definitions for
* the manipulators \ref hex, \ref dec, \ref oct, and \ref bin, which are used
* for changing the radix, and \ref endl for signaling the end of the current
* line.
* \ingroup io
*
* \par Manipulators
* To simplify formatting text and numbers using the class OutputStream, we
* define so-called manipulators. For example, the expression <tt>kout << "a = "
* << dec << a << " is hexadecimal " << hex << a << endl;</tt> should, at first,
* print the value stored in decimal and then in hexadecimal form, followed by a
* line break. The intended properties can be realized by implementing \ref hex,
* \ref dec, \ref oct, \ref bin, and \ref endl as functions (i.e., they are, in
* particular, not methods of \ref OutputStream) that take (as first parameter)
* and return a reference to an OutputStream object. When compiling the
* expression shown above, the method <tt>OutputStream& OutputStream::operator<<
* ((*f*) (OutputStream&))</tt> is chosen when one of the functions \ref hex,
* \ref dec, \ref oct, \ref bin, or \ref endl is streamed into an \ref
* OutputStream, which finally will execute the passed function.
*
* \note The term manipulator originates from the book
* [The C++ Programming Language](http://www.stroustrup.com/4th.html)
* by Bjarne Stroustrup. Refer to this book for further explanations.
*/
#pragma once
#include "../types.h"
#include "./stringbuffer.h"
/*! \brief The class OutputStream corresponds, essentially, to the class ostream
* from the C++ IO-Stream library.
*
* As relying on the method \ref Stringbuffer::put() is quite cumbersome when
* not only printing single characters, but numbers and whole strings, the
* class OutputStream provides a convenient way of composing output of
* variables of varying data types. Therefore, OutputStream implements shift
* operators `operator<<`` for various data types (similar to those known from
* the C++ IO-Stream library)
*
* For further convenience, OutputStream also allows printing integral numbers
* in decimal, binary, octal, and hexadecimal format. Remember that, for
* negative numbers, the sign is only printed when using the decimal number
* system; for binary, octal, and hex, the number is printed as stored in the
* machine word without interpreting the sign. For Intel CPUs, two's complement
* is used for storing negative values, `-1`, for example, will print hex
* `FFFFFFFF` and octal `37777777777`.
*
* OutputStream's public methods/operators all return a reference to the object
* they are called on (i.e. `*this`). Returning `*this` allows chaining those
* stream operators in a single expression, such as
* <tt>kout << "a = " << a</tt>;
*
* At this point in time, OutputStream implements `operator<<`` for chars,
* strings and whole numbers. An additional `operator<<` allows using
* manipulators whose detailed description is given below.
*/
class OutputStream : public Stringbuffer {
OutputStream(const OutputStream&) = delete;
OutputStream& operator=(const OutputStream&) = delete;
public:
/*! \brief Number system used for printing integral numbers (one of 2,
* 8, 10, or 16)
*/
int base;
/*! \brief Default constructor. Initial number system is decimal.
*
*/
OutputStream() : base(10) {}
/*! \brief Destructor
*/
virtual ~OutputStream() {}
/*! \brief Clears the buffer.
*
* Pure virtual method that must be implemented by derived
* (non-abstract) classes.
* Formatting of the buffer contents can be implemented differently by
* different derived classes
*/
virtual void flush() = 0;
/*! \brief Print a single character
*
* \param c Character to be printed
* \return Reference to OutputStream os; allows operator chaining.
*/
OutputStream& operator<<(char c);
/*! \brief Print a single character
* \note In C, there are no "characters" in that sense, but only
* integers. A `char`, therefore, is a 8 bit number with the most
* significant bit (optionally) representing a sign.
* Depending on whether signed or not, the value ranges are [-128, 127]
* or [0; 255]. For GCC, a `char` is a `signed char`.
*
* \param c Character to be printed
* \return Reference to OutputStream os; allows operator chaining.
*/
OutputStream& operator<<(unsigned char c);
/*! \brief Printing a null-terminated string
*
* \param string String to be printed
* \return Reference to OutputStream os; allows operator chaining.
*/
OutputStream& operator<<(const char* string);
/*! \brief Print a boolean value
*
* \param b Boolean to be printed
* \return Reference to OutputStream os; allows operator chaining.
*/
OutputStream& operator<<(bool b);
/*! \brief Print an integral number in radix base
*
* \param ival Number to be printed
* \return Reference to OutputStream os; allows operator chaining.
*/
OutputStream& operator<<(short ival);
/// \copydoc OutputStream::operator<<(short)
OutputStream& operator<<(unsigned short ival);
/// \copydoc OutputStream::operator<<(short)
OutputStream& operator<<(int ival);
/// \copydoc OutputStream::operator<<(short)
OutputStream& operator<<(unsigned int ival);
/// \copydoc OutputStream::operator<<(short)
OutputStream& operator<<(long ival);
/// \copydoc OutputStream::operator<<(short)
OutputStream& operator<<(unsigned long ival);
/// \copydoc OutputStream::operator<<(short)
OutputStream& operator<<(long long ival);
/// \copydoc OutputStream::operator<<(short)
OutputStream& operator<<(unsigned long long ival);
/*! \brief Print a pointer as hexadecimal number
*
* \param ptr Pointer to be printed
* \return Reference to OutputStream os; allows operator chaining.
*/
OutputStream& operator<<(const void* ptr);
/*! \brief Calls one of the manipulator functions.
*
* Method that calls the manipulator functions defined below, which
* allow modifying the stream's behavior by, for instance, changing the
* number system.
*
* \param f Manipulator function to be called
* \return Reference to OutputStream os; allows operator chaining.
*/
OutputStream& operator<<(OutputStream& (*f)(OutputStream&));
};
/*! \brief Enforces a buffer flush.
*
* \param os Reference to stream to be flushed.
* \return Reference to OutputStream os; allows operator chaining.
*/
OutputStream& flush(OutputStream& os);
/*! \brief Prints a newline character to the stream and issues a buffer flush.
*
* \param os Reference to stream to be modified.
* \return Reference to OutputStream os; allows operator chaining.
*/
OutputStream& endl(OutputStream& os);
/*! \brief Print subsequent numbers in binary form.
*
* \param os Reference to stream to be modified.
* \return Reference to OutputStream os; allows operator chaining.
*/
OutputStream& bin(OutputStream& os);
/*! \brief Print subsequent numbers in octal form.
*
* \param os Reference to stream to be modified.
* \return Reference to OutputStream os; allows operator chaining.
*/
OutputStream& oct(OutputStream& os);
/*! \brief Print subsequent numbers in decimal form.
*
* \param os Reference to stream to be modified.
* \return Reference to OutputStream os; allows operator chaining.
*/
OutputStream& dec(OutputStream& os);
/*! \brief Print subsequent numbers in hex form.
*
* \param os Reference to stream to be modified.
* \return Reference to OutputStream os; allows operator chaining.
*/
OutputStream& hex(OutputStream& os);

@ -0,0 +1,293 @@
/*! \file
* \brief Templated \ref Queue for arbitrary objects.
*/
#pragma once
#include "../arch/core.h"
#include "../debug/assert.h"
#include "../object/outputstream.h"
#include "../types.h"
/*! \brief Templated Queue for arbitrary objects.
*
* Queue is implemented by a head-object (Queue<T>) and next-pointers embedded
* in the queued objects. This Queue supports arrays of next-pointers by passing
* an index into the constructor identifying the index into the next-pointer
* array. By passing a different get_link function into the constructor, the
* member name of the next-pointer array can be changed and objects can be
* contained in different independent queues.
*/
template <class T>
class Queue {
/*! \brief Default get_link implementation returns a pointer to the
* link_index'th element of the next-pointer array.
* The function assumes a member named "queue_link" that stores the
* next-pointer.
*
* If your object contains a queue_link member you can just ignore this
* function and the get_link keyword argument of the constructor.
*
* \param[in] obj the object whose link should be accessed.
* \param[in] link_index the index within the array.
*
* \return A pointer to the next-object pointer.
*/
static T** default_get_link(T& obj, unsigned link_index) {
assert(link_index < sizeof(T::queue_link) / sizeof(void*));
return &obj.queue_link[link_index];
}
/// Type definition for the get_link function
typedef T** (*NextFunc)(T&, unsigned);
/// Queue-local index into the next-pointer array
unsigned link_index;
/// Provides the same signature for single- and multi-core Queue
T** get_link_wrapped(T& obj) { return get_link(obj, link_index); }
/// Function pointer to the get_link function, called whenever the
/// next pointer array is referenced
const NextFunc get_link;
/// head points to the first element (the one returned on first dequeue).
/// Can be nullptr if the queue is empty.
T* head;
/// tail points to the last element (the one last added).
/// Is only valid if head != nullptr
T* tail;
// Prevent copies and assignments
Queue(const Queue&) = delete;
Queue& operator=(const Queue&) = delete;
public:
/*! \brief Minimal forward iterator
* You can use this iterator to iterate the queue like a normal STL container.
* It only supports forward iteration, since the queue is single linked.
*/
class Iterator {
private:
Queue<T>& queue;
T* current;
friend class Queue<T>;
Iterator(Queue<T>& queue, T* current) : queue(queue), current(current) {}
public:
Iterator operator+(unsigned num) {
if (current == nullptr) {
return *this;
}
T* temp = current;
while (num--) {
temp = queue.next(*temp);
}
return Iterator(queue, temp);
}
// pre increment
Iterator& operator++() {
current = queue.next(*current);
return *this;
}
// post increment
Iterator operator++(int) {
auto temp = Iterator(queue, current);
current = queue.next(*current);
return temp;
}
T* operator*() { return current; }
bool operator==(const Iterator& other) { return current == other.current; }
bool operator!=(const Iterator& other) { return !(*this == other); }
};
constexpr Queue(Queue&&) = default;
/*! \brief Constructor
* \param[in] link_index denotes the index into the next-pointer array
* to be used by this
*queue-object
* \param[in] get_link A function pointer to the get_link, i.e. a function
* which returns a pointer to the
*next-pointer of an element in the Queue.
*/
explicit Queue(unsigned link_index, NextFunc get_link = default_get_link)
: link_index(link_index),
get_link(get_link),
head(nullptr),
tail(nullptr) {}
/*! \brief Enqueues the provided item at the end of the queue. If the element
* is already contained in the queue, false will be returned
* \param[in] item element to be appended (enqueued).
* \return false if the element already was enqueued (and nothing was done)
* or not (and it is now enqueued, then true)
*/
bool enqueue(T& item) {
T** nextptr = get_link_wrapped(item);
if (*nextptr != nullptr || (head != nullptr && tail == &item)) {
return false;
}
*nextptr = nullptr;
if (head == nullptr) {
head = tail = &item;
} else {
assert(tail != nullptr);
*get_link_wrapped(*tail) = &item;
tail = &item;
}
return true;
}
/*! \brief insert a new element at the start of the queue
* \param[in] item the new item to add
* \return true if successful, false if item was already in the queue
**/
bool insertFirst(T& item) {
T** nextptr = get_link_wrapped(item);
if (*nextptr != nullptr || (head != nullptr && tail == &item)) {
return false;
}
if (head == nullptr) {
tail = &item;
}
*nextptr = head;
head = &item;
return true;
}
/*! \brief Insert a new element item into the list after an element after.
* Returns false if item is already in the/a list or after is not in this
*list
* \param[in] after the element after which the new one should be inserted
* \param[in] item the new element to add
* \return true if successful, false if item was in the list or after was not
**/
bool insertAfter(T& after, T& item) {
// if queue is empty there is no after
// and tail is not valid so we need to check head here
if (head == nullptr) {
return false;
}
if (&after == tail) {
return enqueue(item);
}
T** nextptr = get_link_wrapped(item);
// if item is already in the list return false
if (*nextptr != nullptr || tail == &item) {
return false;
}
T** pnextptr = get_link_wrapped(after);
// if after is NOT in the list, return false
if (!(pnextptr != nullptr || tail == &after)) {
return false;
}
*nextptr = *pnextptr;
*pnextptr = &item;
return true;
}
/*! \brief return the next element of a given one or nullptr if the end is
*reached
* \param[in] item the current item
* \return the next element or nullptr if the end is reached or the item is
*not in this list
**/
T* next(T& item) {
T** nextptr = get_link_wrapped(item);
// if item is already in the list return nullptr
if (head == nullptr || (*nextptr == nullptr && tail != &item)) {
return nullptr;
}
return *nextptr;
}
/*! \brief Return whether or not the queue is empty
* \return True if the queue is empty or false otherwise.
*/
bool is_empty() const { return (head == nullptr); }
/*! \brief Removes the first element in the queue and returns it.
* \note Does not update the tail-pointer
* \return Pointer to the removed item or `nullptr` if the queue was empty.
*/
T* dequeue() {
T* out = head;
if (head != nullptr) {
T** nextptr = get_link_wrapped(*head);
head = *nextptr;
*nextptr = nullptr;
}
return out;
}
/*! \brief Removes a given element from the queue and returns that element,
* or nullptr if it was not present
* \return pointer to the removed element, or nullptr if not present
*/
T* remove(T* that) {
if (!that) return nullptr;
T* cur = head;
T** next_link;
if (head == that) {
head = *get_link_wrapped(*head);
*get_link_wrapped(*that) = nullptr;
return that;
}
while (cur) {
next_link = get_link_wrapped(*cur);
if (*next_link == that) {
*next_link = *get_link_wrapped(**next_link);
if (that == tail) {
tail = cur;
}
*get_link_wrapped(*that) = nullptr;
return that;
}
cur = *next_link;
}
return nullptr;
}
/// get an iterator to the first element
Queue<T>::Iterator begin() { return Queue<T>::Iterator(*this, head); }
/// get an iterator that marks the end of list
Queue<T>::Iterator end() { return Queue<T>::Iterator(*this, nullptr); }
/// get the first element of the queue
T* first() { return head; }
/// get the last element of the queue
T* last() { return (head == nullptr ? nullptr : tail); }
};
/*! \brief Overload stream operator for list printing.
*
* With this a list can be printed. The elements itself are not printed, just
* the pointer.
*/
template <class T>
OutputStream& operator<<(OutputStream& os, Queue<T>& queue) {
os << "{";
for (typename Queue<T>::Iterator it = queue.begin(); it != queue.end();
++it) {
os << *it;
if (it + 1 != queue.end()) {
os << ", ";
}
}
return os << "}";
}

@ -0,0 +1,3 @@
#include "stringbuffer.h"
void Stringbuffer::put(char c) { (void)c; }

@ -0,0 +1,71 @@
/*! \file
* \brief \ref Stringbuffer composes single characters into a buffer
*/
#pragma once
#include "../types.h"
/*! \brief The class Stringbuffer composes single characters into a longer text
* that can be processed on block.
*
* To make Stringbuffer as versatile as possible, the class does make
* assumptions about neither the underlying hardware, nor the meaning of
* "processing". When flush() is called (i.e., either on explicit request or
* once the buffer is full). To be hardware independent, flush() is to be
* implemented by the derived classes.
*
* \par Hints for Implementation
* Use a buffer of fixed size for caching characters, which should be
* accessible by derived classes.
* Keep in mind that the derived implementation of flush() will need to know
* about numbers of characters in the buffer.
*
* \par Notes
* Reason for the existence of this class is that generating longer texts is
* often implemented by assembly of small fragments (such as single characters
* or numbers).
* However, writing such small fragments directly to (for example) screen is
* quite inefficient (e.g., due to the use of IO ports, syscalls, or locks) and
* can be improved drastically by delaying the output step until the assembly
* is finished (or the buffer runs full).
*/
class Stringbuffer {
// Prevent copies and assignments
Stringbuffer(const Stringbuffer&) = delete;
Stringbuffer& operator=(const Stringbuffer&) = delete;
// All variables and methods are protected in this class,
// as the derived classes need direct access to be buffer,
// the constructor, the destructor, and the method put.
// flush() is to be implemented either way and may be redefined
// as public.
protected:
/*! \brief Constructor; Marks the buffer as empty
*/
Stringbuffer() {}
/*! \brief Inserts a character into the buffer.
*
* Once the buffer is full, a call to flush() will be issued and
* thereby clearing the buffer.
*
* \param c Char to be added
*
* \todo(11) Implement
*/
void put(char c);
/*! \brief Flush the buffer contents
*
* This method is to be defined in derived classes, as only those know
* how to print characters.
* flush() is required to reset the position pos.
*/
virtual void flush() = 0;
public:
/*! \brief Destructor (nothing to do here)
*/
virtual ~Stringbuffer() {}
};

@ -0,0 +1,29 @@
#include "bellringer.h"
#include "../interrupt/guard.h"
#include "../thread/thread.h"
struct Bell {
// link pointer to the next bell in the bellringers bell list
Bell *queue_link[1] = {nullptr};
Thread *thread;
size_t counter;
};
Bell **Bellringer::bell_link(Bell &obj, unsigned link_index) {
return &obj.queue_link[link_index];
}
// check: Checks whether bells are running out of time and rings them if
// necessary
void Bellringer::check(Vault &vault) { (void)vault; }
// job: Give a bell to the bellringer & ring it when the specified time ran out.
void Bellringer::sleep(Vault &vault, unsigned int ms) {
(void)vault;
(void)ms;
}
// Are there bells in the queue?
bool Bellringer::bellPending() const { return false; }

@ -0,0 +1,69 @@
/*! \file
* \brief \ref Bellringer that manages and activates time-triggered activities.
*/
#pragma once
#include "../object/queue.h"
#include "../types.h"
struct Vault;
struct Bell;
/*! \brief Manages and activates time-triggered activities.
* \ingroup ipc
*
* The Bellringer is regularly activated and checks whether any of the bells
* should ring. The bells are stored in a Queue<Bell> that is managed by the
* Bellringer. A clever implementation avoids iterating through the whole list
* for every iteration by keeping the bells sorted and storing delta times. This
* approach leads to a complexity of O(1) for the method called by the timer
* interrupt in case no bells need to be rung.
*/
class Bellringer {
// Prevent copies and assignments
Bellringer(const Bellringer&) = delete;
Bellringer& operator=(const Bellringer&) = delete;
/*! \brief List of bells currently managed.
*
* This list contains non-expired bells enqueued by job().
* These bells will be checked on every call to check().
*
* All elements that should be inserted into a Queue instance
* are required to be derived from Queue<Bell>::Node.
*/
Queue<Bell> bells;
//! Link pointer for bells
static Bell** bell_link(Bell& obj, unsigned link_index);
public:
// constructor
Bellringer() : bells(0, bell_link) {}
/*! \brief Checks whether there are bells to be rung.
*
* Every call to check elapses a tick. Once such a tick reduces a bells
* remaining time to zero, the bell will be rung.
*
* \todo(16) Implement Method
*/
void check(Vault& vault);
/*! \brief Passes a `bell` to the bellringer to be rung after `ms`
* milliseconds.
* \param bell Bell that should be rung after `ms` milliseconds
* \param ms number of milliseconds that should be waited before
* ringing the bell
*
* \todo(16) Implement Method
*/
void sleep(Vault& vault, unsigned int ms);
/*! \brief Checks whether there are enqueued bells.
* \return true if there are enqueued bells, false otherwise
*
* \todo(16) Implement Method
*/
bool bellPending() const;
};

@ -0,0 +1,16 @@
#include "./semaphore.h"
#include "../interrupt/guard.h"
#include "../thread/thread.h"
Semaphore::Semaphore(unsigned c) { (void)c; }
Thread **Semaphore::thread_link(Thread &obj, unsigned link_index) {
(void)obj;
(void)link_index;
return nullptr;
}
void Semaphore::p(Vault &vault) { (void)vault; }
void Semaphore::v(Vault &vault) { (void)vault; }

@ -0,0 +1,57 @@
#pragma once
#include "../object/queue.h"
#include "../types.h"
/*! \file
* \brief \ref Semaphore for synchronization of threads.
*/
/*!
* \defgroup ipc Inter-Process Communication
* \brief Communication between threads
*/
// Forward declarations to break cyclic includes
struct Vault;
class Thread;
/*! \brief Semaphore used for synchronization of threads.
* \ingroup ipc
*
* The class Semaphore implements the concept of counting semaphores.
* The waiting list is provided by the base class Waitingroom.
*/
class Semaphore {
// Prevent copies and assignments
Semaphore(const Semaphore&) = delete;
Semaphore& operator=(const Semaphore&) = delete;
static Thread** thread_link(Thread& obj, unsigned link_index);
public:
/*! \brief Constructor; initialized the counter with provided value `c`
* \param c Initial counter value
*
* \todo(16) Implement Constructor
*/
explicit Semaphore(unsigned c = 0);
/*! \brief Wait for access to the critical area.
*
* Enter/Wait operation: If the counter is greater than 0, then it is
* decremented by one. Otherwise the calling thread will be enqueued
* into the Waitingroom and marked as blocked.
*
* \todo(16) Implement Method
*/
void p(Vault& vault);
/*! \brief Leave the critical area.
*
* Leave operation: If there are threads in the Waitingroom, wake the
* first one; otherwise increment the counter by one.
*
* \todo(16) Implement Method
*/
void v(Vault& vault);
};

@ -0,0 +1,60 @@
/*! \file
* \brief Contains the class Spinlock
*/
#pragma once
#include "../types.h"
/*! \brief Using Spinlocks, it is possible to serialize blocks of code
* that might otherwise run in parallel on multiple CPU cores,
* or be interleaved due to interrupts or scheduling.
*
* \ingroup sync
*
* Synchronization is implemented using a lock variable. Once a thread enters
* the critical area, it sets the lock variable (to a non-zero value); when
* this thread leaves the critical area, it resets the lock variable to zero.
* Threads trying to enter an already locked critical area, actively wait,
* continuously checking until the critical area is free again.
*
* Use the following two GCC intrinsics
* - `bool __atomic_test_and_set(void *ptr, int memorder)`
* - `void __atomic_clear (bool *ptr, int memorder)`
*
* These intrinsics are translated into atomic, architecture-specific
* CPU instructions.
*
* If you want that things just work, choose __ATOMIC_SEQ_CST as memorder.
* This is not the most efficient memory order but works reasonably well.
*
* <a
* href="https://gcc.gnu.org/onlinedocs/gcc/_005f_005fatomic-Builtins.html">Atomic
* Builtins in GCC manual</a>
*/
class Spinlock {
// Prevent copies and assignments
Spinlock(const Spinlock& copy) = delete;
Spinlock& operator=(const Spinlock&) = delete;
public:
/*! \brief Constructor; Initializes as unlocked.
*
* \todo(12) Complete Constructor (for \MPStuBS, or use \ref Ticketlock)
*
*/
consteval Spinlock() {}
/*! \brief Enters the critical area. In case the area is already locked,
* \ref lock() will actively wait until the area can be entered.
*
* \see \ref Core::pause()
* \todo(12) Implement Method (for \MPStuBS, or use \ref Ticketlock)
*/
void lock() {}
/*! \brief Unblocks the critical area.
*
* \todo(12) Implement Method (for \MPStuBS, or use \ref Ticketlock)
*/
void unlock() {}
};

@ -0,0 +1,52 @@
/*! \file
* \brief Contains the class Ticketlock
*/
#pragma once
/*! \brief Using Ticketlocks, it is possible to serialize blocks of code
* that might otherwise run in parallel on multiple CPU cores,
* or be interleaved due to interrupts or scheduling.
*
* \ingroup sync
*
* Synchronization is implemented using a lock and a ticket variable.
* Once a thread tries to enter the critical area, it obtains a ticket by
* atomically incrementing the ticket variable and waiting until the lock
* counter reaches this ticket, if it is not there already.
* When a thread leaves the critical area, it increments the lock variable by
* one and thereby allows the next thread to enter the critical area.
*
* If you want that things just work, choose __ATOMIC_SEQ_CST as memorder.
* This is not the most efficient memory order but works reasonably well.
*
* <a
* href="https://gcc.gnu.org/onlinedocs/gcc/_005f_005fatomic-Builtins.html">Atomic
* Builtins in GCC manual</a>
*/
class Ticketlock {
// Prevent copies and assignments
Ticketlock(const Ticketlock& copy) = delete;
Ticketlock& operator=(const Ticketlock&) = delete;
public:
/*! \brief Constructor
*
* \todo(12) Complete Constructor (for \MPStuBS)
*/
consteval Ticketlock() {}
/*! \brief Enters the critical area. In case the area is already locked,
* \ref lock() will actively wait until the area can be entered.
*
* \see \ref Core::pause()
* \todo(12) Implement Method (for \MPStuBS)
*/
void lock() {}
/*! \brief Unblocks the critical area.
*
* \todo(12) Implement Method (for \MPStuBS)
*/
void unlock() {}
};

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save