This commit is contained in:
Niklas Gollenstede
2025-10-31 22:37:36 +01:00
commit 174fe17e89
197 changed files with 79558 additions and 0 deletions

4
.clang-format Normal file
View File

@@ -0,0 +1,4 @@
BasedOnStyle: Google
UseTab: Always
TabWidth: 4
IndentWidth: 4

2
.clang-format-ignore Normal file
View File

@@ -0,0 +1,2 @@
**/assets/*
**/fonts/*

6
.clang-tidy Normal file
View File

@@ -0,0 +1,6 @@
FormatStyle: google
HeaderFilterRegex: '.*'
WarningsAsErrors: 'readability*'
Checks: 'readability*,google-readability-casting,google-explicit-constructor,bugprone*,-bugprone-narrowing-conversions,-bugprone-reserved-identifier,-readability-else-after-return,-readability-magic-numbers,-readability-identifier-length,-readability-braces-around-statements,cppcoreguidelines-*,-cppcoreguidelines-avoid-magic-numbers,-cppcoreguidelines-avoid-c-arrays,-cppcoreguidelines-pro-*,-cppcoreguidelines-avoid-do-while,-cppcoreguidelines-owning-memory'
CheckOptions:
readability-simplify-boolean-expr.IgnoreMacros: 'true'

1
.envrc Normal file
View File

@@ -0,0 +1 @@
use flake $PWD/utils/flake.nix --no-pure-eval

2
.gitignore vendored Normal file
View File

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

16
CPPLINT.cfg Normal file
View File

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

14
LICENSE Normal file
View File

@@ -0,0 +1,14 @@
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.

31
Makefile Normal file
View File

@@ -0,0 +1,31 @@
# Default target
.DEFAULT_GOAL = all
echo=$(shell which echo) -e
# Paths to kernel and userspace for StuBSmI
# (Note: also defined as relative paths in {user,kernel}/Makefile!)
INITRD = user/$(BUILDDIR)/initrd.img
KERNEL = kernel/$(BUILDDIR)/system
KERNEL64 = kernel/$(BUILDDIR)/system64
KERNEL_LINK = kernel/$(BUILDDIR)/system
ISOFILE = kernel/$(BUILDDIR)/stubs.iso
SUBDIRS = kernel
SUBDIRS += user
# Default targets
include tools/common.mk
all:
$(VERBOSE) $(foreach DIR,$(SUBDIRS),$(MAKE) -C $(DIR) && ) true
lint::
$(VERBOSE) $(foreach DIR,$(SUBDIRS),$(MAKE) -C $(DIR) lint && ) true
tidy::
$(VERBOSE) $(foreach DIR,$(SUBDIRS),$(MAKE) -C $(DIR) tidy && ) true
# clean all apps additionally to default clean recipe from common.mk
clean::
$(VERBOSE) $(foreach DIR,$(SUBDIRS),$(MAKE) -C $(DIR) clean && ) true

42
README.md Normal file
View File

@@ -0,0 +1,42 @@
# StuBSmI - Studenten Betriebssystem mit Isolation
## 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

3
kernel/CPPLINT.cfg Normal file
View File

@@ -0,0 +1,3 @@
exclude_files=memory/*
exclude_files=test_imgs/*
exclude_files=config\.h

36
kernel/Makefile Normal file
View File

@@ -0,0 +1,36 @@
# Kernel Makefile
# try `make help` for more information
echo=$(shell which echo) -e
# Default target
.DEFAULT_GOAL = all
# 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
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 $< $@

146
kernel/arch/acpi.cc Normal file
View File

@@ -0,0 +1,146 @@
#include "acpi.h"
#include "../debug/output.h"
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Warray-bounds"
namespace ACPI {
static RSDP *rsdp = nullptr;
static RSDT *rsdt = nullptr;
static XSDT *xsdt = nullptr;
constexpr 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

270
kernel/arch/acpi.h Normal file
View File

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

161
kernel/arch/apic.cc Normal file
View File

@@ -0,0 +1,161 @@
#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[static_cast<uint8_t>(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

82
kernel/arch/apic.h Normal file
View File

@@ -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 class Device : uint8_t {
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

22
kernel/arch/cache.h Normal file
View File

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

78
kernel/arch/cga.cc Normal file
View File

@@ -0,0 +1,78 @@
#include "cga.h"
#include "../utils/math.h"
#include "ioport.h"
namespace CGA {
// I/O Ports required for hardware cursor
constexpr IOPort index_port(0x3d4); ///< Graphic card register selection
constexpr IOPort data_port(0x3d5); ///< Read/Write selected register
/*! \brief Helper structure for the hardware cursor
*/
union Cursor {
uint16_t value;
struct {
uint16_t low : 8;
uint16_t high : 6;
uint16_t : 2;
} __attribute__((packed));
} __attribute__((packed));
/*! \brief Register (for index_port)
*/
enum RegisterIndex {
CURSOR_START =
10, ///< Control the cursor (blink timing and start scanline)
CURSOR_END = 11, ///< Control the cursors end scanline
START_ADDRESS_HIGH = 12, ///< Base offset address for output (high)
START_ADDRESS_LOW = 13, ///< Base offset address for output (low)
CURSOR_HIGH = 14, ///< Cursor offset address (high)
CURSOR_LOW = 15, ///< Cursor offset address (low)
};
void setCursor(unsigned abs_x, unsigned abs_y) {
// Using the hardware cursor is quite important:
// The access to the IO Ports is rather slow and -- if interrupted by
// another output -- it will result in a strange word jumble.
// Hence this helps in a quite visible manner to understand the
// necessity of correct synchronization (and its performance impact).
if (abs_x < COLUMNS && abs_y < ROWS) {
Cursor pos = {static_cast<uint16_t>((abs_y * COLUMNS) + abs_x)};
// Select index 14 / CURSOR_HIGH register
index_port.outb(CURSOR_HIGH);
// Write high part of value to selected register (cursor high)
data_port.outb(pos.high);
// Select index 15 / CURSOR_LOW register
index_port.outb(CURSOR_LOW);
// Write low part of value to selected register (cursor low)
data_port.outb(pos.low);
}
}
void getCursor(unsigned& abs_x, unsigned& abs_y) {
Cursor pos = {.value = 0};
// Select index 14 / CURSOR_HIGH register
index_port.outb(CURSOR_HIGH);
// Read high part part of value from selected register (cursor high)
pos.high = data_port.inb();
// Select index 15 / CURSOR_LOW register
index_port.outb(CURSOR_LOW);
// Read low part of value from selected register (cursor low)
pos.low = data_port.inb();
// Calculate absolute position
abs_x = pos.value % COLUMNS;
abs_y = Math::min(pos.value / COLUMNS, ROWS - 1);
}
void show(unsigned abs_x, unsigned abs_y, char character, Attribute attrib) {
// Output only on current page
if (abs_x < COLUMNS && abs_y < ROWS) {
Cell cell(character, attrib);
TEXT_BUFFER_BASE[(abs_y * COLUMNS) + abs_x] = cell;
}
}
}; // namespace CGA

135
kernel/arch/cga.h Normal file
View File

@@ -0,0 +1,135 @@
/*! \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 class Color {
BLACK, ///< Black (fore- and background)
BLUE, ///< Blue (fore- and background)
GREEN, ///< Green (fore- and background)
CYAN, ///< Cyan (fore- and background)
RED, ///< Red (fore- and background)
MAGENTA, ///< Magenta (fore- and background)
BROWN, ///< Brown (fore- and background)
LIGHT_GREY, ///< Light grey (fore- and background)
DARK_GREY, ///< Dark grey (foreground only)
LIGHT_BLUE, ///< Light blue (foreground only)
LIGHT_GREEN, ///< Light green (foreground only)
LIGHT_CYAN, ///< Light cyan (foreground only)
LIGHT_RED, ///< Light red (foreground only)
LIGHT_MAGENTA, ///< Light magenta (foreground only)
YELLOW, ///< Yellow (foreground only)
WHITE ///< White (foreground only)
};
/*! \brief Structure of a character attribute
* consists of 4 bit fore- and 3 bit background color, and a single blink bit.
*
* [Bit fields](https://en.cppreference.com/w/cpp/language/bit_field) can
* notably simplify the access and code readability.
*
* \note [Type punning](https://en.wikipedia.org/wiki/Type_punning#Use_of_union)
* is indeed undefined behavior in C++. However, *gcc* explicitly allows
* this construct as a [language extension](https://gcc.gnu.org/bugs/#nonbugs).
* Some compilers ([other than
* gcc](https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html#Type%2Dpunning)
* might allow this feature only by disabling strict aliasing
* (`-fno-strict-aliasing`). In \StuBS we use this feature extensively due to
* the improved code readability.
*
*/
union Attribute {
struct {
uint8_t foreground : 4; ///< `.... XXXX` Foreground color
uint8_t background : 3; ///< `.XXX ....` Background color
uint8_t blink : 1; ///< `X... ....` Blink
} __attribute__((packed));
uint8_t value; ///< combined value
/*! \brief Attribute constructor (with default values)
*
*
* \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 = Color::LIGHT_GREY,
Color background = Color::BLACK, bool blink = false)
: foreground(uint8_t(foreground)),
background(uint8_t(background) & 0x07),
blink(blink ? 1 : 0) { // NOLINT
}
} __attribute__((packed)); // prevent padding by the compiler
/*! \brief Set the keyboard hardware cursor to absolute screen position
*
*
* \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
*
*
* \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
*
*/
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 = reinterpret_cast<CGA::Cell*>(0xb8000);
}; // namespace CGA

61
kernel/arch/cmos.cc Normal file
View File

