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

144
arch/acpi.cc Normal file
View File

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

270
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

150
arch/apic.cc Normal file
View File

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

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

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

21
arch/cga.cc Normal file
View File

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

138
arch/cga.h Normal file
View File

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

61
arch/cmos.cc Normal file
View File

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

46
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 Register {
REG_SECOND = 0x0, ///< RTC
REG_ALARM_SECOND = 0x1, ///< RTC
REG_MINUTE = 0x2, ///< RTC
REG_ALARM_MINUTE = 0x3, ///< RTC
REG_HOUR = 0x4, ///< RTC
REG_ALARM_HOUR = 0x5, ///< RTC
REG_WEEKDAY = 0x6, ///< RTC
REG_DAYOFMONTH = 0x7, ///< RTC
REG_MONTH = 0x8, ///< RTC
REG_YEAR = 0x9, ///< RTC
REG_STATUS_A = 0xa, ///< RTC
REG_STATUS_B = 0xb, ///< RTC
REG_STATUS_C = 0xc, ///< RTC
REG_STATUS_D = 0xd, ///< RTC
REG_STATUS_DIAGNOSE = 0xe,
REG_STATUS_SHUTDOWN = 0xf
};
uint8_t read(enum Register reg);
void write(enum Register reg, uint8_t value);
namespace NMI {
void enable();
void disable();
bool isEnabled();
} // namespace NMI
} // namespace CMOS

20
arch/context.asm Normal file
View File

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

9
arch/context.cc Normal file
View File

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

118
arch/context.h Normal file
View File

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

73
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

117
arch/core.h Normal file
View File

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

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

141
arch/core_interrupt.h Normal file
View File

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

103
arch/core_msr.h Normal file
View File

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

201
arch/cpuid.h Normal file
View File

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

36
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_32BIT),
// Global data segment von 0-4GB
SegmentDescriptor::Segment(0, UINT32_MAX, false, 0, SIZE_32BIT),
};
extern "C" constexpr Pointer gdt_protected_mode_pointer(protected_mode);
// The static 64-bit Global Descriptor Table (GDT)
// \see [ISDMv3 3.2.4 Segmentation in IA-32e
// Mode](intel_manual_vol3.pdf#page=91)
alignas(16) constinit SegmentDescriptor long_mode[] = {
// Null descriptor
SegmentDescriptor::Null(),
// Global code segment
SegmentDescriptor::Segment64(true, 0),
// Global data segment
SegmentDescriptor::Segment64(false, 0),
};
extern "C" constexpr Pointer gdt_long_mode_pointer(long_mode);
} // namespace GDT

199
arch/gdt.h Normal file
View File

@@ -0,0 +1,199 @@
/*! \file
* \brief The \ref GDT "Global Descriptor Table (GDT)".
*/
#pragma once
#include "../types.h"
/*! \brief Abstracts the GDT that, primarily, contains descriptors to memory
* segments.
* \ingroup memory
*
* The GDT is a table that primarily contains segment descriptors. Segment
* descriptors has a size of 8 Bytes and contains the size, position, access
* rights, and purpose of such a segment. Unlike the LDT, the GDT is shared
* between all processes and may contain TSS and LDT descriptors. For the
* kernel, the first entry is required to be a null descriptor and the code and
* data segments. To support user-mode processes, additional TSS, code, and data
* segments for ring 3 must be added.
*
* The base address and size of the GDT are written to the GDTR register during
* boot (via. `lgdt`).
*
* \see [ISDMv3, 2.4.1; Global Descriptor Table Register
* (GDTR)](intel_manual_vol3.pdf#page=72)
* \see [ISDMv3, 3.5.1; Segment
* Descriptor Tables](intel_manual_vol3.pdf#page=99)
*/
namespace GDT {
enum Segments {
SEGMENT_NULL = 0,
SEGMENT_KERNEL_CODE,
SEGMENT_KERNEL_DATA,
};
/*! \brief Unit of the segment limit
*/
enum Granularity {
GRANULARITY_BYTES = 0, ///< Segment limit in Bytes
GRANULARITY_4KBLOCK = 1 ///< Segment limit in blocks of 4 Kilobytes
};
/*! \brief Descriptor type */
enum DescriptorType {
DESCRIPTOR_SYSTEM = 0, ///< entry is a system segment
DESCRIPTOR_CODEDATA = 1, ///< entry is a code/data segment
};
/*! \brief Address width
*/
enum Size {
SIZE_16BIT = 0, ///< 16-bit (D/B = 0, L = 0)
SIZE_32BIT = 2, ///< 32-bit (D/B = 1, L = 0)
SIZE_64BIT = 1, ///< 64-bit (D/B = 0, L = 1)
};
/*! \brief Type flags for used descriptor types
*/
enum TypeFlags {
TYPE_DATA_RW = 0b0010ull, ///< Data rw, not expanding down
TYPE_CODE_RX = 0b1010ull, ///< Code rx, non-conforming
};
/*! \brief Describes the structure of segment descriptors
*
* A data structure that contains size, position, access rights, and purpose of
* any segment. Segment descriptors are used in both the GDT, as well as in
* LDTs.
*
* \see [ISDMv3, 3.4.5; Segment Descriptors](intel_manual_vol3.pdf#page=95)
* \see [AAPMv2, 4.7 Legacy Segment Descriptors](amd64_manual_vol2.pdf#page=132)
* \see [AAPMv2, 4.8 Long-Mode Segment
* Descriptors](amd64_manual_vol2.pdf#page=140)
*/
union SegmentDescriptor {
// Universally valid values (shared across all segment types)
struct {
uint64_t limit_low : 16; ///< Least-significant bits of segment size
///< (influenced by granularity!)
uint64_t base_low : 24; ///< Least-significant bits of base address
uint64_t
type : 4; ///< Meaning of those 4 bits depends on descriptor_type below
DescriptorType descriptor_type : 1; ///< Descriptor type (influences the
///< meaning of the 3 bits above)
uint64_t privilege_level : 2; ///< Ring for this segment
bool present : 1; ///< Entry is valid iff set to `true`
uint64_t limit_high : 4; ///< Most-significant bits of segment size
bool available : 1; ///< Bit which can be used for other purposes (in
///< software)
uint64_t custom : 2; ///< Meaning of those 2 bits relate to descriptor_type
///< and type
Granularity
granularity : 1; ///< Unit used as granularity for the segment limit
uint64_t base_high : 8; ///< most-significant bits of base address
} __attribute__((packed));
uint64_t value; ///!< Merged value
/*! \brief Explicitly constructs a null descriptor.
*/
consteval static SegmentDescriptor Null() {
return SegmentDescriptor{
.value = 0,
};
}
/*! \brief Constructs a code/data segment descriptor.
* \param base Base Address of segment
* \param limit Size of segment
* \param code Code or data segment
* \param ring Privilege level
* \param size Address width
*/
consteval static SegmentDescriptor Segment(uintptr_t base, uint32_t limit,
bool code, uint64_t ring,
Size size) {
return SegmentDescriptor{
.limit_low = limit >> (limit > 0xFFFFF ? 12 : 0) & 0xFFFF,
.base_low = base & 0xFFFFFF,
.type = code ? TYPE_CODE_RX : TYPE_DATA_RW,
.descriptor_type = DESCRIPTOR_CODEDATA,
.privilege_level = ring,
.present = true,
.limit_high = (limit > 0xFFFFF ? (limit >> 28) : (limit >> 16)) & 0xF,
.available = false,
.custom = size,
.granularity =
limit > 0xFFFFF ? GRANULARITY_4KBLOCK : GRANULARITY_BYTES,
.base_high = (base >> 24) & 0xFF,
};
}
/*! \brief Constructs a 64bit code/data segment descriptor.
* \param code Code or data segment
* \param ring Privilege level
*/
consteval static SegmentDescriptor Segment64(bool code, int ring) {
return SegmentDescriptor::Segment(0, 0, code, ring, SIZE_64BIT);
}
} __attribute__((packed));
static_assert(sizeof(SegmentDescriptor) == 8,
"GDT::SegmentDescriptor has wrong size");
/*! \brief Structure that describes a GDT Pointer (aka GDT Descriptor)
*
* It contains both the length (in bytes) of the GDT (minus 1 byte) and the
* pointer to the GDT. The pointer to the GDT can be loaded using the
* instruction `lgdt`.
*
* \note As Intel uses little endian for representing multi-byte values, the
* GDT::Pointer structure can be used for 16, 32, and 64 bit descriptor tables:
* \verbatim
* | 16 bit | 16 bit | 16 bit | 16 bit | 16 bit |
* +--------+---------------------------------------+
* Pointer | limit | base (up to 64 bit) |
* +--------+---------+---------+---------+---------+
* | used for 16 bit | ignored... |
* | used for 32 bit | ignored... |
* | used for 64 bit |
* \endverbatim
*
* \see [ISDMv3, Figure 2-6; Memory Management
* Registers](intel_manual_vol3.pdf#page=72)
*/
struct Pointer {
uint16_t limit; //!< GDT size in bytes (minus 1 byte)
void* base; //!< GDT base address
/*! \brief Constructor (automatic length)
* \param desc Array of GDT segment descriptors -- must be defined in the same
* module!
*/
template <typename T, size_t LEN>
explicit constexpr Pointer(const T (&desc)[LEN])
: limit(LEN * sizeof(T) - 1), base(const_cast<T*>(desc)) {}
/*! \brief Constructor
* \param desc Address of the GDT segment descriptors
* \param len Number of entries
*/
consteval Pointer(void* desc, size_t len)
: limit(len * sizeof(SegmentDescriptor) - 1), base(desc) {}
/*! \brief Set an address
* \note On change, `lgdt` must be executed again
* \param desc Address of the GDT segment descriptors
* \param len Number of entries
*/
constexpr void set(void* desc, size_t len) {
limit = len * sizeof(SegmentDescriptor) - 1;
base = desc;
}
} __attribute__((packed));
static_assert(sizeof(Pointer) == 10, "GDT::Pointer has wrong size");
} // namespace GDT