@@ -0,0 +1,61 @@
#include "cmos.h"
#include "core_interrupt.h"
#include "ioport.h"
namespace CMOS {
static constexpr IOPort address(0x70);
static constexpr IOPort data(0x71);
namespace NMI {
static constexpr 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 = static_cast<uint8_t>(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

46
kernel/arch/cmos.h Normal file
View File

@@ -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 class Register {
SECOND = 0x0, ///< RTC
ALARM_SECOND = 0x1, ///< RTC
MINUTE = 0x2, ///< RTC
ALARM_MINUTE = 0x3, ///< RTC
HOUR = 0x4, ///< RTC
ALARM_HOUR = 0x5, ///< RTC
WEEKDAY = 0x6, ///< RTC
DAYOFMONTH = 0x7, ///< RTC
MONTH = 0x8, ///< RTC
YEAR = 0x9, ///< RTC
STATUS_A = 0xa, ///< RTC
STATUS_B = 0xb, ///< RTC
STATUS_C = 0xc, ///< RTC
STATUS_D = 0xd, ///< RTC
STATUS_DIAGNOSE = 0xe,
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

53
kernel/arch/context.asm Normal file
View File

@@ -0,0 +1,53 @@
[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:
; RDI: Pointer to next context structure
; RSI: Pointer to current context structure
; Save non-scratch register of current context
mov [rsi + 0], rbx
mov [rsi + 8], rbp
mov [rsi + 16], r12
mov [rsi + 24], r13
mov [rsi + 32], r14
mov [rsi + 40], r15
; Save stack pointer of current context
mov [rsi + 48], rsp
; 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:
; RDI: Pointer to next context structure
; Restore registers of next context
mov rbx, [rdi + 0]
mov rbp, [rdi + 8]
mov r12, [rdi + 16]
mov r13, [rdi + 24]
mov r14, [rdi + 32]
mov r15, [rdi + 40]
; Load stack pointer of next context
mov rsp, [rdi + 48]
; Context switched, return
ret
; 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:
; Copy first parameter (placed in R15 by context_switch) to RDI
mov rdi, r15
; Copy second parameter from R14 to RSI
mov rsi, r14
; Copy third parameter from R13 to RDX
mov rdx, r13
; Return to the actual target function (kickoff)
ret

39
kernel/arch/context.cc Normal file
View File

@@ -0,0 +1,39 @@
#include "context.h"
#include "../debug/assert.h"
#include "../debug/kernelpanic.h"
/*! \brief Panic function
*
* If the kickoff function (assigned in \ref prepareContext) *returns*, this
* parameterless function will be called -- and stop the execution.
*/
static void panic() {
// we should not come back here
kernelpanic("Application should not return from context_kickoff");
}
void prepareContext(void* tos, Context& context,
void (*kickoff)(uintptr_t, uintptr_t, uintptr_t),
uintptr_t param1, uintptr_t param2, uintptr_t param3) {
assert(tos != nullptr && "Top Of Stack must not be nullptr");
assert((uintptr_t)tos % 16 == 0 && "Top Of Stack be 16 byte aligned");
assert(kickoff != nullptr && "Kickoff function missing");
// XXX Double check alignment. This seems to work, but the stack pointer for
// `panic` should not be aligned to 16-bit?
void** sp = reinterpret_cast<void**>(tos);
*(--sp) = reinterpret_cast<void*>(panic); // return address
*(--sp) = reinterpret_cast<void*>(kickoff); // start the thread in C land
*(--sp) = reinterpret_cast<void*>(fake_systemv_abi); // pass via registers
context = {
.rbx = 0,
.rbp = 0,
.r12 = 0,
.r13 = param3,
.r14 = param2, // Second parameter to kickoff
.r15 = param1, // First parameter to kickoff
.rsp = reinterpret_cast<void*>(sp),
};
}

116
kernel/arch/context.h Normal file
View File

@@ -0,0 +1,116 @@
/*! \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 initial 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
* \param param2 second parameter for \ref Thread::kickoff function
* \param param3 third parameter for \ref Thread::kickoff function
*/
void prepareContext(void* tos, Context& context,
void (*kickoff)(uintptr_t, uintptr_t, uintptr_t),
uintptr_t param1, uintptr_t param2, uintptr_t param3);
/*! \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
*
*/
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
*
*/
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
*
*/
extern "C" void fake_systemv_abi();

73
kernel/arch/core.cc Normal file
View File

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

118
kernel/arch/core.h Normal file
View File

@@ -0,0 +1,118 @@
/*! \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" TEMPLATE(10-){ as well
* as \ref Core::MSR "model specific registers"}.
*/
namespace Core {
/*! \brief Maximum number of supported CPUs
*/
constexpr unsigned MAX = 1;
/*! \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

83
kernel/arch/core_cr.h Normal file
View File

@@ -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 class CR0 : uint32_t {
PE = 1U << 0, ///< Protected Mode enabled
MP = 1U << 1, ///< Monitor co-processor
EM = 1U << 2, ///< Emulation (no x87 floating-point unit present)
TS = 1U << 3, ///< Task switched
ET = 1U << 4, ///< Extension type
NE = 1U << 15, ///< Numeric error
WP = 1U << 16, ///< Write protect
AM = 1U << 18, ///< Alignment mask
NW = 1U << 29, ///< Not-write through caching
CD = 1U << 30, ///< Cache disable
PG = 1U << 31, ///< Paging
};
/*! \brief Control Register 4
*
* \see [ISDMv3, 2.5 Control Registers](intel_manual_vol3.pdf#page=77)
*/
enum class CR4 : uint32_t {
VME = 1U << 0, ///< Virtual 8086 Mode Extensions
PVI = 1U << 1, ///< Protected-mode Virtual Interrupts
TSD = 1U << 2, ///< Time Stamp Disable
DE = 1U << 3, ///< Debugging Extensions
PSE = 1U << 4, ///< Page Size Extension
PAE = 1U << 5, ///< Physical Address Extension
MCE = 1U << 6, ///< Machine Check Exception
PGE = 1U << 7, ///< Page Global Enabled
PCE = 1U << 8, ///< Performance-Monitoring Counter enable
/// Operating system support for FXSAVE and FXRSTOR instructions
OSFXSR = 1U << 9,
OSXMMEXCPT = 1U << 10, ///< Operating System Support for Unmasked SIMD
///< Floating-Point Exceptions
UMIP = 1U << 11, ///< User-Mode Instruction Prevention
VMXE = 1U << 13, ///< Virtual Machine Extensions Enable
SMXE = 1U << 14, ///< Safer Mode Extensions Enable
FSGSBASE = 1U << 16, ///< Enables the instructions RDFSBASE, RDGSBASE,
///< WRFSBASE, and WRGSBASE.
PCIDE = 1U << 17, ///< PCID Enable
OSXSAVE = 1U << 18, ///< XSAVE and Processor Extended States Enable
SMEP = 1U << 20, ///< Supervisor Mode Execution Protection Enable
SMAP = 1U << 21, ///< Supervisor Mode Access Prevention Enable
PKE = 1U << 22, ///< Protection Key Enable
};
/*! \brief Access to the Control Register
*
* \see [ISDMv3, 2.5 Control Registers](intel_manual_vol3.pdf#page=73)
* \tparam id Control Register to access
*/
template <uint8_t id>
class CR {
public:
/*! \brief Read the value of the current Control Register
*
* \return Value stored in the CR
*/
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
*/
static void write(uintptr_t value) {
asm volatile("mov %0, %%cr%c1" : : "r"(value), "n"(id));
}
};
} // namespace Core

View File

@@ -0,0 +1,146 @@
/*! \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)
*/
enum class Vector : uint8_t {
// 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, check \ref Core::MSR,
///< activated by \ref Core::CR4_MCE
SIMD_FP_EXCEPTION =
19, ///< SSE/MMX error (if \ref Core::CR4_OSXMMEXCPT activated)
SECURITY_EXCEPTION = 31,
// Interrupts
KEYBOARD = 33, ///< Keyboard interrupt (key press / release)
PANIC = 99, ///< Panic interrupt to stop CPU (default value for \ref IOAPIC
///< devices)
TIMER = 32, ///< Periodic CPU local \ref LAPIC::Timer interrupt
};
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

101
kernel/arch/core_msr.h Normal file
View File

@@ -0,0 +1,101 @@
/*! \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 class MSRs : uint32_t {
PLATFORM_INFO =
0xce, ///< Platform information including bus frequency (Intel)
TSC_DEADLINE = 0x6e0, ///< Register for LAPIC::Timer Deadline mode
// Fast system calls
EFER =
0xC0000080, ///< Extended Feature Enable Register, see Core::MSR_EFER
STAR = 0xC0000081, ///< eip (protected mode), ring 0 and 3 segment bases
LSTAR = 0xC0000082, ///< rip (long mode)
SFMASK = 0xC0000084, ///< lower 32 bit: flag mask, if bit is set
///< corresponding rflag is cleared through syscall
// CPU local variables
FS_BASE = 0xC0000100,
GS_BASE = 0xC0000101, ///< Current GS base pointer
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 class MSR_EFER : uint32_t {
SCE = 1 << 0, ///< System Call Extensions
LME = 1 << 8, ///< Long mode enable
LMA = 1 << 10, ///< Long mode active
NXE = 1 << 11, ///< No-Execute Enable
SVME = 1 << 12, ///< Secure Virtual Machine Enable
LMSLE = 1 << 13, ///< Long Mode Segment Limit Enable
FFXSR = 1 << 14, ///< Fast `FXSAVE`/`FXRSTOR` instruction
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 <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 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 void write(uint64_t value) {
uint64_parts p(value);
asm volatile("wrmsr \n\t" : : "c"(id), "a"(p.low), "d"(p.high));
}
};
} // namespace Core

17
kernel/arch/core_ring.cc Normal file
View File

@@ -0,0 +1,17 @@
#include "./core_ring.h"
#include "./core_interrupt.h"
#include "./gdt.h"
namespace Core {
namespace Ring {
[[gnu::noreturn]]
void switchToUsermode(void *stackpointer, void *kickoff,
void *kickoff_parameter) {
(void)stackpointer;
(void)kickoff;
(void)kickoff_parameter;
__builtin_unreachable();
}
} // namespace Ring
} // namespace Core

31
kernel/arch/core_ring.h Normal file
View File

@@ -0,0 +1,31 @@
#pragma once
#include "../types.h"
namespace Core {
/*! \brief Protection Rings
*/
namespace Ring {
/*! \brief Switch to Ring 3
*
* \param stackpointer usermode stack pointer
* \param kickoff pointer to function in user mode
* \param kickoff_parameter put in register for 1. function parameter
*/
[[gnu::noreturn]]
void switchToUsermode(void *stackpointer, void *kickoff,
void *kickoff_parameter);
/*! \brief Get current protection level
*
* \return current ring
*/
inline unsigned get() {
unsigned cs;
asm volatile("mov %%cs, %0\n\t" : "=r"(cs)::"memory");
return cs & 0b11; // last bits define the ring
}
} // namespace Ring
} // namespace Core

205
kernel/arch/cpuid.h Normal file
View File

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

36
kernel/arch/gdt.cc Normal file
View File

@@ -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::Bit32),
// Global data segment von 0-4GB
SegmentDescriptor::Segment(0, UINT32_MAX, false, 0, Size::Bit32),
};
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

203
kernel/arch/gdt.h Normal file
View File

@@ -0,0 +1,203 @@
/*! \file
* \brief The \ref GDT "Global Descriptor Table (GDT)".
* \defgroup memory Memory management
*/
#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 class Segments {
Null = 0,
KernelCode,
KernelData,
};
/*! \brief Unit of the segment limit
*/
enum class Granularity {
Bytes = 0, ///< Segment limit in Bytes
Pages = 1 ///< Segment limit in blocks of 4 Kilobytes
};
/*! \brief Descriptor type */
enum class DescriptorType {
System = 0, ///< entry is a system segment
CodeData = 1, ///< entry is a code/data segment
};
/*! \brief Address width
*/
enum class Size {
Bit16 = 0, ///< 16-bit (D/B = 0, L = 0)
Bit32 = 2, ///< 32-bit (D/B = 1, L = 0)
Bit64 = 1, ///< 64-bit (D/B = 0, L = 1)
};
/*! \brief Type flags for used descriptor types
*/
enum class TypeFlags : uint64_t {
DataRW = 0b0010ULL, ///< Data rw, not expanding down
CodeRX = 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 ? uint64_t(TypeFlags::CodeRX)
: uint64_t(TypeFlags::DataRW),
.descriptor_type = DescriptorType::CodeData,
.privilege_level = ring,
.present = true,
.limit_high =
(limit > 0xFFFFF ? (limit >> 28) : (limit >> 16)) & 0xF,
.available = false,
.custom = uint64_t(size),
.granularity =
limit > 0xFFFFF ? Granularity::Pages : 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::Bit64);
}
} __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

36
kernel/arch/idt.cc Normal file
View File

@@ -0,0 +1,36 @@
#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

233
kernel/arch/idt.h Normal file
View File

@@ -0,0 +1,233 @@
/*! \file
* \brief \ref IDT "Interrupt Descriptor Table (IDT)" containing the entry
* points for interrupt handling.
*/
#pragma once
#include "../types.h"
#include "core_interrupt.h"
#include "object/outputstream.h"
/*! \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");
/// Page fault error codes
struct PageFaultError {
uint64_t present : 1;
uint64_t write : 1;
uint64_t user : 1;
uint64_t reserved : 1;
uint64_t ifetch : 1;
uint64_t : 59; // padding to 64 bits
explicit PageFaultError(uint64_t error) {
*reinterpret_cast<uint64_t *>(this) = error;
}
friend OutputStream &operator<<(OutputStream &os,
const PageFaultError &error) {
os << "PageFaultError { present: " << error.present
<< ", write: " << error.write << ", user: " << error.user
<< ", reserved: " << error.reserved << ", ifetch: " << error.ifetch
<< " }";
return os;
}
} __attribute__((packed));
/*! \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)
*/
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 TSS 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 TSS 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 TSS 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 TSS 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 TSS 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

123
kernel/arch/ioapic.cc Normal file
View File

@@ -0,0 +1,123 @@
#include "ioapic.h"
#include "../debug/assert.h"
#include "apic.h"
#include "core.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;
// Helper function that reads a single register
static Register read(Index reg) {
*IOREGSEL_REG = reg;
Register value = *IOWIN_REG;
return value;
}
// Helper function that writes a single register
static void write(Index reg, Register value) {
*IOREGSEL_REG = reg;
*IOWIN_REG = value;
}
// Helper function that reads an entry (= reads from TWO registers) from the
// redirection table
static RedirectionTableEntry getRedirectionTableEntry(uint8_t slot) {
assert(slot < slot_max);
return RedirectionTableEntry(read(IOREDTBL_IDX + 2 * slot + 0),
read(IOREDTBL_IDX + 2 * slot + 1));
}
// Helper function that writes an entry (= writes to TWO registers) to the
// redirection table
static void setRedirectionTableEntry(uint8_t slot, RedirectionTableEntry rt) {
assert(slot < slot_max);
write(IOREDTBL_IDX + 2 * slot + 0, rt.value_low);
write(IOREDTBL_IDX + 2 * slot + 1, rt.value_high);
}
static void setID(uint8_t id) {
Identification reg(read(IOAPICID_IDX));
reg.id = id;
write(IOAPICID_IDX, reg.value);
}
void init() {
// optional:
IOREGSEL_REG = reinterpret_cast<volatile Index *>(APIC::getIOAPICAddress());
IOWIN_REG =
reinterpret_cast<volatile Index *>(APIC::getIOAPICAddress() + 0x10);
// set 4-bit ID to correct IO-APIC value, determined at startup
setID(APIC::getIOAPICID());
for (uint8_t i = 0; i < slot_max; ++i) {
RedirectionTableEntry rt = getRedirectionTableEntry(i);
/* The old solution used 0xff as `destination` here, but the Intel
* manual (3A, 10.6.2.2 - Logical Destination Mode) states the
* following: "For both configurations of logical destination mode, when
* combined with lowest priority delivery mode, software is responsible
* for ensuring that all of the local APICs included in or addressed by
* the IPI or I/O subsystem interrupt are present and enabled to receive
* the interrupt." More recent processors assume the correctness of the
* value written to `destination`, and, therefore, will try to redirect
* the interrupt to non-existing LAPIC if misconfigured and wait for
* response.
*/
rt.destination = 1;
rt.vector = static_cast<uint8_t>(Core::Interrupt::Vector::PANIC);
rt.delivery_mode = DeliveryMode::LOWEST_PRIORITY;
rt.destination_mode = DestinationMode::LOGICAL;
rt.polarity = Polarity::HIGH;
rt.trigger_mode = TriggerMode::EDGE;
rt.interrupt_mask = InterruptMask::MASKED;
setRedirectionTableEntry(i, rt);
}
}
void config(uint8_t slot, Core::Interrupt::Vector vector,
TriggerMode trigger_mode, Polarity polarity) {
RedirectionTableEntry rt = getRedirectionTableEntry(slot);
rt.vector = static_cast<uint8_t>(vector);
rt.polarity = polarity;
rt.trigger_mode = trigger_mode;
setRedirectionTableEntry(slot, rt);
}
void allow(uint8_t slot) {
RedirectionTableEntry rt = getRedirectionTableEntry(slot);
rt.interrupt_mask = InterruptMask::UNMASKED;
setRedirectionTableEntry(slot, rt);
}
void forbid(uint8_t slot) {
RedirectionTableEntry rt = getRedirectionTableEntry(slot);
rt.interrupt_mask = InterruptMask::MASKED;
setRedirectionTableEntry(slot, rt);
}
bool status(uint8_t slot) {
RedirectionTableEntry rt = getRedirectionTableEntry(slot);
return rt.interrupt_mask == InterruptMask::UNMASKED;
}
} // namespace IOAPIC

76
kernel/arch/ioapic.h Normal file
View File

@@ -0,0 +1,76 @@
/*! \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() )
*
*/
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)
*
*/
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).
*
* \param slot Number of the external interrupt that should be enabled.
*
*/
void allow(uint8_t slot);
/*! \brief Selectively masks external interrupts by slot number.
* \param slot Slot number of the interrupt to be disabled.
*
*/
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
*
*/
bool status(uint8_t slot);
} // namespace IOAPIC

View File

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

63
kernel/arch/ioport.h Normal file
View File

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

191
kernel/arch/lapic.cc Normal file
View File

@@ -0,0 +1,191 @@
#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

194
kernel/arch/lapic.h Normal file
View File

@@ -0,0 +1,194 @@
/*! \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 LAPIC timer's 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 the 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 the measurement, the timer is disabled again.
*
* \note The LAPIC-timer counts towards zero.
*
* \return Number of LAPIC-timer ticks per millisecond
*
*/
uint32_t measureTickRate(); // XXX: these should be "private"
/*! \brief Configures the LAPIC timer hardware
* \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 repeated periodically
* \param masked If set, interrupts on counter expiry are suppressed
*
*/
void set(uint32_t counter, uint8_t divide, uint8_t vector, bool periodic,
bool masked = false); // XXX: these should be "private"
/*! \brief Prepares \ref LAPIC::Timer.
*
* Prepares the internal state of the \ref LAPIC::Timer
* such that regular interrupts are triggered approx. every `us`
* microseconds when \ref LAPIC::Timer::activate() is called.
* For this purpose, a suitable timer divisor is calculated
* based on the timer frequency determined with \ref
* LAPIC::Timer::measureTickRate(). 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 Whether the interval could be set.
*
*/
bool setup(uint32_t us); // XXX: rename to "prepare", as it does not actually
// "set up" the timer?
/*! \brief Retrieve the interrupt interval set during \ref LAPIC::Timer::setup()
*
* \return Interval in microseconds
*
*/
uint32_t interval();
/*! \brief Activate the timer on this core.
*
* Starts the core local timer with the interval previously calculated 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).
*
*/
void activate();
/*! \brief Set the LAPIC-timer interrupt mask
* \param masked If set, interrupts are suppressed on counter expiry.
*
*/
void setMasked(bool masked);
} // namespace Timer
} // namespace LAPIC

245
kernel/arch/lapic_ipi.cc Normal file
View File

@@ -0,0 +1,245 @@
#include "lapic_registers.h"
namespace LAPIC {
namespace IPI {
/*! \brief Delivery mode specifies the type of interrupt sent to the CPU. */
enum class DeliveryMode : uint8_t {
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 class DestinationMode : uint8_t {
PHYSICAL = 0, ///< Destination contains the physical destination APIC ID
LOGICAL = 1 ///< Destination contains a mask of logical APIC IDs
};
/*! \brief Interrupt state */
enum class DeliveryStatus : uint8_t {
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 class Level : uint8_t {
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 class TriggerMode : uint8_t {
EDGE_TRIGGERED = 0, ///< edge triggered
LEVEL_TRIGGERED = 1 ///< level triggered
};
/*! \brief Shorthand for commonly used destinations */
enum class DestinationShorthand : uint8_t {
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 class InterruptMask : uint8_t {
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

View File

@@ -0,0 +1,61 @@
/*! \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 {
/// Local APIC ID Register, RO (sometimes R/W). Do not change!
IDENTIFICATION = 0x020,
/// Local APIC Version Register, RO
VERSION = 0x030,
/// Task Priority Register, R/W
TASK_PRIORITY = 0x080,
/// EOI Register, WO
EOI = 0x0b0,
/// Logical Destination Register, R/W
LOGICAL_DESTINATION = 0x0d0,
/// Destination Format Register, bits 0-27 RO, bits 28-31 R/W
DESTINATION_FORMAT = 0x0e0,
/// Spurious Interrupt Vector Register, bits 0-8 R/W, bits 9-1 R/W
SPURIOUS_INTERRUPT_VECTOR = 0x0f0,
/// Interrupt Command Register 1, R/W
INTERRUPT_COMMAND_REGISTER_LOW = 0x300,
/// Interrupt Command Register 2, R/W
INTERRUPT_COMMAND_REGISTER_HIGH = 0x310,
/// LAPIC timer control register, R/W
TIMER_CONTROL = 0x320,
/// LAPIC timer initial counter register, R/W
TIMER_INITIAL_COUNTER = 0x380,
/// LAPIC timer current counter register, RO
TIMER_CURRENT_COUNTER = 0x390,
/// LAPIC timer divide configuration register, RW
TIMER_DIVIDE_CONFIGURATION = 0x3e0
};
/*! \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

177
kernel/arch/lapic_timer.cc Normal file
View File

@@ -0,0 +1,177 @@
#include "../debug/assert.h"
#include "core_interrupt.h"
#include "lapic.h"
#include "lapic_registers.h"
#include "pit.h"
namespace LAPIC {
namespace Timer {
/*! \brief Timer Delivery Status */
enum class DeliveryStatus : uint8_t { IDLE = 0, SEND_PENDING = 1 };
/*! \brief Timer Mode */
enum class TimerMode : uint8_t {
ONE_SHOT = 0,
PERIODIC = 1,
DEADLINE = 2
// reserved
};
/*! \brief Timer Mask */
enum class Mask : uint8_t { 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) {
// div is zero or not a power of two?
if (div == 0 || (div & (div - 1)) != 0) {
return INVALID_DIV;
}
int trail = __builtin_ctz(div); // count trailing 0-bits
if (trail > 7) {
return INVALID_DIV;
}
return div_masks[trail];
}
uint32_t measureTickRate(void) {
uint32_t ticks = 0; // ticks per millisecond
// Calculation (Assignment 5)
// Steps taken for precise measurement of LAPIC-timer ticks per ms:
// 1. Disable Interrupts to ensure measurement is not disturbed
// 2. Configure a timeout of 50 ms (nearly PIT's maximum possible delay)
// Using a "large" value decreases the overhead induced by measurement
// and thereby increases the accuracy.
// 3. Now measure the number of passed LAPIC-timer ticks while waiting for
// the PIT
// Note that configuring the PIT takes quite some time and therefore
// should be done prior to starting LAPIC-timer measurement.
// 4. Restore previous state (disable PIT, LAPIC timer, restore interrupts)
// 5. Derive the ticks per millisecond (take care, the counter is counting
// towards zero)
// Start LAPIC counter, single shot, no interrupts
set(UINT32_MAX, 1, 0, false, true);
bool status = Core::Interrupt::disable();
const uint32_t ms = 50;
PIT::set(ms * 1000);
Register start = read(TIMER_CURRENT_COUNTER);
PIT::waitForTimeout();
Register end = read(TIMER_CURRENT_COUNTER);
set(0, 1, 0, false, true);
PIT::disable();
Core::Interrupt::restore(status);
ticks = (start - end) / ms;
return ticks;
}
void set(uint32_t counter, uint8_t divide, uint8_t vector, bool periodic,
bool masked) {
// stop timer by setting counter to 0
write(TIMER_INITIAL_COUNTER, 0);
// write control register
ControlRegister tcr;
tcr.value = read(TIMER_CONTROL);
tcr.vector = vector;
tcr.timer_mode = periodic ? TimerMode::PERIODIC : TimerMode::ONE_SHOT;
tcr.masked = masked ? Mask::MASKED : Mask::NOT_MASKED;
write(TIMER_CONTROL, tcr.value);
// set divider
Register clock_div = getClockDiv(divide);
assert(clock_div != INVALID_DIV);
write(TIMER_DIVIDE_CONFIGURATION, clock_div);
// start timer
write(TIMER_INITIAL_COUNTER, static_cast<Register>(counter));
}
static uint32_t interv;
static uint32_t nticks;
static uint32_t div;
bool setup(uint32_t us) {
if (us == 0) {
return false;
}
uint64_t ticks_ms = measureTickRate();
uint64_t tmp_ticks = (ticks_ms * us) / 1000;
uint32_t tmp32_ticks = static_cast<uint32_t>(tmp_ticks);
// Calculate LAPIC timer divider
for (int i = 1; i <= 128; i *= 2) {
if (tmp_ticks == static_cast<uint64_t>(tmp32_ticks)) {
nticks = tmp32_ticks;
div = i;
interv = us;
return true;
}
tmp_ticks = tmp_ticks >> 1;
tmp32_ticks = static_cast<uint32_t>(tmp_ticks);
}
return false;
}
uint32_t interval() { return interv; }
void activate() {
set(nticks, div, static_cast<uint8_t>(Core::Interrupt::Vector::TIMER),
true);
}
void setMasked(bool masked) {
ControlRegister tcr;
tcr.value = read(TIMER_CONTROL);
tcr.masked = masked ? Mask::MASKED : Mask::NOT_MASKED;
write(TIMER_CONTROL, tcr.value);
}
} // namespace Timer
} // namespace LAPIC

65
kernel/arch/pic.cc Normal file
View File

@@ -0,0 +1,65 @@
#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

18
kernel/arch/pic.h Normal file
View File

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

226
kernel/arch/pit.cc Normal file
View File

@@ -0,0 +1,226 @@
#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

80
kernel/arch/pit.h Normal file
View File

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

55
kernel/arch/serial.cc Normal file
View File

@@ -0,0 +1,55 @@
#include "serial.h"
#include "ioport.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(RegisterIndex::LINE_CONTROL,
static_cast<uint8_t>(RegisterMask::DIVISOR_LATCH_ACCESS_BIT));
// config divisor
uint16_t divisor = static_cast<uint16_t>(baud_rate);
// write low byte of divisor latch
writeReg(RegisterIndex::DIVISOR_LOW, divisor & 0xff);
// write high byte
writeReg(RegisterIndex::DIVISOR_HIGH, (divisor >> 8) & 0xff);
// deselect r/w of divisor latch register
// configure line control
writeReg(RegisterIndex::LINE_CONTROL, static_cast<uint8_t>(data_bits) |
static_cast<uint8_t>(stop_bits) |
static_cast<uint8_t>(parity));
// FIFO: Enable & clear buffers
writeReg(RegisterIndex::FIFO_CONTROL,
static_cast<uint8_t>(RegisterMask::ENABLE_FIFO) |
static_cast<uint8_t>(RegisterMask::CLEAR_RECEIVE_FIFO) |
static_cast<uint8_t>(RegisterMask::CLEAR_TRANSMIT_FIFO));
// Modem Control: OUT2 (0000 1000) must be set for interrupt
writeReg(RegisterIndex::MODEM_CONTROL,
static_cast<uint8_t>(RegisterMask::OUT_2));
}
void Serial::writeReg(RegisterIndex reg, uint8_t out) {
IOPort p(static_cast<uint16_t>(port) + static_cast<uint16_t>(reg));
p.outb(out);
}
uint8_t Serial::readReg(RegisterIndex reg) {
IOPort p(static_cast<uint16_t>(port) + static_cast<uint16_t>(reg));
return p.inb();
}
int Serial::write(uint8_t out) {
while ((readReg(RegisterIndex::LINE_STATUS) &
static_cast<uint8_t>(RegisterMask::TRANSMITTER_EMPTY)) == 0) {
}
writeReg(RegisterIndex::TRANSMIT_BUFFER, out);
return out;
}

196
kernel/arch/serial.h Normal file
View File

@@ -0,0 +1,196 @@
/*! \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 class ComPort : uint16_t {
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 class BaudRate : uint16_t {
B300 = 384,
B600 = 192,
B1200 = 96,
B2400 = 48,
B4800 = 24,
B9600 = 12,
B19200 = 6,
B38400 = 3,
B57600 = 2,
B115200 = 1,
};
/*! \brief Number of data bits per character */
enum class DataBits : uint8_t {
D5 = 0,
D6 = 1,
D7 = 2,
D8 = 3,
};
/*! \brief Number of stop bits per character */
enum class StopBits : uint8_t {
S1 = 0,
S1_5 = 4,
S2 = 4,
};
/*! \brief parity bit */
enum class Parity : uint8_t {
NONE = 0,
ODD = 8,
EVEN = 24,
MARK = 40,
SPACE = 56,
};
private:
/*! \brief register index */
enum class RegisterIndex : uint8_t {
RECEIVE_BUFFER = 0,
TRANSMIT_BUFFER = 0,
INTERRUPT_ENABLE = 1,
DIVISOR_LOW = 0,
DIVISOR_HIGH = 1,
INTERRUPT_IDENT = 2,
FIFO_CONTROL = 2,
LINE_CONTROL = 3,
MODEM_CONTROL = 4,
LINE_STATUS = 5,
MODEM_STATUS = 6
};
/*! \brief Mask for the respective register */
enum class RegisterMask : uint8_t {
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
*
*
* \param reg Register index
* \return The value read from register
*/
uint8_t readReg(RegisterIndex reg);
/*! \brief Write value to register
*
*
* \param reg Register index
* \param out value to be written
*/
void writeReg(RegisterIndex reg, uint8_t 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.
*
*/
explicit Serial(ComPort port = ComPort::COM1,
BaudRate baud_rate = BaudRate::B115200,
DataBits data_bits = DataBits::D8,
StopBits stop_bits = StopBits::S1,
Parity parity = Parity::NONE);
/*! \brief Write one byte to the serial interface
*
*
* \param out Byte to be written
* \return Byte written (or `-1` if writing byte failed)
*/
int write(uint8_t out);
};

16
kernel/arch/system.cc Normal file
View File

@@ -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::Register::STATUS_SHUTDOWN, 0);
system_control_port_a.outb(0x3);
}
} // namespace System

15
kernel/arch/system.h Normal file
View File

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

129
kernel/arch/textwindow.cc Normal file
View File

@@ -0,0 +1,129 @@
#include "textwindow.h"
#include "../utils/math.h"
TextWindow::TextWindow(unsigned from_col, unsigned to_col, unsigned from_row,
unsigned to_row, bool use_cursor)
: from_col(from_col),
to_col(to_col),
from_row(from_row),
to_row(to_row),
use_cursor(use_cursor),
pos_x(from_col),
pos_y(from_row) {
if (to_col > from_col && to_row > from_row) {
reset();
}
}
void TextWindow::setPos(unsigned rel_x, unsigned rel_y) {
unsigned abs_x = rel_x + from_col;
unsigned abs_y = rel_y + from_row;
// Check if visible
if (abs_x < to_col && abs_y < to_row) {
if (use_cursor) {
CGA::setCursor(abs_x, abs_y);
} else {
pos_x = abs_x;
pos_y = abs_y;
}
}
}
void TextWindow::getPos(unsigned& rel_x, unsigned& rel_y) const {
unsigned abs_x = pos_x;
unsigned abs_y = pos_y;
if (use_cursor) {
CGA::getCursor(abs_x, abs_y);
}
rel_x = Math::min(Math::max(abs_x, from_col), to_col - 1) - from_col;
rel_y = Math::min(Math::max(abs_y, from_row), to_row - 1) - from_row;
}
void TextWindow::setPos(int rel_x, int rel_y) {
int x = rel_x < 0 ? rel_x + to_col - from_col : rel_x;
int y = rel_y < 0 ? rel_y + to_row - from_row : rel_y;
if (x >= 0 && y >= 0) {
setPos(static_cast<unsigned>(x), static_cast<unsigned>(y));
}
}
void TextWindow::getPos(int& rel_x, int& rel_y) const {
unsigned x;
unsigned y;
getPos(x, y);
rel_x = static_cast<int>(x);
rel_y = static_cast<int>(y);
}
void TextWindow::print(const char* str, size_t length, CGA::Attribute attrib) {
// Get position
unsigned abs_x = pos_x;
unsigned abs_y = pos_y;
if (use_cursor) {
CGA::getCursor(abs_x, abs_y);
}
// Print each character
while (length > 0) {
switch (*str) {
case '\n':
abs_x = from_col;
abs_y++;
break;
case '\r':
abs_x = from_col;
break;
default:
show(abs_x, abs_y, *str, attrib);
abs_x++;
if (abs_x >= to_col) {
abs_x = from_col;
abs_y++;
}
break;
}
str++;
if (abs_y >= to_row) {
scrollUp();
abs_y--;
}
length--;
}
// Write position back
if (use_cursor) {
CGA::setCursor(abs_x, abs_y);
} else {
pos_x = abs_x;
pos_y = abs_y;
}
}
void TextWindow::reset(char character, CGA::Attribute attrib) {
for (unsigned abs_y = from_row; abs_y < to_row; ++abs_y) {
for (unsigned abs_x = from_col; abs_x < to_col; ++abs_x) {
show(abs_x, abs_y, character, attrib);
}
}
setPos(0, 0);
}
void TextWindow::scrollUp() const {
for (unsigned y = from_row + 1; y < to_row; ++y) {
for (unsigned x = from_col; x < to_col; ++x) {
unsigned pos = (y * CGA::COLUMNS) + x;
show(x, y - 1, CGA::TEXT_BUFFER_BASE[pos].character,
CGA::TEXT_BUFFER_BASE[pos].attribute);
}
}
for (unsigned x = from_col; x < to_col; ++x) {
CGA::show(x, to_row - 1, ' ');
}
}

168
kernel/arch/textwindow.h Normal file
View File

@@ -0,0 +1,168 @@
/*! \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;
/*! \brief Scroll the contents of a window a line up
*
* Moves the screen content up one line.
* The new line at the bottom of the screen is filled with blanks.
*/
void scrollUp() const;
protected:
/*! \brief Start column position (inclusive) of window
*/
unsigned from_col;
/*! \brief End column position (exclusive) of window
*/
unsigned to_col;
/*! \brief Start row position (inclusive) of window
*/
unsigned from_row;
/*! \brief End row position (exclusive) of window
*/
unsigned to_row;
/*! \brief use hardware cursor
*
* otherwise the position is stored in local variables
*/
bool use_cursor;
/*! \brief Software cursor column
*/
unsigned pos_x;
/*! \brief Software cursor row
*/
unsigned pos_y;
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
*
*/
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
* 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.
*
* \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
* 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
*/
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
*/
void reset(char character = ' ', CGA::Attribute attrib = CGA::Attribute());
};