35
arch/idt.cc Normal file
View File

@@ -0,0 +1,35 @@
#include "idt.h"
#include "core_interrupt.h"
namespace IDT {
// Interrupt Descriptor Table, 8 Byte aligned
constinit struct InterruptDescriptor idt[256] = {};
// Struct used for loading (the address of) the Interrupt Descriptor Table into
// the IDT-Register
struct Register {
uint16_t limit; // Address of the last valid byte (relative to base)
struct InterruptDescriptor* base;
explicit Register(uint8_t max = 255) {
limit = (max + static_cast<uint16_t>(1)) * sizeof(InterruptDescriptor) - 1;
base = idt;
}
} __attribute__((packed));
static_assert(sizeof(InterruptDescriptor) == 16,
"IDT::InterruptDescriptor has wrong size");
static_assert(sizeof(Register) == 10, "IDT::Register has wrong size");
static_assert(alignof(decltype(idt)) % 8 == 0, "IDT must be 8 byte aligned!");
void load() {
// Create structure required for writing to idtr and load via lidt
Register idtr(Core::Interrupt::VECTORS - 1);
asm volatile("lidt %0\n\t" ::"m"(idtr));
}
void set(Core::Interrupt::Vector vector, InterruptDescriptor descriptor) {
idt[(uint8_t)vector] = descriptor;
}
} // namespace IDT

210
arch/idt.h Normal file
View File