49
kernel/arch/tss.h Normal file
View File

@@ -0,0 +1,49 @@
/*! \file
* \brief Task State Segment
*/
#pragma once
#include "../types.h"
/*! \brief Task State Segment
*/
namespace TSS {
/*! \brief TSS Format structure
*
* \see [ISDMv3 7.7 Task Management in 64-bit](intel_manual_vol3.pdf#page=252)
*/
struct Entry {
uint32_t : 32;
void* sp0; ///< Saves the stack pointer for every ring
void* sp1;
void* sp2;
uint64_t : 64;
void* ist1;
void*
ist2; ///< Interrupt Stack Table, stack pointer used by the instruction
void* ist3; ///< handlers when the interrupt descriptor table (IDT) is set
///< up accordingly:
void* ist4; ///< If IST is set to a non-zero value (i.e., 1 - 7),
void* ist5; ///< the CPU automagically locals the respective value into the
///< RSP.
void* ist6; ///<
void* ist7; ///< See https://wiki.osdev.org/Task_State_Segment
uint64_t : 64;
uint32_t : 16;
uint32_t iomap_base : 16; ///< Offset to IO Permission Bitmap
} __attribute__((packed));
static_assert(sizeof(Entry) == 104, "TaskStateSegment Entry has wrong size");
/*! \brief Initialize TSS for active core
*/
void init();
/*! \brief Set initial stack pointer in TSS
* \param ptr New (next) stack pointer
*/
void setStackpointer(void* ptr);
} // namespace TSS

4420
kernel/assets/cat.h Normal file

File diff suppressed because it is too large Load Diff

BIN
kernel/assets/cat.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

10984
kernel/assets/demon.h Normal file

File diff suppressed because it is too large Load Diff

BIN
kernel/assets/demon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

3175
kernel/assets/ibr.h Normal file

File diff suppressed because it is too large Load Diff

3175
kernel/assets/osg.h Normal file

File diff suppressed because it is too large Load Diff

101
kernel/assets/pc.h Normal file
View File

@@ -0,0 +1,101 @@
unsigned char __pc_png[] = {
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d,
0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0xa4, 0x00, 0x00, 0x00, 0x98,
0x08, 0x06, 0x00, 0x00, 0x00, 0x6a, 0x0d, 0x42, 0x97, 0x00, 0x00, 0x04,
0x5f, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0xed, 0xdd, 0xbf, 0x6f, 0x1b,
0x65, 0x18, 0x07, 0xf0, 0x27, 0xd0, 0x09, 0xb1, 0x41, 0x08, 0x61, 0x40,
0x44, 0xaa, 0x97, 0x0e, 0x20, 0x96, 0x0a, 0x29, 0xcc, 0xe9, 0xc4, 0x90,
0xfc, 0x05, 0x5d, 0x52, 0xb1, 0xa2, 0xb0, 0x23, 0xf6, 0x44, 0x6c, 0x08,
0x95, 0xa5, 0x2b, 0x4b, 0x3b, 0x74, 0x6a, 0x06, 0x58, 0xe8, 0x52, 0x21,
0xa1, 0x76, 0x75, 0xa4, 0x20, 0x96, 0x10, 0xb5, 0x6c, 0xec, 0x61, 0x68,
0x5f, 0xa4, 0x9c, 0xb8, 0xfa, 0xec, 0xbc, 0x77, 0xf7, 0xde, 0xdd, 0xe7,
0xb3, 0x24, 0xb1, 0x5d, 0xff, 0x38, 0x7f, 0xfb, 0xcd, 0xf3, 0x9e, 0x2f,
0xf6, 0x5a, 0x44, 0x5c, 0x44, 0x8b, 0xee, 0x3d, 0x3a, 0x0e, 0xc6, 0xe3,
0xf6, 0xad, 0x9d, 0x56, 0xaf, 0xff, 0x0d, 0x9b, 0x98, 0x92, 0xac, 0xb5,
0xd5, 0x90, 0xa9, 0x19, 0x4f, 0xe6, 0x67, 0xb6, 0xf2, 0x88, 0x5c, 0x9f,
0x6d, 0xb6, 0xda, 0x94, 0x1a, 0x92, 0x71, 0x37, 0xe4, 0xfe, 0xc1, 0x61,
0x44, 0x44, 0x6c, 0x6c, 0xad, 0xdb, 0xba, 0x9a, 0x52, 0x43, 0x32, 0x6c,
0xd7, 0xfa, 0xba, 0xe1, 0xf3, 0xd3, 0xe7, 0xb6, 0x7e, 0xc1, 0xfa, 0xfa,
0x0d, 0xa7, 0x21, 0x99, 0x76, 0x43, 0xa6, 0x66, 0x7c, 0x72, 0xff, 0x81,
0xad, 0x5f, 0xb0, 0x9b, 0x7b, 0xbb, 0xbd, 0x34, 0xa5, 0x86, 0x64, 0x9a,
0x0d, 0x99, 0x56, 0x65, 0x0f, 0x8f, 0xee, 0xda, 0xea, 0x03, 0x90, 0x7e,
0x83, 0x75, 0xdd, 0x94, 0x1a, 0x12, 0xab, 0x6c, 0xba, 0xf7, 0xee, 0xe6,
0xec, 0xd2, 0xcf, 0x6f, 0xbf, 0xbf, 0x11, 0x11, 0x11, 0x7f, 0xfc, 0xfe,
0x6b, 0x51, 0xf7, 0x53, 0x43, 0xa2, 0x21, 0xe9, 0xde, 0x8b, 0xb3, 0xf9,
0xff, 0xfe, 0xfc, 0xd1, 0xa7, 0x9f, 0x17, 0xd5, 0x94, 0x1a, 0x12, 0x0d,
0x49, 0x39, 0x52, 0x33, 0x96, 0xd2, 0x94, 0x1a, 0x12, 0x0d, 0x49, 0x79,
0xfe, 0xf9, 0xeb, 0xdc, 0x0c, 0x09, 0x02, 0x89, 0x40, 0x82, 0x19, 0x92,
0xa5, 0x54, 0xf7, 0x53, 0x6a, 0x48, 0x10, 0x48, 0x04, 0x12, 0x04, 0x12,
0x81, 0x04, 0x81, 0x44, 0x20, 0x41, 0x20, 0x11, 0x48, 0x10, 0x48, 0x04,
0x12, 0x04, 0x12, 0x04, 0x12, 0x81, 0x04, 0x81, 0x44, 0x20, 0x41, 0x20,
0x11, 0x48, 0x10, 0x48, 0x04, 0x12, 0x04, 0x12, 0x81, 0x04, 0x81, 0x04,
0x81, 0x44, 0x20, 0x41, 0x20, 0x11, 0x48, 0x10, 0x48, 0x04, 0x12, 0x04,
0x12, 0x81, 0x04, 0x81, 0x44, 0x20, 0x41, 0x20, 0x41, 0x20, 0x11, 0x48,
0x10, 0x48, 0x04, 0x12, 0x04, 0x12, 0x81, 0x04, 0x81, 0x44, 0x20, 0x41,
0x20, 0x11, 0x48, 0x10, 0x48, 0x10, 0x48, 0x04, 0x12, 0x04, 0x12, 0x81,
0x04, 0x81, 0x44, 0x20, 0xa1, 0x25, 0xd7, 0xba, 0xba, 0xa1, 0x93, 0xf9,
0x59, 0x44, 0x44, 0x7c, 0xf5, 0xc3, 0x37, 0x11, 0x11, 0xf1, 0xdd, 0x97,
0xdf, 0xda, 0xfa, 0x05, 0x4b, 0xcf, 0x53, 0x7a, 0xde, 0x34, 0x24, 0x1a,
0xb2, 0x8f, 0xa6, 0xa4, 0x4c, 0x5d, 0x37, 0xa3, 0x86, 0x44, 0x43, 0x96,
0xf0, 0x3f, 0x10, 0xab, 0x6c, 0x68, 0x6c, 0x2d, 0x22, 0x2e, 0xda, 0xb8,
0xe2, 0xfd, 0x83, 0xc3, 0x88, 0x88, 0xd8, 0xd8, 0x5a, 0xb7, 0x95, 0x47,
0xe4, 0xfc, 0xf4, 0x79, 0x44, 0x44, 0xfc, 0x78, 0xf4, 0xb5, 0x86, 0x44,
0x43, 0x66, 0x6b, 0x4a, 0xc6, 0xa1, 0xad, 0x66, 0xd4, 0x90, 0x4c, 0xb3,
0x21, 0x93, 0x4f, 0xb6, 0xb6, 0x6d, 0xed, 0x01, 0x7a, 0x7a, 0xfa, 0xd8,
0x2a, 0x1b, 0x0d, 0xd9, 0xd9, 0xec, 0xf8, 0xe4, 0xfe, 0x83, 0x88, 0x88,
0xb8, 0xb9, 0xb7, 0xbb, 0xd2, 0xf9, 0x8c, 0x7b, 0x96, 0xd4, 0x90, 0x14,
0x25, 0xfb, 0x2b, 0x35, 0xa9, 0x19, 0xb7, 0x77, 0x3e, 0x7e, 0xed, 0xe5,
0xae, 0x7a, 0x3e, 0xfd, 0xda, 0xde, 0x39, 0x8e, 0x88, 0x88, 0xdb, 0xb7,
0x76, 0x34, 0x24, 0x1a, 0x72, 0x69, 0x8b, 0x5e, 0xab, 0xae, 0x9e, 0x7f,
0x7d, 0xb6, 0xb9, 0xd4, 0xbf, 0xa7, 0x5f, 0xd5, 0xe7, 0x4b, 0x43, 0xa2,
0x21, 0x19, 0xbe, 0xf4, 0x5a, 0x74, 0x52, 0x77, 0xac, 0x41, 0xd3, 0xcb,
0x69, 0x48, 0x34, 0x24, 0xe3, 0x99, 0xf9, 0x7e, 0x39, 0xfa, 0x3e, 0x22,
0x22, 0x66, 0x1f, 0xbc, 0xf9, 0xb2, 0x09, 0x63, 0xef, 0x52, 0x03, 0xa6,
0xcb, 0xfd, 0xfc, 0xdb, 0x4f, 0x11, 0x11, 0xb1, 0xbe, 0xf1, 0xd6, 0xcb,
0x2b, 0x38, 0xfd, 0xac, 0x93, 0xa6, 0xd4, 0x90, 0x68, 0x48, 0xfa, 0x73,
0xf2, 0xf7, 0x9f, 0xaf, 0xbe, 0xfb, 0x30, 0x22, 0x22, 0x6e, 0xd4, 0x5c,
0xee, 0x6c, 0x3e, 0x7f, 0xf5, 0xdd, 0x2c, 0x22, 0x22, 0xde, 0x7b, 0xa7,
0x9b, 0xfb, 0xa7, 0x21, 0x29, 0x4a, 0xf6, 0xd7, 0xb2, 0x9b, 0x1e, 0x29,
0x5e, 0x5d, 0xc5, 0x75, 0xbd, 0x9a, 0x9b, 0xfa, 0x2c, 0xf9, 0x5f, 0x63,
0xd6, 0xec, 0xef, 0x5d, 0x74, 0xb9, 0x74, 0xbe, 0x57, 0x6a, 0x30, 0x43,
0xb6, 0x41, 0x13, 0xf6, 0x34, 0x43, 0x36, 0x7c, 0x05, 0xcc, 0xdf, 0x65,
0x83, 0x40, 0x22, 0x90, 0xd0, 0xe7, 0x0c, 0xd9, 0xd6, 0x51, 0x21, 0x7d,
0xcd, 0x56, 0x68, 0x48, 0x34, 0x64, 0x3e, 0x43, 0x79, 0x1f, 0xc8, 0x2f,
0x0e, 0xee, 0x0c, 0xe2, 0x7e, 0xd6, 0xed, 0xbf, 0xad, 0x1a, 0xea, 0x5e,
0x0c, 0x0d, 0xc9, 0xb4, 0x1a, 0x72, 0x28, 0xcd, 0xb3, 0xea, 0x4c, 0xbc,
0x68, 0xf6, 0x6c, 0xda, 0x68, 0x4d, 0xa5, 0xbf, 0xca, 0x5c, 0x24, 0xf7,
0x5f, 0x6d, 0x3a, 0x1e, 0x12, 0x0d, 0x69, 0x95, 0xbd, 0x7c, 0xd3, 0xa5,
0xc7, 0x57, 0xf7, 0x5a, 0xef, 0xc3, 0xa3, 0xbb, 0xbd, 0x3c, 0x9e, 0xa6,
0x4d, 0xda, 0x54, 0x6a, 0x5c, 0xc7, 0x43, 0xa2, 0x21, 0xad, 0xb2, 0xaf,
0xee, 0xf1, 0xf1, 0xb3, 0x88, 0xc8, 0xff, 0x5e, 0xea, 0x4d, 0xb7, 0x67,
0xee, 0xdb, 0x4d, 0x8f, 0xc7, 0x0c, 0x89, 0x86, 0xb4, 0xca, 0xae, 0x5f,
0x65, 0x2e, 0xbb, 0x1f, 0x30, 0x77, 0xb3, 0x34, 0x6d, 0xbe, 0xdc, 0xb7,
0x6b, 0x95, 0x8d, 0x86, 0xb4, 0xca, 0xce, 0xdf, 0x14, 0xb9, 0x9b, 0xa5,
0x69, 0xf3, 0x79, 0xa5, 0x06, 0xac, 0xb2, 0xa7, 0x37, 0xeb, 0x8e, 0xfd,
0x48, 0x7b, 0x0d, 0x89, 0x55, 0x36, 0x68, 0x48, 0x06, 0xa1, 0xf3, 0xf7,
0x18, 0x67, 0x1c, 0xbc, 0xc7, 0x38, 0x1a, 0x12, 0xcc, 0x90, 0x58, 0x65,
0xe7, 0x70, 0xef, 0xd1, 0xb1, 0xad, 0x39, 0x61, 0xb9, 0xde, 0xe3, 0x47,
0x43, 0x32, 0xae, 0x19, 0xd2, 0xe7, 0x62, 0x13, 0x91, 0xef, 0xdd, 0xd0,
0x34, 0x24, 0xe3, 0x68, 0xc8, 0x6a, 0x33, 0xa6, 0xe3, 0x04, 0xab, 0x9f,
0xc0, 0x95, 0x8e, 0x4e, 0x99, 0xfa, 0xe9, 0x75, 0x47, 0x13, 0xe5, 0xda,
0x6e, 0x6d, 0x5f, 0x7f, 0xd3, 0xdb, 0xbd, 0x6a, 0x53, 0x6a, 0x48, 0x86,
0xdd, 0x90, 0x8b, 0x66, 0xc6, 0xba, 0xcf, 0x37, 0x71, 0xfa, 0x72, 0xef,
0x28, 0x5c, 0xea, 0xf5, 0x37, 0xbd, 0xdd, 0x55, 0x9b, 0x52, 0x43, 0x52,
0x94, 0xc6, 0xfb, 0x21, 0x9b, 0xce, 0x8c, 0x4e, 0xbf, 0xdc, 0x10, 0x8b,
0x66, 0xbc, 0xa6, 0x0d, 0x77, 0xd5, 0x4f, 0xc7, 0xad, 0x3e, 0x6f, 0xe9,
0xfe, 0xa5, 0xaf, 0x69, 0x46, 0x5c, 0xf5, 0xf4, 0xea, 0xe3, 0x4c, 0x5f,
0xd3, 0xfe, 0xe9, 0xa6, 0x4d, 0xa9, 0x21, 0x19, 0xd6, 0x0c, 0x69, 0x66,
0x5c, 0xed, 0xf4, 0x65, 0x1b, 0x70, 0x91, 0xdc, 0x9f, 0x3d, 0x98, 0xfb,
0x71, 0xe7, 0x5a, 0x7d, 0x6b, 0x48, 0x86, 0xd1, 0x90, 0x5d, 0xef, 0x67,
0xec, 0x5b, 0x5f, 0xfb, 0x21, 0xeb, 0x9a, 0x6b, 0x68, 0xb7, 0x97, 0x6b,
0x3f, 0xa5, 0x86, 0xa4, 0xec, 0x86, 0xac, 0x1e, 0xe9, 0x5d, 0x9d, 0x55,
0xba, 0x9a, 0xb9, 0xba, 0xd6, 0xf7, 0x7e, 0xc8, 0xb6, 0x66, 0xbc, 0xbe,
0x66, 0xca, 0x45, 0xf7, 0xa3, 0xae, 0x29, 0x35, 0x24, 0x65, 0x36, 0x64,
0x6a, 0xc6, 0xd2, 0x66, 0x3b, 0xa6, 0x21, 0x35, 0xa5, 0x86, 0xa4, 0xac,
0x86, 0xdc, 0x3f, 0x38, 0xbc, 0x58, 0x65, 0xe6, 0x80, 0x9c, 0xd2, 0x4c,
0xa9, 0x21, 0x29, 0x8a, 0x40, 0x22, 0x90, 0x20, 0x90, 0x08, 0x24, 0x2c,
0xbd, 0xca, 0x8e, 0xca, 0x7e, 0x48, 0xe8, 0x43, 0x7a, 0xaf, 0x20, 0x0d,
0x49, 0x51, 0xfe, 0x05, 0xdb, 0xdb, 0x5a, 0x5d, 0x55, 0x55, 0xee, 0xa0,
0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82};
unsigned int __pc_png_len = 1176;

BIN
kernel/assets/pc.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

250
kernel/boot/longmode.asm Normal file
View File

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

View File