@@ -0,0 +1,210 @@
/*! \file
* \brief \ref IDT "Interrupt Descriptor Table (IDT)" containing the entry
* points for interrupt handling.
*/
#pragma once
#include "../types.h"
#include "core_interrupt.h"
/*! \brief "Interrupt Descriptor Table (IDT)
* \ingroup interrupts
*
* \see [ISDMv3 6.14 Exception and Interrupt Handling in 64-bit
* Mode](intel_manual_vol3.pdf#page=200)
*/
/*! \brief Preserved interrupt context
*
* After an interrupt was triggered, the core first saves the basic context
* (current code- & stack segment, instruction & stack pointer and the status
* flags register) and looks up the handling function for the vector using the
* \ref IDT. No other registers are saved or restored automatically. It is the
* handlers (our) job to save and restore all modified registers. However, most
* handlers in StuBS are implemented directly in C++ utilizing the `interrupt`
* attribute: The compiler treats all modified registers as callee-saved, which
* saves us a lot of work, but prevents us from knowing the exact contents of
* each regiser (we don't know if/when the compiler modified it). `interrupt`
* functions receive up to two parameters: A pointer to this /ref
* InterruptContext and, depending on the interrupt, an error code, which is
* also pushed onto the stack by the CPU. Contrary to "normal" functions, the
* compiler will return using the `iret` instruction.
*/
struct InterruptContext {
// Context saved by CPU
// uintptr_t error_code; ///< Error Code
uintptr_t ip; ///< Instruction Pointer (at interrupt)
uintptr_t cs : 16; ///< Code segment (in case of a ring switch it is the
///< segment of the user mode)
uintptr_t : 0; ///< Alignment (due to 16 bit code segment)
uintptr_t flags; ///< Status flags register
uintptr_t sp; ///< Stack pointer (at interrupt)
uintptr_t ss : 16; ///< Stack segment (in case of a ring switch it is the
///< segment of the user mode)
uintptr_t : 0; ///< Alignment (due to 16 bit stack segment)
} __attribute__((packed));
static_assert(sizeof(InterruptContext) == 5 * 8,
"InterruptContext has wrong size");
namespace IDT {
/*! \brief Gate types
*
* \see [ISDMv3 3.5 System Descriptor Types](intel_manual_vol3.pdf#page=99)
*/
enum Gate {
GATE_INT = 0x6, ///< Interrupt Gate (CPU disables interrupts unpon entry)
GATE_TRAP = 0x7, ///< Trap Gate (interrupts remain enabled unpon entry)
};
/*! \brief Segment type
*
* \see [ISDMv3 3.5 System Descriptor Types](intel_manual_vol3.pdf#page=99)
*/
enum GateSize {
GATE_SIZE_16 = 0, ///< 16 bit
GATE_SIZE_32 = 1, ///< 32 bit
GATE_SIZE_64 = 1, ///< 64 bit
};
/*! \brief Descriptor Privilege Level
*/
enum DPL {
DPL_KERNEL = 0, ///< Ring 0 / Kernel mode
/* DPLs 1 and 2 are unused */
DPL_USER = 3, ///< Ring 3 / User mode
};
/*! \brief Interrupt handler that returns after execution (trap/fault).
*/
using ReturningHandler = void (*)(InterruptContext*);
/*! \brief Interrupt handler that returns after execution (trap/fault) and
* receives an error code.
*/
using ReturningHandlerWithError = void (*)(InterruptContext*, uint64_t);
/*! \brief Interrupt handler that does **not** return after execution (abort).
*/
using DivergingHandler = void (*)(InterruptContext*);
/*! \brief Interrupt handler that does **not** return after execution (abort)
* and receives an error code.
*/
using DivergingHandlerWithError = void (*)(InterruptContext*, uint64_t);
/*! \brief Interrupt Descriptor stored in the Interrupt-Descriptor Table (IDT)
*/
struct alignas(8) InterruptDescriptor {
uint16_t address_low; ///< lower interrupt function offset
uint16_t selector; ///< code segment selector in GDT or LDT
union {
struct {
uint8_t ist : 3; ///< Interrupt Stack Index
uint8_t : 5; ///< unused, has to be 0
Gate type : 3; ///< gate type
GateSize size : 1; ///< gate size
uint8_t : 1; ///< unused, has to be 0
DPL dpl : 2; ///< descriptor privilege level
bool present : 1; ///< present: 1 for interrupts
} __attribute__((packed));
uint16_t flags;
};
uint64_t address_high : 48; ///< higher interrupt function offset
uint64_t : 0; ///< fill until aligned with 64 bit
/*! \brief Create a non-present interrupt descriptor.
*/
InterruptDescriptor() = default;
/*! \brief Create an interrupt descriptor.
*
*
* \param handler Entry point for interrupt handling
* \param ist Stack index from the \ref TaskStateSegment for the interrupt
* handler. Set to 0 to use current stack.
* \param dpl Permissions required for enter this interrupt handler
* (kernel- or user space)
*/
InterruptDescriptor(uintptr_t handler, uint8_t ist, DPL dpl)
: address_low(handler & 0xffff),
selector(8), // XXX: This should come from `Segments`
ist(ist),
type(GATE_INT),
size(GATE_SIZE_64),
dpl(dpl),
present(true),
address_high((handler >> 16) & 0xffffffffffff) {}
/*! \brief Create an interrupt descriptor for a handler that does return
* (trap/fault).
*
* \param handler Entry point for interrupt handling
* \param ist Stack index from the \ref TaskStateSegment for the interrupt
* handler. Set to 0 to use current stack.
* \param dpl Permissions required for enter this interrupt handler
* (kernel- or user space)
*/
static InterruptDescriptor Returning(ReturningHandler handler,
uint8_t ist = 0, DPL dpl = DPL_KERNEL) {
return {reinterpret_cast<uintptr_t>(handler), ist, dpl};
}
/*! \brief Create an interrupt descriptor for a handler that does return
* (traps/fault) and receives an error code.
*
*
* \param handler Entry point for interrupt handling
* \param ist Stack index from the \ref TaskStateSegment for the interrupt
* handler. Set to 0 to use current stack.
* \param dpl Permissions required for enter this interrupt handler
* (kernel- or user space)
*/
static InterruptDescriptor ReturningWithError(
ReturningHandlerWithError handler, uint8_t ist = 0,
DPL dpl = DPL_KERNEL) {
return {reinterpret_cast<uintptr_t>(handler), ist, dpl};
}
/*! \brief Create an interrupt descriptor for a handler that does **not**
* return (abort).
*
*
* \param handler Entry point for interrupt handling
* \param ist Stack index from the \ref TaskStateSegment for the interrupt
* handler. Set to 0 to use current stack.
* \param dpl Permissions required for enter this interrupt handler
* (kernel- or user space)
*/
static InterruptDescriptor Diverging(DivergingHandler handler,
uint8_t ist = 0, DPL dpl = DPL_KERNEL) {
return {reinterpret_cast<uintptr_t>(handler), ist, dpl};
}
/*! \brief Create an interrupt descriptor for a handler that does **not**
* return (abort) and receives an error code.
*
*
* \param handler Entry point for interrupt handling
* \param ist Stack index from the \ref TaskStateSegment for the interrupt
* handler. Set to 0 to use current stack.
* \param dpl Permissions required for enter this interrupt handler
* (kernel- or user space)
*/
static InterruptDescriptor DivergingWithError(
DivergingHandlerWithError handler, uint8_t ist = 0,
DPL dpl = DPL_KERNEL) {
return {reinterpret_cast<uintptr_t>(handler), ist, dpl};
}
} __attribute__((packed));
static_assert(sizeof(InterruptDescriptor) == 16,
"IDT::InterruptDescriptor has wrong size");
/*! \brief Load the IDT's address and size into the IDT-Register via `idtr`.
*/
void load();
/*! \brief Set the idt entry for the given interrupt vector.
*/
void set(Core::Interrupt::Vector vector, InterruptDescriptor descriptor);
} // namespace IDT