@@ -0,0 +1,22 @@
; Magic Header, has to be present in Kernel to indicate Multiboot compliance
MULTIBOOT_HEADER_MAGIC_OS equ 0x1badb002
; Answer by the boot loader for Multiboot compliance, written in eax register
MULTIBOOT_HEADER_MAGIC_LOADER equ 0x2badb002
; Flags instructing the Multiboot compliant boot loader to setup the system
; according to your needs
MULTIBOOT_PAGE_ALIGN equ 1<<0 ; Align boot modules (initrds) at 4 KiB border
MULTIBOOT_MEMORY_INFO equ 1<<1 ; Request Memory Map information
MULTIBOOT_VIDEO_MODE equ 1<<2 ; Configure video mode
MULTIBOOT_HEADER_FLAGS equ 0
; Desired video mode (only considered if MULTIBOOT_VIDEO_MODE set)
; (boot loader will choose the best fitting mode, which might differ from the settings below)
MULTIBOOT_VIDEO_WIDTH equ 1920 ; Desired width
MULTIBOOT_VIDEO_HEIGHT equ 1080 ; Desired height
MULTIBOOT_VIDEO_BITDEPTH equ 32 ; Desired bit depth
; Checksum
MULTIBOOT_HEADER_CHKSUM equ -(MULTIBOOT_HEADER_MAGIC_OS + MULTIBOOT_HEADER_FLAGS)

View File

@@ -0,0 +1,160 @@
#include "./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 {
return reinterpret_cast<void *>(static_cast<uintptr_t>(addr));
}
void *Memory::getEndAddress() const {
uint64_t end = addr + len;
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

View File

@@ -0,0 +1,229 @@
/*! \file
* \brief \ref Multiboot Interface
*/
#pragma once
#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

View File

@@ -0,0 +1,33 @@
; The first 8192 bytes of the kernel binary must contain a header with
; predefined (and sometimes "magic") values according to the Multiboot standard.
; Based on these values, the boot loader decides whether and how to load the
; kernel -- which is compiled and linked into an ELF file.
; To make this possible with your StuBS kernel, the linker places the following
; entry `multiboot_header` at the very beginning of the file thanks to the
; linker script (located in compiler/sections.ld).
[SECTION .multiboot_header]
; Include configuration
%include 'boot/multiboot/config.inc'
; Multiboot Header
align 4
multiboot_header:
dd MULTIBOOT_HEADER_MAGIC_OS ; Magic Header Value
dd MULTIBOOT_HEADER_FLAGS ; Flags (affects following entries)
dd MULTIBOOT_HEADER_CHKSUM ; Header Checksum
; Following fields would have been required to be defined
; if flag A_OUT KLUDGE was set (but we don't need this)
dd 0 ; Header address
dd 0 ; Begin of load address
dd 0 ; end of load address
dd 0 ; end of bss segment
dd 0 ; address of entry function
; Following fields are required for video mode (flag MULTIBOOT_VIDEO_MODE)
dd 0 ; Mode: 0 = Graphic / 1 = Text
dd MULTIBOOT_VIDEO_WIDTH ; Width (pixels / columns)
dd MULTIBOOT_VIDEO_HEIGHT ; Height (pixels / rows)
dd MULTIBOOT_VIDEO_BITDEPTH ; color depth / number of colors

78
kernel/boot/startup.asm Normal file
View File

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

49
kernel/boot/startup.cc Normal file
View File

@@ -0,0 +1,49 @@
#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"
extern "C" [[noreturn]] void kernel_init() {
// 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();
// Call global destructors
CSU::finalizer();
// wait forever
while (true) {
Core::die();
}
}

35
kernel/boot/startup.h Normal file
View File

@@ -0,0 +1,35 @@
/*! \file
* \brief Startup of the first core, also known as bootstrap processor (BSP)
* \defgroup Startup "Bootloader and system startup"
*
*/
#pragma once
#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();
/*! \brief Initializes the C++ environment and detects system components
*
* \ingroup Startup
*
* The startup code TEMPLATE(m){(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()
*/
extern "C" [[noreturn]] void kernel_init();
/*! \brief Kernels main function
*
* Called after initialization of the system by \ref kernel_init()
*/
extern "C" int main();

46
kernel/compiler/libc.cc Normal file
View File

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

23
kernel/compiler/libc.h Normal file
View File

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

33
kernel/compiler/libcxx.cc Normal file
View File

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

106
kernel/compiler/sections.ld Normal file
View File

@@ -0,0 +1,106 @@
/* 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)
}
.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 = .);
}
.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")
*/
}
}

11
kernel/debug/assert.cc Normal file
View File

@@ -0,0 +1,11 @@
#include "assert.h"
#include "../arch/core.h"
#include "output.h"
[[noreturn]] void assertion_failed(const char* exp, const char* func,
const char* file, int line) {
DBG << "Assertion '" << exp << "' failed (" << func << " @ " << file << ":"
<< dec << line << ") - CPU stopped." << endl;
Core::die();
}

74
kernel/debug/assert.h Normal file
View File

@@ -0,0 +1,74 @@
// 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`.
*
*
* \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

44
kernel/debug/copystream.h Normal file
View File

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

View File

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

View File

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

45
kernel/debug/nullstream.h Normal file
View File

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

69
kernel/debug/output.h Normal file
View File

@@ -0,0 +1,69 @@
// vim: set noet ts=4 sw=4:
/*! \file
* \brief Debug macros enabling debug output on a separate window for each
* core.
*/
#pragma once
#include "../object/outputstream.h"
#include "../types.h"
#include "nullstream.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 << __FILE__ << ":" << __LINE__ << " ")
#else
// Otherwise sent everything to the NullStream (which will simply discard
// everything)
#define DBG_VERBOSE nullstream
#endif
/*! \def DBG
* \brief An output stream, which is displayed in the debug window
*
* This is also used by the \ref assert macro and its underlying \ref
* assertion_failed method.
*
* In single core (\OOStuBS) this is just an alias to the debug window object
* `dout`.
*/
#define DBG *copyout
#include "../device/textstream.h"
/*! \brief Debug window
*
* Debug output using \ref DBG like
* `DBG << "var = " << var << endl`
* should be displayed in separate window.
*
* Ideally, this window should be placed below the normal output window
* without any overlap and be able to display 4 lines.
*
*/
extern TextStream dout;
/*! \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
*
*/
extern OutputStream* copyout;

189
kernel/device/graphics.cc Normal file
View File

@@ -0,0 +1,189 @@
#include "./graphics.h"
#include "../boot/multiboot/data.h"
#include "./debug/output.h"
#include "./utils/string.h"
/// Replace size with `Graphics::SCREEN_BUF_SIZE`
/// We don't do this here, as this drastically increases the size of the kernel
/// image.
char frontbuffer[1] __attribute__((aligned(8)));
char backbuffer[1] __attribute__((aligned(8)));
/*! \brief Mode Information of the *Vesa BIOS Extension*
*
* \see Vesa BIOS Extension [ModeInfoBlock struc](vbe3.pdf#page=38)
*/
struct VbeModeInfo {
enum ModeAttributes : uint16_t {
SUPPORTED = 1 << 0, ///< Mode supported by hardware configuration
TTY = 1 << 2, ///< TTY Output functions supported by BIOS
COLOR = 1 << 3, ///< Color mode (otherwise monochrome)
GRAPHICS = 1 << 4, ///< Graphic mode (otherwise text)
VGA = 1 << 5, ///< VGA compatible
VGA_PAGED =
1 << 6, ///< VGA compatible windowed memory mode is available
LFB = 1 << 7, ///< Linear frame buffer mode is available
};
uint16_t mode_attributes;
// Window functions (used by all VBE revisions, but ignored here)
struct __attribute__((packed)) {
uint8_t attrib_a;
uint8_t attrib_b;
uint16_t granularity;
uint16_t size;
uint16_t segment_a;
uint16_t segment_b;
uint32_t func_ptr;
} win;
uint16_t pitch; ///< Bytes per scan line
uint16_t
width; ///< Horizontal resolution in pixels (GRAPHICS) or characters
uint16_t
height; ///< Vertical resolution in pixels (GRAPHICS) or characters
uint8_t char_width; ///< Character cell width in pixels (deprecated)
uint8_t char_height; ///< Character cell height in pixels (deprecated)
uint8_t planes; ///< Number of memory planes
uint8_t bpp; ///< Bits per pixel
uint8_t banks; ///< Number of banks
enum MemoryModel : uint8_t {
TEXT_MODE = 0, ///< Text mode
CGA = 1, ///< CGA graphics
HERCULES = 2, ///< Hercules graphics
PLANAR = 3, ///< Planar
PACKED = 4, ///< Packed pixel
NON_CHAIN_4 = 5, ///< Non-chain 4, 256 color
DIRECT_COLOR = 6, ///< Direct Color
YUV = 7 ///< YUV
} memory_model; ///< Memory model type
uint8_t bank_size; ///< Bank size in KB
uint8_t image_pages; ///< Number of images
uint8_t reserved; ///< Reserved for page function
// Direct Color fields (required for DIRECT_COLOR and YUV memory models)
uint8_t bits_red; ///< Size of direct color red mask in bits
uint8_t offset_red; ///< Bit position of lsb of red mask
uint8_t bits_green; ///< Size of direct color green mask in bits
uint8_t offset_green; ///< Bit position of lsb of green mask
uint8_t bits_blue; ///< Size of direct color blue mask in bits
uint8_t offset_blue; ///< Bit position of lsb of blue mask
uint8_t bits_rsv; ///< Size of direct color reserved mask in bits
uint8_t offset_rsv; ///< Bit position of lsb of reserved mask
enum DirectColorAttributes : uint8_t {
DYNAMIC_COLOR_RAMP =
1 << 0, ///< Programmable (otherwise fixed) color ramp
USABLE_BITS =
1 << 1 ///< Bits in reserved mask are usable (otherwise reserved)
};
uint8_t directcolor_attributes; ///< direct color mode attributes
// Mandatory information for VBE 2.0 and above
uint32_t address; ///< physical address for flat memory frame buffer
uint32_t offscreen_memory_offset; ///< reserved
uint16_t offscreen_memory_size; ///< reserved
} __attribute__((packed));
Graphics::Graphics()
: buffer_size(SCREEN_BUF_SIZE),
buffer{frontbuffer, backbuffer},
scanout_buffer(0),
refresh(false) {}
bool Graphics::init(bool force) {
assert(sizeof(frontbuffer) >= buffer_size &&
sizeof(backbuffer) >= buffer_size &&
"Buffers are too small, adjust them!!!");
// Most boot loaders support the Vesa BIOS Extension (VBE) information quite
// well
Multiboot::VBE *vbe = Multiboot::getVesaBiosExtensionInfo();
if (vbe != nullptr) {
// However, you have to manually parse the required data out of all the
// outdated crap
struct VbeModeInfo *vbe_info = reinterpret_cast<struct VbeModeInfo *>(
static_cast<uintptr_t>(vbe->mode_info));
// Is there linear frame buffer (or just ignore the check with `force`
// due to a misleading information)?
if (force || (vbe_info->mode_attributes &
VbeModeInfo::ModeAttributes::LFB) != 0) {
// Is there a suitable precompiled graphic mode
printer = AbstractGraphicsPrinter::getMode(
vbe_info->bpp, vbe_info->offset_red, vbe_info->offset_green,
vbe_info->offset_blue, vbe_info->bits_red, vbe_info->bits_green,
vbe_info->bits_blue);
if (printer != nullptr) {
address = reinterpret_cast<void *>(
static_cast<uintptr_t>(vbe_info->address));
size = vbe_info->height * vbe_info->pitch;
if (size > buffer_size) {
DBG_VERBOSE << "The current graphic buffer (" << buffer_size
<< " bytes) is too small (at least " << size
<< " bytes required)!" << endl;
return false;
} else {
printer->init(vbe_info->width, vbe_info->height,
vbe_info->pitch);
printer->buffer(buffer[1 - scanout_buffer]);
return true;
}
}
} else {
DBG_VERBOSE << "Unsupported graphic mode" << endl;
}
}
// Actually, \ref Multiboot::Framebuffer has everything we need in a real
// smart structure, however boot loader like PXE don't support them (yet --
// quite new, added in the last Multiboot revision).
Multiboot::Framebuffer *fb = Multiboot::getFramebufferInfo();
if (fb != nullptr) {
if (force || fb->type == Multiboot::Framebuffer::RGB) {
printer = AbstractGraphicsPrinter::getMode(
fb->bpp, fb->offset_red, fb->offset_green, fb->offset_blue,
fb->bits_red, fb->bits_green, fb->bits_blue);
if (printer != nullptr) {
address = reinterpret_cast<void *>(
static_cast<uintptr_t>(fb->address));
size = fb->height * fb->pitch;
if (size > buffer_size) {
DBG_VERBOSE << "The current graphic buffer (" << buffer_size
<< " bytes) is too small (at least " << size
<< " bytes required)!" << endl;
return false;
} else {
printer->init(fb->width, fb->height, fb->pitch);
printer->buffer(buffer[1 - scanout_buffer]);
return true;
}
}
} else {
DBG_VERBOSE << "Unsupported graphic mode" << endl;
}
}
// Unable to initialize mode
return false;
}
bool Graphics::switchBuffers() {
if (!refresh) {
printer->buffer(buffer[scanout_buffer]);
scanout_buffer = 1 - scanout_buffer;
refresh = true;
return true;
} else {
return false;
}
}
void Graphics::scanoutFrontbuffer() {
if (refresh) {
memcpy(address, buffer[scanout_buffer], size);
refresh = false;
}
}