44
arch/ioapic.cc Normal file
View File

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

82
arch/ioapic.h Normal file
View File

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

227
arch/ioapic_registers.h Normal file
View File

@@ -0,0 +1,227 @@
/*! \file
* \brief Helper structures for interacting with the \ref IOAPIC "I/O APIC".
*/
#pragma once
#include "../types.h"
namespace IOAPIC {
typedef uint32_t Index;
typedef uint32_t Register;
extern volatile Index *IOREGSEL_REG;
extern volatile Register *IOWIN_REG;
/*! \brief I/O APIC Identification
*
* The IOAPICID register is register number 0x0. The I/O APIC's ID will be read
* from the system configuration tables (provided by the BIOS) during boot. The
* number can be queried by calling \ref APIC::getIOAPICID(). During
* initialization, this number must be written to the IOAPICID register.
*
* \see [IO-APIC manual](intel_ioapic.pdf#page=9), page 9
*/
union Identification {
struct {
uint32_t : 24, ///< Reserved, do not modify
id : 4, ///< I/O APIC Identification
: 4; ///< Reserved, do not modify
};
Register value;
explicit Identification(Register value) : value(value) {}
} __attribute__((packed));
static_assert(sizeof(Identification) == 4,
"IOAPIC Identification has wrong size");
/*! \brief Delivery mode specifies the type of interrupt sent to the CPU. */
enum DeliveryMode {
FIXED = 0, ///< "ordinary" interrupt; send to ALL cores listed in the
///< destination bit mask
LOWEST_PRIORITY = 1, ///< "ordinary" interrupt; send to the lowest priority
///< core from destination mask
SMI = 2, ///< System Management Interrupt; vector number required to be 0
// Reserved
NMI = 4, ///< Non-Maskable Interrupt, vector number ignored, only edge
///< triggered
INIT = 5, ///< Initialization interrupt (always treated as edge triggered)
// Reserved
EXTERN_INT = 7 ///< external interrupt (only edge triggered)
};
/*! \brief Way of interpreting the value written to the destination field. */
enum DestinationMode {
PHYSICAL = 0, ///< Destination contains the physical destination APIC ID
LOGICAL = 1 ///< Destination contains a mask of logical APIC IDs
};
/*! \brief Interrupt polarity for the redirection-table entry */
enum Polarity {
HIGH = 0, ///< active high
LOW = 1 ///< active low
};
/*! \brief Trigger mode */
enum TriggerMode {
EDGE = 0, ///< edge triggered
LEVEL = 1 ///< level triggered
};
/*! \brief Interrupt state */
enum DeliveryStatus {
IDLE = 0, ///< No activity for this interrupt
SEND_PENDING =
1 ///< Interrupt will be sent as soon as the bus / LAPIC is ready
};
/*! \brief Interrupt masking */
enum InterruptMask {
UNMASKED = 0, ///< Redirection-table entry is active (non-masked)
MASKED = 1 ///< Redirection-table entry is inactive (masked)
};
/*! \brief Entry in the redirection table.
*
* The redirection table begins with I/O APIC register `0x10` and ends at
* `0x3f`.
*
* Each entry has a size of 64 bit, equaling two I/O APIC registers.
* For instance, entry 0 is stored in registers `0x10` and `0x11`, in which the
* low-order 32 bit (equals \ref value_low) and high-order 32 bit (equals \ref
* value_high) need to be stored.
*
* The union defined below provides an overlay allowing convenient modification
* of individual bits, while the 32-bit values \ref value_low and \ref
* value_high can be used for writing to the I/O APIC registers.
*
* \note [Type punning](https://en.wikipedia.org/wiki/Type_punning#Use_of_union)
* is indeed undefined behavior in C++. However, *gcc* explicitly allows
* this construct as a [language extension](https://gcc.gnu.org/bugs/#nonbugs).
* Some compilers ([other than
* gcc](https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html#Type%2Dpunning)
* might allow this feature only by disabling strict aliasing
* (`-fno-strict-aliasing`). In \StuBS we use this feature extensively due to
* the improved code readability.
*
* \see [IO-APIC manual](intel_ioapic.pdf#page=11), page 11-13
*/
union RedirectionTableEntry {
// @cond ANONYMOUS_STRUCT
struct {
// @endcond
/*! \brief Interrupt vector in the \ref IDT "Interrupt Descriptor Table
* (IDT)" will be activated when the corresponding external interrupt
* triggers.
*/
uint64_t vector : 8;
/*! \brief The delivery mode denotes the way the interrupts will be
* delivered to the local CPU cores, respectively to their local APICs.
*
* For StuBS, we use \ref LOWEST_PRIORITY, as all CPU cores have the same
* priority and we want to distribute interrupts evenly among them.
* It, however, is not guaranteed that this method of load balancing will
* work on every system.
*/
DeliveryMode delivery_mode : 3;
/*! \brief The destination mode defines how the value stored in \ref
* destination will be interpreted.
*
* For StuBS, we use \ref LOGICAL
*/
DestinationMode destination_mode : 1;
/*! \brief Delivery status holds the current status of interrupt delivery.
*
* \note This field is read only; write accesses to this field will be
* ignored.
*/
DeliveryStatus delivery_status : 1;
/*! \brief The polarity denotes when an interrupt should be issued.
*
* For StuBS, we usually use \ref HIGH (i.e., when the interrupt line is,
* logically, `1`).
*/
Polarity polarity : 1;
/*! \brief The remote IRR bit indicates whether the local APIC(s) accept the
* level interrupt.
*
* Once the LAPIC sends an \ref LAPIC::endOfInterrupt "End Of Interrupt
* (EOI)", this bit is reset to `0`.
*
* \note This field is read only and is only meaningful for level-triggered
* interrupts.
*/
uint64_t remote_irr : 1;
/*! \brief The trigger mode states whether the interrupt signaling is level
* or edge triggered.
*
* StuBS uses \ref EDGE for the Timer, the Keybaord and (optional) serial
* interface need \ref LEVEL
*/
TriggerMode trigger_mode : 1;
/*! \brief Mask or unmask interrupts for a particular, external source.
*
* The interrupt mask denotes whether interrupts should be
* accepted/unmasked (value \ref UNMASKED) or ignored/masked (value \ref
* MASKED).
*/
InterruptMask interrupt_mask : 1;
/*! \brief Reserved, do not modify. */
uint64_t : 39;
/*! \brief Interrupt destination.
*
* The meaning of destination depends on the destination mode:
* For the logical destination mode, destination holds a bit mask made up
* of the cores that are candidates for receiving the interrupt. In the
* single-core case, this value is `1`, in the multi-core case, the `n`
* low-order bits needs to be set (with `n` being the number of CPU cores,
* see \ref Core::count() ). Setting the `n` low-order bits marks all
* available cores as candidates for receiving interrupts and thereby
* balancing the number of interrupts between the cores.
*
* \note This form of load balancing depends on the hardware's behavior and
* may not work on all systems in the same fashion. Most notably, in QEMU
* all interrupts are sent to the BSP (core 0).
*/
uint64_t destination : 8;
// @cond ANONYMOUS_STRUCT
} __attribute__((packed));
// @endcond
// @cond ANONYMOUS_STRUCT
struct {
// @endcond
Register value_low; ///< Low-order 32 bits (for the register with the
///< smaller index)
Register value_high; ///< High-order 32 bits (for the register with the
///< higher index)
// @cond ANONYMOUS_STRUCT
} __attribute__((packed));
// @endcond
/*! \brief Constructor for an redirection-table entry
*
* Every entry in the redirection table represents an external source of
* interrupts and has a size of 64 bits. Due to the I/O APIC registers being
* only 32 bits wide, the constructor takes two 32 bit values.
*
* \param value_low First, low-order 32 bit value
* \param value_high Second, high-order 32 bit value
*/
RedirectionTableEntry(Register value_low, Register value_high)
: value_low(value_low), value_high(value_high) {}
};
static_assert(sizeof(RedirectionTableEntry) == 8,
"IOAPIC::RedirectionTableEntry has wrong size");
} // namespace IOAPIC

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

190
arch/lapic.cc Normal file
View File

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

199
arch/lapic.h Normal file
View File

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

244
arch/lapic_ipi.cc Normal file
View File

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

54
arch/lapic_registers.h Normal file
View File

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

91
arch/lapic_timer.cc Normal file
View File

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

63
arch/pic.cc Normal file
View File

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

18
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

225
arch/pit.cc Normal file
View File

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

80
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

41
arch/serial.cc Normal file
View File

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

205
arch/serial.h Normal file
View File

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

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

15
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

41
arch/textwindow.cc Normal file
View File

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

134
arch/textwindow.h Normal file
View File

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