284
kernel/device/graphics.h Normal file
View File

@@ -0,0 +1,284 @@
/*! \file
* \brief The \ref Graphics device is an interface for the \ref Framebuffer
*/
#pragma once
/*! \defgroup gfx Graphics
*
* Graphical VESA video modes.
*/
#include "../graphics/primitives.h"
#include "../graphics/printer.h"
/*! \brief Driver managing the video mode and synchronizing its buffer with the
* \ref AbstractGraphicsPrinter "graphics printer"
* \ingroup gfx
*
* This device detects the current video mode set by the \ref Multiboot
* compliant boot loader and initializes a suitable \ref GraphicsPrinter.
*
* With the methods \ref Graphics::switchBuffers() (to exchange front- and
* backbuffer) and \ref Graphics::scanoutFrontbuffer() (copying the contents of
* the frontbuffer into the video memory) it provides some kind of manually
* [triple
* buffering](https://en.wikipedia.org/wiki/Multiple_buffering#Triple_buffering).
*
* A typical usage is to fully prepare the back buffer before switching it with
* the front buffer
* \code{.cpp}
* graphics.init();
* while(true) {
* // Draw on back buffer
* // using the primitives provided by the driver
*
* graphics.switchBuffers();
* }
* \endcode
*
* The method \ref Graphics::scanoutFrontbuffer() has to be executed either
* inside the loop (right after \ref Graphics::switchBuffers() in the example
* above) or at a predefined interval by employing the \ref LAPIC::Timer.
*
* \note The driver requires \ref Multiboot to initialize a video mode, which
* can be configured using the flags in `boot/multiboot/config.inc`.
*/
class Graphics {
/*! \brief Pointer to a \ref GraphicsPrinter supporting the current video
* mode
*/
AbstractGraphicsPrinter* printer;
/*! \brief Pointer to the physical address of the video memory (linear frame
* buffer)
*/
void* address;
/*! \brief Video memory size required for a full screen picture
*/
unsigned size;
/*! \brief Size of the front (or back) buffer (which has to be at least \ref
* size)
*/
unsigned buffer_size;
/*! \brief Pointer to the two buffers
* (used alternately as front and back buffers)
*/
void* const buffer[2];
/*! \brief Index of the current front buffer
*/
int scanout_buffer;
/*! \brief Has the current front buffer already been drawn?
*/
bool refresh;
public:
/// MULTIBOOT_VIDEO_WIDTH * MULTIBOOT_VIDEO_HEIGHT *
/// MULTIBOOT_VIDEO_BITDEPTH (in bits)
static constexpr size_t SCREEN_BUF_SIZE =
1920UL * 1080UL * 32UL / sizeof(char);
/*! \brief Constructor
*/
Graphics();
/*! \brief Initialize \ref GraphicsPrinter according to the current video
* mode
*
* \param force Do not check video attributes for the linear frame buffer
* (required on our test systems due to some strange VBE
* behaviour)
* \return 'true' if a suitable \ref GraphicsPrinter was found for the video
* mode
*/
bool init(bool force = false);
/*! \brief Switch front and back buffer
* (only if front buffer was already copied to video memory)
*
* \return `true` if buffers have been switched, `false` if previous front
* buffer wasn't yet fully copied to video memory.
*/
bool switchBuffers();
/*! \brief Copy current front buffer to the video memory
*/
void scanoutFrontbuffer();
/*! \brief Clear all pixel of the current back buffer
* (set full screen to black)
*/
void clear() { printer->clear(); }
/*! \brief Check if a \ref Point can be displayed at the current resolution
*
* \param p Coordinates to check
* \return 'true' if can be displayed
*/
bool valid(const Point& p) { return printer->valid(p); }
/*! \brief Number of vertical pixels in current resolution
*
* \return Height of the screen in current video mode
*/
unsigned height() { return printer->height(); }
/*! \brief Number of horizontal pixels in current resolution
*
* \return Width of the screen in current video mode
*/
unsigned width() { return printer->width(); }
/*! \brief Draw a pixel on the current back buffer
*
* \param p Coordinates of the pixel
* \param color Color of the pixel
*/
void pixel(const Point& p, const Color& color) { printer->pixel(p, color); }
/// \copydoc pixel
void pixel(const Point& p, const ColorAlpha& color) {
printer->pixel(p, color);
}
/*! \brief Draw a line on the current back buffer
*
* \param start Coordinates of the begin of the line
* \param end Coordinates of the end of the line
* \param color Color of the line
*/
void line(const Point& start, const Point& end, const Color& color) {
printer->line(start, end, color);
}
/// \copydoc line
void line(const Point& start, const Point& end, const ColorAlpha& color) {
printer->line(start, end, color);
}
/*! \brief Draw a rectangle on the current back buffer
*
* \param start Coordinate of the rectangles upper left corner
* \param end Coordinate of the rectangles lower right corner
* \param color Color of the rectangle
* \param filled If set, the rectangle will be filled with the same color.
* (otherwise only borders will be drawn)
*/
void rectangle(const Point& start, const Point& end, const Color& color,
bool filled = true) {
printer->rectangle(start, end, color, filled);
}
/// \copydoc rectangle
void rectangle(const Point& start, const Point& end,
const ColorAlpha& color, bool filled = true) {
printer->rectangle(start, end, color, filled);
}
/*! \brief Change the current font for text output in video mode
*
* \param new_font Font to be used on subsequent calls to \ref text (without
* explicit font parameter)
*/
void font(const Font& new_font) { printer->font(new_font); }
/*! \brief Print text (without automatic word wrap) on the current back
* buffer
*
* \param p Upper left start position of the text
* \param string Pointer to char array containing the text to be displayed
* \param len Number of characters to be displayed
* \param color Color for the text characters
* \param font Explicit font -- or `nullptr` to use default font (set by
* \ref font method)
*/
void text(const Point& p, const char* string, unsigned len,
const Color& color, const Font* font = nullptr) {
printer->text(p, string, len, color, font);
}
/// \copydoc text
void text(const Point& p, const char* string, unsigned len,
const ColorAlpha& color, const Font* font = nullptr) {
printer->text(p, string, len, color, font);
}
/*! \brief Draw a \ref PNG image [detail] on the current back buffer.
*
* The image can has to be in a supported \ref PNG format.
* Alpha blending (transparency) is supported.
*
* \param p Coordinate of the images upper left corner
* \param image Source image to display
* \param width Width of the image detail (full image width of the source
* image if zero/default value)
* \param height Height of the image detail (full image height of the source
* if zero/default value)
* \param offset_x Right offset of the source image
* \param offset_y Top offset of the source image
*/
void image(const Point& p, PNG& image, unsigned width = 0,
unsigned height = 0, unsigned offset_x = 0,
unsigned offset_y = 0) {
printer->image(p, image, width, height, offset_x, offset_y);
}
/*! \brief Draw a GIMP image [detail] on the current back buffer.
*
* The image has to be exported as C-source (without `Glib` types!) in
* [GIMP](https://www.gimp.org/), alpha blending (transparency) is
* supported.
*
* \param p Coordinate of the images upper left corner
* \param image Source image to display
* \param width Width of the image detail (full image width of the source
* image if zero/default value)
* \param height Height of the image detail (full image height of the source
* if zero/default value)
* \param offset_x Right offset of the source image
* \param offset_y Top offset of the source image
*/
void image(const Point& p, const GIMP& image, unsigned width = 0,
unsigned height = 0, unsigned offset_x = 0,
unsigned offset_y = 0) {
printer->image(p, image, width, height, offset_x, offset_y);
}
/*! \brief Draw a sprite on the current back buffer.
*
* Each element in the source array will be displayed as a single pixel.
*
* \param p Coordinate of the sprites upper left corner
* \param image Source sprite to display
* \param width Width of the sprite detail
* \param height Height of the sprite detail
* \param offset_x Right offset of the source sprite
* \param offset_y Top offset of the source sprite
*/
void image(const Point& p, const Color* image, unsigned width,
unsigned height, unsigned offset_x = 0, unsigned offset_y = 0) {
printer->image(p, image, width, height, offset_x, offset_y);
}
/*! \brief Draw a sprite with alpha blending (transparency) on the current
* back buffer.
*
* Each element in the source array will be displayed as a single pixel.
*
* \param p Coordinate of the sprites upper left corner
* \param image Source sprite to display
* \param width Width of the sprite detail
* \param height Height of the sprite detail
* \param offset_x Right offset of the source sprite
* \param offset_y Top offset of the source sprite
*/
void image(const Point& p, const ColorAlpha* image, unsigned width,
unsigned height, unsigned offset_x = 0, unsigned offset_y = 0) {
printer->image(p, image, width, height, offset_x, offset_y);
}
};

View File

@@ -0,0 +1,131 @@
#include "./graphicsstream.h"
#include "../debug/assert.h"
#include "../graphics/fonts/font.h"
#include "../interrupt/guard.h"
#include "../utils/alloc.h"
#include "../utils/math.h"
const Color GraphicsStream::BLACK(0x00, 0x00, 0x00);
const Color GraphicsStream::BLUE(0x00, 0x00, 0xAA);
const Color GraphicsStream::GREEN(0x00, 0xAA, 0x00);
const Color GraphicsStream::CYAN(0x00, 0xAA, 0xAA);
const Color GraphicsStream::RED(0xAA, 0x00, 0x00);
const Color GraphicsStream::MAGENTA(0xAA, 0x00, 0xAA);
const Color GraphicsStream::BROWN(0xAA, 0x55, 0x00);
const Color GraphicsStream::LIGHT_GREY(0xAA, 0xAA, 0xAA);
const Color GraphicsStream::DARK_GREY(0x55, 0x55, 0x55);
const Color GraphicsStream::LIGHT_BLUE(0x55, 0x55, 0xFF);
const Color GraphicsStream::LIGHT_GREEN(0x55, 0xFF, 0x55);
const Color GraphicsStream::LIGHT_CYAN(0x55, 0xFF, 0xFF);
const Color GraphicsStream::LIGHT_RED(0xFF, 0x55, 0x55);
const Color GraphicsStream::LIGHT_MAGENTA(0xFF, 0x55, 0xFF);
const Color GraphicsStream::YELLOW(0xFF, 0xFF, 0x55);
const Color GraphicsStream::WHITE(0xFF, 0xFF, 0xFF);
GraphicsStream::GraphicsStream(const Point &start, unsigned width,
unsigned height, Font *const font)
: offset(0),
x(0),
y(0),
FONT(font == nullptr ? Font::get() : font),
START(start),
ROWS(height / this->FONT->height),
COLUMNS(width / this->FONT->width) {
cell = reinterpret_cast<Cell *>(calloc(ROWS * COLUMNS, sizeof(Cell)));
}
void GraphicsStream::setPos(int x, int y) {
if (x < 0) {
x += COLUMNS;
}
if (y < 0) {
y += ROWS;
}
if (x >= 0 && static_cast<unsigned>(x) < COLUMNS && y >= 0 &&
static_cast<unsigned>(y) <= ROWS) {
this->x = x;
this->y = y;
}
}
void GraphicsStream::getPos(int &x, int &y) const {
x = this->x;
y = this->y;
}
void GraphicsStream::show(int x, int y, char character, const Color &color) {
if (x < 0) {
x += COLUMNS;
}
if (y < 0) {
y += ROWS;
}
// only print if position within the screen range
if (x >= 0 && static_cast<unsigned>(x) < COLUMNS && y >= 0 &&
static_cast<unsigned>(y) < ROWS) {
cell[((offset + y) % ROWS) * COLUMNS + x] = (Cell){character, color};
}
}
void GraphicsStream::print(char *str, int length, const Color &color) {
while (length > 0) {
switch (*str) {
case '\n':
for (unsigned i = x; i < COLUMNS; ++i) {
show(i, y, ' ', color);
}
x = 0;
y++;
break;
default:
show(x, y, *str, color);
if (++x >= COLUMNS) {
x = 0;
y++;
}
break;
}
str++;
if (y >= ROWS) {
offset = (offset + 1) % ROWS;
y--;
for (unsigned i = 0; i < COLUMNS; ++i) {
show(i, y, ' ', color);
}
}
length--;
}
}
void GraphicsStream::reset(char character, const Color &color) {
for (unsigned y = 0; y < ROWS; ++y) {
for (unsigned x = 0; x < COLUMNS; ++x) {
show(x, y, character, color);
}
}
setPos(0, 0);
}
void GraphicsStream::flush() {
print(buffer, pos);
pos = 0;
}
void GraphicsStream::draw() {
flush();
Point pos = START;
for (unsigned y = 0; y < ROWS; y++) {
for (unsigned x = 0; x < COLUMNS; x++) {
Cell &c = cell[((offset + y) % ROWS) * COLUMNS + x];
Guard::enter().vault().graphics.text(pos, &c.character, 1, c.color,
FONT);
pos.x += FONT->width;
}
pos.x = START.x;
pos.y += FONT->height;
}
}

View File

@@ -0,0 +1,135 @@
/*! \file
* \brief \ref GraphicsStream, a \ref Graphics "graphical" \ref OutputStream
* "output stream" inspired by (and compatible to) \ref TextStream
*/
#pragma once
#include "../graphics/primitives.h"
#include "../object/outputstream.h"
#include "./graphics.h"
/*! \brief Output text (form different data type sources) on screen in \ref
* Graphics "graphic mode" (similar to \ref TextStream)
* \ingroup gfx
* \ingroup io
*
* Enables output of different data types using a monospaced font on a
* predefined area of the screen with activated graphics mode.
*/
class GraphicsStream : public OutputStream {
// Prevent copies and assignments
GraphicsStream(const GraphicsStream&) = delete;
GraphicsStream& operator=(const GraphicsStream&) = delete;
struct Cell {
char character;
Color color;
};
Cell* cell;
unsigned offset;
unsigned x, y; ///< Cursor position
protected:
/*! \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.
*/
void flush();
public:
Font* const FONT; ///< Default font
const Point START; ///< Upper left corner of the window
const unsigned ROWS; ///< Number of rows in the window
const unsigned COLUMNS; ///< Number of columns in the window
/*! \brief CGA color palette
*/
static const Color BLACK, BLUE, GREEN, CYAN, RED, MAGENTA, BROWN,
LIGHT_GREY, DARK_GREY, LIGHT_BLUE, LIGHT_GREEN, LIGHT_CYAN, LIGHT_RED,
LIGHT_MAGENTA, YELLOW, WHITE;
/*! \brief Constructor
*
* Creates a window (= area on the screen) for text output.
* Within the window text uses a virtual (invisible) cursor to offer a
* very similar behavior to \ref TextStream -- including automatic
* scrolling and column/row based positioning.
*
* \param start Coordinate of the upper left corner for the output window
* \param width Width of the output window
* \param height Height of the output window
* \param font Font used for output text (or `nullptr` for default font)
*/
GraphicsStream(const Point& start, unsigned width, unsigned height,
Font* font = nullptr);
/*! \brief Set the cursor position
*
* \param x Column in window
* \param y Row in window
*/
void setPos(int x, int y);
/*! \brief Read the current cursor position
*
* \param x Column in window
* \param y Row in window
*/
void getPos(int& x, int& y) const;
/*! \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. When the
* output is complete, the cursor will be positioned after the last
* character printed. The entire text uniformly has the color `color`
*
* If there is not enough space left at the end of the line, the output will
* be continued on the following line. As soon as the last window line is
* filled, the entire window area will be moved up one line.
* The first line disappears and the last line is blank, continuing output
* there.
*
* A line break will also occurs wherever the character `\\n` is inserted
* in the text to be output.
*
* \param str String to output
* \param length length of string
* \param color Foreground color of string
*/
void print(char* str, int length, const Color& color = LIGHT_GREY);
/*! \brief Clear window and reset cursor
*
* \param character Filling character
* \param color Foreground color
*/
void reset(char character = ' ', const Color& color = LIGHT_GREY);
/*! \brief Basic output of a (colored) character at a certain position on
* the screen.
*
* Outputs `character` at the absolute position (`x`, `y`) with the
* specified color: `x` specifies the column and `y` the row of the desired
* position, with 0 ≤ x < \ref COLUMNS and 0 ≤ `y` < \ref ROWS.
* The position (0,0) indicates the upper left corner of the window (at
* the coordinates \ref START).
*
* \param x Column for output of the character
* \param y Row for output of the character
* \param character Character to be output
* \param color Foreground color
*/
void show(int x, int y, char character, const Color& color = LIGHT_GREY);
/*! \brief Draw using the \ref Graphics device
*/
void draw();
};

131
kernel/device/keydecoder.cc Normal file
View File

@@ -0,0 +1,131 @@
#include "keydecoder.h"
#include "ps2controller.h"
// Constants used for key decoding
constexpr unsigned char BREAK_BIT = 0x80;
constexpr unsigned char PREFIX_1 = 0xe0;
constexpr 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[static_cast<size_t>(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;
}

View File

@@ -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[static_cast<size_t>(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);
};

View File

@@ -0,0 +1,169 @@
#include "ps2controller.h"
#include "../arch/apic.h"
#include "../arch/core_interrupt.h"
#include "../arch/ioapic.h"
#include "../arch/ioport.h"
#include "keydecoder.h"
namespace PS2Controller {
// I/O Ports of the PS2 Controller
/// Access status- (read) and command (write) register
static const IOPort ctrl_port(0x64);
/// Access PS/2 device [keyboard] output- (read) and input (write) buffer
static const IOPort data_port(0x60);
/* 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).
*
*
* \param value data to be sent
*/
[[maybe_unused]] static void sendData(uint8_t value) {
// First of all, we have to wait for the controller to fetch all the
// characters we have written so far.
while ((ctrl_port.inb() & INPUT_PENDING) != 0) {
} // wait for keyboard inbuffer
// Send the data byte, which gets acked (each) by the keyboard
data_port.outb(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);
IOAPIC::config(APIC::getIOAPICSlot(APIC::Device::KEYBOARD),
Core::Interrupt::Vector::KEYBOARD,
IOAPIC::TriggerMode::LEVEL);
IOAPIC::allow(APIC::getIOAPICSlot(APIC::Device::KEYBOARD));
drainBuffer();
}
bool fetch(Key& pressed) {
int control = ctrl_port.inb();
if ((control & HAS_OUTPUT) != 0) {
// Get data
uint8_t code = data_port.inb();
// Ignore mouse
if ((control & IS_MOUSE) == 0) {
// Decode key
pressed = key_decoder.decode(code);
// Return if valid
if (pressed.valid()) {
return true;
}
}
}
return false;
}
void setRepeatRate(Speed speed, Delay delay) {
// Implementation was hidden behind the `keyboard_out` tag
union {
struct {
Speed speed : 5;
Delay delay : 2;
uint8_t : 1;
} __attribute__((packed));
uint8_t value;
} rate;
rate.speed = speed;
rate.delay = delay;
sendData(KEYBOARD_SET_SPEED); // Command for the Keyboard
sendData(rate.value); // Parameter
}
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() {
while ((ctrl_port.inb() & HAS_OUTPUT) != 0) {
data_port.inb();
}
}
} // namespace PS2Controller

View File

@@ -0,0 +1,150 @@
/*! \file
* \brief \ref PS2Controller "PS/2 Controller" (Intel 8042, also known as
* Keyboard Controller)
*/
#pragma once
#include "../object/key.h"
#include "../types.h"
#include "./textstream.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).
*
*/
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.
*
*
*
* \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).
*
*/
void drainBuffer();
} // namespace PS2Controller

View File

@@ -0,0 +1,37 @@
#include "serialstream.h"
SerialStream::SerialStream(ComPort port, BaudRate baud_rate, DataBits data_bits,
StopBits stop_bits, Parity parity)
: Serial(port, baud_rate, data_bits, stop_bits, parity) {}
void SerialStream::flush() {
print(buffer, pos);
pos = 0;
}
void SerialStream::setForeground(Color c) {
*this << "\e[" << (30 + static_cast<uint32_t>(c)) << 'm';
}
void SerialStream::setBackground(Color c) {
*this << "\e[" << (40 + static_cast<uint32_t>(c)) << 'm';
}
void SerialStream::setAttribute(Attrib a) {
*this << "\e[" << static_cast<uint32_t>(a) << 'm';
}
void SerialStream::reset() { *this << "\ec" << ::flush; }
void SerialStream::setPos(int x, int y) {
*this << "\e[" << (y + 1) << ';' << (x + 1) << 'H' << ::flush;
}
void SerialStream::print(char* str, int length) {
for (int p = 0; p < length; p++) {
if (str[p] == '\n' && (p == 0 || str[p - 1] != '\r')) {
write('\r');
}
write(str[p]);
}
}

View File

@@ -0,0 +1,138 @@
/*! \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 class Attrib : uint32_t {
RESET = 0, ///< Turn off character attributes
BRIGHT = 1, ///< Bold
DIM = 2, ///< Low intensity (dimmed)
ITALIC = 3, ///< Italic
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 class Color : uint32_t {
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
*
*/
explicit SerialStream(ComPort port = Serial::ComPort::COM1,
BaudRate baud_rate = Serial::BaudRate::B115200,
DataBits data_bits = Serial::DataBits::D8,
StopBits stop_bits = Serial::StopBits::S1,
Parity parity = Serial::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.
*
*/
void flush() override;
/*! \brief Change foreground color (for subsequent output)
*
*
* \param c Color
*/
void setForeground(Color c);
/*! \brief Change background color (for subsequent output)
*
*
* \param c Color
*/
void setBackground(Color c);
/*! \brief Change text attribute (for subsequent output)
*
*
* \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.
*
*/
void reset();
/*! \brief Set the cursor position
*
* \param x Column in window
* \param y Row in window
*
*/
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);
};

View File

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

View File

@@ -0,0 +1,45 @@
/*! \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 "../arch/textwindow.h"
#include "../object/outputstream.h"
#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 : public OutputStream, public TextWindow {
// 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.
*
*
*/
// NOTE: We can only add the `override` once we inherit from `TextWindow`
// which is part of the solution.
void flush() override;
};

View File

@@ -0,0 +1 @@
exclude_files=font_.*\.h

View File

@@ -0,0 +1,44 @@
#include "font.h"
#include "../../types.h"
#include "../../utils/size.h"
#include "../../utils/string.h"
#include "font_10x18.h"
#include "font_6x10.h"
#include "font_6x11.h"
#include "font_7x14.h"
#include "font_8x16.h"
#include "font_8x8.h"
#include "font_acorn_8x8.h"
#include "font_mini_4x6.h"
#include "font_pearl_8x8.h"
#include "font_sun_12x22.h"
#include "font_sun_8x16.h"
#include "font_ter_16x32.h"
static Font fonts[] = {
Font("Standard", 6, 10, fontdata_6x10),
Font("Standard", 7, 14, fontdata_7x14),
Font("Standard", 8, 8, fontdata_8x8),
Font("Standard", 8, 16, fontdata_8x16),
Font("Standard", 10, 18, fontdata_10x18),
Font("Acorn", 8, 8, acorndata_8x8),
Font("Mini", 4, 6, fontdata_mini_4x6),
Font("Pearl", 8, 8, fontdata_pearl_8x8),
Font("Sun", 12, 22, fontdata_sun_12x22),
Font("Sun", 8, 16, fontdata_sun_8x16),
Font("Terminus", 16, 32, fontdata_ter16x32),
};
unsigned Font::number() { return ::size(fonts); }
Font* Font::get(const char* name, const unsigned width, const unsigned height) {
for (unsigned i = 0; i < number(); i++) {
if ((name == nullptr || strcmp(name, fonts[i].name) == 0) &&
(width == 0 || width == fonts[i].width) &&
(height == 0 || height == fonts[i].height)) {
return &fonts[i];
}
}
return nullptr;
}

View File

@@ -0,0 +1,73 @@
/*! \file
* \brief \ref Font "Monospaced fonts"
*/
#pragma once
#include "../../types.h"
/*! \brief Monospaced fonts
* \ingroup gfx
*
* Console fonts are extracted from the Linux kernel
* ([/lib/fonts/](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/lib/fonts)).
*/
class Font {
/*! \brief Pointer to bitmap font
*/
const unsigned char* data;
/*! \brief Size in memory of bitmap font
*/
const size_t size;
public:
/*! \brief Name of font
*/
const char* name;
/*! \brief Width of a character
*/
const unsigned width;
/*! \brief Height of a character
*/
const unsigned height;
/*! \brief Constructor for a font
*
* \param name Name of font
* \param width character width
* \param height character height
* \param data Pointer to bitmap font
*/
Font(const char* name, unsigned width, unsigned height,
const unsigned char* data)
: data(data),
size((((width + (8 >> 1)) / 8) * height)),
name(name),
width(width),
height(height) {}
/*! \brief Get bitmap address for a given character
*
* \param c character (ASCII)
* \return Pointer to bitmap of character
*/
const void* symbol(unsigned char c) const { return &data[size * c]; }
/*! \brief Find font
*
* \param name Name of font (or `nullptr` for any)
* \param width Width of a character (or `0` for any)
* \param height Height of a character (or `0` for any)
* \return Pointer to font or `nullptr` if no matching font was found
*/
static Font* get(const char* name = nullptr, unsigned width = 0,
unsigned height = 0);
/*! \brief Get the number of available fonts
* \return number of fonts
*/
static unsigned number();
};

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

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