Handout
commit
5a2e32aaeb
@ -0,0 +1,2 @@
|
||||
.build*
|
||||
/build*
|
||||
@ -0,0 +1,16 @@
|
||||
# StuBS Coding Style Checker
|
||||
#
|
||||
# Wir orientieren uns grob an den Google C++ Style Guide ( http://google.github.io/styleguide/cppguide.html )
|
||||
# mit primär folgenden Änderungen/Anpassungen:
|
||||
#
|
||||
# - Tabs statt Leerzeichen. Spart Bytes ;)
|
||||
# - Zeilenlänge ist 120
|
||||
# - Keine Angaben zum Copyright
|
||||
# - Aufgrund des Aufgabenbuildsystems sind neue / leere Zeilen leider nicht immer vermeidbar
|
||||
#
|
||||
# Zum Prüfen empfiehlt sich beispielsweise das Pythonscript CPPLINT.py ( https://github.com/cpplint/cpplint )
|
||||
# welches mit dieser Konfigurationsdatei arbeiten kann.
|
||||
#
|
||||
set noparent
|
||||
filter=-whitespace/tab,-legal/copyright,-runtime/int,-runtime/threadsafe_fn,-readability/todo,-build/include_subdir,-runtime/references,-build/include_what_you_use,-whitespace/blank_line,-build/include,-whitespace/end_of_line,-whitespace/indent
|
||||
linelength=120
|
||||
@ -0,0 +1,13 @@
|
||||
Copyright 1998-2002 Institut für Verteilte Systeme (IVS), Otto-von-Guericke-Universität Magdeburg
|
||||
Copyright 2002-2019 Lehrstuhl für Informatik 4, Friedrich-Alexander-Universität Erlangen-Nürnberg
|
||||
Copyright 2017- System- und Rechnerarchitektur (SRA), Leibniz Universität Hannover
|
||||
Copyright 2021-2023 Operating System Group (OSG), Technische Universität Hamburg
|
||||
Copyright 2024- Verlässliche Systemsoftware (VSS), Technische Universität Braunschweig
|
||||
|
||||
Diese Vorlage dient als Grundlage für Lehrveranstaltungen und darf nicht ohne vorherige, schriftliche Erlaubnis der Urheberrechtsinhaber veröffentlicht oder weitergegeben werden.
|
||||
Es ist erlaubt und wünschenswert, diese Vorlage als Inspiration für eigene Projekte zu verwenden, es wird allerdings erbeten, dass die Vorgabe nicht mit deiner Lösung veröffentlicht wird.
|
||||
Wir, als Lehrende, möchten alle teilnehmenden Studierenden dazu ermutigen eine eigene Lösung zu erstellen; eine veröffentlichte Lösung ist ein Anreiz zum Abschreiben, den wir gerne vermeiden möchten.
|
||||
|
||||
This skeleton is provided as a foundation for educational purposes and therefore MUST NOT BE DISTRIBUTED OR PUBLISHED without prior, written consent of the copyright holders.
|
||||
You are free to use this skeleton as inspiration for your projects, but, please, do not publish it along with your solution.
|
||||
We, as lecturers, want to encourage every participating student to write a solution themself; a public solution is an allurement to copying we want to avoid.
|
||||
@ -0,0 +1,62 @@
|
||||
# Kernel Makefile
|
||||
# try `make help` for more information
|
||||
echo=$(shell which echo) -e
|
||||
|
||||
# Default target
|
||||
.DEFAULT_GOAL = all
|
||||
|
||||
# Path to the files for the initial ramdisk (for Assignment 7)
|
||||
INITRD_DIR ?= initrd/
|
||||
INITRD_TOOL ?= fs/tool/fstool
|
||||
INITRD_DEP =
|
||||
# 1MB free space
|
||||
INITRD_FREE ?= 1048576
|
||||
|
||||
# Kernel source files
|
||||
LINKER_SCRIPT = compiler/sections.ld
|
||||
CC_SOURCES = $(shell find * -name "*.cc" -a ! -name '.*' -a ! -path 'test*' -a ! -path 'fs/tool/*' -a ! -path 'assets/*' -a ! -path 'tools/*')
|
||||
ASM_SOURCES = $(shell find * -name "*.asm" -a ! -name '.*')
|
||||
|
||||
# Target files
|
||||
KERNEL = $(BUILDDIR)/system
|
||||
KERNEL64 = $(KERNEL)64
|
||||
ISOFILE = $(BUILDDIR)/stubs.iso
|
||||
KERNEL_LINK = $(ROOTBUILDDIR)/system.img
|
||||
|
||||
# Include global variables and standard recipes
|
||||
include tools/common.mk
|
||||
|
||||
# Initial Ramdisk
|
||||
ifneq ($(wildcard $(INITRD_DIR)*),)
|
||||
INITRD = $(BUILDDIR)/initrd.img
|
||||
INITRD_DEP += $(shell find $(INITRD_DIR) -type f )
|
||||
# Additional dependency for kernel
|
||||
$(KERNEL): $(INITRD)
|
||||
endif
|
||||
|
||||
all: $(KERNEL)
|
||||
|
||||
# Linking the system image
|
||||
# We use the C++ compiler (which calls the actual linker)
|
||||
$(KERNEL64): $(ASM_OBJECTS) $(CC_OBJECTS) $(LINKER_SCRIPT) $(MAKEFILE_LIST)
|
||||
@echo "LD $@"
|
||||
@mkdir -p $(@D)
|
||||
$(VERBOSE) $(CXX) $(CXXFLAGS) -Wl,-T $(LINKER_SCRIPT) -o $@ $(LDFLAGS) $(ASM_OBJECTS) $(CC_OBJECTS)
|
||||
@echo "LN $(KERNEL_LINK)"
|
||||
$(VERBOSE) ln -sf $(@:$(ROOTBUILDDIR)/%=%) "$(KERNEL_LINK)"
|
||||
|
||||
# The kernel must be a 32bit elf for multiboot compliance
|
||||
$(KERNEL): $(KERNEL64)
|
||||
$(VERBOSE) $(OBJCOPY) -I elf64-x86-64 -O elf32-i386 $< $@
|
||||
|
||||
# Tool for editing a Minix v3 file system image (Assignment 7)
|
||||
$(INITRD_TOOL): $(shell test -d $(dir $(INITRD_TOOL)) && find $(dir $(INITRD_TOOL)) -name "*.cc" -or -name '*.h')
|
||||
@echo "Make $@"
|
||||
@make -C $(dir $(INITRD_TOOL))
|
||||
|
||||
# Initial Ramdisk with Minix v3 file system
|
||||
$(INITRD): $(INITRD_TOOL) $(INITRD_DEP)
|
||||
@echo "INITRD $@"
|
||||
@dd if=/dev/zero of=$@ bs=$(shell du -s $(INITRD_DIR) | cut -f1 | xargs expr $(INITRD_FREE) + ) count=1
|
||||
@mkfs.minix -3 $@ # optional --inodes <number>
|
||||
@./$(INITRD_TOOL) put "$(INITRD_DIR)" $@
|
||||
@ -0,0 +1,42 @@
|
||||
MPStuBS - Multiprozessor Studenten Betriebssystem
|
||||
=================================================
|
||||
|
||||
Coding Guidelines
|
||||
-----------------
|
||||
|
||||
Similar to [Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html) but with following exceptions:
|
||||
- No license boilerplate
|
||||
- *Tabs* instead of *Spaces*
|
||||
- Line length of 120 characters
|
||||
- `#pragma once` instead of `#include` guards
|
||||
|
||||
The code should be *self-documenting*, don't state the obvious!
|
||||
However, this does not make comments superfluous:
|
||||
Since good naming is sometimes not enough, more advanced parts need to be documented,
|
||||
so any operating system developer should be able to easily understand your code.
|
||||
|
||||
### Naming Convention
|
||||
|
||||
- **Variables**: lowercase with underscore
|
||||
|
||||
char* variable_name;
|
||||
|
||||
- **Constants** (and **enum** values): uppercase with underscore
|
||||
|
||||
const int CONST_VALUE = 42;
|
||||
|
||||
- **Type Names** (`class`/`struct`/`namespace`/`enum`): Capital letter, camel case
|
||||
|
||||
class SomeClassName;
|
||||
|
||||
- **Methods/Functions** (C++): start with lowercase letter, then camel case
|
||||
|
||||
void someFunctionName();
|
||||
|
||||
- **extern "C" Functions**: lowercase with underscore (like variables).
|
||||
|
||||
void interrupt_handler(int vector);
|
||||
|
||||
- **File Names**: lowercase, main type name, underscores only if is a sub type
|
||||
|
||||
folder/classname.cc
|
||||
@ -0,0 +1,144 @@
|
||||
#include "acpi.h"
|
||||
|
||||
#include "../debug/output.h"
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Warray-bounds"
|
||||
|
||||
namespace ACPI {
|
||||
|
||||
static RSDP *rsdp = 0;
|
||||
static RSDT *rsdt = 0;
|
||||
static XSDT *xsdt = 0;
|
||||
|
||||
const char *RSDP_SIGNATURE = "RSD PTR ";
|
||||
|
||||
static int checksum(const void *pos, unsigned len) {
|
||||
const uint8_t *mem = reinterpret_cast<const uint8_t *>(pos);
|
||||
uint8_t sum = 0;
|
||||
for (unsigned i = 0; i < len; i++) {
|
||||
sum += mem[i];
|
||||
}
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
static const RSDP *findRSDP(const void *pos, unsigned len) {
|
||||
/* since the RSDP is 16-Byte aligned, we only need to check
|
||||
every second 64bit memory block */
|
||||
for (unsigned block = 0; block < len / 8; block += 2) {
|
||||
const uint64_t *mem = reinterpret_cast<const uint64_t *>(pos) + block;
|
||||
if (*mem == *reinterpret_cast<const uint64_t *>(RSDP_SIGNATURE)) {
|
||||
const RSDP *rsdp = reinterpret_cast<const RSDP *>(mem);
|
||||
/* ACPI Specification Revision 4.0a: 5.2.5.3*/
|
||||
if ((rsdp->revision == 0 && checksum(mem, 20) == 0) ||
|
||||
(rsdp->length > 20 && checksum(mem, rsdp->length) == 0)) {
|
||||
return rsdp;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool init() {
|
||||
/* ACPI Specification Revision 4.0a:
|
||||
* 5.2.5.1 Finding the RSDP on IA-PC Systems
|
||||
* OSPM finds the Root System Description Pointer (RSDP) structure by
|
||||
* searching physical memory ranges on 16-byte boundaries for a valid
|
||||
* Root System Description Pointer structure signature and checksum
|
||||
* match as follows:
|
||||
* * The first 1 KB of the Extended BIOS Data Area (EBDA). For EISA or
|
||||
* MCA systems, the EBDA can be found in the two-byte location 40:0Eh
|
||||
* on the BIOS data area.
|
||||
* * The BIOS read-only memory space between 0E0000h and 0FFFFFh.
|
||||
*/
|
||||
const uintptr_t ebda =
|
||||
static_cast<uintptr_t>(*reinterpret_cast<uint32_t *>(0x40e));
|
||||
const RSDP *rsdp = findRSDP(reinterpret_cast<void *>(ebda), 1024);
|
||||
if (rsdp == nullptr) {
|
||||
rsdp = findRSDP(reinterpret_cast<void *>(0xe0000), 0xfffff - 0xe0000);
|
||||
}
|
||||
|
||||
if (rsdp == nullptr) {
|
||||
DBG_VERBOSE << "No ACPI!" << endl;
|
||||
return false;
|
||||
}
|
||||
rsdt = reinterpret_cast<RSDT *>(static_cast<uintptr_t>(rsdp->rsdtaddress));
|
||||
|
||||
/* If the XSDT is present we must use it; see:
|
||||
* ACPI Specification Revision 4.0a:
|
||||
* "An ACPI-compatible OS must use the XSDT if present."
|
||||
*/
|
||||
if (rsdp->revision != 0 && rsdp->length >= 36) {
|
||||
xsdt = reinterpret_cast<XSDT *>(rsdp->xsdtaddress);
|
||||
}
|
||||
DBG_VERBOSE << "ACPI revision " << rsdp->revision << endl;
|
||||
for (unsigned i = 0; i != count(); ++i) {
|
||||
SDTH *sdt = get(i);
|
||||
if (sdt != nullptr) {
|
||||
char *c = reinterpret_cast<char *>(&sdt->signature);
|
||||
DBG_VERBOSE << i << ". " << c[0] << c[1] << c[2] << c[3] << " @ "
|
||||
<< reinterpret_cast<void *>(sdt) << endl;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
unsigned count() {
|
||||
if (xsdt != nullptr) {
|
||||
return (xsdt->length - 36) / 8;
|
||||
} else if (rsdt != nullptr) {
|
||||
return (rsdt->length - 36) / 4;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
SDTH *get(unsigned num) {
|
||||
if (xsdt != nullptr) {
|
||||
SDTH *entry = reinterpret_cast<SDTH *>(xsdt->entries[num]);
|
||||
if (checksum(entry, entry->length) == 0) {
|
||||
return entry;
|
||||
}
|
||||
} else if (rsdt != nullptr) {
|
||||
SDTH *entry =
|
||||
reinterpret_cast<SDTH *>(static_cast<uintptr_t>(rsdt->entries[num]));
|
||||
if (checksum(entry, entry->length) == 0) {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
SDTH *get(char a, char b, char c, char d) {
|
||||
union {
|
||||
char signature[4];
|
||||
uint32_t value;
|
||||
};
|
||||
signature[0] = a;
|
||||
signature[1] = b;
|
||||
signature[2] = c;
|
||||
signature[3] = d;
|
||||
|
||||
if (xsdt != nullptr) {
|
||||
for (unsigned i = 0; i < count(); i++) {
|
||||
SDTH *entry = reinterpret_cast<SDTH *>(xsdt->entries[i]);
|
||||
if (entry->signature == value && checksum(entry, entry->length) == 0) {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
} else if (rsdt != nullptr) {
|
||||
for (unsigned i = 0; i < count(); i++) {
|
||||
SDTH *entry =
|
||||
reinterpret_cast<SDTH *>(static_cast<uintptr_t>(rsdt->entries[i]));
|
||||
if (entry->signature == value && checksum(entry, entry->length) == 0) {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int revision() { return rsdp != nullptr ? rsdp->revision : -1; }
|
||||
|
||||
} // namespace ACPI
|
||||
@ -0,0 +1,270 @@
|
||||
/*! \file
|
||||
* \brief Structs and methods related to the \ref ACPI "Advanced Configuration
|
||||
* and Power Interface (ACPI)""
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "../types.h"
|
||||
|
||||
/*! \brief Abstracts the ACPI standard that provides interfaces for hardware
|
||||
* detection, device configuration, and energy management.
|
||||
* \ingroup io
|
||||
*
|
||||
* ACPI is the successor to APM (Advanced Power Management), aiming to give the
|
||||
* operating system more control over the hardware. This extended control, for
|
||||
* instance, enables the operating system to assign a particular amount of
|
||||
* energy to every device (e.g., by disabling a device or changing to standby
|
||||
* mode). For this purpose, BIOS and chipset provide a set of tables that
|
||||
* describe the system and its components and provide routines the OS can call.
|
||||
* These tables contain details about the system, such as the number of CPU
|
||||
* cores and the LAPIC/IOAPIC, which are determined during system boot.
|
||||
*/
|
||||
|
||||
namespace ACPI {
|
||||
|
||||
/*! \brief Root System Description Pointer (RSDP)
|
||||
*
|
||||
* The first step to using ACPI is finding the RSDP that is used to find the
|
||||
* RSDT / XSDT, which themselves contain pointers to even more tables.
|
||||
*
|
||||
* On UEFI systems, the RSDP can be found in the EFI_SYSTEM_TABLE; for non-UEFI
|
||||
* systems we have to search for the signature 'RSD PTR ' in the EBDA (Extended
|
||||
* Bios Data Area) or in the memory area up to `FFFFFh`.
|
||||
*
|
||||
* \see [ACPI-Specification 5.2.5.3; Root System Description Pointer (RSDP)
|
||||
* Structure](acpi.pdf#page=161)
|
||||
*/
|
||||
|
||||
struct RSDP {
|
||||
char signature[8]; /* must exactly be equal to 'RSD PTR ' */
|
||||
uint8_t checksum;
|
||||
char oemid[6];
|
||||
uint8_t revision; /* specifies the ACPI version */
|
||||
uint32_t rsdtaddress; /* physical address of the RSDT */
|
||||
uint32_t length;
|
||||
uint64_t xsdtaddress; /* physical address of the XSDT */
|
||||
uint8_t extended_checksum;
|
||||
uint8_t reserved[3];
|
||||
} __attribute__((packed));
|
||||
|
||||
/*! \brief System Description Table Header (SDTH)
|
||||
*
|
||||
* All System Description Tables (e.g., the RSDT) contain the same entries at
|
||||
* the very beginning of the structure, which are abstracted in the SDTH.
|
||||
*
|
||||
* \see [ACPI-Specification 5.2.6; System Description Table
|
||||
* Header](acpi.pdf#page=162)
|
||||
*/
|
||||
struct SDTH {
|
||||
uint32_t signature; /* table id */
|
||||
uint32_t length;
|
||||
uint8_t revision;
|
||||
uint8_t checksum;
|
||||
char oemid[6];
|
||||
char oem_table_id[8];
|
||||
uint32_t oem_revision;
|
||||
uint32_t creator_id;
|
||||
uint32_t creator_revision;
|
||||
|
||||
/* \brief Helper method
|
||||
* \return Pointer to the end of the table
|
||||
*/
|
||||
void *end() { return reinterpret_cast<uint8_t *>(this) + length; }
|
||||
} __attribute__((packed));
|
||||
|
||||
/*! \brief Root System Description Table (RSDT)
|
||||
*
|
||||
* The RSDT can be found in the RSDP. The RSDT contains physical addresses of
|
||||
* all other System Description Tables, for example the MADT.
|
||||
*
|
||||
* \see [ACPI-Specification 5.2.7; Root System Description Table
|
||||
* (RSDT)](acpi.pdf#page=167)
|
||||
*/
|
||||
|
||||
struct RSDT : SDTH {
|
||||
uint32_t entries[];
|
||||
} __attribute__((packed));
|
||||
|
||||
/*! \brief Extended System Description Table (XSDT)
|
||||
*
|
||||
* Like RSDT, but contains 64-bit instead of 32-bit addresses.
|
||||
*
|
||||
* \see [ACPI-Specification 5.2.8; Extended System Description Table
|
||||
* (XSDT)](acpi.pdf#page=168)
|
||||
*/
|
||||
|
||||
struct XSDT : SDTH {
|
||||
uint64_t entries[];
|
||||
} __attribute__((packed));
|
||||
|
||||
/*! \brief Helper structure
|
||||
*
|
||||
* Is used for accessing the substructures present in SRAT / MADT.
|
||||
*
|
||||
*/
|
||||
struct SubHeader {
|
||||
uint8_t type;
|
||||
uint8_t length;
|
||||
|
||||
/* Method to traverse multiple substructures */
|
||||
SubHeader *next() {
|
||||
return reinterpret_cast<SubHeader *>(reinterpret_cast<uint8_t *>(this) +
|
||||
length);
|
||||
}
|
||||
} __attribute__((packed));
|
||||
|
||||
/*! \brief Multiple APIC Description Table (MADT)
|
||||
*
|
||||
* Describes all interrupt controllers present within the system. Is used to
|
||||
* obtain the IDs of the APICs, along with the number of available processor
|
||||
* cores.
|
||||
*
|
||||
* \see [ACPI-Specification 5.2.12; Multiple APIC Description Table
|
||||
* (MADT)](acpi.pdf#page=193)
|
||||
*/
|
||||
struct MADT : SDTH {
|
||||
uint32_t local_apic_address;
|
||||
uint32_t flags_pcat_compat : 1, flags_reserved : 31;
|
||||
|
||||
/* method to access the first subheader */
|
||||
SubHeader *first() {
|
||||
return reinterpret_cast<SubHeader *>(reinterpret_cast<uint8_t *>(this) +
|
||||
sizeof(MADT));
|
||||
}
|
||||
} __attribute__((packed));
|
||||
|
||||
enum class AddressSpace : uint8_t {
|
||||
MEMORY = 0x0,
|
||||
IO = 0x1,
|
||||
};
|
||||
|
||||
/*! \brief ACPI address format
|
||||
*
|
||||
* The ACPI standard defines its own address format that is able to handle
|
||||
* addresses both in memory address space, as well as IO-port address space.
|
||||
*/
|
||||
struct Address {
|
||||
AddressSpace address_space;
|
||||
uint8_t register_bit_width;
|
||||
uint8_t register_bit_offset;
|
||||
uint8_t reserved;
|
||||
uint64_t address;
|
||||
} __attribute__((packed));
|
||||
|
||||
// Multiple APIC Definition Structure
|
||||
namespace MADS {
|
||||
enum Type {
|
||||
Type_LAPIC = 0,
|
||||
Type_IOAPIC = 1,
|
||||
Type_Interrupt_Source_Override = 2,
|
||||
Type_LAPIC_Address_Override = 5,
|
||||
};
|
||||
|
||||
/*! \brief Processor Local APIC (LAPIC) Structure
|
||||
*
|
||||
* Represents a physical processor along with its local interrupt controller.
|
||||
* The MADT contains a LAPIC structure for every processor available in the
|
||||
* system.
|
||||
*
|
||||
* \see [ACPI-Specification 5.2.12.2; Processor Local APIC
|
||||
* Structure](acpi.pdf#page=195)
|
||||
*/
|
||||
struct LAPIC : SubHeader {
|
||||
uint8_t acpi_processor_id;
|
||||
uint8_t apic_id;
|
||||
uint32_t flags_enabled : 1, flags_reserved : 31; /* must be 0 */
|
||||
} __attribute__((packed));
|
||||
|
||||
/*! \brief I/O APIC Structure
|
||||
*
|
||||
* Represents an I/O-APIC.
|
||||
* The MADT contains an IOAPIC structure for every I/O APIC present in the
|
||||
* system.
|
||||
*
|
||||
* \see [ACPI-Specification 5.2.12.3; I/O APIC Structure](acpi.pdf#page=196)
|
||||
*/
|
||||
|
||||
struct IOAPIC : SubHeader {
|
||||
uint8_t ioapic_id;
|
||||
uint8_t reserved;
|
||||
uint32_t ioapic_address;
|
||||
uint32_t global_system_interrupt_base;
|
||||
} __attribute__((packed));
|
||||
|
||||
/*! \brief Interrupt Source Override Structure
|
||||
*
|
||||
* Is required to describe differences between the IA-PC standard interrupt
|
||||
* definition and the actual hardware implementation.
|
||||
*
|
||||
* \see [ACPI-Specification 5.2.12.5; Interrupt Source Override
|
||||
* Structure](acpi.pdf#page=197)
|
||||
*/
|
||||
struct Interrupt_Source_Override : SubHeader {
|
||||
uint8_t bus;
|
||||
uint8_t source;
|
||||
uint32_t global_system_interrupt;
|
||||
uint16_t flags_polarity : 2, flags_trigger_mode : 2,
|
||||
flags_reserved : 12; /* must be 0 */
|
||||
} __attribute__((packed));
|
||||
|
||||
/*! \brief Local APIC Address Override Structure
|
||||
*
|
||||
* Support for 64-bit systems is achieved by replacing the 32-bit physical LAPIC
|
||||
* address stored in the MADT with the corresponding 64-bit address.
|
||||
*
|
||||
* \see [ACPI-Specification 5.2.12.8; Local APIC Address Override
|
||||
* Structure](acpi.pdf#page=199)
|
||||
*/
|
||||
|
||||
struct LAPIC_Address_Override : SubHeader {
|
||||
uint16_t reserved;
|
||||
union {
|
||||
uint64_t lapic_address;
|
||||
struct {
|
||||
uint32_t lapic_address_low;
|
||||
uint32_t lapic_address_high;
|
||||
} __attribute__((packed));
|
||||
};
|
||||
} __attribute__((packed));
|
||||
|
||||
} // namespace MADS
|
||||
|
||||
/*! \brief Initialize the ACPI description table
|
||||
*
|
||||
* Searches physical memory ranges o 16-byte boundaries for a valid Root System
|
||||
* Description Pointer (RSDP) structure signature and checksum. If present, the
|
||||
* superseding Extended System Description Table (XSDT) is used.
|
||||
*
|
||||
* \see [ACPI-Specification 5.2.5 Root System Description Pointer
|
||||
* (RSDP)](acpi.pdf#page=160)
|
||||
* \see [ACPI-Specification 5.2.8 Extended System Description Table
|
||||
* (XSDT)](acpi.pdf#page=168)
|
||||
*/
|
||||
bool init();
|
||||
|
||||
/*! \brief Number of entries in the description table
|
||||
*/
|
||||
unsigned count();
|
||||
|
||||
/*! \brief Get entry of description table by index
|
||||
*
|
||||
* \param num index in description table
|
||||
* \return Pointer to corresponding entry or `nullptr` if not available
|
||||
*/
|
||||
SDTH *get(unsigned num);
|
||||
|
||||
/*! \brief Get entry of description table by four character identifier
|
||||
*
|
||||
* \param a first character of identifier
|
||||
* \param b second character of identifier
|
||||
* \param c third character of identifier
|
||||
* \param d forth and last character of identifier
|
||||
* \return Pointer to corresponding entry or `nullptr` if not available
|
||||
*/
|
||||
SDTH *get(char a, char b, char c, char d);
|
||||
|
||||
/*! \brief Retrieve the revision from the Root System Description Pointer (RSDP)
|
||||
*/
|
||||
int revision();
|
||||
|
||||
} // namespace ACPI
|
||||
@ -0,0 +1,150 @@
|
||||
#include "apic.h"
|
||||
|
||||
#include "../debug/assert.h"
|
||||
#include "../debug/output.h"
|
||||
#include "acpi.h"
|
||||
#include "core.h"
|
||||
#include "ioport.h"
|
||||
#include "lapic_registers.h"
|
||||
|
||||
namespace APIC {
|
||||
|
||||
static struct {
|
||||
uint32_t id;
|
||||
uintptr_t address;
|
||||
uint32_t interrupt_base;
|
||||
} ioapic;
|
||||
|
||||
static uint8_t slot_map[16];
|
||||
|
||||
static uint8_t lapic_id[Core::MAX];
|
||||
static unsigned lapics = 0;
|
||||
|
||||
bool init() {
|
||||
// get Multiple APIC Definition Table (MADT) from ACPI
|
||||
ACPI::MADT* madt = static_cast<ACPI::MADT*>(ACPI::get('A', 'P', 'I', 'C'));
|
||||
if (madt == 0) {
|
||||
DBG_VERBOSE << "ERROR: no MADT found in ACPI" << endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
// read the local APIC address
|
||||
LAPIC::base_address = static_cast<uintptr_t>(madt->local_apic_address);
|
||||
DBG_VERBOSE << "LAPIC Address "
|
||||
<< reinterpret_cast<void*>(
|
||||
static_cast<uintptr_t>(madt->local_apic_address))
|
||||
<< endl;
|
||||
|
||||
// PC/AT compatibility mode
|
||||
if (madt->flags_pcat_compat != 0) {
|
||||
// The APIC operating mode is set to compatible PIC mode - we have to change
|
||||
// it.
|
||||
DBG_VERBOSE << "PIC comp mode, disabling PICs." << endl;
|
||||
|
||||
// Select Interrupt Mode Control Register (IMCR)
|
||||
// (this register will only exist if hardware supports the PIC mode)
|
||||
IOPort reg(0x22);
|
||||
reg.outb(0x70);
|
||||
// disable PIC mode, use APIC
|
||||
IOPort imcr(0x23);
|
||||
imcr.outb(0x01);
|
||||
}
|
||||
|
||||
// Set default mapping of external interrupt slots (might be overwritten
|
||||
// below)
|
||||
for (unsigned i = 0; i < sizeof(slot_map) / sizeof(slot_map[0]); i++) {
|
||||
slot_map[i] = i;
|
||||
}
|
||||
|
||||
// Initialize invalid lapic_ids
|
||||
for (unsigned i = 0; i < Core::MAX; i++) {
|
||||
lapic_id[i] = INVALID_ID;
|
||||
}
|
||||
|
||||
// reset numbers, store apic data into arrays
|
||||
for (ACPI::SubHeader* mads = madt->first(); mads < madt->end();
|
||||
mads = mads->next()) {
|
||||
switch (mads->type) {
|
||||
case ACPI::MADS::Type_LAPIC: {
|
||||
ACPI::MADS::LAPIC* mads_lapic = static_cast<ACPI::MADS::LAPIC*>(mads);
|
||||
if (mads_lapic->flags_enabled == 0) {
|
||||
DBG_VERBOSE << "Detected disabled LAPIC with ID "
|
||||
<< static_cast<unsigned>(mads_lapic->apic_id) << endl;
|
||||
} else if (lapics >= Core::MAX) {
|
||||
DBG_VERBOSE << "Got more LAPICs than Core::MAX" << endl;
|
||||
} else if (mads_lapic->apic_id == INVALID_ID) {
|
||||
DBG_VERBOSE << "Got invalid APIC ID" << endl;
|
||||
} else {
|
||||
DBG_VERBOSE << "Detected LAPIC with ID "
|
||||
<< static_cast<unsigned>(mads_lapic->apic_id) << endl;
|
||||
lapic_id[lapics++] = mads_lapic->apic_id;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ACPI::MADS::Type_IOAPIC: {
|
||||
ACPI::MADS::IOAPIC* mads_ioapic =
|
||||
static_cast<ACPI::MADS::IOAPIC*>(mads);
|
||||
DBG_VERBOSE << "Detected IO APIC with ID "
|
||||
<< static_cast<unsigned>(mads_ioapic->ioapic_id)
|
||||
<< " / Base "
|
||||
<< reinterpret_cast<void*>(static_cast<uintptr_t>(
|
||||
mads_ioapic->global_system_interrupt_base))
|
||||
<< endl;
|
||||
if (mads_ioapic->global_system_interrupt_base > 23) {
|
||||
DBG_VERBOSE << "Ignoring IOAPIC since we currently only support one."
|
||||
<< endl;
|
||||
} else {
|
||||
ioapic.id = mads_ioapic->ioapic_id;
|
||||
ioapic.address = static_cast<uintptr_t>(mads_ioapic->ioapic_address);
|
||||
ioapic.interrupt_base = mads_ioapic->global_system_interrupt_base;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ACPI::MADS::Type_Interrupt_Source_Override: {
|
||||
ACPI::MADS::Interrupt_Source_Override* mads_iso =
|
||||
static_cast<ACPI::MADS::Interrupt_Source_Override*>(mads);
|
||||
if (mads_iso->bus == 0) {
|
||||
DBG_VERBOSE << "Overriding Interrupt Source "
|
||||
<< static_cast<unsigned>(mads_iso->source) << " with "
|
||||
<< mads_iso->global_system_interrupt << endl;
|
||||
if (mads_iso->source < sizeof(slot_map) / sizeof(slot_map[0])) {
|
||||
slot_map[mads_iso->source] = mads_iso->global_system_interrupt;
|
||||
}
|
||||
} else {
|
||||
DBG_VERBOSE << "Override for bus " << mads_iso->bus
|
||||
<< " != ISA. Does not conform to ACPI." << endl;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ACPI::MADS::Type_LAPIC_Address_Override: {
|
||||
ACPI::MADS::LAPIC_Address_Override* mads_lao =
|
||||
static_cast<ACPI::MADS::LAPIC_Address_Override*>(mads);
|
||||
LAPIC::base_address =
|
||||
static_cast<uintptr_t>(mads_lao->lapic_address_low);
|
||||
DBG_VERBOSE << "Overriding LAPIC address with "
|
||||
<< reinterpret_cast<void*>(
|
||||
static_cast<uintptr_t>(mads_lao->lapic_address))
|
||||
<< endl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
uint8_t getIOAPICSlot(APIC::Device device) { return slot_map[device]; }
|
||||
|
||||
uintptr_t getIOAPICAddress() { return ioapic.address; }
|
||||
|
||||
uint8_t getIOAPICID() { return ioapic.id; }
|
||||
|
||||
uint8_t getLogicalAPICID(uint8_t core) {
|
||||
return core < Core::MAX ? (1 << core) : 0;
|
||||
}
|
||||
|
||||
uint8_t getLAPICID(uint8_t core) {
|
||||
assert(core < Core::MAX);
|
||||
return lapic_id[core];
|
||||
}
|
||||
|
||||
} // namespace APIC
|
||||
@ -0,0 +1,82 @@
|
||||
/*! \file
|
||||
* \brief Gather system information from the \ref ACPI about the \ref APIC
|
||||
* "Advanced Programmable Interrupt Controller (APIC)"
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "../types.h"
|
||||
|
||||
/*! \brief Information about the (extended) Advanced Programmable Interrupt
|
||||
* Controller
|
||||
*/
|
||||
namespace APIC {
|
||||
/*! \brief Historic order of interrupt lines (PIC)
|
||||
*/
|
||||
enum Device {
|
||||
TIMER = 0, ///< Programmable Interrupt Timer (\ref PIT)
|
||||
KEYBOARD = 1, ///< Keyboard
|
||||
COM1 = 4, ///< First serial interface
|
||||
COM2 = 3, ///< Second serial interface
|
||||
COM3 = 4, ///< Third serial interface (shared with COM1)
|
||||
COM4 = 3, ///< Forth serial interface (shared with COM2)
|
||||
FLOPPY = 6, ///< Floppy device
|
||||
LPT1 = 7, ///< Printer
|
||||
REALTIMECLOCK = 8, ///< Real time clock
|
||||
PS2MOUSE = 12, ///< Mouse
|
||||
IDE1 = 14, ///< First hard disk
|
||||
IDE2 = 15 ///< Second hard disk
|
||||
};
|
||||
|
||||
/*! \brief Invalid APIC ID
|
||||
*
|
||||
* The highest address is reserved according to xAPIC specification
|
||||
*/
|
||||
const uint8_t INVALID_ID = 0xff;
|
||||
|
||||
/*! \brief Executes system detection
|
||||
*
|
||||
* Searches and evaluates the APIC entries in the \ref ACPI table.
|
||||
* This function recognizes a possibly existing multicore system.
|
||||
* After successful detection, the number of available CPUs (which is equal
|
||||
* to the number of \ref LAPIC "local APICs") ) can be queried
|
||||
* using the method \ref Core::count().
|
||||
*
|
||||
* \note Called by \ref kernel_init() on BSP
|
||||
*
|
||||
* \return `true` if detection of the APIC entries was successful
|
||||
*/
|
||||
bool init();
|
||||
|
||||
/*! \brief Queries the I/O-APIC address determined during system boot
|
||||
*
|
||||
* \return Base address of the (first & only supported) I/O APIC
|
||||
*/
|
||||
uintptr_t getIOAPICAddress();
|
||||
|
||||
/*! \brief Queries of ID of the I/O-APIC determined during system boot
|
||||
*
|
||||
* \return Identification of the (first & only supported) I/O APIC
|
||||
*/
|
||||
uint8_t getIOAPICID();
|
||||
|
||||
/*! \brief Returns the pin number the \p device is connected to.
|
||||
*/
|
||||
uint8_t getIOAPICSlot(APIC::Device device);
|
||||
|
||||
/*! \brief Returns the logical ID of the Local APIC passed for \a core.
|
||||
*
|
||||
* The LAPIC's logical ID is set (by StuBS) during boot such that exactly one
|
||||
* bit is set per CPU core. For core 0, bit 0 is set in its ID, while core 1 has
|
||||
* bit 1 set, etc.
|
||||
*
|
||||
* \param core The queried CPU core
|
||||
*/
|
||||
uint8_t getLogicalAPICID(uint8_t core);
|
||||
|
||||
/*! \brief Get the Local APIC ID of a CPU
|
||||
* \param core Query CPU core number
|
||||
* \return LAPIC ID of CPU or INVALID_ID if invalid CPU ID
|
||||
*/
|
||||
uint8_t getLAPICID(uint8_t core);
|
||||
|
||||
} // namespace APIC
|
||||
@ -0,0 +1,22 @@
|
||||
/*! \file
|
||||
* \brief Helper for cache alignment
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../debug/assert.h"
|
||||
|
||||
// Helper for aligning to cache line (to prevent false sharing)
|
||||
#ifndef CACHE_LINE_SIZE
|
||||
#define CACHE_LINE_SIZE 64
|
||||
#endif
|
||||
#define cache_aligned alignas(CACHE_LINE_SIZE)
|
||||
|
||||
/*!
|
||||
* \def assert_cache_aligned(TYPE)
|
||||
* \brief Compile time check of cache alignment
|
||||
* \param TYPE data type to check
|
||||
*/
|
||||
#define assert_cache_aligned(TYPE) \
|
||||
static_assert(sizeof(TYPE) % CACHE_LINE_SIZE == 0, \
|
||||
STRINGIFY(TYPE) "Not aligned on cache boundary")
|
||||
@ -0,0 +1,21 @@
|
||||
#include "cga.h"
|
||||
|
||||
namespace CGA {
|
||||
|
||||
void setCursor(unsigned abs_x, unsigned abs_y) {
|
||||
(void)abs_x;
|
||||
(void)abs_y;
|
||||
}
|
||||
|
||||
void getCursor(unsigned& abs_x, unsigned& abs_y) {
|
||||
(void)abs_x;
|
||||
(void)abs_y;
|
||||
}
|
||||
|
||||
void show(unsigned abs_x, unsigned abs_y, char character, Attribute attrib) {
|
||||
(void)abs_x;
|
||||
(void)abs_y;
|
||||
(void)character;
|
||||
(void)attrib;
|
||||
}
|
||||
}; // namespace CGA
|
||||
@ -0,0 +1,138 @@
|
||||
/*! \file
|
||||
* \brief \ref CGA provides a basic interface to display a character in
|
||||
* VGA-compatible text mode
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "../types.h"
|
||||
|
||||
/*! \brief Basic operations in the VGA-compatible text mode
|
||||
* \ingroup io
|
||||
*
|
||||
* This namespace provides an interface to access the screen in text mode
|
||||
* (also known as CGA mode), with access directly on the hardware
|
||||
* level, i.e. the video memory and the I/O ports of the graphics
|
||||
* card.
|
||||
*/
|
||||
namespace CGA {
|
||||
constexpr unsigned ROWS = 25; ///< Visible rows in text mode
|
||||
constexpr unsigned COLUMNS = 80; ///< Visible columns in text mode
|
||||
|
||||
/*! \brief CGA color palette
|
||||
*
|
||||
* Colors for the attribute byte.
|
||||
* All 16 colors can be used for the foreground while the background colors
|
||||
* are limited to the first eight (from`BLACK` to `LIGHT_GREY`)
|
||||
*/
|
||||
enum Color {
|
||||
BLACK, ///< Black (fore- and background)
|
||||
BLUE, ///< Blue (fore- and background)
|
||||
GREEN, ///< Green (fore- and background)
|
||||
CYAN, ///< Cyan (fore- and background)
|
||||
RED, ///< Red (fore- and background)
|
||||
MAGENTA, ///< Magenta (fore- and background)
|
||||
BROWN, ///< Brown (fore- and background)
|
||||
LIGHT_GREY, ///< Light grey (fore- and background)
|
||||
DARK_GREY, ///< Dark grey (foreground only)
|
||||
LIGHT_BLUE, ///< Light blue (foreground only)
|
||||
LIGHT_GREEN, ///< Light green (foreground only)
|
||||
LIGHT_CYAN, ///< Light cyan (foreground only)
|
||||
LIGHT_RED, ///< Light red (foreground only)
|
||||
LIGHT_MAGENTA, ///< Light magenta (foreground only)
|
||||
YELLOW, ///< Yellow (foreground only)
|
||||
WHITE ///< White (foreground only)
|
||||
};
|
||||
|
||||
/*! \brief Structure of a character attribute
|
||||
* consists of 4 bit fore- and 3 bit background color, and a single blink bit.
|
||||
*
|
||||
* [Bit fields](https://en.cppreference.com/w/cpp/language/bit_field) can
|
||||
* notably simplify the access and code readability.
|
||||
*
|
||||
* \note [Type punning](https://en.wikipedia.org/wiki/Type_punning#Use_of_union)
|
||||
* is indeed undefined behavior in C++. However, *gcc* explicitly allows
|
||||
* this construct as a [language extension](https://gcc.gnu.org/bugs/#nonbugs).
|
||||
* Some compilers ([other than
|
||||
* gcc](https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html#Type%2Dpunning)
|
||||
* might allow this feature only by disabling strict aliasing
|
||||
* (`-fno-strict-aliasing`). In \StuBS we use this feature extensively due to
|
||||
* the improved code readability.
|
||||
*
|
||||
* \todo(11) Fill in the bitfield
|
||||
*/
|
||||
union Attribute {
|
||||
struct {
|
||||
uint8_t todo : 8;
|
||||
} __attribute__((packed));
|
||||
uint8_t value; ///< combined value
|
||||
|
||||
/*! \brief Attribute constructor (with default values)
|
||||
*
|
||||
* \todo(11) Complete constructor
|
||||
*
|
||||
* \param foreground Foreground color (Default: \ref LIGHT_GREY)
|
||||
* \param background Background color (Default: \ref BLACK)
|
||||
* \param blink Blink if `true` (default: no blinking)
|
||||
*/
|
||||
explicit Attribute(Color foreground = LIGHT_GREY, Color background = BLACK,
|
||||
bool blink = false) { // NOLINT
|
||||
(void)foreground;
|
||||
(void)background;
|
||||
(void)blink;
|
||||
}
|
||||
} __attribute__((packed)); // prevent padding by the compiler
|
||||
|
||||
/*! \brief Set the keyboard hardware cursor to absolute screen position
|
||||
*
|
||||
* \todo(11) Implement the method using \ref IOPort
|
||||
*
|
||||
* \param abs_x absolute column of the keyboard hardware cursor
|
||||
* \param abs_y absolute row of the keyboard hardware cursor
|
||||
*/
|
||||
void setCursor(unsigned abs_x, unsigned abs_y);
|
||||
|
||||
/*! \brief Retrieve the keyboard hardware cursor position on screen
|
||||
*
|
||||
* \todo(11) Implement the method using the \ref IOPort
|
||||
*
|
||||
* \param abs_x absolute column of the keyboard hardware cursor
|
||||
* \param abs_y absolute row of the keyboard hardware cursor
|
||||
*/
|
||||
void getCursor(unsigned& abs_x, unsigned& abs_y);
|
||||
|
||||
/*! \brief Basic output of a character at a specific position on the screen.
|
||||
*
|
||||
* This method outputs the given character at the absolute screen position
|
||||
* (`x`, `y`) with the specified color attribute.
|
||||
*
|
||||
* The position (`0`,`0`) indicates the upper left corner of the screen.
|
||||
* The attribute defines characteristics such as background color,
|
||||
* foreground color and blinking.
|
||||
*
|
||||
* \param abs_x Column (`abs_x` < \ref COLUMNS) in which the character should be
|
||||
* displayed
|
||||
* \param abs_y Row (`abs_y` < \ref ROWS) in which the character should be
|
||||
* displayed
|
||||
* \param character Character to be displayed
|
||||
* \param attrib Attribute with color settings
|
||||
*
|
||||
* \todo(11) Implement the method
|
||||
*/
|
||||
void show(unsigned abs_x, unsigned abs_y, char character,
|
||||
Attribute attrib = Attribute());
|
||||
|
||||
/*! \brief Structure for a cell in text mode
|
||||
*
|
||||
* Consisting of two bytes, character and attribute
|
||||
*/
|
||||
struct Cell {
|
||||
char character;
|
||||
Attribute attribute;
|
||||
Cell(char character, Attribute attribute)
|
||||
: character(character), attribute(attribute) {}
|
||||
} __attribute__((packed));
|
||||
|
||||
/*! \brief Base address for linear text buffer in video memory
|
||||
*/
|
||||
Cell* const TEXT_BUFFER_BASE = nullptr;
|
||||
}; // namespace CGA
|
||||
@ -0,0 +1,61 @@
|
||||
#include "cmos.h"
|
||||
|
||||
#include "core_interrupt.h"
|
||||
#include "ioport.h"
|
||||
|
||||
namespace CMOS {
|
||||
static IOPort address(0x70);
|
||||
static IOPort data(0x71);
|
||||
|
||||
namespace NMI {
|
||||
static const uint8_t mask = 0x80;
|
||||
// Cache NMI to speed things up
|
||||
static bool disabled = false;
|
||||
|
||||
void enable() {
|
||||
bool status = Core::Interrupt::disable();
|
||||
uint8_t value = address.inb();
|
||||
value &= ~mask;
|
||||
address.outb(value);
|
||||
Core::Interrupt::restore(status);
|
||||
disabled = false;
|
||||
}
|
||||
|
||||
void disable() {
|
||||
bool status = Core::Interrupt::disable();
|
||||
uint8_t value = address.inb();
|
||||
value |= mask;
|
||||
address.outb(value);
|
||||
Core::Interrupt::restore(status);
|
||||
disabled = true;
|
||||
}
|
||||
|
||||
bool isEnabled() {
|
||||
disabled = (address.inb() & mask) != 0;
|
||||
return !disabled;
|
||||
}
|
||||
} // namespace NMI
|
||||
|
||||
static void setAddress(enum Register reg) {
|
||||
uint8_t value = reg;
|
||||
// The highest bit controls the Non Maskable Interrupt
|
||||
// so we don't want to accidentally change it.
|
||||
if (NMI::disabled) {
|
||||
value |= NMI::mask;
|
||||
} else {
|
||||
value &= ~NMI::mask;
|
||||
}
|
||||
address.outb(value);
|
||||
}
|
||||
|
||||
uint8_t read(enum Register reg) {
|
||||
setAddress(reg);
|
||||
return data.inb();
|
||||
}
|
||||
|
||||
void write(enum Register reg, uint8_t value) {
|
||||
setAddress(reg);
|
||||
data.outb(value);
|
||||
}
|
||||
|
||||
} // namespace CMOS
|
||||
@ -0,0 +1,46 @@
|
||||
/*! \file
|
||||
* \brief Controlling the \ref CMOS "complementary metal oxide semiconductor
|
||||
* (CMOS)"
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "../types.h"
|
||||
|
||||
/*!
|
||||
* \defgroup CMOS CMOS
|
||||
* \brief complementary metal oxide semiconductor (CMOS)
|
||||
*/
|
||||
|
||||
/*! \brief CMOS
|
||||
* \ingroup CMOS
|
||||
*/
|
||||
namespace CMOS {
|
||||
|
||||
enum Register {
|
||||
REG_SECOND = 0x0, ///< RTC
|
||||
REG_ALARM_SECOND = 0x1, ///< RTC
|
||||
REG_MINUTE = 0x2, ///< RTC
|
||||
REG_ALARM_MINUTE = 0x3, ///< RTC
|
||||
REG_HOUR = 0x4, ///< RTC
|
||||
REG_ALARM_HOUR = 0x5, ///< RTC
|
||||
REG_WEEKDAY = 0x6, ///< RTC
|
||||
REG_DAYOFMONTH = 0x7, ///< RTC
|
||||
REG_MONTH = 0x8, ///< RTC
|
||||
REG_YEAR = 0x9, ///< RTC
|
||||
REG_STATUS_A = 0xa, ///< RTC
|
||||
REG_STATUS_B = 0xb, ///< RTC
|
||||
REG_STATUS_C = 0xc, ///< RTC
|
||||
REG_STATUS_D = 0xd, ///< RTC
|
||||
REG_STATUS_DIAGNOSE = 0xe,
|
||||
REG_STATUS_SHUTDOWN = 0xf
|
||||
};
|
||||
|
||||
uint8_t read(enum Register reg);
|
||||
void write(enum Register reg, uint8_t value);
|
||||
|
||||
namespace NMI {
|
||||
void enable();
|
||||
void disable();
|
||||
bool isEnabled();
|
||||
} // namespace NMI
|
||||
} // namespace CMOS
|
||||
@ -0,0 +1,20 @@
|
||||
[SECTION .text]
|
||||
[GLOBAL context_switch]
|
||||
[GLOBAL context_launch]
|
||||
[GLOBAL fake_systemv_abi]
|
||||
|
||||
; context_switch saves the registers in the current context structure
|
||||
; and populates the registers from the the next context.
|
||||
align 16
|
||||
context_switch:
|
||||
|
||||
; context_launch populates the register set from the next context structure.
|
||||
; It does not save the current registers.
|
||||
align 16 ; When only one parameter is used for `align`, it will use NOP
|
||||
context_launch:
|
||||
|
||||
; fake_systemv_abi is used to populate the volatile argument registers used by the systemv abi (rdi, rsi, ...)
|
||||
; with values from the non-volatile registers saved within the thread context (r15, r14, ...)
|
||||
align 16
|
||||
fake_systemv_abi:
|
||||
|
||||
@ -0,0 +1,9 @@
|
||||
#include "context.h"
|
||||
|
||||
void prepareContext(void* tos, Context& context, void (*kickoff)(void*),
|
||||
void* param1) {
|
||||
(void)tos;
|
||||
(void)context;
|
||||
(void)kickoff;
|
||||
(void)param1;
|
||||
}
|
||||
@ -0,0 +1,118 @@
|
||||
/*! \file
|
||||
* \brief Functionality required for \ref context_switch "context switching"
|
||||
*/
|
||||
|
||||
/*! \defgroup context Context Switch
|
||||
* \brief Low-Level functionality required for context switching
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "../types.h"
|
||||
|
||||
/*! \brief Structure for saving the CPU context when switching coroutines.
|
||||
* \ingroup context
|
||||
*/
|
||||
struct Context {
|
||||
uintptr_t rbx; ///< RBX of the thread
|
||||
uintptr_t rbp; ///< RBP of the thread
|
||||
uintptr_t r12; ///< R12 of the thread
|
||||
uintptr_t r13; ///< R13 of the thread
|
||||
uintptr_t r14; ///< R14 of the thread
|
||||
uintptr_t r15; ///< R15 of the thread
|
||||
void* rsp; ///< Current stack pointer of the thread
|
||||
} __attribute__((packed));
|
||||
|
||||
/*! \brief Prepares a context for its first activation.
|
||||
*
|
||||
* \ingroup context
|
||||
*
|
||||
* On first activation (during *some* context switch), the execution of a
|
||||
* thread should start at its entry point (typically an implementation of \ref
|
||||
* Thread::kickoff).
|
||||
*
|
||||
* For this, we have to prepare the thread context such that \ref
|
||||
* context_switch and \ref context_launch can work with it.
|
||||
*
|
||||
* Just pushing the entry point onto the stack as a return address is not
|
||||
* sufficient, however.
|
||||
* \ref Thread::kickoff requires a pointer to the current thread as a
|
||||
* parameter, which we also have to transfer. According to the 64 bit systemv
|
||||
* calling convention, parameters are passed via the volatile registers `rdi,
|
||||
* rsi, rcx, rdx, r8, r9`. But theses are never set during the intial context
|
||||
* switch (why?). Therefore we pass the parameter using the non-volatile
|
||||
* register `r15` and use a trampoline function as the actual entry point. See
|
||||
* \ref fake_systemv_abi for details.
|
||||
*
|
||||
* `prepareContext()` can be implemented in the high-level programming language
|
||||
* C++ (in file `context.cc`).
|
||||
*
|
||||
* \param tos Pointer to the top of stack (= address of first byte beyond
|
||||
* the memory reserved for the stack)
|
||||
* \param context Reference to the Context structure to be filled
|
||||
* \param kickoff Pointer to the \ref Thread::kickoff function
|
||||
* \param param1 first parameter for \ref Thread::kickoff function
|
||||
*/
|
||||
/*!
|
||||
* \todo(14) Implement Function (and helper functions, if required)
|
||||
*/
|
||||
void prepareContext(void* tos, Context& context, void (*kickoff)(void*),
|
||||
void* param1 = nullptr);
|
||||
|
||||
/*! \brief Executes the context switch.
|
||||
*
|
||||
* \ingroup context
|
||||
*
|
||||
* For a clean context switch, the current register values must be stored in
|
||||
* the given context struct. Subsequently, these values must be restored
|
||||
* accordingly from the `next` context struct.
|
||||
*
|
||||
* This function must be implemented in assembler in the file `context.asm`
|
||||
* (why?). It must be declared as `extern "C"`, so assembler functions are not
|
||||
* C++ name mangled.
|
||||
*
|
||||
* \param next Pointer to the structure that the next context will be read
|
||||
* from
|
||||
* \param current Pointer to the structure that the current context will be
|
||||
* stored in
|
||||
*
|
||||
* \todo(14) Implement Method
|
||||
*/
|
||||
extern "C" void context_switch(Context* next, Context* current);
|
||||
|
||||
/*! \brief Launch context switching.
|
||||
*
|
||||
* To start context switching, the current context (from the boot-routines) is
|
||||
* thrown away and the prepared register values within the given `next` context
|
||||
* replace it.
|
||||
*
|
||||
* This function must be implemented in assembler in the file `context.asm`
|
||||
* (why?). It must be declared as `extern "C"`, so assembler functions are not
|
||||
* C++ name mangled.
|
||||
*
|
||||
* \ingroup context
|
||||
*
|
||||
* \param next Pointer to the structure that the next context will be read
|
||||
* from
|
||||
*
|
||||
* \todo(14) Implement Method
|
||||
*/
|
||||
extern "C" void context_launch(Context* next);
|
||||
|
||||
/*! \brief Fakes a systemv abi call.
|
||||
*
|
||||
* When a thread is first started, only non-volatile registers are "restored"
|
||||
* from our prepared context (which is where we stored our \ref Thread::kickoff
|
||||
* parameters). However, the 64 bit calling convention (systemv) dictates that
|
||||
* parameters are passed via the volatile registers `rdi, rsi, rcx, rdx, r8,
|
||||
* r9`. In order to call a C++ function, we have to transfer our parameters from
|
||||
* the non-volatile registers (e.g. `r15, ...`) to the correct volatile ones
|
||||
* (`rdi, ...`).
|
||||
*
|
||||
* This function must be implemented in assembler in the file `context.asm`
|
||||
* (why?). It must be declared as `extern "C"`, so assembler functions are not
|
||||
* C++ name mangled.
|
||||
* \ingroup context
|
||||
*
|
||||
* \todo(14) Implement Method
|
||||
*/
|
||||
extern "C" void fake_systemv_abi();
|
||||
@ -0,0 +1,73 @@
|
||||
#include "core.h"
|
||||
|
||||
#include "apic.h"
|
||||
#include "lapic.h"
|
||||
|
||||
/*! \brief Initial size of CPU core stacks
|
||||
*
|
||||
* Used during startup in `boot/startup.asm`
|
||||
*/
|
||||
extern "C" const unsigned long CPU_CORE_STACK_SIZE = 4096;
|
||||
|
||||
/*! \brief Reserved memory for CPU core stacks
|
||||
*/
|
||||
alignas(
|
||||
16) static unsigned char cpu_core_stack[Core::MAX * CPU_CORE_STACK_SIZE];
|
||||
|
||||
/*! \brief Pointer to stack memory
|
||||
*
|
||||
* Incremented during startup of each core (bootstrap and application
|
||||
* processors) in `boot/startup.asm`
|
||||
*/
|
||||
unsigned char* cpu_core_stack_pointer = cpu_core_stack;
|
||||
|
||||
namespace Core {
|
||||
|
||||
static unsigned cores = 0; ///< Number of available CPU cores
|
||||
static volatile unsigned
|
||||
core_id[255]; ///< Lookup table for CPU core IDs with LAPIC ID as index
|
||||
|
||||
static unsigned online_cores = 0; ///< Number of currently online CPU cores
|
||||
static bool online_core[Core::MAX]; ///< Lookup table for online CPU cores with
|
||||
///< CPU core ID as index
|
||||
|
||||
void init() {
|
||||
// Increment number of online CPU cores
|
||||
if (__atomic_fetch_add(&online_cores, 1, __ATOMIC_RELAXED) == 0) {
|
||||
// Fill Lookup table
|
||||
for (unsigned i = 0; i < Core::MAX; i++) {
|
||||
uint8_t lapic_id = APIC::getLAPICID(i);
|
||||
if (lapic_id < APIC::INVALID_ID) { // ignore invalid LAPICs
|
||||
core_id[lapic_id] = i;
|
||||
cores++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get CPU ID
|
||||
uint8_t cpu = getID();
|
||||
|
||||
// initialize local APIC with logical APIC ID
|
||||
LAPIC::init(APIC::getLogicalAPICID(cpu));
|
||||
|
||||
// set current CPU online
|
||||
online_core[cpu] = true;
|
||||
}
|
||||
|
||||
void exit() {
|
||||
// CPU core offline
|
||||
online_core[getID()] = false;
|
||||
__atomic_fetch_sub(&online_cores, 1, __ATOMIC_RELAXED);
|
||||
}
|
||||
|
||||
unsigned getID() { return core_id[LAPIC::getID()]; }
|
||||
|
||||
unsigned count() { return cores; }
|
||||
|
||||
unsigned countOnline() { return online_cores; }
|
||||
|
||||
bool isOnline(uint8_t core_id) {
|
||||
return core_id > Core::MAX ? false : online_core[core_id];
|
||||
}
|
||||
|
||||
} // namespace Core
|
||||
@ -0,0 +1,117 @@
|
||||
/*! \file
|
||||
* \brief Access to internals of a CPU \ref Core
|
||||
*/
|
||||
|
||||
/*! \defgroup sync CPU Synchronization
|
||||
*
|
||||
* The synchronization module houses functions useful for orchestrating multiple
|
||||
* processors and their activities. Synchronisation, in this case, means
|
||||
* handling the resource contention between multiple participants, running on
|
||||
* either the same or different cores.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "../types.h"
|
||||
#include "core_cr.h"
|
||||
#include "core_interrupt.h"
|
||||
#include "core_msr.h"
|
||||
|
||||
/*! \brief Implements an abstraction for CPU internals.
|
||||
*
|
||||
* These internals include functions to \ref Core::Interrupt "allow or deny
|
||||
* interrupts", access \ref Core::CR "control registers".
|
||||
*/
|
||||
namespace Core {
|
||||
|
||||
/*! \brief Maximum number of supported CPUs
|
||||
*/
|
||||
constexpr unsigned MAX = 8;
|
||||
|
||||
/*! \brief Get the ID of the current CPU core
|
||||
* using \ref LAPIC::getID() with an internal lookup table.
|
||||
*
|
||||
* \return ID of current Core (a number between 0 and \ref Core::MAX)
|
||||
*/
|
||||
unsigned getID();
|
||||
|
||||
/*! \brief Initialize this CPU core
|
||||
*
|
||||
* Mark this core as *online* and setup the cores \ref LAPIC by assigning it a
|
||||
* unique \ref APIC::getLogicalAPICID() "logical APIC ID"
|
||||
*
|
||||
* \note Should only be called from \ref kernel_init() during startup.
|
||||
*/
|
||||
void init();
|
||||
|
||||
/*! \brief Deinitialize this CPU core
|
||||
*
|
||||
* Mark this Core as *offline*
|
||||
*
|
||||
* \note Should only be called from \ref kernel_init() after returning from
|
||||
* `main()` or `main_ap()`.
|
||||
*/
|
||||
void exit();
|
||||
|
||||
/*! \brief Get number of available CPU cores
|
||||
*
|
||||
* \return total number of cores
|
||||
*/
|
||||
unsigned count();
|
||||
|
||||
/*! \brief Get number of successfully started (and currently active) CPU cores
|
||||
*
|
||||
* \return total number of online cores
|
||||
*/
|
||||
unsigned countOnline();
|
||||
|
||||
/*! \brief Check if CPU core is currently active
|
||||
* \param core_id ID of the CPU core
|
||||
* \return `true` if successfully started and is currently active
|
||||
*/
|
||||
bool isOnline(uint8_t core_id);
|
||||
|
||||
/*! \brief Gives the core a hint that it is executing a spinloop and should
|
||||
* sleep "shortly"
|
||||
*
|
||||
* Improves the over-all performance when executing a spinloop by waiting a
|
||||
* short moment reduce the load on the memory.
|
||||
*
|
||||
* \see [ISDMv2, Chapter 4. PAUSE - Spin Loop
|
||||
* Hint](intel_manual_vol2.pdf#page=887)
|
||||
*/
|
||||
inline void pause() { asm volatile("pause\n\t" : : : "memory"); }
|
||||
|
||||
/*! \brief Halt the CPU core until the next interrupt.
|
||||
*
|
||||
* Halts the current CPU core such that it will wake up on the next interrupt.
|
||||
* Internally, this function first enables the interrupts via `sti` and then
|
||||
* halts the core using `hlt`. Halted cores can only be woken by interrupts. The
|
||||
* effect of `sti` is delayed by one instruction, making the sequence `sti hlt`
|
||||
* atomic (if interrupts were disabled previously).
|
||||
*
|
||||
* \see [ISDMv2, Chapter 4. STI - Set Interrupt
|
||||
* Flag](intel_manual_vol2.pdf#page=1297)
|
||||
* \see [ISDMv2, Chapter 3. HLT - Halt](intel_manual_vol2.pdf#page=539)
|
||||
*/
|
||||
inline void idle() { asm volatile("sti\n\t hlt\n\t" : : : "memory"); }
|
||||
|
||||
/*! \brief Permanently halts the core.
|
||||
*
|
||||
* Permanently halts the current CPU core. Internally, this function first
|
||||
* disables the interrupts via `cli` and then halts the CPU core using `hlt`. As
|
||||
* halted CPU cores can only be woken by interrupts, it is guaranteed that this
|
||||
* core will be halted until the next reboot. The execution of die never
|
||||
* returns. On multicore systems, only the executing CPU core will be halted
|
||||
* permanently, other cores will continue execution.
|
||||
*
|
||||
* \see [ISDMv2, Chapter 3. CLI - Clear Interrupt
|
||||
* Flag](intel_manual_vol2.pdf#page=245)
|
||||
* \see [ISDMv2, Chapter 3. HLT - Halt](intel_manual_vol2.pdf#page=539)
|
||||
*/
|
||||
[[noreturn]] inline void die() {
|
||||
while (true) {
|
||||
asm volatile("cli\n\t hlt\n\t" : : : "memory");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Core
|
||||
@ -0,0 +1,83 @@
|
||||
/*! \file
|
||||
* \brief Access to \ref Core::CR "Control Register" of a \ref Core "CPU core"
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "../types.h"
|
||||
|
||||
namespace Core {
|
||||
/*! \brief Control Register 0
|
||||
*
|
||||
* \see [ISDMv3, 2.5 Control Registers](intel_manual_vol3.pdf#page=74)
|
||||
*/
|
||||
enum CR0 {
|
||||
CR0_PE = 1 << 0, ///< Protected Mode enabled
|
||||
CR0_MP = 1 << 1, ///< Monitor co-processor
|
||||
CR0_EM = 1 << 2, ///< Emulation (no x87 floating-point unit present)
|
||||
CR0_TS = 1 << 3, ///< Task switched
|
||||
CR0_ET = 1 << 4, ///< Extension type
|
||||
CR0_NE = 1 << 15, ///< Numeric error
|
||||
CR0_WP = 1 << 16, ///< Write protect
|
||||
CR0_AM = 1 << 18, ///< Alignment mask
|
||||
CR0_NW = 1 << 29, ///< Not-write through caching
|
||||
CR0_CD = 1 << 30, ///< Cache disable
|
||||
CR0_PG = 1 << 31, ///< Paging
|
||||
};
|
||||
|
||||
/*! \brief Control Register 4
|
||||
*
|
||||
* \see [ISDMv3, 2.5 Control Registers](intel_manual_vol3.pdf#page=77)
|
||||
*/
|
||||
enum CR4 {
|
||||
CR4_VME = 1 << 0, ///< Virtual 8086 Mode Extensions
|
||||
CR4_PVI = 1 << 1, ///< Protected-mode Virtual Interrupts
|
||||
CR4_TSD = 1 << 2, ///< Time Stamp Disable
|
||||
CR4_DE = 1 << 3, ///< Debugging Extensions
|
||||
CR4_PSE = 1 << 4, ///< Page Size Extension
|
||||
CR4_PAE = 1 << 5, ///< Physical Address Extension
|
||||
CR4_MCE = 1 << 6, ///< Machine Check Exception
|
||||
CR4_PGE = 1 << 7, ///< Page Global Enabled
|
||||
CR4_PCE = 1 << 8, ///< Performance-Monitoring Counter enable
|
||||
CR4_OSFXSR =
|
||||
1 << 9, ///< Operating system support for FXSAVE and FXRSTOR instructions
|
||||
CR4_OSXMMEXCPT = 1 << 10, ///< Operating System Support for Unmasked SIMD
|
||||
///< Floating-Point Exceptions
|
||||
CR4_UMIP = 1 << 11, ///< User-Mode Instruction Prevention
|
||||
CR4_VMXE = 1 << 13, ///< Virtual Machine Extensions Enable
|
||||
CR4_SMXE = 1 << 14, ///< Safer Mode Extensions Enable
|
||||
CR4_FSGSBASE = 1 << 16, ///< Enables the instructions RDFSBASE, RDGSBASE,
|
||||
///< WRFSBASE, and WRGSBASE.
|
||||
CR4_PCIDE = 1 << 17, ///< PCID Enable
|
||||
CR4_OSXSAVE = 1 << 18, ///< XSAVE and Processor Extended States Enable
|
||||
CR4_SMEP = 1 << 20, ///< Supervisor Mode Execution Protection Enable
|
||||
CR4_SMAP = 1 << 21, ///< Supervisor Mode Access Prevention Enable
|
||||
CR4_PKE = 1 << 22, ///< Protection Key Enable
|
||||
};
|
||||
|
||||
/*! \brief Access to the Control Register
|
||||
*
|
||||
* \see [ISDMv3, 2.5 Control Registers](intel_manual_vol3.pdf#page=73)
|
||||
* \tparam id Control Register to access
|
||||
*/
|
||||
template <uint8_t id>
|
||||
class CR {
|
||||
public:
|
||||
/*! \brief Read the value of the current Control Register
|
||||
*
|
||||
* \return Value stored in the CR
|
||||
*/
|
||||
inline static uintptr_t read(void) {
|
||||
uintptr_t val;
|
||||
asm volatile("mov %%cr%c1, %0" : "=r"(val) : "n"(id));
|
||||
return val;
|
||||
}
|
||||
|
||||
/*! \brief Write a value into the current Control Register
|
||||
*
|
||||
* \param value Value to write into the CR
|
||||
*/
|
||||
inline static void write(uintptr_t value) {
|
||||
asm volatile("mov %0, %%cr%c1" : : "r"(value), "n"(id));
|
||||
}
|
||||
};
|
||||
} // namespace Core
|
||||
@ -0,0 +1,141 @@
|
||||
/*! \file
|
||||
* \brief \ref Core::Interrupt "Interrupt control" and \ref
|
||||
* Core::Interrupt::Vector "interrupt vector list"
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "../types.h"
|
||||
|
||||
namespace Core {
|
||||
/*! \brief Exception and Interrupt control
|
||||
*
|
||||
* \see [ISDMv3, Chapter 6 Interrupt and Exception
|
||||
* Handling](intel_manual_vol3.pdf#page=185)
|
||||
*/
|
||||
namespace Interrupt {
|
||||
|
||||
/*! \brief Bit in `FLAGS` register corresponding to the current interrupt state
|
||||
*/
|
||||
constexpr uintptr_t FLAG_ENABLE = 1 << 9;
|
||||
|
||||
/*! \brief List of used interrupt vectors.
|
||||
*
|
||||
* The exception vectors from `0` to `31` are reserved for traps, faults and
|
||||
* aborts. Their behavior is different for each exception, some push an *error
|
||||
* code*, some are not recoverable.
|
||||
*
|
||||
* The vectors from `32` to `255` are user defined interrupts.
|
||||
*
|
||||
* \see [ISDMv3, 6.15 Exception and Interrupt
|
||||
* Reference](intel_manual_vol3.pdf#page=203)
|
||||
* \todo(12) Add Keyboard and Panic vector numbers
|
||||
*/
|
||||
enum Vector {
|
||||
// Predefined Exceptions
|
||||
DIVISON_BY_ZERO =
|
||||
0, ///< Divide-by-zero Error (at a `DIV`/`IDIV` instruction)
|
||||
DEBUG = 1, ///< Debug exception
|
||||
NON_MASKABLE_INTERRUPT = 2, ///< Non Maskable Interrupt
|
||||
BREAKPOINT = 3, ///< Breakpoint exception (used for debugging)
|
||||
OVERFLOW = 4, ///< Overflow exception (at `INTO` instruction)
|
||||
BOUND_RANGE_EXCEEDED = 5, ///< Bound Range Exceeded (at `BOUND` instruction)
|
||||
INVALID_OPCODE = 6, ///< Opcode at Instruction Pointer is invalid (you
|
||||
///< probably shouldn't be here)
|
||||
DEVICE_NOT_AVAILABLE =
|
||||
7, ///< FPU/MMX/SSE instruction but corresponding extension not activated
|
||||
DOUBLE_FAULT = 8, ///< Exception occurred while trying to call
|
||||
///< exception/interrupt handler
|
||||
// Coprocessor Segment Overrun (Legacy)
|
||||
INVALID_TSS =
|
||||
10, ///< Invalid Task State Segment selector (see error code for index)
|
||||
SEGMENT_NOT_PRESENT =
|
||||
11, ///< Segment not available (see error code for selector index)
|
||||
STACK_SEGMENT_FAULT = 12, ///< Stack segment not available or invalid (see
|
||||
///< error code for selector index)
|
||||
GENERAL_PROTECTION_FAULT =
|
||||
13, ///< Operation not allowed (see error code for selector index)
|
||||
PAGE_FAULT = 14, ///< Operation on Page (r/w/x) not allowed for current
|
||||
///< privilege (error code + `cr2`)
|
||||
// reserved
|
||||
FLOATING_POINT_EXCEPTION = 16, ///< x87 FPU error (at `WAIT`/`FWAIT`),
|
||||
///< accidentally \ref Core::CR0_NE set?
|
||||
ALIGNMENT_CHECK = 17, ///< Unaligned memory access in userspace (Exception
|
||||
///< activated by \ref Core::CR0_AM)
|
||||
MACHINE_CHECK = 18, ///< Model specific exception
|
||||
SIMD_FP_EXCEPTION =
|
||||
19, ///< SSE/MMX error (if \ref Core::CR4_OSXMMEXCPT activated)
|
||||
SECURITY_EXCEPTION = 31,
|
||||
|
||||
// Interrupts
|
||||
};
|
||||
constexpr size_t VECTORS = 256;
|
||||
|
||||
/*! \brief Check if interrupts are enabled on this CPU
|
||||
*
|
||||
* This is done by pushing the `FLAGS` register onto stack,
|
||||
* reading it into a register and checking the corresponding bit.
|
||||
*
|
||||
* \return `true` if enabled, `false` if disabled
|
||||
*/
|
||||
inline bool isEnabled() {
|
||||
uintptr_t out;
|
||||
asm volatile(
|
||||
"pushf\n\t"
|
||||
"pop %0\n\t"
|
||||
: "=r"(out)
|
||||
:
|
||||
: "memory");
|
||||
return (out & FLAG_ENABLE) != 0;
|
||||
}
|
||||
|
||||
/*! \brief Allow interrupts
|
||||
*
|
||||
* Enables interrupt handling by executing the instruction `sti`.
|
||||
* Since this instruction is delayed by one cycle, an subsequent `nop` is
|
||||
* executed (to ensure deterministic behavior, independent from the compiler
|
||||
* generated code)
|
||||
*
|
||||
* A pending interrupt (i.e., those arriving while interrupts were disabled)
|
||||
* will be delivered after re-enabling interrupts.
|
||||
*
|
||||
* \see [ISDMv2, Chapter 4. STI - Set Interrupt
|
||||
* Flag](intel_manual_vol2.pdf#page=1297)
|
||||
*/
|
||||
inline void enable() { asm volatile("sti\n\t nop\n\t" : : : "memory"); }
|
||||
|
||||
/*! \brief Forbid interrupts
|
||||
*
|
||||
* Prevents interrupt handling by executing the instruction `cli`.
|
||||
* Will return the previous interrupt state.
|
||||
* \return `true` if interrupts were enabled at the time of executing this
|
||||
* function, `false` if they were already disabled.
|
||||
*
|
||||
* \see [ISDMv2, Chapter 3. CLI - Ckear Interrupt
|
||||
* Flag](intel_manual_vol2.pdf#page=245)
|
||||
*/
|
||||
inline bool disable() {
|
||||
bool enabled = isEnabled();
|
||||
asm volatile("cli\n\t" : : : "memory");
|
||||
|
||||
return enabled;
|
||||
}
|
||||
|
||||
/*! \brief Restore interrupt
|
||||
*
|
||||
* Restore the interrupt state to the state prior to calling \ref disable() by
|
||||
* using its return value.
|
||||
*
|
||||
* \note This function will never disable interrupts, even if val is false!
|
||||
* This function is designed to allow nested disabling and restoring of
|
||||
* the interrupt state.
|
||||
*
|
||||
* \param val if set to `true`, interrupts will be enabled; nothing will happen
|
||||
* on false.
|
||||
*/
|
||||
inline void restore(bool val) {
|
||||
if (val) {
|
||||
enable();
|
||||
}
|
||||
}
|
||||
} // namespace Interrupt
|
||||
} // namespace Core
|
||||
@ -0,0 +1,103 @@
|
||||
/*! \file
|
||||
* \brief \ref Core::MSRs "Identifiers" for \ref Core::MSR "Model-Specific
|
||||
* Register"
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "../types.h"
|
||||
|
||||
namespace Core {
|
||||
/*! \brief Model-Specific Register Identifiers
|
||||
*
|
||||
* Selection of useful identifiers.
|
||||
*
|
||||
* \see [ISDMv4](intel_manual_vol4.pdf)
|
||||
*/
|
||||
enum MSRs {
|
||||
MSR_PLATFORM_INFO =
|
||||
0xce, ///< Platform information including bus frequency (Intel)
|
||||
MSR_TSC_DEADLINE = 0x6e0, ///< Register for \ref LAPIC::Timer Deadline mode
|
||||
// Fast system calls
|
||||
// XXX: Remove if we don't do fast syscalls
|
||||
MSR_EFER =
|
||||
0xC0000080, ///< Extended Feature Enable Register, \see Core::MSR_EFER
|
||||
MSR_STAR = 0xC0000081, ///< eip (protected mode), ring 0 and 3 segment bases
|
||||
MSR_LSTAR = 0xC0000082, ///< rip (long mode)
|
||||
MSR_SFMASK = 0xC0000084, ///< lower 32 bit: flag mask, if bit is set
|
||||
///< corresponding rflag is cleared through syscall
|
||||
|
||||
// CPU local variables
|
||||
MSR_FS_BASE = 0xC0000100,
|
||||
MSR_GS_BASE = 0xC0000101, ///< Current GS base pointer
|
||||
MSR_SHADOW_GS_BASE = 0xC0000102, ///< Usually called `MSR_KERNEL_GS_BASE` but
|
||||
///< this is misleading
|
||||
};
|
||||
|
||||
/* \brief Important bits in Extended Feature Enable Register (EFER)
|
||||
*
|
||||
* \see [ISDMv3, 2.2.1 Extended Feature Enable
|
||||
* Register](intel_manual_vol3.pdf#page=69)
|
||||
* \see [AAPMv2, 3.1.7 Extended Feature Enable
|
||||
* Register](amd64_manual_vol2.pdf#page=107)
|
||||
*/
|
||||
enum MSR_EFER {
|
||||
MSR_EFER_SCE = 1 << 0, ///< System Call Extensions
|
||||
MSR_EFER_LME = 1 << 8, ///< Long mode enable
|
||||
MSR_EFER_LMA = 1 << 10, ///< Long mode active
|
||||
MSR_EFER_NXE = 1 << 11, ///< No-Execute Enable
|
||||
MSR_EFER_SVME = 1 << 12, ///< Secure Virtual Machine Enable
|
||||
MSR_EFER_LMSLE = 1 << 13, ///< Long Mode Segment Limit Enable
|
||||
MSR_EFER_FFXSR = 1 << 14, ///< Fast `FXSAVE`/`FXRSTOR` instruction
|
||||
MSR_EFER_TCE = 1 << 15, ///< Translation Cache Extension
|
||||
};
|
||||
|
||||
/*! \brief Access to the Model-Specific Register (MSR)
|
||||
*
|
||||
* \see [ISDMv3, 9.4 Model-Specific Registers
|
||||
* (MSRs)](intel_manual_vol3.pdf#page=319)
|
||||
* \see [ISDMv4](intel_manual_vol4.pdf)
|
||||
* \tparam id ID of the Model-Specific Register to access
|
||||
*/
|
||||
template <enum MSRs id>
|
||||
class MSR {
|
||||
/*! \brief Helper to access low and high bits of a 64 bit value
|
||||
* \internal
|
||||
*/
|
||||
union uint64_parts {
|
||||
struct {
|
||||
uint32_t low;
|
||||
uint32_t high;
|
||||
} __attribute__((packed));
|
||||
uint64_t value;
|
||||
|
||||
explicit uint64_parts(uint64_t value = 0) : value(value) {}
|
||||
};
|
||||
|
||||
public:
|
||||
/*! \brief Read the value of the current MSR
|
||||
*
|
||||
* \return Value stored in the MSR
|
||||
*
|
||||
* \see [ISDMv2, Chapter 4. RDMSR - Read from Model Specific
|
||||
* Register](intel_manual_vol2.pdf#page=1186)
|
||||
*/
|
||||
static inline uint64_t read() {
|
||||
uint64_parts p;
|
||||
asm volatile("rdmsr \n\t" : "=a"(p.low), "=d"(p.high) : "c"(id));
|
||||
return p.value;
|
||||
}
|
||||
|
||||
/*! \brief Write a value into the current MSR
|
||||
*
|
||||
* \param value Value to write into the MSR
|
||||
*
|
||||
* \see [ISDMv2, Chapter 5. WRMSR - Write to Model Specific
|
||||
* Register](intel_manual_vol2.pdf#page=1912)
|
||||
*/
|
||||
static inline void write(uint64_t value) {
|
||||
uint64_parts p(value);
|
||||
asm volatile("wrmsr \n\t" : : "c"(id), "a"(p.low), "d"(p.high));
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Core
|
||||
@ -0,0 +1,201 @@
|
||||
/*! \file
|
||||
* \brief \ref CPUID queries information about the processor
|
||||
*/
|
||||
#pragma once
|
||||
#include "../types.h"
|
||||
|
||||
/*! \brief Query information about the processor
|
||||
*
|
||||
* \note This is an interface to the `cpuid` instruction, which can return
|
||||
* information about the processor. It should therefor **not** be confused with
|
||||
* functionality to
|
||||
* \ref Core::getID() "retrieve the ID of the current CPU (core)"!
|
||||
*/
|
||||
namespace CPUID {
|
||||
|
||||
/*! \brief Structure for register values returned by `cpuid` instruction
|
||||
*/
|
||||
union Reg {
|
||||
struct {
|
||||
uint32_t ebx, edx, ecx, eax;
|
||||
};
|
||||
char value[16];
|
||||
};
|
||||
|
||||
enum Function {
|
||||
HIGHEST_FUNCTION_PARAMETER = 0x0, ///< Maximum Input Value for Basic CPUID
|
||||
///< Information (in register `eax`)
|
||||
MANUFACTURER_ID = 0x0, ///< CPU String (in register `ebx`, `ecx` and `edx`)
|
||||
PROCESSOR_INFO = 0x1, ///< Version Information like Type, Family, Model (in
|
||||
///< register `eax`)
|
||||
FEATURE_BITS = 0x1, ///< Feature Information (in register `ecx` and `edx`)
|
||||
CACHE_INFORMATION = 0x2, ///< Cache and TLB Information
|
||||
PROCESSOR_SERIAL_NUMBER = 0x3, ///< deprecated
|
||||
HIGHEST_EXTENDED_FUNCTION =
|
||||
0x80000000, ///< Maximum Input Value for Extended Function CPUID (in
|
||||
///< register `eax`)
|
||||
EXTENDED_PROCESSOR_INFO = 0x80000001, ///< Extended Processor Signature and
|
||||
///< Feature Bits (in register `eax`)
|
||||
EXTENDED_FEATURE_BITS = 0x80000001, ///< Extended Feature Information (in
|
||||
///< register `ecx` and `edx`)
|
||||
PROCESSOR_BRAND_STRING_1 = 0x80000002, ///< Processor Brand String (1/3)
|
||||
PROCESSOR_BRAND_STRING_2 = 0x80000003, ///< Processor Brand String (2/3)
|
||||
PROCESSOR_BRAND_STRING_3 = 0x80000004, ///< Processor Brand String (3/3)
|
||||
ADVANCED_POWER_MANAGEMENT = 0x80000007, ///< Advanced Power Management (with
|
||||
///< Invariant TSC in register `edx`)
|
||||
ADDRESS_SIZES =
|
||||
0x80000008, ///< Linear/Physical Address size (in register `eax`)
|
||||
};
|
||||
|
||||
/*! \brief Get CPU identification and feature information
|
||||
*
|
||||
* \param eax Requested feature
|
||||
* \return Register values filled by instruction `cpuid` for the requested
|
||||
* feature
|
||||
*
|
||||
* \see [ISDMv2, Chapter 3. CPUID - CPU
|
||||
* Identification](intel_manual_vol2.pdf#page=292)
|
||||
*/
|
||||
inline Reg get(Function eax) {
|
||||
Reg r;
|
||||
asm volatile("cpuid \n\t"
|
||||
: "=a"(r.eax), "=b"(r.ebx), "=c"(r.ecx), "=d"(r.edx)
|
||||
: "0"(eax));
|
||||
return r;
|
||||
}
|
||||
|
||||
enum FeatureECX {
|
||||
FEATURE_SSE3 = 1 << 0, ///< Prescott New Instructions-SSE3 (PNI)
|
||||
FEATURE_PCLMUL = 1 << 1, ///< Carry-less Multiplication
|
||||
FEATURE_DTES64 = 1 << 2, ///< 64-bit debug store (edx bit 21)
|
||||
FEATURE_MONITOR = 1 << 3, ///< MONITOR and MWAIT instructions (SSE3)
|
||||
FEATURE_DS_CPL = 1 << 4, ///< CPL qualified debug store
|
||||
FEATURE_VMX = 1 << 5, ///< Virtual Machine eXtensions
|
||||
FEATURE_SMX = 1 << 6, ///< Safer Mode Extensions (LaGrande)
|
||||
FEATURE_EST = 1 << 7, ///< Enhanced SpeedStep
|
||||
FEATURE_TM2 = 1 << 8, ///< Thermal Monitor 2
|
||||
FEATURE_SSSE3 = 1 << 9, ///< Supplemental SSE3 instructions
|
||||
FEATURE_CID = 1 << 10, ///< L1 Context ID
|
||||
FEATURE_SDBG = 1 << 11, ///< Silicon Debug interface
|
||||
FEATURE_FMA = 1 << 12, ///< Fused multiply-add (FMA3)
|
||||
FEATURE_CX16 = 1 << 13, ///< CMPXCHG16B instruction
|
||||
FEATURE_ETPRD = 1 << 14, ///< Can disable sending task priority messages
|
||||
FEATURE_PDCM = 1 << 15, ///< Perfmon & debug capability
|
||||
FEATURE_PCIDE = 1 << 17, ///< Process context identifiers (CR4 bit 17)
|
||||
FEATURE_DCA = 1 << 18, ///< Direct cache access for DMA writes
|
||||
FEATURE_SSE4_1 = 1 << 19, ///< SSE4.1 instructions
|
||||
FEATURE_SSE4_2 = 1 << 20, ///< SSE4.2 instructions
|
||||
FEATURE_X2APIC = 1 << 21, ///< x2APIC
|
||||
FEATURE_MOVBE = 1 << 22, ///< MOVBE instruction (big-endian)
|
||||
FEATURE_POPCNT = 1 << 23, ///< POPCNT instruction
|
||||
FEATURE_TSC_DEADLINE =
|
||||
1
|
||||
<< 24, ///< APIC implements one-shot operation using a TSC deadline value
|
||||
FEATURE_AES = 1 << 25, ///< AES instruction set
|
||||
FEATURE_XSAVE = 1 << 26, ///< XSAVE, XRESTOR, XSETBV, XGETBV
|
||||
FEATURE_OSXSAVE = 1 << 27, ///< XSAVE enabled by OS
|
||||
FEATURE_AVX = 1 << 28, ///< Advanced Vector Extensions
|
||||
FEATURE_F16C = 1 << 29, ///< F16C (half-precision) FP feature
|
||||
FEATURE_RDRND =
|
||||
1 << 30, ///< RDRAND (on-chip random number generator) feature
|
||||
FEATURE_HYPERVISOR =
|
||||
1 << 31 ///< Hypervisor present (always zero on physical CPUs)
|
||||
};
|
||||
|
||||
enum FeatureEDX {
|
||||
FEATURE_FPU = 1 << 0, ///< Onboard x87 FPU
|
||||
FEATURE_VME =
|
||||
1 << 1, ///< Virtual 8086 mode extensions (such as VIF, VIP, PIV)
|
||||
FEATURE_DE = 1 << 2, ///< Debugging extensions (CR4 bit 3)
|
||||
FEATURE_PSE = 1 << 3, ///< Page Size Extension
|
||||
FEATURE_TSC = 1 << 4, ///< Time Stamp Counter
|
||||
FEATURE_MSR = 1 << 5, ///< Model-specific registers
|
||||
FEATURE_PAE = 1 << 6, ///< Physical Address Extension
|
||||
FEATURE_MCE = 1 << 7, ///< Machine Check Exception
|
||||
FEATURE_CX8 = 1 << 8, ///< CMPXCHG8 (compare-and-swap) instruction
|
||||
FEATURE_APIC =
|
||||
1 << 9, ///< Onboard Advanced Programmable Interrupt Controller
|
||||
FEATURE_SEP = 1 << 11, ///< SYSENTER and SYSEXIT instructions
|
||||
FEATURE_MTRR = 1 << 12, ///< Memory Type Range Registers
|
||||
FEATURE_PGE = 1 << 13, ///< Page Global Enable bit in CR4
|
||||
FEATURE_MCA = 1 << 14, ///< Machine check architecture
|
||||
FEATURE_CMOV = 1 << 15, ///< Conditional move and FCMOV instructions
|
||||
FEATURE_PAT = 1 << 16, ///< Page Attribute Table
|
||||
FEATURE_PSE36 = 1 << 17, ///< 36-bit page size extension
|
||||
FEATURE_PSN = 1 << 18, ///< Processor Serial Number
|
||||
FEATURE_CLF = 1 << 19, ///< CLFLUSH instruction (SSE2)
|
||||
FEATURE_DTES = 1 << 21, ///< Debug store: save trace of executed jumps
|
||||
FEATURE_ACPI = 1 << 22, ///< Onboard thermal control MSRs for ACPI
|
||||
FEATURE_MMX = 1 << 23, ///< MMX instructions
|
||||
FEATURE_FXSR = 1 << 24, ///< FXSAVE, FXRESTOR instructions, CR4 bit 9
|
||||
FEATURE_SSE = 1 << 25, ///< SSE instructions (a.k.a. Katmai New Instructions)
|
||||
FEATURE_SSE2 = 1 << 26, ///< SSE2 instructions
|
||||
FEATURE_SS = 1 << 27, ///< CPU cache implements self-snoop
|
||||
FEATURE_HTT = 1 << 28, ///< Hyper-threading
|
||||
FEATURE_TM1 = 1 << 29, ///< Thermal monitor automatically limits temperature
|
||||
FEATURE_IA64 = 1 << 30, ///< IA64 processor emulating x86
|
||||
FEATURE_PBE = 1 << 31 ///< Pending Break Enable (PBE# pin) wakeup capability
|
||||
};
|
||||
|
||||
enum ExtendedFeatureEDX {
|
||||
EXTENDED_FEATURE_FPU = 1 << 0, ///< Onboard x87 FPU
|
||||
EXTENDED_FEATURE_VME =
|
||||
1 << 1, ///< Virtual 8086 mode extensions (such as VIF, VIP, PIV)
|
||||
EXTENDED_FEATURE_DE = 1 << 2, ///< Debugging extensions (CR4 bit 3)
|
||||
EXTENDED_FEATURE_PSE = 1 << 3, ///< Page Size Extension
|
||||
EXTENDED_FEATURE_TSC = 1 << 4, ///< Time Stamp Counter
|
||||
EXTENDED_FEATURE_MSR = 1 << 5, ///< Model-specific registers
|
||||
EXTENDED_FEATURE_PAE = 1 << 6, ///< Physical Address Extension
|
||||
EXTENDED_FEATURE_MCE = 1 << 7, ///< Machine Check Exception
|
||||
EXTENDED_FEATURE_CX8 = 1 << 8, ///< CMPXCHG8 (compare-and-swap) instruction
|
||||
EXTENDED_FEATURE_APIC =
|
||||
1 << 9, ///< Onboard Advanced Programmable Interrupt Controller
|
||||
EXTENDED_FEATURE_SYSCALL = 1 << 11, ///< SYSCALL and SYSRET instructions
|
||||
EXTENDED_FEATURE_MTRR = 1 << 12, ///< Memory Type Range Registers
|
||||
EXTENDED_FEATURE_PGE = 1 << 13, ///< Page Global Enable bit in CR4
|
||||
EXTENDED_FEATURE_MCA = 1 << 14, ///< Machine check architecture
|
||||
EXTENDED_FEATURE_CMOV = 1 << 15, ///< Conditional move and FCMOV instructions
|
||||
EXTENDED_FEATURE_PAT = 1 << 16, ///< Page Attribute Table
|
||||
EXTENDED_FEATURE_PSE36 = 1 << 17, ///< 36-bit page size extension
|
||||
EXTENDED_FEATURE_MP = 1 << 19, ///< Multiprocessor Capable
|
||||
EXTENDED_FEATURE_NX = 1 << 20, ///< Non-executable bit
|
||||
EXTENDED_FEATURE_MMXEXT = 1 << 22, ///< extended MMX instructions
|
||||
EXTENDED_FEATURE_MMX = 1 << 23, ///< MMX instructions
|
||||
EXTENDED_FEATURE_FXSR = 1
|
||||
<< 24, ///< FXSAVE, FXRESTOR instructions, CR4 bit 9
|
||||
EXTENDED_FEATURE_FXSR_OPT = 1 << 25, ///< FXSAVE, FXRESTOR optimizations
|
||||
EXTENDED_FEATURE_PDPE1GB = 1 << 26, ///< Gibibyte Pages
|
||||
EXTENDED_FEATURE_RDTSCP = 1 << 27, ///< CPU cache implements self-snoop
|
||||
EXTENDED_FEATURE_LM = 1 << 29, ///< Long Mode (x64)
|
||||
EXTENDED_FEATURE_3DNOWEXT = 1 << 30, ///< Extended 3DNow! instructions
|
||||
EXTENDED_FEATURE_3DNOW = 1 << 31 ///< 3DNow! instructions
|
||||
};
|
||||
|
||||
/*! \brief Check if feature is provided by this system
|
||||
*
|
||||
* \param feature Feature to test
|
||||
* \return `true` if available, `false` otherwise
|
||||
*/
|
||||
inline bool has(enum FeatureECX feature) {
|
||||
return (get(FEATURE_BITS).ecx & feature) != 0;
|
||||
}
|
||||
|
||||
/*! \brief Check if feature is provided by this system
|
||||
*
|
||||
* \param feature Feature to test
|
||||
* \return `true` if available, `false` otherwise
|
||||
*/
|
||||
inline bool has(enum FeatureEDX feature) {
|
||||
return (get(FEATURE_BITS).edx & feature) != 0;
|
||||
}
|
||||
|
||||
/*! \brief Check if feature is provided by this system
|
||||
*
|
||||
* \param feature Extended feature to test
|
||||
* \return `true` if available, `false` if either feature or extended features
|
||||
* are unavailable
|
||||
*/
|
||||
inline bool has(enum ExtendedFeatureEDX feature) {
|
||||
return (get(EXTENDED_FEATURE_BITS).edx & feature) != 0;
|
||||
}
|
||||
} // namespace CPUID
|
||||
@ -0,0 +1,36 @@
|
||||
#include "gdt.h"
|
||||
|
||||
#include "core.h"
|
||||
|
||||
namespace GDT {
|
||||
|
||||
// The static 32-bit Global Descriptor Table (GDT)
|
||||
alignas(16) constinit SegmentDescriptor protected_mode[] = {
|
||||
// Null descriptor
|
||||
{},
|
||||
|
||||
// Global code segment von 0-4GB
|
||||
SegmentDescriptor::Segment(0, UINT32_MAX, true, 0, SIZE_32BIT),
|
||||
|
||||
// Global data segment von 0-4GB
|
||||
SegmentDescriptor::Segment(0, UINT32_MAX, false, 0, SIZE_32BIT),
|
||||
};
|
||||
extern "C" constexpr Pointer gdt_protected_mode_pointer(protected_mode);
|
||||
|
||||
// The static 64-bit Global Descriptor Table (GDT)
|
||||
// \see [ISDMv3 3.2.4 Segmentation in IA-32e
|
||||
// Mode](intel_manual_vol3.pdf#page=91)
|
||||
alignas(16) constinit SegmentDescriptor long_mode[] = {
|
||||
// Null descriptor
|
||||
SegmentDescriptor::Null(),
|
||||
|
||||
// Global code segment
|
||||
SegmentDescriptor::Segment64(true, 0),
|
||||
|
||||
// Global data segment
|
||||
SegmentDescriptor::Segment64(false, 0),
|
||||
|
||||
};
|
||||
extern "C" constexpr Pointer gdt_long_mode_pointer(long_mode);
|
||||
|
||||
} // namespace GDT
|
||||
@ -0,0 +1,199 @@
|
||||
/*! \file
|
||||
* \brief The \ref GDT "Global Descriptor Table (GDT)".
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "../types.h"
|
||||
|
||||
/*! \brief Abstracts the GDT that, primarily, contains descriptors to memory
|
||||
* segments.
|
||||
* \ingroup memory
|
||||
*
|
||||
* The GDT is a table that primarily contains segment descriptors. Segment
|
||||
* descriptors has a size of 8 Bytes and contains the size, position, access
|
||||
* rights, and purpose of such a segment. Unlike the LDT, the GDT is shared
|
||||
* between all processes and may contain TSS and LDT descriptors. For the
|
||||
* kernel, the first entry is required to be a null descriptor and the code and
|
||||
* data segments. To support user-mode processes, additional TSS, code, and data
|
||||
* segments for ring 3 must be added.
|
||||
*
|
||||
* The base address and size of the GDT are written to the GDTR register during
|
||||
* boot (via. `lgdt`).
|
||||
*
|
||||
* \see [ISDMv3, 2.4.1; Global Descriptor Table Register
|
||||
* (GDTR)](intel_manual_vol3.pdf#page=72)
|
||||
* \see [ISDMv3, 3.5.1; Segment
|
||||
* Descriptor Tables](intel_manual_vol3.pdf#page=99)
|
||||
*/
|
||||
namespace GDT {
|
||||
|
||||
enum Segments {
|
||||
SEGMENT_NULL = 0,
|
||||
SEGMENT_KERNEL_CODE,
|
||||
SEGMENT_KERNEL_DATA,
|
||||
};
|
||||
|
||||
/*! \brief Unit of the segment limit
|
||||
*/
|
||||
enum Granularity {
|
||||
GRANULARITY_BYTES = 0, ///< Segment limit in Bytes
|
||||
GRANULARITY_4KBLOCK = 1 ///< Segment limit in blocks of 4 Kilobytes
|
||||
};
|
||||
|
||||
/*! \brief Descriptor type */
|
||||
enum DescriptorType {
|
||||
DESCRIPTOR_SYSTEM = 0, ///< entry is a system segment
|
||||
DESCRIPTOR_CODEDATA = 1, ///< entry is a code/data segment
|
||||
};
|
||||
|
||||
/*! \brief Address width
|
||||
*/
|
||||
enum Size {
|
||||
SIZE_16BIT = 0, ///< 16-bit (D/B = 0, L = 0)
|
||||
SIZE_32BIT = 2, ///< 32-bit (D/B = 1, L = 0)
|
||||
SIZE_64BIT = 1, ///< 64-bit (D/B = 0, L = 1)
|
||||
};
|
||||
|
||||
/*! \brief Type flags for used descriptor types
|
||||
*/
|
||||
enum TypeFlags {
|
||||
TYPE_DATA_RW = 0b0010ull, ///< Data rw, not expanding down
|
||||
TYPE_CODE_RX = 0b1010ull, ///< Code rx, non-conforming
|
||||
};
|
||||
|
||||
/*! \brief Describes the structure of segment descriptors
|
||||
*
|
||||
* A data structure that contains size, position, access rights, and purpose of
|
||||
* any segment. Segment descriptors are used in both the GDT, as well as in
|
||||
* LDTs.
|
||||
*
|
||||
* \see [ISDMv3, 3.4.5; Segment Descriptors](intel_manual_vol3.pdf#page=95)
|
||||
* \see [AAPMv2, 4.7 Legacy Segment Descriptors](amd64_manual_vol2.pdf#page=132)
|
||||
* \see [AAPMv2, 4.8 Long-Mode Segment
|
||||
* Descriptors](amd64_manual_vol2.pdf#page=140)
|
||||
*/
|
||||
union SegmentDescriptor {
|
||||
// Universally valid values (shared across all segment types)
|
||||
struct {
|
||||
uint64_t limit_low : 16; ///< Least-significant bits of segment size
|
||||
///< (influenced by granularity!)
|
||||
uint64_t base_low : 24; ///< Least-significant bits of base address
|
||||
uint64_t
|
||||
type : 4; ///< Meaning of those 4 bits depends on descriptor_type below
|
||||
DescriptorType descriptor_type : 1; ///< Descriptor type (influences the
|
||||
///< meaning of the 3 bits above)
|
||||
uint64_t privilege_level : 2; ///< Ring for this segment
|
||||
bool present : 1; ///< Entry is valid iff set to `true`
|
||||
uint64_t limit_high : 4; ///< Most-significant bits of segment size
|
||||
bool available : 1; ///< Bit which can be used for other purposes (in
|
||||
///< software)
|
||||
uint64_t custom : 2; ///< Meaning of those 2 bits relate to descriptor_type
|
||||
///< and type
|
||||
Granularity
|
||||
granularity : 1; ///< Unit used as granularity for the segment limit
|
||||
uint64_t base_high : 8; ///< most-significant bits of base address
|
||||
} __attribute__((packed));
|
||||
|
||||
uint64_t value; ///!< Merged value
|
||||
|
||||
/*! \brief Explicitly constructs a null descriptor.
|
||||
*/
|
||||
consteval static SegmentDescriptor Null() {
|
||||
return SegmentDescriptor{
|
||||
.value = 0,
|
||||
};
|
||||
}
|
||||
|
||||
/*! \brief Constructs a code/data segment descriptor.
|
||||
* \param base Base Address of segment
|
||||
* \param limit Size of segment
|
||||
* \param code Code or data segment
|
||||
* \param ring Privilege level
|
||||
* \param size Address width
|
||||
*/
|
||||
consteval static SegmentDescriptor Segment(uintptr_t base, uint32_t limit,
|
||||
bool code, uint64_t ring,
|
||||
Size size) {
|
||||
return SegmentDescriptor{
|
||||
.limit_low = limit >> (limit > 0xFFFFF ? 12 : 0) & 0xFFFF,
|
||||
.base_low = base & 0xFFFFFF,
|
||||
.type = code ? TYPE_CODE_RX : TYPE_DATA_RW,
|
||||
.descriptor_type = DESCRIPTOR_CODEDATA,
|
||||
.privilege_level = ring,
|
||||
.present = true,
|
||||
.limit_high = (limit > 0xFFFFF ? (limit >> 28) : (limit >> 16)) & 0xF,
|
||||
.available = false,
|
||||
.custom = size,
|
||||
.granularity =
|
||||
limit > 0xFFFFF ? GRANULARITY_4KBLOCK : GRANULARITY_BYTES,
|
||||
.base_high = (base >> 24) & 0xFF,
|
||||
};
|
||||
}
|
||||
|
||||
/*! \brief Constructs a 64bit code/data segment descriptor.
|
||||
* \param code Code or data segment
|
||||
* \param ring Privilege level
|
||||
*/
|
||||
consteval static SegmentDescriptor Segment64(bool code, int ring) {
|
||||
return SegmentDescriptor::Segment(0, 0, code, ring, SIZE_64BIT);
|
||||
}
|
||||
|
||||
} __attribute__((packed));
|
||||
|
||||
static_assert(sizeof(SegmentDescriptor) == 8,
|
||||
"GDT::SegmentDescriptor has wrong size");
|
||||
|
||||
/*! \brief Structure that describes a GDT Pointer (aka GDT Descriptor)
|
||||
*
|
||||
* It contains both the length (in bytes) of the GDT (minus 1 byte) and the
|
||||
* pointer to the GDT. The pointer to the GDT can be loaded using the
|
||||
* instruction `lgdt`.
|
||||
*
|
||||
* \note As Intel uses little endian for representing multi-byte values, the
|
||||
* GDT::Pointer structure can be used for 16, 32, and 64 bit descriptor tables:
|
||||
* \verbatim
|
||||
* | 16 bit | 16 bit | 16 bit | 16 bit | 16 bit |
|
||||
* +--------+---------------------------------------+
|
||||
* Pointer | limit | base (up to 64 bit) |
|
||||
* +--------+---------+---------+---------+---------+
|
||||
* | used for 16 bit | ignored... |
|
||||
* | used for 32 bit | ignored... |
|
||||
* | used for 64 bit |
|
||||
* \endverbatim
|
||||
*
|
||||
* \see [ISDMv3, Figure 2-6; Memory Management
|
||||
* Registers](intel_manual_vol3.pdf#page=72)
|
||||
*/
|
||||
struct Pointer {
|
||||
uint16_t limit; //!< GDT size in bytes (minus 1 byte)
|
||||
void* base; //!< GDT base address
|
||||
|
||||
/*! \brief Constructor (automatic length)
|
||||
* \param desc Array of GDT segment descriptors -- must be defined in the same
|
||||
* module!
|
||||
*/
|
||||
template <typename T, size_t LEN>
|
||||
explicit constexpr Pointer(const T (&desc)[LEN])
|
||||
: limit(LEN * sizeof(T) - 1), base(const_cast<T*>(desc)) {}
|
||||
|
||||
/*! \brief Constructor
|
||||
* \param desc Address of the GDT segment descriptors
|
||||
* \param len Number of entries
|
||||
*/
|
||||
consteval Pointer(void* desc, size_t len)
|
||||
: limit(len * sizeof(SegmentDescriptor) - 1), base(desc) {}
|
||||
|
||||
/*! \brief Set an address
|
||||
* \note On change, `lgdt` must be executed again
|
||||
* \param desc Address of the GDT segment descriptors
|
||||
* \param len Number of entries
|
||||
*/
|
||||
constexpr void set(void* desc, size_t len) {
|
||||
limit = len * sizeof(SegmentDescriptor) - 1;
|
||||
base = desc;
|
||||
}
|
||||
} __attribute__((packed));
|
||||
|
||||
static_assert(sizeof(Pointer) == 10, "GDT::Pointer has wrong size");
|
||||
|
||||
} // namespace GDT
|
||||
@ -0,0 +1,35 @@
|
||||
#include "idt.h"
|
||||
|
||||
#include "core_interrupt.h"
|
||||
|
||||
namespace IDT {
|
||||
|
||||
// Interrupt Descriptor Table, 8 Byte aligned
|
||||
constinit struct InterruptDescriptor idt[256] = {};
|
||||
|
||||
// Struct used for loading (the address of) the Interrupt Descriptor Table into
|
||||
// the IDT-Register
|
||||
struct Register {
|
||||
uint16_t limit; // Address of the last valid byte (relative to base)
|
||||
struct InterruptDescriptor* base;
|
||||
explicit Register(uint8_t max = 255) {
|
||||
limit = (max + static_cast<uint16_t>(1)) * sizeof(InterruptDescriptor) - 1;
|
||||
base = idt;
|
||||
}
|
||||
} __attribute__((packed));
|
||||
|
||||
static_assert(sizeof(InterruptDescriptor) == 16,
|
||||
"IDT::InterruptDescriptor has wrong size");
|
||||
static_assert(sizeof(Register) == 10, "IDT::Register has wrong size");
|
||||
static_assert(alignof(decltype(idt)) % 8 == 0, "IDT must be 8 byte aligned!");
|
||||
|
||||
void load() {
|
||||
// Create structure required for writing to idtr and load via lidt
|
||||
Register idtr(Core::Interrupt::VECTORS - 1);
|
||||
asm volatile("lidt %0\n\t" ::"m"(idtr));
|
||||
}
|
||||
|
||||
void set(Core::Interrupt::Vector vector, InterruptDescriptor descriptor) {
|
||||
idt[(uint8_t)vector] = descriptor;
|
||||
}
|
||||
} // namespace IDT
|
||||
@ -0,0 +1,210 @@
|
||||
/*! \file
|
||||
* \brief \ref IDT "Interrupt Descriptor Table (IDT)" containing the entry
|
||||
* points for interrupt handling.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "../types.h"
|
||||
#include "core_interrupt.h"
|
||||
|
||||
/*! \brief "Interrupt Descriptor Table (IDT)
|
||||
* \ingroup interrupts
|
||||
*
|
||||
* \see [ISDMv3 6.14 Exception and Interrupt Handling in 64-bit
|
||||
* Mode](intel_manual_vol3.pdf#page=200)
|
||||
*/
|
||||
|
||||
/*! \brief Preserved interrupt context
|
||||
*
|
||||
* After an interrupt was triggered, the core first saves the basic context
|
||||
* (current code- & stack segment, instruction & stack pointer and the status
|
||||
* flags register) and looks up the handling function for the vector using the
|
||||
* \ref IDT. No other registers are saved or restored automatically. It is the
|
||||
* handlers (our) job to save and restore all modified registers. However, most
|
||||
* handlers in StuBS are implemented directly in C++ utilizing the `interrupt`
|
||||
* attribute: The compiler treats all modified registers as callee-saved, which
|
||||
* saves us a lot of work, but prevents us from knowing the exact contents of
|
||||
* each regiser (we don't know if/when the compiler modified it). `interrupt`
|
||||
* functions receive up to two parameters: A pointer to this /ref
|
||||
* InterruptContext and, depending on the interrupt, an error code, which is
|
||||
* also pushed onto the stack by the CPU. Contrary to "normal" functions, the
|
||||
* compiler will return using the `iret` instruction.
|
||||
*/
|
||||
struct InterruptContext {
|
||||
// Context saved by CPU
|
||||
// uintptr_t error_code; ///< Error Code
|
||||
uintptr_t ip; ///< Instruction Pointer (at interrupt)
|
||||
uintptr_t cs : 16; ///< Code segment (in case of a ring switch it is the
|
||||
///< segment of the user mode)
|
||||
uintptr_t : 0; ///< Alignment (due to 16 bit code segment)
|
||||
uintptr_t flags; ///< Status flags register
|
||||
uintptr_t sp; ///< Stack pointer (at interrupt)
|
||||
uintptr_t ss : 16; ///< Stack segment (in case of a ring switch it is the
|
||||
///< segment of the user mode)
|
||||
uintptr_t : 0; ///< Alignment (due to 16 bit stack segment)
|
||||
} __attribute__((packed));
|
||||
static_assert(sizeof(InterruptContext) == 5 * 8,
|
||||
"InterruptContext has wrong size");
|
||||
|
||||
namespace IDT {
|
||||
/*! \brief Gate types
|
||||
*
|
||||
* \see [ISDMv3 3.5 System Descriptor Types](intel_manual_vol3.pdf#page=99)
|
||||
*/
|
||||
enum Gate {
|
||||
GATE_INT = 0x6, ///< Interrupt Gate (CPU disables interrupts unpon entry)
|
||||
GATE_TRAP = 0x7, ///< Trap Gate (interrupts remain enabled unpon entry)
|
||||
};
|
||||
|
||||
/*! \brief Segment type
|
||||
*
|
||||
* \see [ISDMv3 3.5 System Descriptor Types](intel_manual_vol3.pdf#page=99)
|
||||
*/
|
||||
enum GateSize {
|
||||
GATE_SIZE_16 = 0, ///< 16 bit
|
||||
GATE_SIZE_32 = 1, ///< 32 bit
|
||||
GATE_SIZE_64 = 1, ///< 64 bit
|
||||
};
|
||||
|
||||
/*! \brief Descriptor Privilege Level
|
||||
*/
|
||||
enum DPL {
|
||||
DPL_KERNEL = 0, ///< Ring 0 / Kernel mode
|
||||
/* DPLs 1 and 2 are unused */
|
||||
DPL_USER = 3, ///< Ring 3 / User mode
|
||||
};
|
||||
|
||||
/*! \brief Interrupt handler that returns after execution (trap/fault).
|
||||
*/
|
||||
using ReturningHandler = void (*)(InterruptContext*);
|
||||
|
||||
/*! \brief Interrupt handler that returns after execution (trap/fault) and
|
||||
* receives an error code.
|
||||
*/
|
||||
using ReturningHandlerWithError = void (*)(InterruptContext*, uint64_t);
|
||||
|
||||
/*! \brief Interrupt handler that does **not** return after execution (abort).
|
||||
*/
|
||||
using DivergingHandler = void (*)(InterruptContext*);
|
||||
|
||||
/*! \brief Interrupt handler that does **not** return after execution (abort)
|
||||
* and receives an error code.
|
||||
*/
|
||||
using DivergingHandlerWithError = void (*)(InterruptContext*, uint64_t);
|
||||
|
||||
/*! \brief Interrupt Descriptor stored in the Interrupt-Descriptor Table (IDT)
|
||||
*/
|
||||
struct alignas(8) InterruptDescriptor {
|
||||
uint16_t address_low; ///< lower interrupt function offset
|
||||
uint16_t selector; ///< code segment selector in GDT or LDT
|
||||
union {
|
||||
struct {
|
||||
uint8_t ist : 3; ///< Interrupt Stack Index
|
||||
uint8_t : 5; ///< unused, has to be 0
|
||||
Gate type : 3; ///< gate type
|
||||
GateSize size : 1; ///< gate size
|
||||
uint8_t : 1; ///< unused, has to be 0
|
||||
DPL dpl : 2; ///< descriptor privilege level
|
||||
bool present : 1; ///< present: 1 for interrupts
|
||||
} __attribute__((packed));
|
||||
uint16_t flags;
|
||||
};
|
||||
uint64_t address_high : 48; ///< higher interrupt function offset
|
||||
uint64_t : 0; ///< fill until aligned with 64 bit
|
||||
|
||||
/*! \brief Create a non-present interrupt descriptor.
|
||||
*/
|
||||
InterruptDescriptor() = default;
|
||||
|
||||
/*! \brief Create an interrupt descriptor.
|
||||
*
|
||||
*
|
||||
* \param handler Entry point for interrupt handling
|
||||
* \param ist Stack index from the \ref TaskStateSegment for the interrupt
|
||||
* handler. Set to 0 to use current stack.
|
||||
* \param dpl Permissions required for enter this interrupt handler
|
||||
* (kernel- or user space)
|
||||
*/
|
||||
InterruptDescriptor(uintptr_t handler, uint8_t ist, DPL dpl)
|
||||
: address_low(handler & 0xffff),
|
||||
selector(8), // XXX: This should come from `Segments`
|
||||
ist(ist),
|
||||
type(GATE_INT),
|
||||
size(GATE_SIZE_64),
|
||||
dpl(dpl),
|
||||
present(true),
|
||||
address_high((handler >> 16) & 0xffffffffffff) {}
|
||||
|
||||
/*! \brief Create an interrupt descriptor for a handler that does return
|
||||
* (trap/fault).
|
||||
*
|
||||
* \param handler Entry point for interrupt handling
|
||||
* \param ist Stack index from the \ref TaskStateSegment for the interrupt
|
||||
* handler. Set to 0 to use current stack.
|
||||
* \param dpl Permissions required for enter this interrupt handler
|
||||
* (kernel- or user space)
|
||||
*/
|
||||
static InterruptDescriptor Returning(ReturningHandler handler,
|
||||
uint8_t ist = 0, DPL dpl = DPL_KERNEL) {
|
||||
return {reinterpret_cast<uintptr_t>(handler), ist, dpl};
|
||||
}
|
||||
|
||||
/*! \brief Create an interrupt descriptor for a handler that does return
|
||||
* (traps/fault) and receives an error code.
|
||||
*
|
||||
*
|
||||
* \param handler Entry point for interrupt handling
|
||||
* \param ist Stack index from the \ref TaskStateSegment for the interrupt
|
||||
* handler. Set to 0 to use current stack.
|
||||
* \param dpl Permissions required for enter this interrupt handler
|
||||
* (kernel- or user space)
|
||||
*/
|
||||
static InterruptDescriptor ReturningWithError(
|
||||
ReturningHandlerWithError handler, uint8_t ist = 0,
|
||||
DPL dpl = DPL_KERNEL) {
|
||||
return {reinterpret_cast<uintptr_t>(handler), ist, dpl};
|
||||
}
|
||||
|
||||
/*! \brief Create an interrupt descriptor for a handler that does **not**
|
||||
* return (abort).
|
||||
*
|
||||
*
|
||||
* \param handler Entry point for interrupt handling
|
||||
* \param ist Stack index from the \ref TaskStateSegment for the interrupt
|
||||
* handler. Set to 0 to use current stack.
|
||||
* \param dpl Permissions required for enter this interrupt handler
|
||||
* (kernel- or user space)
|
||||
*/
|
||||
static InterruptDescriptor Diverging(DivergingHandler handler,
|
||||
uint8_t ist = 0, DPL dpl = DPL_KERNEL) {
|
||||
return {reinterpret_cast<uintptr_t>(handler), ist, dpl};
|
||||
}
|
||||
|
||||
/*! \brief Create an interrupt descriptor for a handler that does **not**
|
||||
* return (abort) and receives an error code.
|
||||
*
|
||||
*
|
||||
* \param handler Entry point for interrupt handling
|
||||
* \param ist Stack index from the \ref TaskStateSegment for the interrupt
|
||||
* handler. Set to 0 to use current stack.
|
||||
* \param dpl Permissions required for enter this interrupt handler
|
||||
* (kernel- or user space)
|
||||
*/
|
||||
static InterruptDescriptor DivergingWithError(
|
||||
DivergingHandlerWithError handler, uint8_t ist = 0,
|
||||
DPL dpl = DPL_KERNEL) {
|
||||
return {reinterpret_cast<uintptr_t>(handler), ist, dpl};
|
||||
}
|
||||
|
||||
} __attribute__((packed));
|
||||
static_assert(sizeof(InterruptDescriptor) == 16,
|
||||
"IDT::InterruptDescriptor has wrong size");
|
||||
|
||||
/*! \brief Load the IDT's address and size into the IDT-Register via `idtr`.
|
||||
*/
|
||||
void load();
|
||||
|
||||
/*! \brief Set the idt entry for the given interrupt vector.
|
||||
*/
|
||||
void set(Core::Interrupt::Vector vector, InterruptDescriptor descriptor);
|
||||
} // namespace IDT
|
||||
@ -0,0 +1,44 @@
|
||||
#include "ioapic.h"
|
||||
|
||||
namespace IOAPIC {
|
||||
/*! \brief IOAPIC registers memory mapped into the CPU's address space.
|
||||
*
|
||||
* Access to the actual IOAPIC registers can be obtained by performing the
|
||||
* following steps:
|
||||
* 1. Write the number of the IOAPIC register to the address stored in
|
||||
* `IOREGSEL_REG`
|
||||
* 2. Read the value from / write the value to the address referred to by
|
||||
* `IOWIN_REG`.
|
||||
*
|
||||
* \see [IO-APIC manual](intel_ioapic.pdf#page=8)
|
||||
*/
|
||||
volatile Index *IOREGSEL_REG = reinterpret_cast<volatile Index *>(0xfec00000);
|
||||
/// \copydoc IOREGSEL_REG
|
||||
volatile Register *IOWIN_REG =
|
||||
reinterpret_cast<volatile Register *>(0xfec00010);
|
||||
|
||||
// IOAPIC manual, p. 8
|
||||
const Index IOAPICID_IDX = 0x00;
|
||||
const Index IOREDTBL_IDX = 0x10;
|
||||
|
||||
const uint8_t slot_max = 24;
|
||||
|
||||
void init() {}
|
||||
|
||||
void config(uint8_t slot, Core::Interrupt::Vector vector,
|
||||
TriggerMode trigger_mode, Polarity polarity) {
|
||||
(void)slot;
|
||||
(void)vector;
|
||||
(void)trigger_mode;
|
||||
(void)polarity;
|
||||
}
|
||||
|
||||
void allow(uint8_t slot) { (void)slot; }
|
||||
|
||||
void forbid(uint8_t slot) { (void)slot; }
|
||||
|
||||
bool status(uint8_t slot) {
|
||||
(void)slot;
|
||||
return false;
|
||||
}
|
||||
} // namespace IOAPIC
|
||||
@ -0,0 +1,82 @@
|
||||
/*! \file
|
||||
* \brief \ref IOAPIC abstracts the access to the I/O \ref APIC
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "../types.h"
|
||||
#include "core_interrupt.h"
|
||||
#include "ioapic_registers.h"
|
||||
|
||||
/*! \brief Abstraction of the I/O APIC that is used for management of external
|
||||
* interrupts.
|
||||
* \ingroup interrupts
|
||||
*
|
||||
* The I/O APIC's Core component is the IO-redirection table. This table is
|
||||
* used to configure a flexible mapping between the interrupt number and the
|
||||
* external interruption. Entries within this table have a width of 64 bit. For
|
||||
* convenience, the union \ref IOAPIC::RedirectionTableEntry should be used for
|
||||
* modifying these tables (see file `ioapic_registers.h` for details).
|
||||
*/
|
||||
|
||||
namespace IOAPIC {
|
||||
/*! \brief Initializes the I/O APIC.
|
||||
*
|
||||
* This function will initialize the I/O APIC by initializing the
|
||||
* IO-redirection table with sane default values. The default interrupt-vector
|
||||
* number is chosen such that, in case the interrupt is issued, the panic
|
||||
* handler is executed. In the beginning, all external interrupts are disabled
|
||||
* within the I/O APIC. Apart from the redirection table, the `APICID` (read
|
||||
* from the system description tables during boot) needs to be written to the
|
||||
* `IOAPICID` register (see \ref APIC::getIOAPICID() )
|
||||
*
|
||||
* \todo(12) Implement Function
|
||||
*/
|
||||
void init();
|
||||
|
||||
/*! \brief Creates a mapping between an interrupt vector and an external
|
||||
interrupt.
|
||||
*
|
||||
* \param slot Number of the slot (i.e., the external interrupt) to
|
||||
configure.
|
||||
* \param vector Number of the interrupt vector that will be issued for
|
||||
the external interrupt.
|
||||
* \param trigger_mode Edge or level triggered interrupt signaling
|
||||
(level-triggered interrupts required for the optional serial interface)
|
||||
* \param polarity Polarity of the interrupt signaling (active high or
|
||||
active low)
|
||||
*
|
||||
* \todo(12) Implement Function
|
||||
*/
|
||||
void config(uint8_t slot, Core::Interrupt::Vector vector,
|
||||
TriggerMode trigger_mode = TriggerMode::EDGE,
|
||||
Polarity polarity = Polarity::HIGH);
|
||||
|
||||
/*! \brief Enables the redirection of particular external interrupts to the
|
||||
* CPU(s).
|
||||
*
|
||||
* To fully enable interrupt handling, the interrupts must be enabled for every
|
||||
* CPU (e.g., by calling
|
||||
* \ref Core::Interrupt::enable() in main).
|
||||
* \todo(12) Do that somewhere appropriate.
|
||||
*
|
||||
* \param slot Number of the external interrupt that should be enabled.
|
||||
*
|
||||
* \todo(12) Implement Function
|
||||
*/
|
||||
void allow(uint8_t slot);
|
||||
|
||||
/*! \brief Selectively masks external interrupts by slot number.
|
||||
* \param slot Slot number of the interrupt to be disabled.
|
||||
*
|
||||
* \todo(12) Implement Function
|
||||
*/
|
||||
void forbid(uint8_t slot);
|
||||
|
||||
/*! \brief Check whether an external interrupt source is masked.
|
||||
* \param slot Slot number of the interrupt to be checked.
|
||||
* \return Returns `true` iff the interrupt is unmasked, `false` otherwise
|
||||
*
|
||||
* \todo(12) Implement Function
|
||||
*/
|
||||
bool status(uint8_t slot);
|
||||
} // namespace IOAPIC
|
||||
@ -0,0 +1,227 @@
|
||||
/*! \file
|
||||
* \brief Helper structures for interacting with the \ref IOAPIC "I/O APIC".
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "../types.h"
|
||||
|
||||
namespace IOAPIC {
|
||||
typedef uint32_t Index;
|
||||
typedef uint32_t Register;
|
||||
|
||||
extern volatile Index *IOREGSEL_REG;
|
||||
extern volatile Register *IOWIN_REG;
|
||||
|
||||
/*! \brief I/O APIC Identification
|
||||
*
|
||||
* The IOAPICID register is register number 0x0. The I/O APIC's ID will be read
|
||||
* from the system configuration tables (provided by the BIOS) during boot. The
|
||||
* number can be queried by calling \ref APIC::getIOAPICID(). During
|
||||
* initialization, this number must be written to the IOAPICID register.
|
||||
*
|
||||
* \see [IO-APIC manual](intel_ioapic.pdf#page=9), page 9
|
||||
*/
|
||||
union Identification {
|
||||
struct {
|
||||
uint32_t : 24, ///< Reserved, do not modify
|
||||
id : 4, ///< I/O APIC Identification
|
||||
: 4; ///< Reserved, do not modify
|
||||
};
|
||||
Register value;
|
||||
explicit Identification(Register value) : value(value) {}
|
||||
} __attribute__((packed));
|
||||
static_assert(sizeof(Identification) == 4,
|
||||
"IOAPIC Identification has wrong size");
|
||||
|
||||
/*! \brief Delivery mode specifies the type of interrupt sent to the CPU. */
|
||||
enum DeliveryMode {
|
||||
FIXED = 0, ///< "ordinary" interrupt; send to ALL cores listed in the
|
||||
///< destination bit mask
|
||||
LOWEST_PRIORITY = 1, ///< "ordinary" interrupt; send to the lowest priority
|
||||
///< core from destination mask
|
||||
SMI = 2, ///< System Management Interrupt; vector number required to be 0
|
||||
// Reserved
|
||||
NMI = 4, ///< Non-Maskable Interrupt, vector number ignored, only edge
|
||||
///< triggered
|
||||
INIT = 5, ///< Initialization interrupt (always treated as edge triggered)
|
||||
// Reserved
|
||||
EXTERN_INT = 7 ///< external interrupt (only edge triggered)
|
||||
};
|
||||
|
||||
/*! \brief Way of interpreting the value written to the destination field. */
|
||||
enum DestinationMode {
|
||||
PHYSICAL = 0, ///< Destination contains the physical destination APIC ID
|
||||
LOGICAL = 1 ///< Destination contains a mask of logical APIC IDs
|
||||
};
|
||||
|
||||
/*! \brief Interrupt polarity for the redirection-table entry */
|
||||
enum Polarity {
|
||||
HIGH = 0, ///< active high
|
||||
LOW = 1 ///< active low
|
||||
};
|
||||
|
||||
/*! \brief Trigger mode */
|
||||
enum TriggerMode {
|
||||
EDGE = 0, ///< edge triggered
|
||||
LEVEL = 1 ///< level triggered
|
||||
};
|
||||
|
||||
/*! \brief Interrupt state */
|
||||
enum DeliveryStatus {
|
||||
IDLE = 0, ///< No activity for this interrupt
|
||||
SEND_PENDING =
|
||||
1 ///< Interrupt will be sent as soon as the bus / LAPIC is ready
|
||||
};
|
||||
|
||||
/*! \brief Interrupt masking */
|
||||
enum InterruptMask {
|
||||
UNMASKED = 0, ///< Redirection-table entry is active (non-masked)
|
||||
MASKED = 1 ///< Redirection-table entry is inactive (masked)
|
||||
};
|
||||
|
||||
/*! \brief Entry in the redirection table.
|
||||
*
|
||||
* The redirection table begins with I/O APIC register `0x10` and ends at
|
||||
* `0x3f`.
|
||||
*
|
||||
* Each entry has a size of 64 bit, equaling two I/O APIC registers.
|
||||
* For instance, entry 0 is stored in registers `0x10` and `0x11`, in which the
|
||||
* low-order 32 bit (equals \ref value_low) and high-order 32 bit (equals \ref
|
||||
* value_high) need to be stored.
|
||||
*
|
||||
* The union defined below provides an overlay allowing convenient modification
|
||||
* of individual bits, while the 32-bit values \ref value_low and \ref
|
||||
* value_high can be used for writing to the I/O APIC registers.
|
||||
*
|
||||
* \note [Type punning](https://en.wikipedia.org/wiki/Type_punning#Use_of_union)
|
||||
* is indeed undefined behavior in C++. However, *gcc* explicitly allows
|
||||
* this construct as a [language extension](https://gcc.gnu.org/bugs/#nonbugs).
|
||||
* Some compilers ([other than
|
||||
* gcc](https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html#Type%2Dpunning)
|
||||
* might allow this feature only by disabling strict aliasing
|
||||
* (`-fno-strict-aliasing`). In \StuBS we use this feature extensively due to
|
||||
* the improved code readability.
|
||||
*
|
||||
* \see [IO-APIC manual](intel_ioapic.pdf#page=11), page 11-13
|
||||
*/
|
||||
union RedirectionTableEntry {
|
||||
// @cond ANONYMOUS_STRUCT
|
||||
struct {
|
||||
// @endcond
|
||||
|
||||
/*! \brief Interrupt vector in the \ref IDT "Interrupt Descriptor Table
|
||||
* (IDT)" will be activated when the corresponding external interrupt
|
||||
* triggers.
|
||||
*/
|
||||
uint64_t vector : 8;
|
||||
|
||||
/*! \brief The delivery mode denotes the way the interrupts will be
|
||||
* delivered to the local CPU cores, respectively to their local APICs.
|
||||
*
|
||||
* For StuBS, we use \ref LOWEST_PRIORITY, as all CPU cores have the same
|
||||
* priority and we want to distribute interrupts evenly among them.
|
||||
* It, however, is not guaranteed that this method of load balancing will
|
||||
* work on every system.
|
||||
*/
|
||||
DeliveryMode delivery_mode : 3;
|
||||
|
||||
/*! \brief The destination mode defines how the value stored in \ref
|
||||
* destination will be interpreted.
|
||||
*
|
||||
* For StuBS, we use \ref LOGICAL
|
||||
*/
|
||||
DestinationMode destination_mode : 1;
|
||||
|
||||
/*! \brief Delivery status holds the current status of interrupt delivery.
|
||||
*
|
||||
* \note This field is read only; write accesses to this field will be
|
||||
* ignored.
|
||||
*/
|
||||
DeliveryStatus delivery_status : 1;
|
||||
|
||||
/*! \brief The polarity denotes when an interrupt should be issued.
|
||||
*
|
||||
* For StuBS, we usually use \ref HIGH (i.e., when the interrupt line is,
|
||||
* logically, `1`).
|
||||
*/
|
||||
Polarity polarity : 1;
|
||||
|
||||
/*! \brief The remote IRR bit indicates whether the local APIC(s) accept the
|
||||
* level interrupt.
|
||||
*
|
||||
* Once the LAPIC sends an \ref LAPIC::endOfInterrupt "End Of Interrupt
|
||||
* (EOI)", this bit is reset to `0`.
|
||||
*
|
||||
* \note This field is read only and is only meaningful for level-triggered
|
||||
* interrupts.
|
||||
*/
|
||||
uint64_t remote_irr : 1;
|
||||
|
||||
/*! \brief The trigger mode states whether the interrupt signaling is level
|
||||
* or edge triggered.
|
||||
*
|
||||
* StuBS uses \ref EDGE for the Timer, the Keybaord and (optional) serial
|
||||
* interface need \ref LEVEL
|
||||
*/
|
||||
TriggerMode trigger_mode : 1;
|
||||
|
||||
/*! \brief Mask or unmask interrupts for a particular, external source.
|
||||
*
|
||||
* The interrupt mask denotes whether interrupts should be
|
||||
* accepted/unmasked (value \ref UNMASKED) or ignored/masked (value \ref
|
||||
* MASKED).
|
||||
*/
|
||||
InterruptMask interrupt_mask : 1;
|
||||
|
||||
/*! \brief Reserved, do not modify. */
|
||||
uint64_t : 39;
|
||||
|
||||
/*! \brief Interrupt destination.
|
||||
*
|
||||
* The meaning of destination depends on the destination mode:
|
||||
* For the logical destination mode, destination holds a bit mask made up
|
||||
* of the cores that are candidates for receiving the interrupt. In the
|
||||
* single-core case, this value is `1`, in the multi-core case, the `n`
|
||||
* low-order bits needs to be set (with `n` being the number of CPU cores,
|
||||
* see \ref Core::count() ). Setting the `n` low-order bits marks all
|
||||
* available cores as candidates for receiving interrupts and thereby
|
||||
* balancing the number of interrupts between the cores.
|
||||
*
|
||||
* \note This form of load balancing depends on the hardware's behavior and
|
||||
* may not work on all systems in the same fashion. Most notably, in QEMU
|
||||
* all interrupts are sent to the BSP (core 0).
|
||||
*/
|
||||
uint64_t destination : 8;
|
||||
|
||||
// @cond ANONYMOUS_STRUCT
|
||||
} __attribute__((packed));
|
||||
// @endcond
|
||||
|
||||
// @cond ANONYMOUS_STRUCT
|
||||
struct {
|
||||
// @endcond
|
||||
|
||||
Register value_low; ///< Low-order 32 bits (for the register with the
|
||||
///< smaller index)
|
||||
Register value_high; ///< High-order 32 bits (for the register with the
|
||||
///< higher index)
|
||||
// @cond ANONYMOUS_STRUCT
|
||||
} __attribute__((packed));
|
||||
// @endcond
|
||||
|
||||
/*! \brief Constructor for an redirection-table entry
|
||||
*
|
||||
* Every entry in the redirection table represents an external source of
|
||||
* interrupts and has a size of 64 bits. Due to the I/O APIC registers being
|
||||
* only 32 bits wide, the constructor takes two 32 bit values.
|
||||
*
|
||||
* \param value_low First, low-order 32 bit value
|
||||
* \param value_high Second, high-order 32 bit value
|
||||
*/
|
||||
RedirectionTableEntry(Register value_low, Register value_high)
|
||||
: value_low(value_low), value_high(value_high) {}
|
||||
};
|
||||
|
||||
static_assert(sizeof(RedirectionTableEntry) == 8,
|
||||
"IOAPIC::RedirectionTableEntry has wrong size");
|
||||
} // namespace IOAPIC
|
||||
@ -0,0 +1,63 @@
|
||||
/*! \file
|
||||
* \brief \ref IOPort provides access to the x86 IO address space
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "../types.h"
|
||||
|
||||
/*! \brief Abstracts access to the I/O address space
|
||||
*
|
||||
* x86 PCs have a separated I/O address space that is accessible only via the
|
||||
* machine instructions `in` and `out`. An IOPort object encapsulates the
|
||||
* corresponding address in the I/O address space and can be used for byte or
|
||||
* word-wise reading or writing.
|
||||
*/
|
||||
|
||||
class IOPort {
|
||||
/*! \brief Address in I/O address space
|
||||
*
|
||||
*/
|
||||
uint16_t address;
|
||||
|
||||
public:
|
||||
/*! \brief Constructor
|
||||
* \param addr Address from the I/O address space
|
||||
*/
|
||||
explicit constexpr IOPort(uint16_t addr) : address(addr) {}
|
||||
|
||||
/*! \brief Write one byte to the I/O port
|
||||
* \param val The value to be written
|
||||
*/
|
||||
void outb(uint8_t val) const {
|
||||
asm volatile("out %%al, %%dx\n\t" : : "a"(val), "d"(address) :);
|
||||
}
|
||||
|
||||
/*! \brief Write one word (2 bytes) to the I/O port
|
||||
* \param val The value to be written
|
||||
*/
|
||||
void outw(uint16_t val) const {
|
||||
asm volatile("out %%ax, %%dx\n\t" : : "a"(val), "d"(address) :);
|
||||
}
|
||||
|
||||
/*! \brief Read one byte from the I/O port
|
||||
* \return Read byte
|
||||
*/
|
||||
uint8_t inb() const {
|
||||
uint8_t out = 0;
|
||||
|
||||
asm volatile("in %%dx, %%al\n\t" : "=a"(out) : "d"(address) :);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
/*! \brief Read one word (2 bytes) from the I/O port
|
||||
* \return Read word (2 bytes)
|
||||
*/
|
||||
uint16_t inw() const {
|
||||
uint16_t out = 0;
|
||||
|
||||
asm volatile("inw %%dx, %%ax\n\t" : "=a"(out) : "d"(address) :);
|
||||
|
||||
return out;
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,190 @@
|
||||
#include "lapic.h"
|
||||
|
||||
#include "lapic_registers.h"
|
||||
|
||||
namespace LAPIC {
|
||||
|
||||
/*! \brief Base Address
|
||||
* used with offset to access memory mapped registers
|
||||
*/
|
||||
volatile uintptr_t base_address = 0xfee00000;
|
||||
|
||||
Register read(Index idx) {
|
||||
return *reinterpret_cast<volatile Register *>(base_address + idx);
|
||||
}
|
||||
void write(Index idx, Register value) {
|
||||
*reinterpret_cast<volatile Register *>(base_address + idx) = value;
|
||||
}
|
||||
|
||||
/*! \brief Local APIC ID (for Pentium 4 and newer)
|
||||
*
|
||||
* Is assigned automatically during boot and should not be changed.
|
||||
*
|
||||
* \see [ISDMv3, 10.4.6 Local APIC ID](intel_manual_vol3.pdf#page=371)
|
||||
*/
|
||||
union IdentificationRegister {
|
||||
struct {
|
||||
uint32_t : 24, ///< (reserved)
|
||||
apic_id : 8; ///< APIC ID
|
||||
};
|
||||
Register value;
|
||||
|
||||
IdentificationRegister() : value(read(Index::IDENTIFICATION)) {}
|
||||
} __attribute__((packed));
|
||||
|
||||
/*! \brief Local APIC Version
|
||||
*
|
||||
* \see [ISDMv3 10.4.8 Local APIC Version
|
||||
* Register](intel_manual_vol3.pdf#page=373)
|
||||
*/
|
||||
union VersionRegister {
|
||||
struct {
|
||||
uint32_t
|
||||
version : 8, ///< 0x14 for P4 and Xeon, 0x15 for more recent hardware
|
||||
: 8, ///< (reserved)
|
||||
max_lvt_entry : 8, ///< Maximum number of local vector entries
|
||||
suppress_eoi_broadcast : 1, ///< Support for suppressing EOI broadcasts
|
||||
: 7; ///< (reserved)
|
||||
};
|
||||
Register value;
|
||||
|
||||
VersionRegister() : value(read(Index::VERSION)) {}
|
||||
} __attribute__((packed));
|
||||
|
||||
/*! \brief Logical Destination Register
|
||||
* \see [ISDMv3 10.6.2.2 Logical Destination
|
||||
* Mode](intel_manual_vol3.pdf#page=385)
|
||||
*/
|
||||
union LogicalDestinationRegister {
|
||||
struct {
|
||||
uint32_t : 24, ///< (reserved)
|
||||
lapic_id : 8; ///< Logical APIC ID
|
||||
};
|
||||
Register value;
|
||||
|
||||
LogicalDestinationRegister() : value(read(Index::LOGICAL_DESTINATION)) {}
|
||||
~LogicalDestinationRegister() { write(Index::LOGICAL_DESTINATION, value); }
|
||||
} __attribute__((packed));
|
||||
|
||||
enum Model { CLUSTER = 0x0, FLAT = 0xf };
|
||||
|
||||
/*! \brief Destination Format Register
|
||||
*
|
||||
* \see [ISDMv3 10.6.2.2 Logical Destination
|
||||
* Mode](intel_manual_vol3.pdf#page=385)
|
||||
*/
|
||||
union DestinationFormatRegister {
|
||||
struct {
|
||||
uint32_t : 28; ///< (reserved)
|
||||
Model model : 4; ///< Model (Flat vs. Cluster)
|
||||
};
|
||||
Register value;
|
||||
DestinationFormatRegister() : value(read(Index::DESTINATION_FORMAT)) {}
|
||||
~DestinationFormatRegister() { write(Index::DESTINATION_FORMAT, value); }
|
||||
} __attribute__((packed));
|
||||
|
||||
/*! \brief Task Priority Register
|
||||
*
|
||||
* \see [ISDMv3 10.8.3.1 Task and Processor
|
||||
* Priorities](intel_manual_vol3.pdf#page=391)
|
||||
*/
|
||||
union TaskPriorityRegister {
|
||||
struct {
|
||||
uint32_t task_prio_sub : 4, ///< Task Priority Sub-Class
|
||||
task_prio : 4, ///< Task Priority
|
||||
: 24; ///< (reserved)
|
||||
};
|
||||
Register value;
|
||||
TaskPriorityRegister() : value(read(Index::TASK_PRIORITY)) {}
|
||||
~TaskPriorityRegister() { write(Index::TASK_PRIORITY, value); }
|
||||
} __attribute__((packed));
|
||||
|
||||
/*! \brief APIC Software Status for Spurious Interrupt Vector */
|
||||
enum APICSoftware {
|
||||
APIC_DISABLED = 0,
|
||||
APIC_ENABLED = 1,
|
||||
};
|
||||
|
||||
/*! \brief Focus Processor Checking for Spurious Interrupt Vector */
|
||||
enum FocusProcessorChecking {
|
||||
CHECKING_ENABLED = 0,
|
||||
CHECKING_DISABLED = 1,
|
||||
};
|
||||
|
||||
/*! \brief Suppress End-Of-Interrupt-Broadcast for Spurious Interrupt Vector */
|
||||
enum SuppressEOIBroadcast {
|
||||
BROADCAST = 0,
|
||||
SUPPRESS_BROADCAST = 1,
|
||||
};
|
||||
|
||||
/*! \brief Spurious Interrupt Vector Register
|
||||
*
|
||||
* \see [ISDMv3 10.9 Spurious Interrupt](intel_manual_vol3.pdf#page=394)
|
||||
*/
|
||||
union SpuriousInterruptVectorRegister {
|
||||
struct {
|
||||
uint32_t spurious_vector : 8; ///< Spurious Vector
|
||||
APICSoftware apic_software : 1; ///< APIC Software Enable/Disable
|
||||
FocusProcessorChecking
|
||||
focus_processor_checking : 1; ///< Focus Processor Checking
|
||||
uint32_t reserved_1 : 2;
|
||||
SuppressEOIBroadcast eoi_broadcast_suppression : 1;
|
||||
uint32_t reserved : 19;
|
||||
};
|
||||
Register value;
|
||||
|
||||
SpuriousInterruptVectorRegister()
|
||||
: value(read(Index::SPURIOUS_INTERRUPT_VECTOR)) {}
|
||||
~SpuriousInterruptVectorRegister() {
|
||||
write(Index::SPURIOUS_INTERRUPT_VECTOR, value);
|
||||
}
|
||||
} __attribute__((packed));
|
||||
static_assert(sizeof(SpuriousInterruptVectorRegister) == 4,
|
||||
"LAPIC Spurious Interrupt Vector has wrong size");
|
||||
|
||||
uint8_t getID() {
|
||||
IdentificationRegister ir;
|
||||
return ir.apic_id;
|
||||
}
|
||||
|
||||
uint8_t getLogicalID() {
|
||||
LogicalDestinationRegister ldr;
|
||||
return ldr.lapic_id;
|
||||
}
|
||||
|
||||
uint8_t getVersion() {
|
||||
VersionRegister vr;
|
||||
return vr.version;
|
||||
}
|
||||
|
||||
void init(uint8_t logical_id) {
|
||||
// reset logical destination ID
|
||||
// can be set using setLogicalLAPICID()
|
||||
LogicalDestinationRegister ldr;
|
||||
ldr.lapic_id = logical_id;
|
||||
|
||||
// set task priority to 0 -> accept all interrupts
|
||||
TaskPriorityRegister tpr;
|
||||
tpr.task_prio = 0;
|
||||
tpr.task_prio_sub = 0;
|
||||
|
||||
// set flat delivery mode
|
||||
DestinationFormatRegister dfr;
|
||||
dfr.model = Model::FLAT;
|
||||
|
||||
// use 255 as spurious vector, enable APIC and disable focus processor
|
||||
SpuriousInterruptVectorRegister sivr;
|
||||
sivr.spurious_vector = 0xff;
|
||||
sivr.apic_software = APICSoftware::APIC_ENABLED;
|
||||
sivr.focus_processor_checking = FocusProcessorChecking::CHECKING_DISABLED;
|
||||
}
|
||||
|
||||
void endOfInterrupt() {
|
||||
// dummy read
|
||||
read(SPURIOUS_INTERRUPT_VECTOR);
|
||||
|
||||
// signal end of interrupt
|
||||
write(EOI, 0);
|
||||
}
|
||||
|
||||
} // namespace LAPIC
|
||||
@ -0,0 +1,199 @@
|
||||
/*! \file
|
||||
* \brief \ref LAPIC abstracts access to the Local \ref APIC
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "../types.h"
|
||||
|
||||
/*! \brief Abstracts the local APIC (which is integrated into every CPU core)
|
||||
* \ingroup interrupts
|
||||
*
|
||||
* In modern (x86) PCs, every CPU core has its own Local APIC (LAPIC). The
|
||||
* LAPIC is the link between the local CPU core and the I/O APIC (that takes
|
||||
* care about external interrupt sources. Interrupt messages received by the
|
||||
* LAPIC will be passed to the corresponding CPU core and trigger the interrupt
|
||||
* handler on this core.
|
||||
*
|
||||
* \see [ISDMv3 10.4 Local APIC](intel_manual_vol3.pdf#page=366)
|
||||
*/
|
||||
namespace LAPIC {
|
||||
/*! \brief Initialized the local APIC of the calling CPU core and sets the
|
||||
* logical LAPIC ID in the LDR register
|
||||
* \param logical_id APIC ID to be set
|
||||
*/
|
||||
void init(uint8_t logical_id);
|
||||
|
||||
/*! \brief Signalize EOI (End of interrupt)
|
||||
*
|
||||
* Signals to the LAPIC that the handling of the current interrupt finished.
|
||||
* This function must be called at the end of interrupt handling before ireting.
|
||||
*/
|
||||
void endOfInterrupt();
|
||||
|
||||
/*! \brief Get the ID of the current core's LAPIC
|
||||
* \return LAPIC ID
|
||||
*/
|
||||
uint8_t getID();
|
||||
|
||||
/*! \brief Get the Logical ID of the current core's LAPIC
|
||||
* \return Logical ID
|
||||
*/
|
||||
uint8_t getLogicalID();
|
||||
|
||||
/*! \brief Set the Logical ID of the current core's LAPIC
|
||||
* \param id new Logical ID
|
||||
*/
|
||||
void setLogicalID(uint8_t id);
|
||||
|
||||
/*! \brief Get version number of local APIC
|
||||
* \return version number
|
||||
*/
|
||||
uint8_t getVersion();
|
||||
|
||||
/*! \brief Inter-Processor Interrupts
|
||||
*
|
||||
* For multi-core systems, the LAPIC enables sending messages (Inter-Processor
|
||||
* Interrupts, IPIs) to other CPU cores and receiving those sent from other
|
||||
* cores.
|
||||
*
|
||||
* \see [ISDMv3 10.6 Issuing Interprocessor
|
||||
* Interrupts](intel_manual_vol3.pdf#page=380)
|
||||
*/
|
||||
namespace IPI {
|
||||
|
||||
/*! \brief Check if the previously sent IPI has reached its destination.
|
||||
*
|
||||
* \return `true` if the previous IPI was accepted from its target processor,
|
||||
* otherwise `false`
|
||||
*/
|
||||
bool isDelivered();
|
||||
|
||||
/*! \brief Send an Inter-Processor Interrupt (IPI)
|
||||
* \param destination ID of the target processor (use APIC::getLAPICID(core) )
|
||||
* \param vector Interrupt vector number to be triggered
|
||||
*/
|
||||
void send(uint8_t destination, uint8_t vector);
|
||||
|
||||
/*! \brief Send an Inter-Processor Interrupt (IPI) to a group of processors
|
||||
* \param logical_destination Mask containing the logical APIC IDs of the target
|
||||
* processors (use APIC::getLogicalLAPICID())
|
||||
* \param vector Interrupt vector number to be triggered
|
||||
*/
|
||||
void sendGroup(uint8_t logical_destination, uint8_t vector);
|
||||
|
||||
/*! \brief Send an Inter-Processor Interrupt (IPI) to all processors (including
|
||||
* self)
|
||||
* \param vector Interrupt vector number to be triggered
|
||||
*/
|
||||
void sendAll(uint8_t vector);
|
||||
|
||||
/*! \brief Send an Inter-Processor Interrupt (IPI) to all other processors (all
|
||||
* but self)
|
||||
* \param vector Interrupt vector number to be triggered
|
||||
*/
|
||||
void sendOthers(uint8_t vector);
|
||||
|
||||
/*! \brief Send an INIT request IPI to all other processors
|
||||
*
|
||||
* \note Only required for startup
|
||||
*
|
||||
* \param assert if `true` send an INIT,
|
||||
* on `false` send an INIT Level De-assert
|
||||
*/
|
||||
void sendInit(bool assert = true);
|
||||
|
||||
/*! \brief Send an Startup IPI to all other processors
|
||||
*
|
||||
* \note Only required for startup
|
||||
*
|
||||
* \param vector Pointer to a startup routine
|
||||
*/
|
||||
void sendStartup(uint8_t vector);
|
||||
|
||||
} // namespace IPI
|
||||
|
||||
/*! \brief Local Timer (for each LAPIC / CPU)
|
||||
*
|
||||
* \see [ISDMv3 10.5.4 APIC Timer](intel_manual_vol3.pdf#page=378)
|
||||
*/
|
||||
namespace Timer {
|
||||
|
||||
/*! \brief Determines the \ref LAPIC::Timer frequency.
|
||||
*
|
||||
* This function will calculate the number of LAPIC-timer ticks passing in the
|
||||
* course of one millisecond. To do so, this function will rely on PIT timer
|
||||
* functionality and measure the tick delta between start and end of waiting for
|
||||
* a predefined period.
|
||||
*
|
||||
* For measurement, the LAPIC-timer single-shot mode (without interrupts) is
|
||||
* used; after measurement, the timer is disabled again.
|
||||
*
|
||||
* \note The timer is counting towards zero.
|
||||
*
|
||||
* \return Number of LAPIC-timer ticks per millisecond
|
||||
*
|
||||
* \todo(15) Implement Method
|
||||
*/
|
||||
uint32_t ticks(void);
|
||||
|
||||
/*! \brief Set the \ref LAPIC::Timer.
|
||||
* \param counter Initial counter value; decremented on every LAPIC timer tick
|
||||
* \param divide Divider (power of 2, i.e., 1 2 4 8 16 32...) used as
|
||||
* prescaler between bus frequency and LAPIC timer frequency: `LAPIC timer
|
||||
* frequency = divide * bus frequency`. `divide` is a numerical parameter, the
|
||||
* conversion to the corresponding bit mask is done internally by calling
|
||||
* getClockDiv().
|
||||
* \param vector Interrupt vector number to be triggered on counter expiry
|
||||
* \param periodic If set, the interrupt will be issued periodically
|
||||
* \param masked If set, interrupts on counter expiry are suppressed
|
||||
*
|
||||
* \todo(15) Implement Method
|
||||
*/
|
||||
void set(uint32_t counter, uint8_t divide, uint8_t vector, bool periodic,
|
||||
bool masked = false);
|
||||
|
||||
/*! \brief Setup the \ref LAPIC::Timer.
|
||||
*
|
||||
* Initializes the \ref LAPIC::Timer
|
||||
* in such a way that regular interrupts are triggered approx. every `us`
|
||||
* microseconds when \ref LAPIC::Timer:::activate() is called.
|
||||
* For this purpose, a suitable timer divisor is determined
|
||||
* based on the timer frequency determined with \ref LAPIC::Timer::ticks().
|
||||
* This timer divisor has to be as small as possible, but large enough to
|
||||
* prevent the 32bit counter from overflowing.
|
||||
*
|
||||
* \param us Desired interrupt interval in microseconds.
|
||||
* \return Indicates if the interval could be set.
|
||||
*
|
||||
* \todo(15) Implement Method
|
||||
*/
|
||||
bool setup(uint32_t us);
|
||||
|
||||
/*! \brief Retrieve the interrupt interval set during \ref LAPIC::Timer::setup()
|
||||
*
|
||||
* \return Interval in microseconds
|
||||
*
|
||||
* \todo(15) Implement method
|
||||
*/
|
||||
uint32_t interval();
|
||||
|
||||
/*! \brief Activate the timer on this core.
|
||||
*
|
||||
* The core local timer starts with the interval previously configured in
|
||||
* \ref LAPIC::Timer::setup(). To get timer interrupts on all cores, this method
|
||||
* must be called once per core (however, it is sufficient to call \ref
|
||||
* LAPIC::Timer::setup() only once since the APIC-Bus frequency is the same on
|
||||
* each core).
|
||||
*
|
||||
* \todo(15) Implement method
|
||||
*/
|
||||
void activate();
|
||||
|
||||
/*! \brief Set the LAPIC-timer interrupt mask
|
||||
* \param masked If set, interrupts are suppressed on counter expiry.
|
||||
*
|
||||
* \todo(16) Implement for tick-less kernel
|
||||
*/
|
||||
void setMasked(bool masked);
|
||||
} // namespace Timer
|
||||
} // namespace LAPIC
|
||||
@ -0,0 +1,244 @@
|
||||
#include "lapic_registers.h"
|
||||
|
||||
namespace LAPIC {
|
||||
namespace IPI {
|
||||
|
||||
/*! \brief Delivery mode specifies the type of interrupt sent to the CPU. */
|
||||
enum DeliveryMode {
|
||||
FIXED = 0, ///< "ordinary" interrupt; send to ALL cores listed in the
|
||||
///< destination bit mask
|
||||
LOWEST_PRIORITY = 1, ///< "ordinary" interrupt; send to the lowest priority
|
||||
///< core from destination mask
|
||||
SMI = 2, ///< System Management Interrupt; vector number required to be 0
|
||||
// Reserved
|
||||
NMI = 4, ///< Non-Maskable Interrupt, vector number ignored, only edge
|
||||
///< triggered
|
||||
INIT = 5, ///< Initialization interrupt (always treated as edge triggered)
|
||||
INIT_LEVEL_DEASSERT = 5, ///< Synchronization interrupt
|
||||
STARTUP = 6, ///< Dedicated Startup-Interrupt (SIPI)
|
||||
// Reserved
|
||||
};
|
||||
|
||||
/*! \brief Way of interpreting the value written to the destination field. */
|
||||
enum DestinationMode {
|
||||
PHYSICAL = 0, ///< Destination contains the physical destination APIC ID
|
||||
LOGICAL = 1 ///< Destination contains a mask of logical APIC IDs
|
||||
};
|
||||
|
||||
/*! \brief Interrupt state */
|
||||
enum DeliveryStatus {
|
||||
IDLE = 0, ///< No activity for this interrupt
|
||||
SEND_PENDING =
|
||||
1 ///< Interrupt will be sent as soon as the bus / LAPIC is ready
|
||||
};
|
||||
|
||||
/*! \brief Interrupt level */
|
||||
enum Level {
|
||||
DEASSERT = 0, ///< Must be zero when DeliveryMode::INIT_LEVEL_DEASSERT
|
||||
ASSERT = 1 ///< Must be one for all other delivery modes
|
||||
};
|
||||
|
||||
/*! \brief Trigger mode for DeliveryMode::INIT_LEVEL_DEASSERT */
|
||||
enum TriggerMode {
|
||||
EDGE_TRIGGERED = 0, ///< edge triggered
|
||||
LEVEL_TRIGGERED = 1 ///< level triggered
|
||||
};
|
||||
|
||||
/*! \brief Shorthand for commonly used destinations */
|
||||
enum DestinationShorthand {
|
||||
NO_SHORTHAND = 0, ///< Use destination field instead of shorthand
|
||||
SELF = 1, ///< Send IPI to self
|
||||
ALL_INCLUDING_SELF = 2, ///< Send IPI to all including self
|
||||
ALL_EXCLUDING_SELF = 3 ///< Send IPI to all except self
|
||||
};
|
||||
|
||||
/*! \brief Interrupt mask */
|
||||
enum InterruptMask {
|
||||
UNMASKED = 0, ///< Interrupt entry is active (non-masked)
|
||||
MASKED = 1 ///< Interrupt entry is deactivated (masked)
|
||||
};
|
||||
|
||||
/*! \brief Interrupt Command
|
||||
*
|
||||
* \see [ISDMv3 10.6.1 Interrupt Command Register
|
||||
* (ICR)](intel_manual_vol3.pdf#page=381)
|
||||
*/
|
||||
union InterruptCommand {
|
||||
struct {
|
||||
/*! \brief Interrupt vector in the \ref IDT "Interrupt Descriptor Table
|
||||
* (IDT)" will be activated when the corresponding external interrupt
|
||||
* triggers.
|
||||
*//*! \brief Interrupt vector in the \ref IDT "Interrupt Descriptor Table (IDT)" will be
|
||||
* activated when the corresponding external interrupt triggers.
|
||||
*/
|
||||
uint64_t vector : 8;
|
||||
|
||||
/*! \brief The delivery mode denotes the way the interrupts will be
|
||||
* delivered to the local CPU cores, respectively to their local APICs.
|
||||
*
|
||||
* For StuBS, we use `DeliveryMode::LowestPriority`, as all CPU cores have
|
||||
* the same priority and we want to distribute interrupts evenly among them.
|
||||
* It, however, is not guaranteed that this method of load balancing will
|
||||
* work on every system.
|
||||
*/
|
||||
enum DeliveryMode delivery_mode : 3;
|
||||
|
||||
/*! \brief The destination mode defines how the value stored in
|
||||
* `destination` will be interpreted.
|
||||
*
|
||||
* For StuBS, we use `DestinationMode::Logical`.
|
||||
*/
|
||||
enum DestinationMode destination_mode : 1;
|
||||
|
||||
/*! \brief Delivery status holds the current status of interrupt delivery.
|
||||
*
|
||||
* \note This field is read only; write accesses to this field will be
|
||||
* ignored.
|
||||
*/
|
||||
enum DeliveryStatus delivery_status : 1;
|
||||
|
||||
uint64_t : 1; ///< reserved
|
||||
|
||||
/*! \brief The polarity denotes when an interrupt should be issued.
|
||||
*
|
||||
* For StuBS, we use `Polarity::High` (i.e., when the interrupt line is,
|
||||
* logically, 1).
|
||||
*/
|
||||
enum Level level : 1;
|
||||
|
||||
/*! \brief The trigger mode states whether the interrupt signaling is level
|
||||
* or edge triggered.
|
||||
*
|
||||
* StuBS uses `TriggerMode::Edge` for Keyboard and Timer, the (optional)
|
||||
* serial interface, however, needs `TriggerMode::Level`.
|
||||
*/
|
||||
enum TriggerMode trigger_mode : 1;
|
||||
|
||||
uint64_t : 2; ///< reserved
|
||||
|
||||
enum DestinationShorthand destination_shorthand : 2;
|
||||
|
||||
uint64_t : 36; ///< Reserved, do not modify
|
||||
|
||||
/*! \brief Interrupt destination.
|
||||
*
|
||||
* The meaning of destination depends on the destination mode:
|
||||
* For the logical destination mode, destination holds a bit mask made up
|
||||
* of the cores that are candidates for receiving the interrupt. In the
|
||||
* single-core case, this value is `1`, in the multi-core case, the `n`
|
||||
* low-order bits needs to be set (with `n` being the number of CPU cores,
|
||||
* see \ref Core::count() ). Setting the `n` low-order bits marks all
|
||||
* available cores as candidates for receiving interrupts and thereby
|
||||
* balancing the number of interrupts between the cores.
|
||||
*
|
||||
* \note This form of load balancing depends on the hardware's behavior and
|
||||
* may not work on all systems in the same fashion. Most notably, in QEMU
|
||||
* all interrupts are sent to the BSP (core 0).
|
||||
*/
|
||||
uint64_t destination : 8;
|
||||
} __attribute__((packed));
|
||||
|
||||
/*! \brief I/O redirection-table entry
|
||||
*
|
||||
* Every entry in the redirection table represents an external source of
|
||||
* interrupts and has a size of 64 bits. Due to the I/O APIC registers being
|
||||
* only 32 bits wide, the 64-bit value is split in two 32 bit values.
|
||||
*/
|
||||
struct {
|
||||
Register value_low; ///< First, low-order register
|
||||
Register value_high; ///< Second, high-order register
|
||||
} __attribute__((packed));
|
||||
|
||||
/*! \brief Default constructor */
|
||||
InterruptCommand() = default;
|
||||
|
||||
explicit InterruptCommand(
|
||||
uint8_t destination, uint8_t vector = 0,
|
||||
DestinationMode destination_mode = DestinationMode::PHYSICAL,
|
||||
DeliveryMode delivery_mode = DeliveryMode::FIXED,
|
||||
TriggerMode trigger_mode = TriggerMode::EDGE_TRIGGERED,
|
||||
Level level = Level::ASSERT) {
|
||||
readRegister();
|
||||
this->vector = vector;
|
||||
this->delivery_mode = delivery_mode;
|
||||
this->destination_mode = destination_mode;
|
||||
this->level = level;
|
||||
this->trigger_mode = trigger_mode;
|
||||
this->destination_shorthand = DestinationShorthand::NO_SHORTHAND;
|
||||
this->destination = destination;
|
||||
}
|
||||
|
||||
InterruptCommand(DestinationShorthand destination_shorthand, uint8_t vector,
|
||||
DeliveryMode delivery_mode = DeliveryMode::FIXED,
|
||||
TriggerMode trigger_mode = TriggerMode::EDGE_TRIGGERED,
|
||||
Level level = Level::ASSERT) {
|
||||
readRegister();
|
||||
this->vector = vector;
|
||||
this->delivery_mode = delivery_mode;
|
||||
this->level = level;
|
||||
this->trigger_mode = trigger_mode;
|
||||
this->destination_shorthand = destination_shorthand;
|
||||
this->destination = destination;
|
||||
}
|
||||
|
||||
void send() const {
|
||||
write(INTERRUPT_COMMAND_REGISTER_HIGH, value_high);
|
||||
write(INTERRUPT_COMMAND_REGISTER_LOW, value_low);
|
||||
}
|
||||
|
||||
bool isSendPending() {
|
||||
value_low = read(INTERRUPT_COMMAND_REGISTER_LOW);
|
||||
return delivery_status == DeliveryStatus::SEND_PENDING;
|
||||
}
|
||||
|
||||
private:
|
||||
void readRegister() {
|
||||
while (isSendPending()) {
|
||||
}
|
||||
value_high = read(INTERRUPT_COMMAND_REGISTER_HIGH);
|
||||
}
|
||||
};
|
||||
static_assert(sizeof(InterruptCommand) == 8,
|
||||
"LAPIC Interrupt Command has wrong size");
|
||||
|
||||
bool isDelivered() {
|
||||
InterruptCommand ic;
|
||||
return !ic.isSendPending();
|
||||
}
|
||||
|
||||
void send(uint8_t destination, uint8_t vector) {
|
||||
InterruptCommand ic(destination, vector);
|
||||
ic.send();
|
||||
}
|
||||
|
||||
void sendGroup(uint8_t logical_destination, uint8_t vector) {
|
||||
InterruptCommand ic(logical_destination, vector, DestinationMode::LOGICAL);
|
||||
ic.send();
|
||||
}
|
||||
|
||||
void sendAll(uint8_t vector) {
|
||||
InterruptCommand ic(DestinationShorthand::ALL_INCLUDING_SELF, vector);
|
||||
ic.send();
|
||||
}
|
||||
|
||||
void sendOthers(uint8_t vector) {
|
||||
InterruptCommand ic(DestinationShorthand::ALL_EXCLUDING_SELF, vector);
|
||||
ic.send();
|
||||
}
|
||||
|
||||
void sendInit(bool assert) {
|
||||
LAPIC::IPI::InterruptCommand ic(
|
||||
DestinationShorthand::ALL_EXCLUDING_SELF, 0, DeliveryMode::INIT,
|
||||
assert ? TriggerMode::EDGE_TRIGGERED : TriggerMode::LEVEL_TRIGGERED,
|
||||
assert ? Level::ASSERT : Level::DEASSERT);
|
||||
ic.send();
|
||||
}
|
||||
|
||||
void sendStartup(uint8_t vector) {
|
||||
InterruptCommand ic(DestinationShorthand::ALL_EXCLUDING_SELF, vector,
|
||||
DeliveryMode::STARTUP);
|
||||
ic.send();
|
||||
}
|
||||
|
||||
} // namespace IPI
|
||||
} // namespace LAPIC
|
||||
@ -0,0 +1,54 @@
|
||||
/*! \file
|
||||
* \brief Structures and macros for accessing \ref LAPIC "the local APIC".
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "../types.h"
|
||||
|
||||
namespace LAPIC {
|
||||
// Memory Mapped Base Address
|
||||
extern volatile uintptr_t base_address;
|
||||
|
||||
typedef uint32_t Register;
|
||||
|
||||
/*! \brief Register Offset Index
|
||||
*
|
||||
* \see [ISDMv3 10.4.1 The Local APIC Block
|
||||
* Diagram](intel_manual_vol3.pdf#page=368)
|
||||
*/
|
||||
enum Index : uint16_t {
|
||||
IDENTIFICATION =
|
||||
0x020, ///< Local APIC ID Register, RO (sometimes R/W). Do not change!
|
||||
VERSION = 0x030, ///< Local APIC Version Register, RO
|
||||
TASK_PRIORITY = 0x080, ///< Task Priority Register, R/W
|
||||
EOI = 0x0b0, ///< EOI Register, WO
|
||||
LOGICAL_DESTINATION = 0x0d0, ///< Logical Destination Register, R/W
|
||||
DESTINATION_FORMAT =
|
||||
0x0e0, ///< Destination Format Register, bits 0-27 RO, bits 28-31 R/W
|
||||
SPURIOUS_INTERRUPT_VECTOR = 0x0f0, ///< Spurious Interrupt Vector Register,
|
||||
///< bits 0-8 R/W, bits 9-1 R/W
|
||||
INTERRUPT_COMMAND_REGISTER_LOW =
|
||||
0x300, ///< Interrupt Command Register 1, R/W
|
||||
INTERRUPT_COMMAND_REGISTER_HIGH =
|
||||
0x310, ///< Interrupt Command Register 2, R/W
|
||||
TIMER_CONTROL = 0x320, ///< LAPIC timer control register, R/W
|
||||
TIMER_INITIAL_COUNTER = 0x380, ///< LAPIC timer initial counter register, R/W
|
||||
TIMER_CURRENT_COUNTER = 0x390, ///< LAPIC timer current counter register, RO
|
||||
TIMER_DIVIDE_CONFIGURATION =
|
||||
0x3e0 ///< LAPIC timer divide configuration register, RW
|
||||
};
|
||||
|
||||
/*! \brief Get value from APIC register
|
||||
*
|
||||
* \param idx Register Offset Index
|
||||
* \return current value of register
|
||||
*/
|
||||
Register read(Index idx);
|
||||
|
||||
/*! \brief Write value to APIC register
|
||||
*
|
||||
* \param idx Register Offset Index
|
||||
* \param value value to be written into register
|
||||
*/
|
||||
void write(Index idx, Register value);
|
||||
} // namespace LAPIC
|
||||
@ -0,0 +1,91 @@
|
||||
#include "lapic.h"
|
||||
#include "lapic_registers.h"
|
||||
|
||||
namespace LAPIC {
|
||||
namespace Timer {
|
||||
|
||||
/*! \brief Timer Delivery Status */
|
||||
enum DeliveryStatus { IDLE = 0, SEND_PENDING = 1 };
|
||||
|
||||
/*! \brief Timer Mode */
|
||||
enum TimerMode {
|
||||
ONE_SHOT = 0,
|
||||
PERIODIC = 1,
|
||||
DEADLINE = 2
|
||||
// reserved
|
||||
};
|
||||
|
||||
/*! \brief Timer Mask */
|
||||
enum Mask { NOT_MASKED = 0, MASKED = 1 };
|
||||
|
||||
static const Register INVALID_DIV = 0xff;
|
||||
|
||||
/*! \brief LAPIC-Timer Control Register
|
||||
*
|
||||
* \see [ISDMv3 10.5.1 Local Vector Table](intel_manual_vol3.pdf#page=375)
|
||||
*/
|
||||
union ControlRegister {
|
||||
struct {
|
||||
uint32_t vector : 8; ///< Vector
|
||||
uint32_t : 4;
|
||||
DeliveryStatus delivery_status : 1; ///< Delivery Status (readonly)
|
||||
uint32_t : 3;
|
||||
Mask masked : 1; ///< Interrupt Mask (if set, interrupt will not trigger)
|
||||
TimerMode timer_mode : 2; ///< Timer Mode
|
||||
uint32_t : 13;
|
||||
};
|
||||
Register value;
|
||||
} __attribute__((packed));
|
||||
|
||||
/*! \brief LAPIC timer divider table
|
||||
*
|
||||
* \see [ISDMv3 10.5.4 APIC Timer](intel_manual_vol3.pdf#page=378)
|
||||
*/
|
||||
static const Register div_masks[] = {
|
||||
0xb, ///< divides by 1
|
||||
0x0, ///< divides by 2
|
||||
0x1, ///< divides by 4
|
||||
0x2, ///< divides by 8
|
||||
0x3, ///< divides by 16
|
||||
0x8, ///< divides by 32
|
||||
0x9, ///< divides by 64
|
||||
0xa ///< divides by 128
|
||||
};
|
||||
|
||||
/*! \brief Calculate the bit mask for the LAPIC-timer divider.
|
||||
* \param div Divider, must be power of two: 1, 2, 4, 8, 16, 32, 64, 128
|
||||
* \return Bit mask for LAPIC::setTimer() or `0xff` if `div` is invalid.
|
||||
*/
|
||||
Register getClockDiv(uint8_t div) {
|
||||
(void)div;
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t ticks(void) {
|
||||
uint32_t ticks = 0; // ticks per millisecond
|
||||
// Calculation (Assignment 5)
|
||||
return ticks;
|
||||
}
|
||||
|
||||
void set(uint32_t counter, uint8_t divide, uint8_t vector, bool periodic,
|
||||
bool masked) {
|
||||
(void)counter;
|
||||
(void)divide;
|
||||
(void)vector;
|
||||
(void)periodic;
|
||||
(void)masked;
|
||||
}
|
||||
|
||||
bool setup(uint32_t us) {
|
||||
(void)us;
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t interval() { return 0; }
|
||||
|
||||
void activate() {}
|
||||
|
||||
void setMasked(bool masked) { (void)masked; }
|
||||
|
||||
} // namespace Timer
|
||||
} // namespace LAPIC
|
||||
@ -0,0 +1,63 @@
|
||||
#include "pic.h"
|
||||
|
||||
#include "ioport.h"
|
||||
|
||||
namespace PIC {
|
||||
|
||||
void initialize() {
|
||||
// Access primary & secondary PIC via two ports each
|
||||
IOPort primary_port_a(0x20);
|
||||
IOPort primary_port_b(0x21);
|
||||
IOPort secondary_port_a(0xa0);
|
||||
IOPort secondary_port_b(0xa1);
|
||||
|
||||
// Initialization Command Word 1 (ICW1)
|
||||
// Basic PIC configuration, starting initialization
|
||||
enum InitializationCommandWord1 {
|
||||
ICW4_NEEDED = 1 << 0, // use Initialization Command Word 4
|
||||
SINGLE_MODE = 1 << 1, // Single or multiple (cascade mode) 8259A
|
||||
ADDRESS_INTERVAL_HALF =
|
||||
1 << 2, // 4 or 8 bit interval between the interrupt vector locations
|
||||
LEVEL_TRIGGERED = 1 << 3, // Level or edge triggered
|
||||
ALWAYS_1 = 1 << 4,
|
||||
};
|
||||
const uint8_t icw1 = InitializationCommandWord1::ICW4_NEEDED |
|
||||
InitializationCommandWord1::ALWAYS_1;
|
||||
// ICW1 in port A (each)
|
||||
primary_port_a.outb(icw1);
|
||||
secondary_port_a.outb(icw1);
|
||||
|
||||
// Initialization Command Word 2 (ICW2):
|
||||
// Configure interrupt vector base offset in port B
|
||||
primary_port_b.outb(0x20); // Primary: IRQ Offset 32
|
||||
secondary_port_b.outb(0x28); // Secondary: IRQ Offset 40
|
||||
|
||||
// Initialization Command Word 3 (ICW3):
|
||||
// Configure pin on primary PIC connected to secondary PIC
|
||||
const uint8_t pin = 2; // Secondary connected on primary pin 2
|
||||
primary_port_b.outb(1 << pin); // Pin as bit mask for primary
|
||||
secondary_port_b.outb(pin); // Pin as value (ID) for secondary
|
||||
|
||||
// Initialization Command Word 4 (ICW4)
|
||||
// Basic PIC configuration, starting initialization
|
||||
enum InitializationCommandWord4 {
|
||||
MODE_8086 = 1 << 0, // 8086/8088 or 8085 mode
|
||||
AUTO_EOI = 1 << 1, // Single or multiple (cascade mode) 8259A
|
||||
BUFFER_PRIMARY = 1 << 2, // Primary or secondary buffering
|
||||
BUFFERED_MODE =
|
||||
1 << 3, // Enable or disable buffering (for primary or secondary above)
|
||||
SPECIAL_FULLY_NESTED = 1 << 4 // Special or non special fully nested
|
||||
};
|
||||
const uint8_t icw4 = InitializationCommandWord4::MODE_8086 |
|
||||
InitializationCommandWord4::AUTO_EOI;
|
||||
// ICW3 in port B (each)
|
||||
primary_port_b.outb(icw4);
|
||||
secondary_port_b.outb(icw4);
|
||||
|
||||
// Operation Control Word 1 (OCW1):
|
||||
// Disable (mask) all hardware interrupts on both legacy PICs (we'll use APIC)
|
||||
secondary_port_b.outb(0xff);
|
||||
primary_port_b.outb(0xff);
|
||||
}
|
||||
|
||||
} // namespace PIC
|
||||
@ -0,0 +1,18 @@
|
||||
/*! \file
|
||||
* \brief Handle (disable) the old Programmable Interrupt Controller (PIC)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "../types.h"
|
||||
|
||||
/*! \brief The Programmable Interrupt Controller (PIC aka 8259A)
|
||||
*/
|
||||
namespace PIC {
|
||||
|
||||
/*! \brief Initialize the PICs (Programmable Interrupt Controller, 8259A),
|
||||
* such that all 15 hardware interrupts are stored sequentially in the \ref IDT
|
||||
* and the hardware interrupts are disabled (in favor of \ref APIC).
|
||||
*/
|
||||
void initialize();
|
||||
|
||||
} // namespace PIC
|
||||
@ -0,0 +1,225 @@
|
||||
#include "pit.h"
|
||||
|
||||
#include "core.h"
|
||||
#include "ioport.h"
|
||||
|
||||
namespace PIT {
|
||||
|
||||
// we only use PIT channel 2
|
||||
const uint8_t CHANNEL = 2;
|
||||
static IOPort data(0x40 + CHANNEL);
|
||||
|
||||
/*! \brief Access mode
|
||||
*/
|
||||
enum AccessMode {
|
||||
LATCH_COUNT_VALUE = 0,
|
||||
LOW_BYTE_ONLY = 1,
|
||||
HIGH_BYTE_ONLY = 2,
|
||||
LOW_AND_HIGH_BYTE = 3
|
||||
};
|
||||
|
||||
/*! \brief Operating Mode
|
||||
*
|
||||
* \warning Channel 2 is not able to send interrupts, however, the status bit
|
||||
* will be set
|
||||
*/
|
||||
enum OperatingMode {
|
||||
INTERRUPT_ON_TERMINAL_COUNT = 0,
|
||||
PROGRAMMABLE_ONE_SHOT = 1,
|
||||
RATE_GENERATOR = 2,
|
||||
SQUARE_WAVE_GENERATOR = 3, ///< useful for the PC speaker
|
||||
SOFTWARE_TRIGGERED_STROBE = 4,
|
||||
HARDWARE_TRIGGERED_STROBE = 5
|
||||
};
|
||||
|
||||
/*! \brief data format
|
||||
*/
|
||||
enum Format {
|
||||
BINARY = 0,
|
||||
BCD = 1 ///< Binary Coded Decimals
|
||||
};
|
||||
|
||||
// Mode register (only writable)
|
||||
static IOPort mode_register(0x43);
|
||||
union Mode {
|
||||
struct {
|
||||
Format format : 1;
|
||||
OperatingMode operating : 3;
|
||||
AccessMode access : 2;
|
||||
uint8_t channel : 2;
|
||||
};
|
||||
uint8_t value;
|
||||
|
||||
/*! \brief Constructor for mode, takes the numeric value */
|
||||
explicit Mode(uint8_t value) : value(value) {}
|
||||
|
||||
/*! \brief Constructor for counting mode
|
||||
* \param access Access mode to the 16-bit counter value
|
||||
* \param operating Operating mode for the counter
|
||||
* \param format Number format for the 16-bit counter values (binary or
|
||||
* BCD)
|
||||
*/
|
||||
Mode(AccessMode access, OperatingMode operating, Format format)
|
||||
: format(format),
|
||||
operating(operating),
|
||||
access(access),
|
||||
channel(PIT::CHANNEL) {}
|
||||
|
||||
/*! \brief (Default) constructor for reading the counter value
|
||||
*/
|
||||
Mode() : value(0) { this->channel = PIT::CHANNEL; }
|
||||
|
||||
/*! \brief Write the value to the mode register
|
||||
*/
|
||||
void write() const { mode_register.outb(value); }
|
||||
};
|
||||
|
||||
// The NMI Status and Control Register contains details about PIT counter 2
|
||||
static IOPort controlRegister(0x61);
|
||||
union Control {
|
||||
/*! \brief I/O-port bitmap for the NMI Status and Control Register
|
||||
* \note Over time, the meaning of the bits stored at I/O port 0x61 changed;
|
||||
* don't get the structure confused with old documentation on the IBM PC XT
|
||||
* platform.
|
||||
* \see [Intel® I/O Controller Hub 7 (ICH7)
|
||||
* Family](i-o-controller-hub-7-datasheet.pdf#page=415), page 415
|
||||
*/
|
||||
struct {
|
||||
//! If enabled, the interrupt state will be visible at status_timer_counter2
|
||||
uint8_t enable_timer_counter2 : 1;
|
||||
uint8_t enable_speaker_data : 1; ///< If set, speaker output is equal to
|
||||
///< status_timer_counter2
|
||||
uint8_t enable_pci_serr : 1; ///< not important, do not modify
|
||||
uint8_t enable_nmi_iochk : 1; ///< not important, do not modify
|
||||
const uint8_t
|
||||
refresh_cycle_toggle : 1; ///< not important, must be 0 on write
|
||||
const uint8_t
|
||||
status_timer_counter2 : 1; ///< will be set on timer expiration; must
|
||||
///< be 0 on write
|
||||
const uint8_t
|
||||
status_iochk_nmi_source : 1; ///< not important, must be 0 on write
|
||||
const uint8_t
|
||||
status_serr_nmi_source : 1; ///< not important, must be 0 on write
|
||||
};
|
||||
uint8_t value;
|
||||
|
||||
/*! \brief Constructor
|
||||
* \param value Numeric value for the control register
|
||||
*/
|
||||
explicit Control(uint8_t value) : value(value) {}
|
||||
|
||||
/*! \brief Default constructor
|
||||
* Automatically reads the current contents from the control register.
|
||||
*/
|
||||
Control() : value(controlRegister.inb()) {}
|
||||
|
||||
/*! \brief Write the current state to the control register.
|
||||
*/
|
||||
void write() const { controlRegister.outb(value); }
|
||||
};
|
||||
|
||||
// The base frequency is, due to historic reasons, 1.193182 MHz.
|
||||
const uint64_t BASE_FREQUENCY = 1193182ULL;
|
||||
|
||||
bool set(uint16_t us) {
|
||||
// Counter ticks for us
|
||||
uint64_t counter = BASE_FREQUENCY * us / 1000000ULL;
|
||||
|
||||
// As the hardware counter has a size of 16 bit, we want to check whether the
|
||||
// calculated counter value is too large ( > 54.9ms )
|
||||
if (counter > 0xffff) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Interrupt state should be readable in status register, but PC speaker
|
||||
// should remain off
|
||||
Control c;
|
||||
c.enable_speaker_data = 0;
|
||||
c.enable_timer_counter2 = 1;
|
||||
c.write();
|
||||
|
||||
// Channel 2, 16-bit divisor, with mode 0 (interrupt) in binary format
|
||||
Mode m(AccessMode::LOW_AND_HIGH_BYTE,
|
||||
OperatingMode::INTERRUPT_ON_TERMINAL_COUNT, Format::BINARY);
|
||||
m.write();
|
||||
|
||||
// Set the counter's start value
|
||||
data.outb(counter & 0xff); // low
|
||||
data.outb((counter >> 8) & 0xff); // high
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
uint16_t get(void) {
|
||||
// Set mode to reading
|
||||
Mode m;
|
||||
m.write();
|
||||
|
||||
// Read low and high
|
||||
uint16_t value = data.inb();
|
||||
value |= data.inb() << 8;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
bool isActive(void) {
|
||||
Control c; // reads the current value from the control register
|
||||
return c.enable_timer_counter2 == 1 && c.status_timer_counter2 == 0;
|
||||
}
|
||||
|
||||
bool waitForTimeout(void) {
|
||||
while (true) {
|
||||
Control c; // reads the current value from the control register
|
||||
if (c.enable_timer_counter2 == 0) {
|
||||
return false;
|
||||
} else if (c.status_timer_counter2 == 1) {
|
||||
return true;
|
||||
} else {
|
||||
Core::pause();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool delay(uint16_t us) { return set(us) && waitForTimeout(); }
|
||||
|
||||
void pcspeaker(uint32_t freq) {
|
||||
Control c;
|
||||
if (freq == 0) {
|
||||
disable();
|
||||
} else {
|
||||
// calculate frequency divider
|
||||
uint64_t div = BASE_FREQUENCY / freq;
|
||||
if (div > 0xffff) {
|
||||
div = 0xffff;
|
||||
}
|
||||
|
||||
// check if already configured
|
||||
if (c.enable_speaker_data == 0) {
|
||||
// if not, set mode
|
||||
Mode m(AccessMode::LOW_AND_HIGH_BYTE,
|
||||
OperatingMode::SQUARE_WAVE_GENERATOR, Format::BINARY);
|
||||
m.write();
|
||||
}
|
||||
|
||||
// write frequency divider
|
||||
data.outb(div & 0xff);
|
||||
data.outb((div >> 8) & 0xff);
|
||||
|
||||
// already configured? (second part to prevent playing a wrong sound)
|
||||
if (c.enable_speaker_data == 0) {
|
||||
// activate PC speaker
|
||||
c.enable_speaker_data = 1;
|
||||
c.enable_timer_counter2 = 1;
|
||||
c.write();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void disable(void) {
|
||||
Control c;
|
||||
c.enable_speaker_data = 0;
|
||||
c.enable_timer_counter2 = 0;
|
||||
c.write();
|
||||
}
|
||||
|
||||
} // namespace PIT
|
||||
@ -0,0 +1,80 @@
|
||||
/*! \file
|
||||
* \brief The old/historical \ref PIT "Programmable Interval Timer (PIT)"
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "../types.h"
|
||||
|
||||
/*! \brief Abstraction of the historical Programmable Interval Timer (PIT).
|
||||
*
|
||||
* Historically, PCs had a Timer component of type 8253 or 8254, modern systems
|
||||
* come with a compatible chip. Each of these chips provides three 16-bit wide
|
||||
* counters ("channel"), each running at a frequency of 1.19318 MHz. The timer's
|
||||
* counting speed is thereby independent from the CPU frequency.
|
||||
*
|
||||
* Traditionally, the first counter (channel 0) was used for triggering
|
||||
* interrupts, the second one (channel 1) controlled the memory refresh, and the
|
||||
* third counter (channel 2) was assigned to the PC speaker.
|
||||
*
|
||||
* As the PIT's frequency is fixed to a constant value of 1.19318 MHz, the PIT
|
||||
* can be used for calibration. For this purpose, we use channel 2 only.
|
||||
*
|
||||
* \note Interrupts should be disabled while configuring the timer.
|
||||
*/
|
||||
namespace PIT {
|
||||
|
||||
/*! \brief Start timer
|
||||
*
|
||||
* Sets the channel 2 timer to the provided value and starts counting.
|
||||
*
|
||||
* \note The maximum waiting time is approx. 54,900 us (16 bit / 1.193 MHz).
|
||||
* \param us Waiting time in us
|
||||
* \return `true` if the counter is running; `false` if the waiting time
|
||||
* exceeds the limits.
|
||||
*/
|
||||
bool set(uint16_t us);
|
||||
|
||||
/*! \brief Reads the current timer value
|
||||
* \return Current timer value
|
||||
*/
|
||||
uint16_t get(void);
|
||||
|
||||
/*! \brief Check if the timer is running
|
||||
* \return `true` if running, `false` otherwise
|
||||
*/
|
||||
bool isActive(void);
|
||||
|
||||
/*! \brief (Active) waiting for timeout
|
||||
* \return `true` when timeout was successfully hit, `false` if the timer was
|
||||
* not active prior to calling.
|
||||
*/
|
||||
bool waitForTimeout(void);
|
||||
|
||||
/*! \brief Set the timer and wait for timeout
|
||||
* \note The maximum waiting time is approx. 54,900 us (16 bit / 1.193 MHz).
|
||||
* \param us Waiting time in us
|
||||
* \return `true` when waiting successfully terminated; `false` on error (e.g.,
|
||||
* waiting time exceeds its limits)
|
||||
*/
|
||||
bool delay(uint16_t us);
|
||||
|
||||
/*! \brief Play a given frequency on the PC speaker.
|
||||
*
|
||||
* As the PC speaker is connected to PIT channel 2, the PIT can be used to play
|
||||
* an acoustic signal. Playing sounds occupies the PIT, so it cannot be used for
|
||||
* other purposes while playback.
|
||||
*
|
||||
* \note Not every PC has an activated PC speaker
|
||||
* \note Qemu & KVM have to be launched with `-soundhw pcspk`.
|
||||
* If you still cannot hear anything, try to set `QEMU_AUDIO_DRV` to
|
||||
* `alsa` (by launching \StuBS with `QEMU_AUDIO_DRV=alsa make kvm`)
|
||||
* \param freq Frequency (in Hz) of the sound to be played, or 0 to deactivate
|
||||
* playback.
|
||||
*/
|
||||
void pcspeaker(uint32_t freq);
|
||||
|
||||
/*! \brief Deactivate the timer
|
||||
*/
|
||||
void disable(void);
|
||||
|
||||
} // namespace PIT
|
||||
@ -0,0 +1,41 @@
|
||||
#include "serial.h"
|
||||
|
||||
Serial::Serial(ComPort port, BaudRate baud_rate, DataBits data_bits,
|
||||
StopBits stop_bits, Parity parity)
|
||||
: port(port) {
|
||||
// initialize FIFO mode, no irqs for sending, irq if first byte was received
|
||||
|
||||
// line control, select r/w of divisor latch register
|
||||
writeReg(LINE_CONTROL_REGISTER, DIVISOR_LATCH_ACCESS_BIT);
|
||||
|
||||
// TODO: Implement here the correct handling of input arguments
|
||||
(void)baud_rate;
|
||||
(void)data_bits;
|
||||
(void)stop_bits;
|
||||
(void)parity;
|
||||
|
||||
// FIFO: Enable & clear buffers
|
||||
writeReg(FIFO_CONTROL_REGISTER,
|
||||
ENABLE_FIFO | CLEAR_RECEIVE_FIFO | CLEAR_TRANSMIT_FIFO);
|
||||
|
||||
// Modem Control: OUT2 (0000 1000) must be set for interrupt
|
||||
writeReg(MODEM_CONTROL_REGISTER, OUT_2);
|
||||
}
|
||||
|
||||
void Serial::writeReg(RegisterIndex reg, char out) {
|
||||
// TODO: Implement
|
||||
(void)reg;
|
||||
(void)out;
|
||||
}
|
||||
|
||||
char Serial::readReg(RegisterIndex reg) {
|
||||
// TODO: Implement
|
||||
(void)reg;
|
||||
return '\0';
|
||||
}
|
||||
|
||||
int Serial::write(char out) {
|
||||
// TODO: Implement
|
||||
(void)out;
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,205 @@
|
||||
/*! \file
|
||||
* \brief Communication via the \ref Serial interface (RS-232)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "../types.h"
|
||||
|
||||
/*! \brief Serial interface.
|
||||
* \ingroup io
|
||||
*
|
||||
* This class provides a serial interface (COM1 - COM4) for communication with
|
||||
* the outside world.
|
||||
*
|
||||
* The first IBM PC used the external chip [8250
|
||||
* UART](https://de.wikipedia.org/wiki/NSC_8250), whereas, in today's systems,
|
||||
* this functionality is commonly integrated into the motherboard chipset, but
|
||||
* remained compatible.
|
||||
*
|
||||
* \see [PC8250A Data Sheet](uart-8250a.pdf#page=11) (Registers on page 11)
|
||||
* \see [PC16550D Data Sheet](uart-16550d.pdf#page=16) (Successor, for optional
|
||||
* FIFO buffer, page 16)
|
||||
*/
|
||||
|
||||
class Serial {
|
||||
public:
|
||||
/*! \brief COM-Port
|
||||
*
|
||||
* The serial interface and its hardware addresses. Modern desktop PCs have,
|
||||
* at most, a single, physical COM-port (`COM1`)
|
||||
*/
|
||||
enum ComPort {
|
||||
COM1 = 0x3f8,
|
||||
COM2 = 0x2f8,
|
||||
COM3 = 0x3e8,
|
||||
COM4 = 0x2e8,
|
||||
};
|
||||
|
||||
/*! \brief Transmission speed
|
||||
*
|
||||
* The unit Baud describes the transmission speed in number of symbols per
|
||||
* seconds. 1 Baud therefore equals the transmission of 1 symbol per second.
|
||||
* The possible Baud rates are whole-number dividers of the clock frequency
|
||||
* of 115200 Hz..
|
||||
*/
|
||||
enum BaudRate {
|
||||
BAUD_300 = 384,
|
||||
BAUD_600 = 192,
|
||||
BAUD_1200 = 96,
|
||||
BAUD_2400 = 48,
|
||||
BAUD_4800 = 24,
|
||||
BAUD_9600 = 12,
|
||||
BAUD_19200 = 6,
|
||||
BAUD_38400 = 3,
|
||||
BAUD_57600 = 2,
|
||||
BAUD_115200 = 1,
|
||||
};
|
||||
|
||||
/*! \brief Number of data bits per character */
|
||||
enum DataBits : uint8_t {
|
||||
DATA_5BIT = 0,
|
||||
DATA_6BIT = 1,
|
||||
DATA_7BIT = 2,
|
||||
DATA_8BIT = 3,
|
||||
};
|
||||
|
||||
/*! \brief Number of stop bits per character */
|
||||
enum StopBits : uint8_t {
|
||||
STOP_1BIT = 0,
|
||||
STOP_1_5BIT = 4,
|
||||
STOP_2BIT = 4,
|
||||
};
|
||||
|
||||
/*! \brief parity bit */
|
||||
enum Parity : uint8_t {
|
||||
PARITY_NONE = 0,
|
||||
PARITY_ODD = 8,
|
||||
PARITY_EVEN = 24,
|
||||
PARITY_MARK = 40,
|
||||
PARITY_SPACE = 56,
|
||||
};
|
||||
|
||||
private:
|
||||
/*! \brief register index */
|
||||
enum RegisterIndex {
|
||||
// if Divisor Latch Access Bit [DLAB] = 0
|
||||
RECEIVE_BUFFER_REGISTER = 0, ///< read only
|
||||
TRANSMIT_BUFFER_REGISTER = 0, ///< write only
|
||||
INTERRUPT_ENABLE_REGISTER = 1,
|
||||
|
||||
// if Divisor Latch Access Bit [DLAB] = 1
|
||||
DIVISOR_LOW_REGISTER = 0,
|
||||
DIVISOR_HIGH_REGISTER = 1,
|
||||
|
||||
// (irrespective from DLAB)
|
||||
INTERRUPT_IDENT_REGISTER = 2, ///< read only
|
||||
FIFO_CONTROL_REGISTER =
|
||||
2, ///< write only -- 16550 and newer (esp. not 8250a)
|
||||
LINE_CONTROL_REGISTER = 3, ///< highest-order bit is DLAB (see above)
|
||||
MODEM_CONTROL_REGISTER = 4,
|
||||
LINE_STATUS_REGISTER = 5,
|
||||
MODEM_STATUS_REGISTER = 6
|
||||
};
|
||||
|
||||
/*! \brief Mask for the respective register */
|
||||
enum RegisterMask : uint8_t {
|
||||
// Interrupt Enable Register
|
||||
RECEIVED_DATA_AVAILABLE = 1 << 0,
|
||||
TRANSMITTER_HOLDING_REGISTER_EMPTY = 1 << 1,
|
||||
RECEIVER_LINE_STATUS = 1 << 2,
|
||||
MODEM_STATUS = 1 << 3,
|
||||
|
||||
// Interrupt Ident Register
|
||||
INTERRUPT_PENDING = 1 << 0, ///< 0 means interrupt pending
|
||||
INTERRUPT_ID_0 = 1 << 1,
|
||||
INTERRUPT_ID_1 = 1 << 2,
|
||||
|
||||
// FIFO Control Register
|
||||
ENABLE_FIFO = 1 << 0, ///< 0 means disabled ^= conforming to 8250a
|
||||
CLEAR_RECEIVE_FIFO = 1 << 1,
|
||||
CLEAR_TRANSMIT_FIFO = 1 << 2,
|
||||
DMA_MODE_SELECT = 1 << 3,
|
||||
TRIGGER_RECEIVE = 1 << 6,
|
||||
|
||||
// Line Control Register
|
||||
// bits per character: 5 6 7 8
|
||||
WORD_LENGTH_SELECT_0 = 1 << 0, // Setting Select0: 0 1 0 1
|
||||
WORD_LENGTH_SELECT_1 = 1 << 1, // Setting Select1: 0 0 1 1
|
||||
NUMBER_OF_STOP_BITS = 1 << 2, // 0 ≙ one stop bit, 1 ≙ 1.5/2 stop bits
|
||||
PARITY_ENABLE = 1 << 3,
|
||||
EVEN_PARITY_SELECT = 1 << 4,
|
||||
STICK_PARITY = 1 << 5,
|
||||
SET_BREAK = 1 << 6,
|
||||
DIVISOR_LATCH_ACCESS_BIT = 1 << 7, // DLAB
|
||||
|
||||
// Modem Control Register
|
||||
DATA_TERMINAL_READY = 1 << 0,
|
||||
REQUEST_TO_SEND = 1 << 1,
|
||||
OUT_1 = 1 << 2,
|
||||
OUT_2 = 1 << 3, // must be set for interrupts!
|
||||
LOOP = 1 << 4,
|
||||
|
||||
// Line Status Register
|
||||
DATA_READY = 1 << 0, // Set when there is a value in the receive buffer
|
||||
OVERRUN_ERROR = 1 << 1,
|
||||
PARITY_ERROR = 1 << 2,
|
||||
FRAMING_ERROR = 1 << 3,
|
||||
BREAK_INTERRUPT = 1 << 4,
|
||||
TRANSMITTER_HOLDING_REGISTER = 1 << 5,
|
||||
TRANSMITTER_EMPTY = 1 << 6, // Send buffer empty (ready to send)
|
||||
|
||||
// Modem Status Register
|
||||
DELTA_CLEAR_TO_SEND = 1 << 0,
|
||||
DELTA_DATA_SET_READY = 1 << 1,
|
||||
TRAILING_EDGE_RING_INDICATOR = 1 << 2,
|
||||
DELTA_DATA_CARRIER_DETECT = 1 << 3,
|
||||
CLEAR_TO_SEND = 1 << 4,
|
||||
DATA_SET_READY = 1 << 5,
|
||||
RING_INDICATOR = 1 << 6,
|
||||
DATA_CARRIER_DETECT = 1 << 7
|
||||
};
|
||||
|
||||
/*! \brief Read value from register
|
||||
*
|
||||
* \todo(11) Implement Method
|
||||
*
|
||||
* \param reg Register index
|
||||
* \return The value read from register
|
||||
*/
|
||||
char readReg(RegisterIndex reg);
|
||||
|
||||
/*! \brief Write value to register
|
||||
*
|
||||
* \todo(11) Implement Method
|
||||
*
|
||||
* \param reg Register index
|
||||
* \param out value to be written
|
||||
*/
|
||||
void writeReg(RegisterIndex reg, char out);
|
||||
|
||||
protected:
|
||||
/*! \brief Selected COM port */
|
||||
const ComPort port;
|
||||
|
||||
public:
|
||||
/*! \brief Constructor
|
||||
*
|
||||
* Creates a Serial object that encapsulates the used COM port, as well as the
|
||||
* parameters used for the serial connection. Default values are `8N1` (8 bit,
|
||||
* no parity bit, one stop bit) with 115200 Baud using COM1.
|
||||
*
|
||||
* \todo(11) - Implement Constructor
|
||||
*/
|
||||
explicit Serial(ComPort port = COM1, BaudRate baud_rate = BAUD_115200,
|
||||
DataBits data_bits = DATA_8BIT,
|
||||
StopBits stop_bits = STOP_1BIT, Parity parity = PARITY_NONE);
|
||||
|
||||
/*! \brief Write one byte to the serial interface
|
||||
*
|
||||
* \todo(11) - Implement Method
|
||||
*
|
||||
* \param out Byte to be written
|
||||
* \return Byte written (or `-1` if writing byte failed)
|
||||
*/
|
||||
int write(char out);
|
||||
};
|
||||
@ -0,0 +1,16 @@
|
||||
#include "system.h"
|
||||
|
||||
#include "../debug/output.h"
|
||||
#include "cmos.h"
|
||||
#include "ioport.h"
|
||||
|
||||
namespace System {
|
||||
|
||||
void reboot() {
|
||||
const IOPort system_control_port_a(0x92);
|
||||
DBG_VERBOSE << "rebooting smp" << endl;
|
||||
CMOS::write(CMOS::REG_STATUS_SHUTDOWN, 0);
|
||||
system_control_port_a.outb(0x3);
|
||||
}
|
||||
|
||||
} // namespace System
|
||||
@ -0,0 +1,15 @@
|
||||
/*! \file
|
||||
* \brief General \ref System functionality (\ref System::reboot "reboot")
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "../types.h"
|
||||
|
||||
/*! \brief General System functions
|
||||
*/
|
||||
namespace System {
|
||||
|
||||
/*! \brief Perform a reboot
|
||||
*/
|
||||
void reboot();
|
||||
} // namespace System
|
||||
@ -0,0 +1,41 @@
|
||||
#include "textwindow.h"
|
||||
|
||||
TextWindow::TextWindow(unsigned from_col, unsigned to_col, unsigned from_row,
|
||||
unsigned to_row, bool use_cursor) {
|
||||
(void)from_col;
|
||||
(void)to_col;
|
||||
(void)from_row;
|
||||
(void)to_row;
|
||||
(void)use_cursor;
|
||||
}
|
||||
|
||||
void TextWindow::setPos(unsigned rel_x, unsigned rel_y) {
|
||||
(void)rel_x;
|
||||
(void)rel_y;
|
||||
}
|
||||
|
||||
void TextWindow::getPos(unsigned& rel_x, unsigned& rel_y) const {
|
||||
(void)rel_x;
|
||||
(void)rel_y;
|
||||
}
|
||||
|
||||
void TextWindow::setPos(int rel_x, int rel_y) {
|
||||
(void)rel_x;
|
||||
(void)rel_y;
|
||||
}
|
||||
|
||||
void TextWindow::getPos(int& rel_x, int& rel_y) const {
|
||||
(void)rel_x;
|
||||
(void)rel_y;
|
||||
}
|
||||
|
||||
void TextWindow::print(const char* str, size_t length, CGA::Attribute attrib) {
|
||||
(void)str;
|
||||
(void)length;
|
||||
(void)attrib;
|
||||
}
|
||||
|
||||
void TextWindow::reset(char character, CGA::Attribute attrib) {
|
||||
(void)character;
|
||||
(void)attrib;
|
||||
}
|
||||
@ -0,0 +1,134 @@
|
||||
/*! \file
|
||||
* \brief \ref TextWindow provides virtual output windows in text mode
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "../types.h"
|
||||
#include "cga.h"
|
||||
|
||||
/*! \brief Virtual windows in text mode
|
||||
* \ingroup io
|
||||
*
|
||||
* Outputs text on a part of the screen in \ref CGA,
|
||||
* a window is defined in by position and size (with its own cursor).
|
||||
*
|
||||
* This allows to separate the output of the application from the debug output
|
||||
* on the screen without having to synchronize.
|
||||
*/
|
||||
class TextWindow {
|
||||
// Prevent copies and assignments
|
||||
TextWindow(const TextWindow&) = delete;
|
||||
TextWindow& operator=(const TextWindow&) = delete;
|
||||
|
||||
public:
|
||||
/*! \brief Constructor of a text window
|
||||
*
|
||||
* Creates a virtual, rectangular text window on the screen.
|
||||
* The coordinates to construct the window are absolute positions in the
|
||||
* \ref CGA screen.
|
||||
*
|
||||
* \note Overlapping windows are neither supported nor prevented -- better
|
||||
* just try to avoid construction windows with overlapping coordinates!
|
||||
*
|
||||
* \warning Don't use the hardware cursor in more than one window!
|
||||
*
|
||||
* \param from_col Text Window starts in column `from_col`,
|
||||
* the first (leftmost) possible column is `0`
|
||||
* \param to_col Text Window extends to the right to column `to_col`
|
||||
* (exclusive). This column has to be strictly greater than `from_col`, the
|
||||
* maximum allowed value is \ref CGA::COLUMNS (rightmost)
|
||||
* \param from_row Text Window starts in row `from_row`,
|
||||
* the first possible (uppermost) row is `0`
|
||||
* \param to_row Text Window extends down to row `to_row` (exclusive).
|
||||
* This row has to be strictly greater than `from_row`,
|
||||
* the maximum allowed value is \ref CGA::ROWS (bottom-most)
|
||||
* \param use_cursor Specifies whether the hardware cursor (`true`) or a
|
||||
* software cursor/variable (`false`) should be used to
|
||||
* store the current position
|
||||
*
|
||||
* \todo(11) Implement constructor
|
||||
*/
|
||||
TextWindow(unsigned from_col, unsigned to_col, unsigned from_row,
|
||||
unsigned to_row, bool use_cursor = false);
|
||||
|
||||
/*! \brief Set the cursor position in the window
|
||||
*
|
||||
* Depending on the constructor parameter `use_cursor` either the
|
||||
* hardware cursor (and only the hardware cursor!) is used or the position
|
||||
* is stored internally in the object.
|
||||
*
|
||||
* The coordinates are relative to the upper left starting position of
|
||||
* the window.
|
||||
*
|
||||
* \param rel_x Column in window
|
||||
* \param rel_y Row in window
|
||||
* \todo(11) Implement method, use \ref CGA::setCursor() for the hardware
|
||||
* cursor
|
||||
*/
|
||||
void setPos(unsigned rel_x, unsigned rel_y);
|
||||
|
||||
/*! \brief Set the cursor position in the window
|
||||
*
|
||||
* Depending on the constructor parameter `use_cursor` either the
|
||||
* hardware cursor (and only the hardware cursor!) is used or the position
|
||||
* is stored internally in the object.
|
||||
*
|
||||
* The coordinates are relative to the upper left starting position of
|
||||
* the window.
|
||||
* Negative coordinates are interpreted relative to the right and bottom
|
||||
* border of the window.
|
||||
*
|
||||
* \todo(11) Implement this method (it can either use or replace
|
||||
* \ref setPos(unsigned, unsigned))
|
||||
*/
|
||||
void setPos(int rel_x, int rel_y);
|
||||
|
||||
/*! \brief Get the current cursor position in the window
|
||||
*
|
||||
* Depending on the constructor parameter `use_cursor` either the
|
||||
* hardware cursor (and only the hardware cursor!) is used or the position
|
||||
* is retrieved from the internally stored object.
|
||||
*
|
||||
* \param rel_x Column in window
|
||||
* \param rel_y Row in window
|
||||
* \todo(11) Implement Method, use \ref CGA::getCursor() for the hardware
|
||||
* cursor
|
||||
*/
|
||||
void getPos(unsigned& rel_x, unsigned& rel_y) const;
|
||||
|
||||
/// \copydoc TextWindow::getPos(unsigned&,unsigned&) const
|
||||
void getPos(int& rel_x, int& rel_y) const;
|
||||
|
||||
/*! \brief Display multiple characters in the window
|
||||
*
|
||||
* Output a character string, starting at the current cursor position.
|
||||
* Since the string does not need to contain a `\0` termination (unlike the
|
||||
* common C string), a length parameter is required to specify the number
|
||||
* of characters in the string.
|
||||
* When the output is complete, the cursor is positioned after the last
|
||||
* printed character.
|
||||
* The same attributes (colors) are used for the entire text.
|
||||
*
|
||||
* If there is not enough space left at the end of the line,
|
||||
* the output continues on the following line.
|
||||
* As soon as the last window line is filled, the entire window area is
|
||||
* moved up one line: The first line disappears, the bottom line is cleared.
|
||||
*
|
||||
* A line break also occurs whenever the character `\n` appears in the text.
|
||||
*
|
||||
* \param string Text to be printed
|
||||
* \param length Length of text
|
||||
* \param attrib Attribute for text
|
||||
* \todo(11) Implement Method
|
||||
*/
|
||||
void print(const char* string, size_t length,
|
||||
CGA::Attribute attrib = CGA::Attribute()); // NOLINT
|
||||
|
||||
/*! \brief Delete all contents in the window and reset the cursor.
|
||||
*
|
||||
* \param character Fill character
|
||||
* \param attrib Attribute for fill character
|
||||
* \todo(11) Implement Method
|
||||
*/
|
||||
void reset(char character = ' ', CGA::Attribute attrib = CGA::Attribute());
|
||||
};
|
||||
@ -0,0 +1,245 @@
|
||||
; The stony path to Long Mode (64-bit)...
|
||||
; ... begins in 32-bit Protected Mode
|
||||
[BITS 32]
|
||||
|
||||
; Pointer to Long Mode Global Descriptor Table (GDT, arch/gdt.cc)
|
||||
[EXTERN gdt_long_mode_pointer]
|
||||
|
||||
[GLOBAL long_mode]
|
||||
long_mode:
|
||||
|
||||
; You can check if the CPU supports Long Mode by using the `cpuid` command.
|
||||
; Problem: You first have to figure out if the `cpuid` command itself is
|
||||
; supported. Therefore, you have to try to reverse the 21st bit in the EFLAGS
|
||||
; register -- if it works, then there is the 'cpuid' instruction.
|
||||
CPUID_BIT_MASK equ 1 << 21
|
||||
|
||||
check_cpuid:
|
||||
; Save EFLAGS on stack
|
||||
pushfd
|
||||
|
||||
; Copy stored EFLAGS from stack to EAX register
|
||||
mov eax, [esp]
|
||||
|
||||
; Flip the 21st bit (ID) in EAX
|
||||
xor eax, CPUID_BIT_MASK
|
||||
|
||||
; Copy EAX to EFLAGS (using the stack)
|
||||
push eax
|
||||
popfd
|
||||
|
||||
; And reverse: copy EFLAGS to EAX (using the stack)
|
||||
; (but the 21st bit should now still be flipped, if `cpuid` is supported)
|
||||
pushfd
|
||||
pop eax
|
||||
|
||||
; Compare the new EFLAGS copy (residing in EAX) with the EFLAGS stored at
|
||||
; the beginning of this function by using an exclusive OR -- all different
|
||||
; (flipped) bits will be stored in EAX.
|
||||
xor eax, [esp]
|
||||
|
||||
; Restore original EFLAGS
|
||||
popfd
|
||||
|
||||
; If 21st Bit in EAX is set, `cpuid` is supported -- continue at check_long_mode
|
||||
and eax, CPUID_BIT_MASK
|
||||
jnz check_long_mode
|
||||
|
||||
; Show error message "No CPUID" and stop CPU
|
||||
mov dword [0xb8000], 0xcf6fcf4e
|
||||
mov dword [0xb8004], 0xcf43cf20
|
||||
mov dword [0xb8008], 0xcf55cf50
|
||||
mov dword [0xb800c], 0xcf44cf49
|
||||
hlt
|
||||
|
||||
; Now you are able to use the `cpuid` instruction to check if Long Mode is
|
||||
; available -- after you've checked if the `cpuid` is able to perform the
|
||||
; check itself (since it is an extended `cpuid` function)...
|
||||
|
||||
CPUID_GET_LARGEST_EXTENDED_FUNCTION_NUMBER equ 0x80000000
|
||||
CPUID_GET_EXTENDED_PROCESSOR_FEATURES equ 0x80000001
|
||||
CPUID_HAS_LONGMODE equ 1 << 29
|
||||
|
||||
check_long_mode:
|
||||
; Set argument for `cpuid` to check the availability of extended functions
|
||||
; and call cpuid
|
||||
mov eax, CPUID_GET_LARGEST_EXTENDED_FUNCTION_NUMBER
|
||||
cpuid
|
||||
; The return value contains the maximum function number supported by `cpuid`,
|
||||
; You'll need the function number for extended processor features
|
||||
cmp eax, CPUID_GET_EXTENDED_PROCESSOR_FEATURES
|
||||
; If not present, the CPU is definitely too old to support long mode
|
||||
jb no_long_mode
|
||||
|
||||
; Finally, you are able to check the Long Mode support itself
|
||||
mov eax, CPUID_GET_EXTENDED_PROCESSOR_FEATURES
|
||||
cpuid
|
||||
; If the return value in the EDX register has set the 29th bit,
|
||||
; then long mode is supported -- continue with setup_paging
|
||||
test edx, CPUID_HAS_LONGMODE
|
||||
jnz setup_paging
|
||||
|
||||
no_long_mode:
|
||||
; Show error message "No 64bit" and stop CPU
|
||||
mov dword [0xb8000], 0xcf6fcf4e
|
||||
mov dword [0xb8004], 0xcf36cf20
|
||||
mov dword [0xb8008], 0xcf62cf34
|
||||
mov dword [0xb800c], 0xcf74cf69
|
||||
hlt
|
||||
|
||||
; Paging is required for Long Mode.
|
||||
; Since an extensive page manager might be a bit of an overkill to start with,
|
||||
; the following code creates an identity mapping for the first four gigabytes
|
||||
; (using huge pages): each virtual address will point to the same physical one.
|
||||
; This area (up to 4 GiB) is important for some memory mapped devices (APIC)
|
||||
; and you don't want to remap them yet for simplicity reasons.
|
||||
; In the advanced operating systems lecture, this topic is covered in detail,
|
||||
; however, if you want a quick overview, have a look at
|
||||
; https://wiki.osdev.org/Page_Tables#2_MiB_pages_2
|
||||
|
||||
PAGE_SIZE equ 4096
|
||||
PAGE_FLAGS_PRESENT equ 1 << 0
|
||||
PAGE_FLAGS_WRITEABLE equ 1 << 1
|
||||
PAGE_FLAGS_USER equ 1 << 2
|
||||
PAGE_FLAGS_HUGE equ 1 << 7
|
||||
|
||||
setup_paging:
|
||||
; Unlike in Protected Mode, an entry in the page table has a size of 8 bytes
|
||||
; (vs 4 bytes), so there are only 512 (and not 1024) entries per table.
|
||||
; Structure of the 3-level PAE paging: One entry in the
|
||||
; - lv2: Page-Directory-Table (PDT) covers 2 MiB (1 Huge Page)
|
||||
; - lv3: Page-Directory-Pointer-Table (PDPT) covers 1 GiB (512 * 2 MiB)
|
||||
; - lv4: Page-Map-Level-4-Table (PML4) covers 512 GiB (512 * 1 GiB)
|
||||
|
||||
; To address 4 GiB only four level-2 tables are required.
|
||||
; All entries of the level-2 tables should be marked as writeable (attributes)
|
||||
; and map (point to) the corresponding physical memory.
|
||||
|
||||
; This is done in a loop using ECX as counter
|
||||
mov ecx, 0
|
||||
|
||||
.identitymap_level2:
|
||||
; Calculate physical address in EAX (2 MiB multiplied by the counter)
|
||||
mov eax, 0x200000
|
||||
mul ecx
|
||||
; Configure page attributes
|
||||
or eax, PAGE_FLAGS_PRESENT | PAGE_FLAGS_WRITEABLE | PAGE_FLAGS_HUGE | PAGE_FLAGS_USER
|
||||
; Write (8 byte) entry in the level-2 table
|
||||
mov [paging_level2_tables + ecx * 8], eax
|
||||
|
||||
; Increment counter...
|
||||
inc ecx
|
||||
; ... until all four level-2 tables are filled
|
||||
cmp ecx, 512 * 4
|
||||
jne .identitymap_level2
|
||||
|
||||
; The first four entries of the level-3 table should point to the
|
||||
; four level-2 tables (and be writeable as well).
|
||||
; Again, ECX acts as counter for the loop
|
||||
mov ecx, 0
|
||||
|
||||
.identitymap_level3:
|
||||
; Calculate the address: ECX * PAGE_SIZE + paging_level2_tables
|
||||
mov eax, ecx
|
||||
; The size of a page is stored in the EDX register
|
||||
mov edx, PAGE_SIZE
|
||||
mul edx
|
||||
add eax, paging_level2_tables
|
||||
; Configure attributes
|
||||
or eax, PAGE_FLAGS_PRESENT | PAGE_FLAGS_WRITEABLE | PAGE_FLAGS_USER
|
||||
; Write (8 byte) entry in the level-3 table
|
||||
mov [paging_level3_table + ecx * 8], eax
|
||||
|
||||
; Increment counter...
|
||||
inc ecx
|
||||
; ... until all four entries of the table are written
|
||||
cmp ecx, 4
|
||||
jne .identitymap_level3
|
||||
|
||||
mov eax, paging_level2_tables
|
||||
or eax, PAGE_FLAGS_PRESENT | PAGE_FLAGS_WRITEABLE | PAGE_FLAGS_USER
|
||||
mov [paging_level3_table], eax
|
||||
|
||||
; The first entry of the level-4 table should point to to the level-3 table
|
||||
mov eax, paging_level3_table
|
||||
or eax, PAGE_FLAGS_PRESENT | PAGE_FLAGS_WRITEABLE | PAGE_FLAGS_USER
|
||||
mov [paging_level4_table], eax
|
||||
|
||||
; Time to activate paging
|
||||
paging_enable:
|
||||
; First setup the control registers
|
||||
|
||||
; Write the address of the level-4 table into the CR3 register
|
||||
mov eax, paging_level4_table
|
||||
mov cr3, eax
|
||||
|
||||
; Activate Physical Address Extension (PAE)
|
||||
; by setting the 5th bits in the CR4 register
|
||||
mov eax, cr4
|
||||
or eax, 1 << 5
|
||||
mov cr4, eax
|
||||
|
||||
; Set the Long Mode Enable Bit in den EFER MSR
|
||||
; (Extended Feature Enable Register Model Specific Register)
|
||||
mov ecx, 0xC0000080
|
||||
rdmsr
|
||||
or eax, 1 << 8
|
||||
wrmsr
|
||||
|
||||
; Finally, the 31st bit in CR0 is set to enable Paging
|
||||
mov eax, cr0
|
||||
or eax, 1 << 31
|
||||
mov cr0, eax
|
||||
|
||||
; Load Long Mode Global Descriptor Table
|
||||
lgdt [gdt_long_mode_pointer]
|
||||
|
||||
; Far jump to the 64-bit start code
|
||||
jmp 0x8:long_mode_start
|
||||
|
||||
; print `KO` to screen
|
||||
mov dword [0xb8000], 0x3f4f3f4b
|
||||
hlt
|
||||
|
||||
; Memory reserved for page tables
|
||||
[SECTION .bss]
|
||||
|
||||
align 4096
|
||||
|
||||
[GLOBAL paging_level4_table]
|
||||
[GLOBAL paging_level3_table]
|
||||
[GLOBAL paging_level2_tables]
|
||||
; 1x Level-4 Table (Page Map Level 4)
|
||||
paging_level4_table:
|
||||
resb PAGE_SIZE
|
||||
|
||||
; 1x Level-3 Table (Page Directory Pointer Table)
|
||||
paging_level3_table:
|
||||
resb PAGE_SIZE
|
||||
|
||||
; 4x Level-2 Table (Page Directory)
|
||||
paging_level2_tables:
|
||||
resb PAGE_SIZE * 4
|
||||
|
||||
[SECTION .text]
|
||||
[EXTERN kernel_init] ; C++ entry function
|
||||
|
||||
; Continue with 64 bit code
|
||||
[BITS 64]
|
||||
|
||||
long_mode_start:
|
||||
; Zero all segment register
|
||||
mov ax, 0x0
|
||||
mov ss, ax
|
||||
mov ds, ax
|
||||
mov es, ax
|
||||
mov fs, ax
|
||||
mov gs, ax
|
||||
|
||||
; Call high-level (C++) kernel initialization function
|
||||
call kernel_init
|
||||
|
||||
; Print `STOP` to screen and stop
|
||||
mov rax, 0x2f502f4f2f544f53
|
||||
mov qword [0xb8000], rax
|
||||
hlt
|
||||
@ -0,0 +1,22 @@
|
||||
; Magic Header, has to be present in Kernel to indicate Multiboot compliance
|
||||
MULTIBOOT_HEADER_MAGIC_OS equ 0x1badb002
|
||||
|
||||
; Answer by the boot loader for Multiboot compliance, written in eax register
|
||||
MULTIBOOT_HEADER_MAGIC_LOADER equ 0x2badb002
|
||||
|
||||
; Flags instructing the Multiboot compliant boot loader to setup the system
|
||||
; according to your needs
|
||||
MULTIBOOT_PAGE_ALIGN equ 1<<0 ; Align boot modules (initrds) at 4 KiB border
|
||||
MULTIBOOT_MEMORY_INFO equ 1<<1 ; Request Memory Map information
|
||||
MULTIBOOT_VIDEO_MODE equ 1<<2 ; Configure video mode
|
||||
|
||||
MULTIBOOT_HEADER_FLAGS equ 0
|
||||
|
||||
; Desired video mode (only considered if MULTIBOOT_VIDEO_MODE set)
|
||||
; (boot loader will choose the best fitting mode, which might differ from the settings below)
|
||||
MULTIBOOT_VIDEO_WIDTH equ 1280 ; Desired width
|
||||
MULTIBOOT_VIDEO_HEIGHT equ 1024 ; Desired height
|
||||
MULTIBOOT_VIDEO_BITDEPTH equ 32 ; Desired bit depth
|
||||
|
||||
; Checksum
|
||||
MULTIBOOT_HEADER_CHKSUM equ -(MULTIBOOT_HEADER_MAGIC_OS + MULTIBOOT_HEADER_FLAGS)
|
||||
@ -0,0 +1,167 @@
|
||||
#include "boot/multiboot/data.h"
|
||||
|
||||
/*! \brief Multiboot Information Structure according to Specification
|
||||
* \see [Multiboot Specification]{@ref multiboot}
|
||||
*/
|
||||
struct multiboot_info {
|
||||
/*! \brief Helper Structure
|
||||
*/
|
||||
struct Array {
|
||||
uint32_t size; ///< Length
|
||||
uint32_t addr; ///< Begin (physical address)
|
||||
} __attribute__((packed));
|
||||
|
||||
enum Flag : uint32_t {
|
||||
Memory = 1 << 0, ///< is there basic lower/upper memory information?
|
||||
BootDev = 1 << 1, ///< is there a boot device set?
|
||||
CmdLine = 1 << 2, ///< is the command-line defined?
|
||||
Modules = 1 << 3, ///< are there modules to do something with?
|
||||
/* These next two are mutually exclusive */
|
||||
SymbolTable = 1 << 4, ///< is there an a.out symbol table loaded?
|
||||
SectionHeader = 1 << 5, ///< is there an ELF section header table?
|
||||
|
||||
MemoryMap = 1 << 6, ///< is there a full memory map?
|
||||
DriveInfo = 1 << 7, ///< Is there drive info?
|
||||
ConfigTable = 1 << 8, ///< Is there a config table?
|
||||
BootLoaderName = 1 << 9, ///< Is there a boot loader name?
|
||||
ApmTable = 1 << 10, ///< Is there a APM table?
|
||||
|
||||
// Is there video information?
|
||||
VbeInfo = 1 << 11, ///< Vesa bios extension
|
||||
FramebufferInfo = 1 << 12 ///< Framebuffer
|
||||
} flags;
|
||||
|
||||
/*! \brief Available memory retrieved from BIOS
|
||||
*/
|
||||
struct {
|
||||
uint32_t lower; ///< Amount of memory below 1 MiB in kilobytes
|
||||
uint32_t upper; ///< Amount of memory above 1 MiB in kilobytes
|
||||
} mem __attribute__((packed));
|
||||
uint32_t boot_device; ///< "root" partition
|
||||
uint32_t cmdline; ///< Kernel command line
|
||||
Array mods; ///< List of boot modules
|
||||
union {
|
||||
/*! \brief Symbol table for kernel in a.out format
|
||||
*/
|
||||
struct {
|
||||
uint32_t tabsize;
|
||||
uint32_t strsize;
|
||||
uint32_t addr;
|
||||
uint32_t reserved;
|
||||
} aout_symbol_table __attribute__((packed));
|
||||
|
||||
/*! \brief Section header table for kernel in ELF
|
||||
*/
|
||||
struct {
|
||||
uint32_t num; ///< Number of entries
|
||||
uint32_t size; ///< Size per entry
|
||||
uint32_t addr; ///< Start of the header table
|
||||
uint32_t shndx; ///< String table index
|
||||
} elf_section_header_table __attribute__((packed));
|
||||
};
|
||||
|
||||
struct Array mmap; ///< Memory Map
|
||||
struct Array drives; ///< Drive Information
|
||||
uint32_t config_table; ///< ROM configuration table
|
||||
uint32_t boot_loader_name; ///< Boot Loader Name
|
||||
uint32_t apm_table; ///< APM table
|
||||
|
||||
struct Multiboot::VBE vbe; ///< VBE Information
|
||||
struct Multiboot::Framebuffer framebuffer; ///< Framebuffer information
|
||||
|
||||
/*! \brief Check if setting is available
|
||||
* \param flag Flag to check
|
||||
* \return `true` if available
|
||||
*/
|
||||
bool has(enum Flag flag) const { return (flags & flag) != 0; }
|
||||
} __attribute__((packed));
|
||||
assert_size(multiboot_info, 116);
|
||||
|
||||
/*! \brief The pointer to the multiboot structures will be assigned in the
|
||||
* assembler startup code (multiboot.inc)
|
||||
*/
|
||||
struct multiboot_info *multiboot_addr = 0;
|
||||
|
||||
namespace Multiboot {
|
||||
Module *getModule(unsigned i) {
|
||||
if (multiboot_addr != nullptr &&
|
||||
multiboot_addr->has(multiboot_info::Flag::Modules) &&
|
||||
i < multiboot_addr->mods.size) {
|
||||
return i + reinterpret_cast<Module *>(
|
||||
static_cast<uintptr_t>(multiboot_addr->mods.addr));
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned getModuleCount() { return multiboot_addr->mods.size; }
|
||||
|
||||
void *Memory::getStartAddress() const {
|
||||
if (sizeof(void *) == 4 && (addr >> 32) != 0) {
|
||||
return reinterpret_cast<void *>(addr & 0xffffffff);
|
||||
} else {
|
||||
return reinterpret_cast<void *>(static_cast<uintptr_t>(addr));
|
||||
}
|
||||
}
|
||||
|
||||
void *Memory::getEndAddress() const {
|
||||
uint64_t end = addr + len;
|
||||
if (sizeof(void *) == 4 && (end >> 32) != 0) {
|
||||
return reinterpret_cast<void *>(addr & 0xffffffff);
|
||||
} else {
|
||||
return reinterpret_cast<void *>(static_cast<uintptr_t>(end));
|
||||
}
|
||||
}
|
||||
|
||||
bool Memory::isAvailable() const { return type == AVAILABLE; }
|
||||
|
||||
Memory *Memory::getNext() const {
|
||||
if (multiboot_addr != nullptr &&
|
||||
multiboot_addr->has(multiboot_info::Flag::MemoryMap)) {
|
||||
uintptr_t next = reinterpret_cast<uintptr_t>(this) + size + sizeof(size);
|
||||
if (next < multiboot_addr->mmap.addr + multiboot_addr->mmap.size) {
|
||||
return reinterpret_cast<Memory *>(next);
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Memory *getMemoryMap() {
|
||||
if (multiboot_addr != nullptr &&
|
||||
multiboot_addr->has(multiboot_info::Flag::MemoryMap) &&
|
||||
multiboot_addr->mmap.size > 0) {
|
||||
return reinterpret_cast<Memory *>(
|
||||
static_cast<uintptr_t>(multiboot_addr->mmap.addr));
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
char *getCommandLine() {
|
||||
return reinterpret_cast<char *>(
|
||||
static_cast<uintptr_t>(multiboot_addr->cmdline));
|
||||
}
|
||||
|
||||
char *getBootLoader() {
|
||||
return reinterpret_cast<char *>(
|
||||
static_cast<uintptr_t>(multiboot_addr->boot_loader_name));
|
||||
}
|
||||
|
||||
VBE *getVesaBiosExtensionInfo() {
|
||||
if (multiboot_addr != nullptr &&
|
||||
multiboot_addr->has(multiboot_info::Flag::VbeInfo)) {
|
||||
return &(multiboot_addr->vbe);
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
Framebuffer *getFramebufferInfo() {
|
||||
if (multiboot_addr != nullptr &&
|
||||
multiboot_addr->has(multiboot_info::Flag::FramebufferInfo)) {
|
||||
return &(multiboot_addr->framebuffer);
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
} // namespace Multiboot
|
||||
@ -0,0 +1,230 @@
|
||||
/*! \file
|
||||
* \brief \ref Multiboot Interface
|
||||
*/
|
||||
#pragma once
|
||||
#include "../../compiler/fix.h"
|
||||
#include "../../debug/assert.h"
|
||||
#include "../../types.h"
|
||||
|
||||
/*! \brief Interface for Multiboot
|
||||
*
|
||||
* Due to historical reasons, a normal BIOS allows you to do quite an egg dance
|
||||
* until you finally reach the actual kernel (especially with only 512 bytes
|
||||
* available in the master boot record...).
|
||||
* Fortunately, there are [boot loaders](https://wiki.osdev.org/Bootloader) that
|
||||
* (partly) do this ungrateful job for you:
|
||||
* They load your kernel into memory, switch (the bootstrap processor) to
|
||||
* protected mode (32 bit) and jump to the entry point of our kernel -- saving
|
||||
* you a lot of boring (or enlightening?) work: reading ancient systems
|
||||
* documentation. One of the most famous representatives is the [Grand Unified
|
||||
* Bootloader (GRUB)](https://www.gnu.org/software/grub/), which is also the
|
||||
* reference implementation of the [Multiboot Specification]{@ref multiboot}.
|
||||
*
|
||||
* A Multiboot compliant boot loader will prepare the system according to your
|
||||
* needs and can hand you a lot of useful information (e.g. references to
|
||||
* initial ramdisks).
|
||||
*
|
||||
* However, you have to inform the loader that you are also compliant to the
|
||||
* specification, and (if required) instruct the loader to adjust specific
|
||||
* settings (e.g. the graphics mode).
|
||||
*
|
||||
* For this purpose you have to configure the beginning of the kernel (the first
|
||||
* 8192 bytes of the kernel binary) accordingly (see `compiler/section.ld`) --
|
||||
* this is were the boot loader will search for a magic header and parse the
|
||||
* subsequent entries containing the desired system configuration.
|
||||
* In StuBS these flags are set in `boot/multiboot/config.inc` and the header
|
||||
* structure is generated in `boot/multiboot/header.asm`.
|
||||
*
|
||||
* The first step in your \ref startup_bsp() "kernel entry function" is saving
|
||||
* the pointer to the struct with the information from the boot loader
|
||||
* (transferred via register `ebx`) -- and \ref Multiboot provides you the
|
||||
* interface to comfortably access its contents!
|
||||
*/
|
||||
namespace Multiboot {
|
||||
/*! \brief Boot Module
|
||||
* (also known as `initrd` = initial Ramdisk)
|
||||
*
|
||||
* \see [1.7 Boot modules]{@ref multiboot}
|
||||
* \see [3.3 Boot information format]{@ref multiboot}
|
||||
*/
|
||||
class Module {
|
||||
uint32_t start; ///< Start address
|
||||
uint32_t end; ///< End address (excluded)
|
||||
uint32_t cmdline; ///< commandline parameter
|
||||
uint32_t pad [[maybe_unused]]; ///< alignment; must be 0
|
||||
|
||||
public:
|
||||
/*! \brief Get start of this boot module
|
||||
* \return Pointer to begin of modules physical address
|
||||
*/
|
||||
void* getStartAddress() const {
|
||||
return reinterpret_cast<void*>(static_cast<uintptr_t>(start));
|
||||
}
|
||||
|
||||
/*! \brief Get end of this boot module
|
||||
* \return Pointer beyond the modules physical address
|
||||
*/
|
||||
void* getEndAddress() const {
|
||||
return reinterpret_cast<void*>(static_cast<uintptr_t>(end));
|
||||
}
|
||||
|
||||
/*! \brief Get the size of this boot module
|
||||
* \return Module size in bytes (difference of end and start address)
|
||||
*/
|
||||
size_t getSize() const { return static_cast<size_t>(end - start); }
|
||||
|
||||
/*! \brief Get the command line for this module
|
||||
* \return pointer to zero terminated string
|
||||
*/
|
||||
char* getCommandLine() const {
|
||||
return reinterpret_cast<char*>(static_cast<uintptr_t>(cmdline));
|
||||
}
|
||||
} __attribute__((packed));
|
||||
assert_size(Module, 16);
|
||||
|
||||
/*! \brief Retrieve a certain boot module
|
||||
* \param i boot module number
|
||||
* \return Pointer to structure with boot module information
|
||||
*/
|
||||
Module* getModule(unsigned i);
|
||||
|
||||
/*! \brief Get the number of modules
|
||||
* \return Pointer to structure with boot module information
|
||||
*/
|
||||
unsigned getModuleCount();
|
||||
|
||||
/*! \brief Get the kernel command line
|
||||
* \return pointer to zero terminated string
|
||||
*/
|
||||
char* getCommandLine();
|
||||
|
||||
/*! \brief Get the name of the boot loader
|
||||
* \return pointer to zero terminated string
|
||||
*/
|
||||
char* getBootLoader();
|
||||
|
||||
/*! \brief Memory Map
|
||||
*
|
||||
* The boot loader queries the BIOS for a memory map and stores its result in
|
||||
* (something like) a linked list. However, this list may not be complete,
|
||||
* can have contradictory entries and does not take the location of your kernel
|
||||
* or any boot modules into account.
|
||||
* (Anyways, it is still the best memory map you will have in StuBS...)
|
||||
*
|
||||
* \note Needs to be enabled explicitly by setting the `MULTIBOOT_MEMORY_INFO`
|
||||
* flag in the multiboot header (see `boot/multiboot/config.inc`)!
|
||||
*
|
||||
* \see [Detecting Memory](https://wiki.osdev.org/Detecting_Memory_(x86))
|
||||
*/
|
||||
class Memory {
|
||||
uint32_t size; ///< Size of this entry (can exceed size of the class, rest
|
||||
///< will be padding bits)
|
||||
uint64_t addr; ///< Begin of memory area
|
||||
uint64_t len; ///< length of the memory area
|
||||
|
||||
/*! \brief Usage Type
|
||||
*/
|
||||
enum Type : uint32_t {
|
||||
AVAILABLE = 1, ///< Memory is available and usable in kernel
|
||||
RESERVED = 2, ///< Memory is reserved (without further explanation)
|
||||
ACPI = 3, ///< Memory may be reclaimed by ACPI
|
||||
NVS = 4, ///< Memory is non volatile storage for ACPI
|
||||
BADRAM = 5 ///< Area contains bad memory
|
||||
} type;
|
||||
|
||||
public:
|
||||
/*! \brief Get start of this memory area
|
||||
* \return Pointer to begin of the physical address of the memory area
|
||||
*/
|
||||
void* getStartAddress() const;
|
||||
|
||||
/*! \brief Get end of this memory area
|
||||
* \return Pointer beyond the physical address of this memory area
|
||||
*/
|
||||
void* getEndAddress() const;
|
||||
|
||||
/*! \brief Is the memory marked as usable
|
||||
* \return `true` if available, `false` if not usable.
|
||||
*/
|
||||
bool isAvailable() const;
|
||||
|
||||
/*! \brief Get the next memory area
|
||||
* \return pointer to the next memory area entry
|
||||
*/
|
||||
Memory* getNext() const;
|
||||
} __attribute__((packed));
|
||||
assert_size(Memory, 24);
|
||||
|
||||
/*! \brief Retrieve the first entry of the memory map
|
||||
*/
|
||||
Memory* getMemoryMap();
|
||||
|
||||
/*! \brief Video mode: Vesa BIOS Extension
|
||||
*
|
||||
* \see [VESA BIOS Extension (VBE) Core Functions (Version 3)](vbe3.pdf)
|
||||
*/
|
||||
struct VBE {
|
||||
uint32_t control_info; ///< Pointer to VBE control information
|
||||
uint32_t mode_info; ///< Pointer to VBE mode information
|
||||
uint16_t mode; ///< Selected video mode (as defined in the standard)
|
||||
uint16_t interface_seg; ///< Protected mode interface (unused)
|
||||
uint16_t interface_off; ///< Protected mode interface (unused)
|
||||
uint16_t interface_len; ///< Protected mode interface (unused)
|
||||
} __attribute__((packed));
|
||||
assert_size(VBE, 16);
|
||||
|
||||
/*! \brief Get pointer to Vesa BIOS Extension information
|
||||
*
|
||||
* \note Only available if the `MULTIBOOT_VIDEO_MODE` flag was explicitly set
|
||||
* in the multiboot header (see `boot/multiboot/config.inc`)!
|
||||
*/
|
||||
VBE* getVesaBiosExtensionInfo();
|
||||
|
||||
/*! \brief Video mode: Framebuffer
|
||||
*
|
||||
* This beautiful structure contains everything required for using the graphic
|
||||
* framebuffer in a very handy manner -- however, it may not be well supported
|
||||
* by current boot loaders...
|
||||
* These information can be retrieved from \ref VBE as well, though you then
|
||||
* have to parse these huge structures containing a lot of useless stuff.
|
||||
*/
|
||||
struct Framebuffer {
|
||||
uint64_t address; ///< Physical address of the framebuffer
|
||||
uint32_t pitch; ///< Number of bytes per row
|
||||
uint32_t width; ///< Width of framebuffer
|
||||
uint32_t height; ///< Height of framebuffer
|
||||
uint8_t bpp; ///< Bits per pixel
|
||||
enum Type : uint8_t {
|
||||
INDEXED = 0, ///< Using a custom color palette
|
||||
RGB = 1, ///< Standard red-green-blue
|
||||
EGA_TEXT = 2 ///< Enhanced Graphics Adapter color palette
|
||||
} type;
|
||||
union {
|
||||
/*! \brief For INDEXED type
|
||||
*/
|
||||
struct {
|
||||
uint32_t palette_addr; ///< Address of an array with RGB values
|
||||
uint16_t palette_num_colors; ///< Number of colors (in array above)
|
||||
} __attribute__((packed));
|
||||
|
||||
/*! \brief For RGB type
|
||||
*/
|
||||
struct {
|
||||
uint8_t offset_red; ///< Offset of red value
|
||||
uint8_t bits_red; ///< Bits used in red value
|
||||
uint8_t offset_green; ///< Offset of green value
|
||||
uint8_t bits_green; ///< Bits used in green value
|
||||
uint8_t offset_blue; ///< Offset of blue value
|
||||
uint8_t bits_blue; ///< Bits used in blue value
|
||||
} __attribute__((packed));
|
||||
} __attribute__((packed));
|
||||
} __attribute__((packed));
|
||||
assert_size(Framebuffer, 28);
|
||||
|
||||
/*! \brief Get pointer to framebuffer information
|
||||
*
|
||||
* \note Only available if the `MULTIBOOT_VIDEO_MODE` flag was explicitly set
|
||||
* in the multiboot header (see `boot/multiboot/config.inc`)!
|
||||
*/
|
||||
Framebuffer* getFramebufferInfo();
|
||||
} // namespace Multiboot
|
||||
@ -0,0 +1,33 @@
|
||||
; The first 8192 bytes of the kernel binary must contain a header with
|
||||
; predefined (and sometimes "magic") values according to the Multiboot standard.
|
||||
; Based on these values, the boot loader decides whether and how to load the
|
||||
; kernel -- which is compiled and linked into an ELF file.
|
||||
; To make this possible with your StuBS kernel, the linker places the following
|
||||
; entry `multiboot_header` at the very beginning of the file thanks to the
|
||||
; linker script (located in compiler/sections.ld).
|
||||
|
||||
[SECTION .multiboot_header]
|
||||
|
||||
; Include configuration
|
||||
%include 'boot/multiboot/config.inc'
|
||||
|
||||
; Multiboot Header
|
||||
align 4
|
||||
multiboot_header:
|
||||
dd MULTIBOOT_HEADER_MAGIC_OS ; Magic Header Value
|
||||
dd MULTIBOOT_HEADER_FLAGS ; Flags (affects following entries)
|
||||
dd MULTIBOOT_HEADER_CHKSUM ; Header Checksum
|
||||
|
||||
; Following fields would have been required to be defined
|
||||
; if flag A_OUT KLUDGE was set (but we don't need this)
|
||||
dd 0 ; Header address
|
||||
dd 0 ; Begin of load address
|
||||
dd 0 ; end of load address
|
||||
dd 0 ; end of bss segment
|
||||
dd 0 ; address of entry function
|
||||
|
||||
; Following fields are required for video mode (flag MULTIBOOT_VIDEO_MODE)
|
||||
dd 0 ; Mode: 0 = Graphic / 1 = Text
|
||||
dd MULTIBOOT_VIDEO_WIDTH ; Width (pixels / columns)
|
||||
dd MULTIBOOT_VIDEO_HEIGHT ; Height (pixels / rows)
|
||||
dd MULTIBOOT_VIDEO_BITDEPTH ; color depth / number of colors
|
||||
@ -0,0 +1,77 @@
|
||||
; This is the actual entry point of the kernel.
|
||||
; The switch into the 32-bit 'Protected Mode' has already been performed
|
||||
; (by the boot loader).
|
||||
; The assembly code just performs the absolute necessary steps (like setting up
|
||||
; the stack) to be able to jump into the C++ code -- and continue further
|
||||
; initialization in a (more) high-level language.
|
||||
|
||||
[BITS 32]
|
||||
|
||||
; External functions and variables
|
||||
[EXTERN CPU_CORE_STACK_SIZE] ; Constant containing the initial stack size (per CPU core), see `arch/core.cc`
|
||||
[EXTERN cpu_core_stack_pointer] ; Pointer to reserved memory for CPU core stacks, see `arch/core.cc`
|
||||
[EXTERN gdt_protected_mode_pointer] ; Pointer to 32 Bit Global Descriptor Table (located in `arch/gdt.cc`)
|
||||
[EXTERN long_mode] ; Low level function to jump into the 64-bit mode ('Long Mode', see `boot/longmode.asm`)
|
||||
[EXTERN multiboot_addr] ; Variable, in which the Pointer to Multiboot information
|
||||
; structure should be stored (`boot/multiboot/data.cc`)
|
||||
|
||||
; Load Multiboot settings
|
||||
%include "boot/multiboot/config.inc"
|
||||
|
||||
[SECTION .text]
|
||||
|
||||
; Entry point for the bootstrap processor (CPU0)
|
||||
[GLOBAL startup_bsp]
|
||||
startup_bsp:
|
||||
; Check if kernel was booted by a Multiboot compliant boot loader
|
||||
cmp eax, MULTIBOOT_HEADER_MAGIC_LOADER
|
||||
jne skip_multiboot
|
||||
; Pointer to Multiboot information structure has been stored in ebx by the
|
||||
; boot loader -- copy to a variable for later usage.
|
||||
mov [multiboot_addr], ebx
|
||||
|
||||
skip_multiboot:
|
||||
; Disable interrupts
|
||||
cli
|
||||
; Disable non maskable interrupts (NMI)
|
||||
; (we are going to ignore them)
|
||||
mov al, 0x80
|
||||
out 0x70, al
|
||||
|
||||
jmp load_cs
|
||||
|
||||
; Segment initialization
|
||||
; (code used by bootstrap and application processors as well)
|
||||
[GLOBAL segment_init]
|
||||
segment_init:
|
||||
; Load temporary protected mode Global Descriptor Table (GDT)
|
||||
lgdt [gdt_protected_mode_pointer]
|
||||
|
||||
; Initialize segment register
|
||||
mov ax, 0x10
|
||||
mov ds, ax
|
||||
mov es, ax
|
||||
mov fs, ax
|
||||
mov gs, ax
|
||||
mov ss, ax
|
||||
|
||||
; Load code segment register
|
||||
jmp 0x8:load_cs
|
||||
|
||||
load_cs:
|
||||
; Initialize stack pointer:
|
||||
; Atomic increment of `cpu_core_stack_pointer` by `CPU_CORE_STACK_SIZE`
|
||||
; (to avoid race conditions at application processor boot)
|
||||
mov eax, [CPU_CORE_STACK_SIZE]
|
||||
lock xadd [cpu_core_stack_pointer], eax
|
||||
; Since the stack grows into the opposite direction,
|
||||
; Add `CPU_CORE_STACK_SIZE` again
|
||||
add eax, [CPU_CORE_STACK_SIZE]
|
||||
; Assign stack pointer
|
||||
mov esp, eax
|
||||
|
||||
; Clear direction flag for string operations
|
||||
cld
|
||||
|
||||
; Switch to long mode (64 bit)
|
||||
jmp long_mode
|
||||
@ -0,0 +1,73 @@
|
||||
#include "startup.h"
|
||||
|
||||
#include "../arch/acpi.h"
|
||||
#include "../arch/apic.h"
|
||||
#include "../arch/core.h"
|
||||
#include "../arch/idt.h"
|
||||
#include "../arch/pic.h"
|
||||
#include "../compiler/libc.h"
|
||||
#include "../debug/output.h"
|
||||
#include "../interrupt/handlers.h"
|
||||
|
||||
/*! \brief The first processor is the Bootstrap Processor (BSP)
|
||||
*/
|
||||
static bool isBootstrapProcessor = true;
|
||||
|
||||
extern "C" [[noreturn]] void kernel_init() {
|
||||
if (isBootstrapProcessor) {
|
||||
isBootstrapProcessor = false;
|
||||
// Setup and load Interrupt Description Table (IDT)
|
||||
initInterruptHandlers();
|
||||
|
||||
// Initialize PICs
|
||||
PIC::initialize();
|
||||
|
||||
// Call global constructors
|
||||
CSU::initializer();
|
||||
|
||||
// Initialize ACPI
|
||||
if (!ACPI::init()) {
|
||||
DBG_VERBOSE << "No ACPI!";
|
||||
Core::die();
|
||||
}
|
||||
// Initialize APIC (using ACPI)
|
||||
if (!APIC::init()) {
|
||||
DBG_VERBOSE << "APIC Initialization failed";
|
||||
Core::die();
|
||||
}
|
||||
|
||||
// Initialize the Bootstrap Processor
|
||||
Core::init();
|
||||
|
||||
// Go to main function
|
||||
main();
|
||||
|
||||
// Exit CPU
|
||||
DBG_VERBOSE << "CPU core " << Core::getID() << " (BSP) shutdown." << endl;
|
||||
Core::exit();
|
||||
} else {
|
||||
// Load Interrupt Description Table (IDT)
|
||||
IDT::load();
|
||||
|
||||
// Initialize this application processor
|
||||
Core::init();
|
||||
|
||||
// And call the AP main
|
||||
main_ap();
|
||||
|
||||
// Exit CPU
|
||||
DBG_VERBOSE << "CPU core " << Core::getID() << " (AP) shutdown." << endl;
|
||||
Core::exit();
|
||||
}
|
||||
|
||||
// Only on last core
|
||||
if (Core::countOnline() == 1) {
|
||||
// Call global destructors
|
||||
CSU::finalizer();
|
||||
}
|
||||
|
||||
// wait forever
|
||||
while (true) {
|
||||
Core::die();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,50 @@
|
||||
/*! \file
|
||||
* \brief Startup of the first core, also known as bootstrap processor (BSP)
|
||||
*/
|
||||
#pragma once
|
||||
#include "../compiler/fix.h"
|
||||
#include "../types.h"
|
||||
|
||||
/*! \brief Entry point of your kernel
|
||||
*
|
||||
* \ingroup Startup
|
||||
*
|
||||
* Executed by boot loader.
|
||||
* Stores Pointer to \ref Multiboot information structure,
|
||||
* initializes stack pointer,
|
||||
* switches to long mode
|
||||
* and finally calls the C++ \ref kernel_init function
|
||||
*/
|
||||
extern "C" void startup_bsp() ERROR_ON_CALL(
|
||||
"The kernel entry point shall never be called from your code!");
|
||||
|
||||
/*! \brief Initializes the C++ environment and detects system components
|
||||
*
|
||||
* \ingroup Startup
|
||||
*
|
||||
* The startup code(both for \ref startup_bsp "bootstrap" and \ref startup_ap
|
||||
* "application processor") jumps to this high level function. After
|
||||
* initialization it will call \ref main()
|
||||
*/
|
||||
/*! or \ref main_ap() respectively
|
||||
*/
|
||||
extern "C" [[noreturn]] void kernel_init() ERROR_ON_CALL(
|
||||
"The kernel init function shall never be called from your code!");
|
||||
|
||||
/*! \brief Kernels main function
|
||||
*
|
||||
* Called after initialization of the system by \ref kernel_init()
|
||||
*/
|
||||
/*! \note This code will only be executed on the booting CPU (i.e., the one with
|
||||
* ID 0).
|
||||
*/
|
||||
extern "C" int main();
|
||||
|
||||
/*! \brief Entry point for application processors
|
||||
*
|
||||
* Called after initialization of the system by \ref kernel_init()
|
||||
*
|
||||
* \note Code in this function will be executed on all APs (i.e., all CPUs
|
||||
* except ID 0)
|
||||
*/
|
||||
extern "C" int main_ap();
|
||||
@ -0,0 +1,68 @@
|
||||
|
||||
; Startup of the remaining application processors (in real mode)
|
||||
; and switching to 'Protected Mode' with a temporary GDT.
|
||||
; This code is relocated by ApplicationProcessor::relocateSetupCode()
|
||||
|
||||
[SECTION .setup_ap_seg]
|
||||
[GLOBAL setup_ap_gdt]
|
||||
[GLOBAL setup_ap_gdtd]
|
||||
|
||||
; Unlike the bootstrap processor, the application processors have not been
|
||||
; set up by the boot loader -- they start in real mode (16 bit) and have to be
|
||||
; switched manually to protected mode (32 bit)
|
||||
[BITS 16]
|
||||
|
||||
setup_ap:
|
||||
; Initialize segment register
|
||||
mov ax, cs ; Code segment and...
|
||||
mov ds, ax ; .. data segment should point to the same segment
|
||||
; (we don't use stack / stack segment)
|
||||
|
||||
; Disable interrupts
|
||||
cli
|
||||
; Disable non maskable interrupts (NMI)
|
||||
mov al, 0x80
|
||||
out 0x70, al
|
||||
|
||||
; load temporary real mode Global Descriptor Table (GDT)
|
||||
lgdt [setup_ap_gdtd - setup_ap]
|
||||
|
||||
; Switch to protected mode:
|
||||
; enable protected mode bit (1 << 0) in control register 0
|
||||
mov eax, cr0
|
||||
or eax, 1
|
||||
mov cr0, eax
|
||||
; Far jump to 32 bit `startup_ap` function
|
||||
jmp dword 0x08:startup_ap
|
||||
|
||||
; memory reserved for temporary real mode GDT
|
||||
; initialized by ApplicationProcessor::relocateSetupCode()
|
||||
align 4
|
||||
setup_ap_gdt:
|
||||
dq 0,0,0,0,0 ; reserve memory for at least 5 GDT entries
|
||||
|
||||
; memory reserved for temporary real mode GDT descriptor
|
||||
; initialized by ApplicationProcessor::relocateSetupCode()
|
||||
setup_ap_gdtd:
|
||||
dw 0,0,0,0,0 ; reserve memory for GDT descriptor
|
||||
|
||||
[SECTION .text]
|
||||
|
||||
[BITS 32]
|
||||
|
||||
; Segment initialization defined in `boot/startup.asm`
|
||||
[EXTERN segment_init]
|
||||
|
||||
; protected mode (32 bit) startup code for application processor
|
||||
startup_ap:
|
||||
; reload all segment selectors (since they still point to the real mode GDT)
|
||||
mov ax, 0x10
|
||||
mov ds, ax
|
||||
mov es, ax
|
||||
mov fs, ax
|
||||
mov gs, ax
|
||||
mov ss, ax
|
||||
|
||||
; Use same segment initialization function as bootstrap processor
|
||||
jmp segment_init
|
||||
|
||||
@ -0,0 +1,82 @@
|
||||
#include "startup_ap.h"
|
||||
|
||||
#include "../arch/core_interrupt.h"
|
||||
#include "../arch/gdt.h"
|
||||
#include "../arch/lapic.h"
|
||||
#include "../arch/pit.h"
|
||||
#include "../debug/assert.h"
|
||||
#include "../debug/output.h"
|
||||
#include "../utils/size.h"
|
||||
#include "../utils/string.h"
|
||||
|
||||
namespace ApplicationProcessor {
|
||||
|
||||
// Make sure that the RELOCATED_SETUP is in low memory (< 1 MiB)
|
||||
static_assert((RELOCATED_SETUP & ~0x000ff000) == 0,
|
||||
"Not a valid 1 MB address for RELOCATED_SETUP!");
|
||||
|
||||
/*! \brief Temporary Global Descriptor Table
|
||||
*
|
||||
* Blue print, to be copied into real mode code
|
||||
*/
|
||||
constinit GDT::SegmentDescriptor ap_gdt[] = {
|
||||
// nullptr-Deskriptor
|
||||
{},
|
||||
|
||||
// XXX: Can't we just use GDT::protected_mode?
|
||||
// code segment
|
||||
GDT::SegmentDescriptor::Segment(0, UINT32_MAX, true, 0, GDT::SIZE_32BIT),
|
||||
|
||||
// data segment
|
||||
GDT::SegmentDescriptor::Segment(0, UINT32_MAX, false, 0, GDT::SIZE_32BIT),
|
||||
};
|
||||
|
||||
void relocateSetupCode() {
|
||||
// Relocated setup code
|
||||
memcpy(reinterpret_cast<void*>(RELOCATED_SETUP), &___SETUP_AP_START__,
|
||||
&___SETUP_AP_END__ - &___SETUP_AP_START__);
|
||||
|
||||
// Adjust GDT:
|
||||
// Calculate offset for real mode GDT and GDT descriptor
|
||||
uintptr_t ap_gdt_offset = reinterpret_cast<uintptr_t>(&setup_ap_gdt) -
|
||||
reinterpret_cast<uintptr_t>(&___SETUP_AP_START__);
|
||||
uintptr_t ap_gdtd_offset = reinterpret_cast<uintptr_t>(&setup_ap_gdtd) -
|
||||
reinterpret_cast<uintptr_t>(&___SETUP_AP_START__);
|
||||
|
||||
// Copy blue print of real mode GDT to the relocated memory
|
||||
void* relocated_ap_gdt =
|
||||
reinterpret_cast<void*>(RELOCATED_SETUP + ap_gdt_offset);
|
||||
memcpy(relocated_ap_gdt, &ap_gdt, sizeof(ap_gdt));
|
||||
|
||||
// Calculate GDT descriptor for relocated address
|
||||
GDT::Pointer* relocated_ap_gdtd =
|
||||
reinterpret_cast<GDT::Pointer*>(RELOCATED_SETUP + ap_gdtd_offset);
|
||||
relocated_ap_gdtd->set(relocated_ap_gdt, size(ap_gdt));
|
||||
}
|
||||
|
||||
void boot(void) {
|
||||
assert(!Core::Interrupt::isEnabled() &&
|
||||
"Interrupts should not be enabled before APs have booted!");
|
||||
|
||||
// Relocate setup code
|
||||
relocateSetupCode();
|
||||
|
||||
// Calculate Init-IPI vector based on address of relocated setup_ap()
|
||||
uint8_t vector = RELOCATED_SETUP >> 12;
|
||||
|
||||
// Send Init-IPI to all APs
|
||||
LAPIC::IPI::sendInit();
|
||||
|
||||
// wait at least 10ms
|
||||
PIT::delay(10000);
|
||||
|
||||
// Send Startup-IPI twice
|
||||
DBG_VERBOSE << "Sending STARTUP IPI #1" << endl;
|
||||
LAPIC::IPI::sendStartup(vector);
|
||||
// wait at least 200us
|
||||
PIT::delay(200);
|
||||
|
||||
DBG_VERBOSE << "Sending STARTUP IPI #2" << endl;
|
||||
LAPIC::IPI::sendStartup(vector);
|
||||
}
|
||||
} // namespace ApplicationProcessor
|
||||
@ -0,0 +1,114 @@
|
||||
/*! \file
|
||||
* \brief Startup of additional cores, the application processors (APs)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "../compiler/fix.h"
|
||||
#include "../types.h"
|
||||
|
||||
/*! \brief Application Processor Boot
|
||||
*
|
||||
* Interface to boot the APs
|
||||
*/
|
||||
namespace ApplicationProcessor {
|
||||
/*! \brief Address (below 1 MiB) to which the setup code gets relocated
|
||||
*/
|
||||
constexpr uintptr_t RELOCATED_SETUP = 0x40000;
|
||||
|
||||
/*! \brief Relocate the real mode setup code
|
||||
*
|
||||
* The application processors (APs) start in real mode, which means that your
|
||||
* setup code must be placed within the first megabyte -- your operating system
|
||||
* resides currently at a much higher address (16 MiB), so the code has to be
|
||||
* copied down there first.
|
||||
*
|
||||
* Luckily, the code in `setup_ap()` can be relocated by copying -- because it
|
||||
* does not use any absolute addressing (except when jumping to the protected
|
||||
* mode function `startup_ap()`).
|
||||
* The function must be copied to the address of \ref RELOCATED_SETUP (0x40000),
|
||||
* so that the APs can start there.
|
||||
*
|
||||
* The memory section contains a reserved area for the \ref GDT and its
|
||||
* descriptor, which has to be assigned first with the contents of \ref ap_gdt.
|
||||
*
|
||||
* \note You could also tell the linker script to put the code directly
|
||||
* at the appropriate place, but unfortunately the Qemu multiboot
|
||||
* implementation (via `-kernel` parameter) can't handle it properly.
|
||||
*/
|
||||
void relocateSetupCode();
|
||||
|
||||
/*! \brief Boot all application processors
|
||||
*
|
||||
* Performs relocation by calling \ref relocateSetupCode()
|
||||
*
|
||||
* \see [ISDMv3, 8.4.4.2 Typical AP Initialization
|
||||
* Sequence](intel_manual_vol3.pdf#page=276)
|
||||
*/
|
||||
void boot();
|
||||
} // namespace ApplicationProcessor
|
||||
|
||||
/*! \brief Begin of setup code for application processors
|
||||
*
|
||||
* The setup code has to switch from real mode (16 bit) to protected mode (32
|
||||
* bit), hence it is written in assembly and must be executed in low memory (< 1
|
||||
* MiB).
|
||||
*
|
||||
* After kernel start the code is somewhere above 16 MiB (the bootstrap
|
||||
* processor was already launched in protected mode by the boot loader).
|
||||
* Therefore this symbol is required for relocate the code to the position
|
||||
* specified by \ref ApplicationProcessor::RELOCATED_SETUP.
|
||||
*
|
||||
* Luckily, the `setup_ap` code in `boot/startup_ap.asm` is rather simple and
|
||||
* doesn't depend on absolute addressing -- and is therefore relocatable.
|
||||
*
|
||||
* Relocation is done by the function \ref
|
||||
* ApplicationProcessor::relocateSetupCode()
|
||||
*
|
||||
* The `___SETUP_AP_START__` symbol is defined in the linker script
|
||||
* (`compiler/section.ld`)
|
||||
*/
|
||||
extern char ___SETUP_AP_START__;
|
||||
|
||||
/*! \brief End of startup code for application processors
|
||||
*
|
||||
* This Symbol is defined in the linker script (`compiler/section.ld`)
|
||||
*/
|
||||
extern char ___SETUP_AP_END__;
|
||||
|
||||
/*! \brief Memory reserved for a temporary real mode GDT
|
||||
* within the relocatable memory area of the setup code
|
||||
*/
|
||||
extern char setup_ap_gdt;
|
||||
|
||||
/*! \brief Memory reserved for a temporary real mode GDT descriptor
|
||||
* within the relocatable memory area of the setup code
|
||||
*/
|
||||
extern char setup_ap_gdtd;
|
||||
|
||||
/*! \brief Entry point for application processors
|
||||
*
|
||||
* Unlike the bootstrap processor, the application processors have not been
|
||||
* setup by the boot loader -- they start in `Real Mode` (16 bit) and have to be
|
||||
* switched manually to `Protected Mode` (32 bit).
|
||||
* This is exactly what this real mode function does, handing over control
|
||||
* to the (32 bit) function \ref startup_ap()
|
||||
*
|
||||
* This code is written is assembly (`boot/startup_ap.asm`) and relocated by
|
||||
* \ref ApplicationProcessor::relocateSetupCode() during
|
||||
* \ref ApplicationProcessor::boot()
|
||||
*/
|
||||
extern "C" void setup_ap() ERROR_ON_CALL(
|
||||
"The setup function for application processors shall never be called from "
|
||||
"your code!");
|
||||
|
||||
/*! \brief Startup for application processors
|
||||
* \ingroup Startup
|
||||
*
|
||||
* This function behaves similar to \ref startup_bsp():
|
||||
* Initializes stack pointer,
|
||||
* switches to long mode
|
||||
* and calls the C++ \ref kernel_init function
|
||||
*/
|
||||
extern "C" void startup_ap() ERROR_ON_CALL(
|
||||
"The startup function for application processors shall never be called "
|
||||
"from your code!");
|
||||
@ -0,0 +1,13 @@
|
||||
/*! \file
|
||||
* \brief Compiler-dependent fixes & idiosyncrasies
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "../types.h"
|
||||
|
||||
#if defined(__GNUC__) && !defined(__clang__)
|
||||
// Only GCC understands the error attribute
|
||||
#define ERROR_ON_CALL(MSG) __attribute__((error(MSG)));
|
||||
#else
|
||||
#define ERROR_ON_CALL(MSG)
|
||||
#endif
|
||||
@ -0,0 +1,45 @@
|
||||
#include "libc.h"
|
||||
|
||||
/*! \brief Function pointer for initialization/finalization functions for global
|
||||
* objects required since GCC 4.7 and later.
|
||||
*
|
||||
* These symbols appear kind of magically due to the compiler
|
||||
*/
|
||||
extern void (*__preinit_array_start[])();
|
||||
extern void (*__preinit_array_end[])();
|
||||
extern void (*__init_array_start[])();
|
||||
extern void (*__init_array_end[])();
|
||||
extern void (*__fini_array_start[])();
|
||||
extern void (*__fini_array_end[])();
|
||||
|
||||
namespace CSU {
|
||||
|
||||
void initializer() {
|
||||
const unsigned int preinit_size = __preinit_array_end - __preinit_array_start;
|
||||
for (unsigned int i = 0; i != preinit_size; ++i) {
|
||||
(*__preinit_array_start[i])();
|
||||
}
|
||||
|
||||
const size_t size = __init_array_end - __init_array_start;
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
(*__init_array_start[i])();
|
||||
}
|
||||
}
|
||||
|
||||
void finalizer() {
|
||||
const unsigned int fini_size = __fini_array_end - __fini_array_start;
|
||||
for (unsigned int i = 0; i != fini_size; ++i) {
|
||||
(*__fini_array_start[i])();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace CSU
|
||||
|
||||
extern "C" int atexit(void (*func)(void)) {
|
||||
// Registers a function that will be executed on exit.
|
||||
// We simply ignore those functions, as we don't need them for our operating
|
||||
// systems.
|
||||
(void)func;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
/*! \file
|
||||
* \brief Initialization functions for global objects required by the compiler
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "../types.h"
|
||||
|
||||
/*! \brief C StartUp (CSU)
|
||||
* required by the compiler and provided by the c standard library
|
||||
*/
|
||||
namespace CSU {
|
||||
|
||||
/*! \brief Call global constructors and initialization functions
|
||||
* (this is usually done by __libc_csu_init)
|
||||
*/
|
||||
void initializer();
|
||||
|
||||
/*! \brief Call global destructors and finalizer functions
|
||||
* (this is usually done by __libc_csu_fini)
|
||||
*/
|
||||
void finalizer();
|
||||
|
||||
} // namespace CSU
|
||||
@ -0,0 +1,21 @@
|
||||
/*! \file
|
||||
* \brief C++ runtime support functions
|
||||
*/
|
||||
|
||||
#include "../types.h"
|
||||
|
||||
void* operator new(size_t, void* place) { return place; }
|
||||
|
||||
void operator delete(void* ptr) { (void)ptr; }
|
||||
|
||||
void operator delete(void* ptr, size_t size) {
|
||||
(void)ptr;
|
||||
(void)size;
|
||||
}
|
||||
|
||||
extern "C" [[noreturn]] void __cxa_pure_virtual() {
|
||||
// Pure virtual function was called -- this if obviously not valid,
|
||||
// therefore we wait infinitely.
|
||||
while (true) {
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,107 @@
|
||||
/* Entry in our OS -- label 'startup_bsp' in file boot/startup.asm */
|
||||
ENTRY(startup_bsp)
|
||||
|
||||
SECTIONS
|
||||
{
|
||||
/* start address of our kernel */
|
||||
. = 16M;
|
||||
|
||||
___KERNEL_START___ = .;
|
||||
|
||||
.boot :
|
||||
{
|
||||
/* Multiboot Header should be at the very beginning */
|
||||
*(.multiboot_header)
|
||||
}
|
||||
|
||||
___KERNEL_TEXT_START___ = .;
|
||||
|
||||
.text :
|
||||
{
|
||||
*(".text")
|
||||
*(".text$")
|
||||
*(".init")
|
||||
*(".fini")
|
||||
*(".gnu.linkonce.*")
|
||||
KEEP(*(.note.gnu.build-id))
|
||||
}
|
||||
|
||||
/* lists containing the start address of global constructors and destructors (generated by the compiler) */
|
||||
.preinit_array :
|
||||
{
|
||||
PROVIDE_HIDDEN (__preinit_array_start = .);
|
||||
KEEP (*(.preinit_array))
|
||||
PROVIDE_HIDDEN (__preinit_array_end = .);
|
||||
}
|
||||
.init_array :
|
||||
{
|
||||
PROVIDE_HIDDEN (__init_array_start = .);
|
||||
KEEP (*(SORT(.init_array.*)))
|
||||
KEEP (*(.init_array))
|
||||
PROVIDE_HIDDEN (__init_array_end = .);
|
||||
}
|
||||
.fini_array :
|
||||
{
|
||||
PROVIDE_HIDDEN (__fini_array_start = .);
|
||||
KEEP (*(SORT(.fini_array.*)))
|
||||
KEEP (*(.fini_array))
|
||||
PROVIDE_HIDDEN (__fini_array_end = .);
|
||||
}
|
||||
|
||||
___KERNEL_TEXT_END___ = .;
|
||||
|
||||
.data :
|
||||
{
|
||||
*(".data")
|
||||
*(".data$")
|
||||
*(".rodata")
|
||||
___CTOR_LIST__ = .;
|
||||
*(".ctors")
|
||||
*(".ctor")
|
||||
___CTOR_LIST_END__ = .;
|
||||
___DTOR_LIST__ = .;
|
||||
*(".dtors")
|
||||
*(".dtor")
|
||||
___DTOR_LIST_END__ = .;
|
||||
*(".got")
|
||||
*(".got.plt")
|
||||
*(".eh_frame")
|
||||
*(".eh_fram")
|
||||
*(".jcr")
|
||||
}
|
||||
|
||||
/* Start for application processors, relocated by APIC::init()
|
||||
* to a below 1 MB address to boot from real mode.
|
||||
* It is possible to let the linker place it at a below 1 MB address,
|
||||
* while all the rest starts at 16 MB. This will work for multiboot
|
||||
* compliant boot loader like GRUB and PXELINUX, however,
|
||||
* the qemu boot loader cannot handle such ELF files (yet)...
|
||||
* That's why we have to do it in our software */
|
||||
.setup_ap_seg ALIGN(0x10) :
|
||||
{
|
||||
___SETUP_AP_START__ = .;
|
||||
*(".setup_ap_seg")
|
||||
*(".setup_ap_seg$")
|
||||
}
|
||||
___SETUP_AP_END__ = .;
|
||||
|
||||
.bss :
|
||||
{
|
||||
*(".bss")
|
||||
*(".bss.*")
|
||||
*(COMMON)
|
||||
}
|
||||
___KERNEL_END___ = .;
|
||||
|
||||
/DISCARD/ :
|
||||
{
|
||||
*(".note")
|
||||
*(".comment")
|
||||
/* Keep debug information
|
||||
*(".debug_line")
|
||||
*(".debug_info")
|
||||
*(".debug_abbrev")
|
||||
*(".debug_aranges")
|
||||
*/
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
#include "assert.h"
|
||||
|
||||
[[noreturn]] void assertion_failed(const char* exp, const char* func,
|
||||
const char* file, int line) {
|
||||
(void)exp;
|
||||
(void)func;
|
||||
(void)file;
|
||||
(void)line;
|
||||
// TODO: Print error message (in debug window)
|
||||
// TODO: Then stop the current core permanently
|
||||
// Use appropriate method from class Core to do so.
|
||||
while (true) {
|
||||
} // wait forever so we can mark this as [[noreturn]]
|
||||
}
|
||||
@ -0,0 +1,75 @@
|
||||
// vim: set noet ts=4 sw=4:
|
||||
|
||||
/*! \file
|
||||
* \brief Contains several macros usable for making assertions
|
||||
*
|
||||
* Depending on the type of assertion (either static or at runtime), a failing
|
||||
* assertion will trigger an error. For static assertion, this error will be
|
||||
* shown at compile time and abort compilation. Runtime assertions will trigger
|
||||
* a message containing details about the error occurred and will make the CPU
|
||||
* die.
|
||||
*/
|
||||
|
||||
/*!
|
||||
* \defgroup debug Debugging functions
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "../types.h"
|
||||
|
||||
#ifndef STRINGIFY
|
||||
/*! \def STRINGIFY(S)
|
||||
* \brief Converts a macro parameter into a string
|
||||
* \ingroup debug
|
||||
* \param S Expression to be converted
|
||||
* \return stringified version of S
|
||||
*/
|
||||
#define STRINGIFY(S) #S
|
||||
#endif
|
||||
|
||||
/*! \def assert_size(TYPE, SIZE)
|
||||
* \brief Statically ensure (at compile time) that a data type (or variable)
|
||||
* has the expected size.
|
||||
*
|
||||
* \ingroup debug
|
||||
* \param TYPE The type to be checked
|
||||
* \param SIZE Expected size in bytes
|
||||
*/
|
||||
#define assert_size(TYPE, SIZE) \
|
||||
static_assert(sizeof(TYPE) == (SIZE), "Wrong size for " STRINGIFY(TYPE))
|
||||
|
||||
/*! \def assert(EXP)
|
||||
* \brief Ensure (at execution time) an expression evaluates to `true`, print
|
||||
* an error message and stop the CPU otherwise.
|
||||
*
|
||||
* \ingroup debug
|
||||
* \param EXP The expression to be checked
|
||||
*/
|
||||
#ifdef NDEBUG
|
||||
#define assert(EXP) ((void)0)
|
||||
#else
|
||||
#define assert(EXP) \
|
||||
do { \
|
||||
if (__builtin_expect(!(EXP), 0)) { \
|
||||
assertion_failed(STRINGIFY(EXP), __func__, __FILE__, __LINE__); \
|
||||
} \
|
||||
} while (false)
|
||||
|
||||
/*! \brief Handles a failed assertion
|
||||
*
|
||||
* This function will print a message containing further information about the
|
||||
* failed assertion and stops the current CPU permanently.
|
||||
*
|
||||
* \note This function should never be called directly, but only via the macro
|
||||
* `assert`.
|
||||
*
|
||||
* \todo(11) Implement Remainder of Method (output & CPU stopping)
|
||||
*
|
||||
* \param exp Expression that did not hold
|
||||
* \param func Name of the function in which the assertion failed
|
||||
* \param file Name of the file in which the assertion failed
|
||||
* \param line Line in which the assertion failed
|
||||
*/
|
||||
[[noreturn]] void assertion_failed(const char* exp, const char* func,
|
||||
const char* file, int line);
|
||||
#endif
|
||||
@ -0,0 +1,44 @@
|
||||
/*! \file
|
||||
* \brief \ref CopyStream duplicates \ref OutputStream "output streams"
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "../object/outputstream.h"
|
||||
#include "../types.h"
|
||||
|
||||
/*! \brief Duplicate all data passed by the stream operator to two \ref
|
||||
* OutputStream "output streams"
|
||||
* \ingroup io
|
||||
*
|
||||
* Can be used as replacement for any \ref OutputStream -- for example,
|
||||
* forwarding the \ref DBG output simultaneously to screen (\ref TextStream) and
|
||||
* serial console (\ref SerialStream).
|
||||
*
|
||||
*/
|
||||
class CopyStream : public OutputStream {
|
||||
/*! \brief First recipient
|
||||
*/
|
||||
OutputStream* first;
|
||||
|
||||
/*! \brief Second recipient
|
||||
*/
|
||||
OutputStream* second;
|
||||
|
||||
public:
|
||||
/*! \brief Constructor
|
||||
*
|
||||
* \param first First recipient for output passed to this object
|
||||
* \param second Second recipient for output passed to this object
|
||||
*/
|
||||
CopyStream(OutputStream* first, OutputStream* second)
|
||||
: first(first), second(second) {}
|
||||
|
||||
/*! \brief Redirect the buffer to both streams and flush them, too.
|
||||
*/
|
||||
void flush() override {
|
||||
buffer[pos] = '\0'; // make sure buffer will only be printed until pos.
|
||||
*first << buffer << ::flush;
|
||||
*second << buffer << ::flush;
|
||||
pos = 0;
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,27 @@
|
||||
// vim: set noet ts=4 sw=4:
|
||||
|
||||
/*! \file
|
||||
* \brief Macro to print an error message and stop the current core.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "../types.h"
|
||||
|
||||
/*! \def kernelpanic
|
||||
* \brief Print an error message in the debug window and \ref Core::die "stop
|
||||
* the current core"
|
||||
*
|
||||
* \param MSG error message
|
||||
* \ingroup debug
|
||||
*/
|
||||
#define kernelpanic(MSG) \
|
||||
do { \
|
||||
DBG << "PANIC: '" << (MSG) << "' in " << __func__ << " @ " << __FILE__ \
|
||||
<< ":" << __LINE__ << ") - CPU stopped." << endl; \
|
||||
Core::die(); \
|
||||
} while (0)
|
||||
|
||||
// The includes are intentionally placed at the end, so the macro can be used
|
||||
// inside those included files as well.
|
||||
#include "../arch/core.h"
|
||||
#include "./output.h"
|
||||
@ -0,0 +1,4 @@
|
||||
#include "nullstream.h"
|
||||
|
||||
// Instance
|
||||
NullStream nullstream;
|
||||
@ -0,0 +1,45 @@
|
||||
/*! \file
|
||||
* \brief \ref NullStream is a stream discarding everything
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "../object/outputstream.h"
|
||||
#include "../types.h"
|
||||
|
||||
/*! \brief Ignore all data passed by the stream operator
|
||||
* \ingroup io
|
||||
*
|
||||
* Can be used instead of the \ref OutputStream if (for debugging reasons) all
|
||||
* output should be ignored, e.g. for \ref DBG_VERBOSE
|
||||
*
|
||||
* By using template programming, a single generic methods is sufficient
|
||||
* (which simply discard everything).
|
||||
*/
|
||||
class NullStream {
|
||||
/*! \brief Check if type is supported by output stream
|
||||
*/
|
||||
template <typename T>
|
||||
auto check(T v, OutputStream* p = nullptr) -> decltype(*p << v, void()) {}
|
||||
|
||||
public:
|
||||
/*! \brief Empty default constructor
|
||||
*/
|
||||
NullStream() {}
|
||||
|
||||
/*! \brief Generic stream operator for any data type
|
||||
*
|
||||
* Uses template meta programming for a generic & short solution
|
||||
*
|
||||
* \tparam T Type of data to ignore
|
||||
* \param value data to be ignore
|
||||
* \return Reference to the \ref NullStream object allowing concatenation of
|
||||
* operators
|
||||
*/
|
||||
template <typename T>
|
||||
NullStream& operator<<(T value) {
|
||||
check(value);
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
extern NullStream nullstream;
|
||||
@ -0,0 +1,93 @@
|
||||
// vim: set noet ts=4 sw=4:
|
||||
|
||||
/*! \file
|
||||
* \brief Debug macros enabling debug output on a separate window for each
|
||||
* core.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "../types.h"
|
||||
|
||||
/*! \def DBG_VERBOSE
|
||||
* \brief An output stream, which is only displayed in the debug window in
|
||||
* verbose mode
|
||||
*
|
||||
* \note If a serial console has been implemented, the output can be redirected
|
||||
* to the serial stream instead (by changing the macro) -- this makes the
|
||||
* (usually) very large output more readable (since it allows scrolling
|
||||
* back)
|
||||
*/
|
||||
#ifdef VERBOSE
|
||||
// If VERBOSE is defined, forward everything to \ref DBG
|
||||
#define DBG_VERBOSE DBG
|
||||
#else
|
||||
// Otherwise sent everything to the NullStream (which will simply discard
|
||||
// everything)
|
||||
#define DBG_VERBOSE nullstream
|
||||
// in this case we have to include the null stream
|
||||
#include "./nullstream.h"
|
||||
#endif
|
||||
|
||||
/*! \def DBG
|
||||
* \brief An output stream, which is displayed in the debug window of the core
|
||||
* it was executed on
|
||||
*
|
||||
* In single core (\OOStuBS) this is just an alias to the debug window object
|
||||
* `dout`.
|
||||
*/
|
||||
/*! However, on a multi core system a debug window for each core is
|
||||
* required, therefore `dout` has to be an \ref TextStream object array with the
|
||||
* core ID as array index -- the selection is done via Core::getID()
|
||||
*
|
||||
* \warning In case of a very unfavorable scheduling, it is theoretically
|
||||
* possible that the debug output in a multi core system is displayed
|
||||
* on the wrong (previous) core.
|
||||
*/
|
||||
#define DBG nullstream
|
||||
|
||||
#include "../arch/core.h"
|
||||
#include "../device/textstream.h"
|
||||
|
||||
/*! \brief Debug window for the CGA screen
|
||||
*
|
||||
* Debug output using \ref DBG like
|
||||
* `DBG << "var = " << var << endl`
|
||||
* should be displayed in window dedicated to the core it is executed on.
|
||||
*
|
||||
* While this is quite easy on single core systems like \OOStuBS -- they only
|
||||
* require a single \ref TextStream object called `dout` -- multi core systems
|
||||
* like \MPStuBS need an object array with one window per core.
|
||||
* In the latter case direct list initialization can be used:
|
||||
*
|
||||
* \code{.cpp}
|
||||
* TextStream dout[Core::MAX]{
|
||||
* {0, 40, 17, 21}, // Debug window for core 0, like TextStream(0, 40, 17,
|
||||
* 21) {40, 80, 17, 21}, // Debug window for core 1, like TextStream(40, 80,
|
||||
* 17, 21)
|
||||
* //...
|
||||
* };
|
||||
* \endcode
|
||||
*
|
||||
* The debug windows in should be located right below the normal output window
|
||||
* without any overlap and should be able to display at least 3 lines.
|
||||
* In \MPStuBS, two windows can be placed side-by-side, having 40 columns each.
|
||||
*
|
||||
* \todo(11) Define `dout`
|
||||
*/
|
||||
extern TextStream dout[Core::MAX];
|
||||
|
||||
/*! \brief Debug window with copy function to serial
|
||||
*
|
||||
* Provide an additional layer to also ouput debug prints to serial.
|
||||
* While this is a simple CopyStream pointer in the single core case, it is
|
||||
* an array in the multi core case, which consists of three TextStreams and
|
||||
* one CopyStream.
|
||||
* For that, construction is done like:
|
||||
*
|
||||
* \code{.cpp}
|
||||
* OutputStream* copyout[Core::MAX]{&dout[0], &dout[1], ...}
|
||||
* \endcode
|
||||
*
|
||||
* \todo(11) Define `copyout`
|
||||
*/
|
||||
extern OutputStream* copyout[Core::MAX];
|
||||
@ -0,0 +1,122 @@
|
||||
#include "keydecoder.h"
|
||||
|
||||
#include "ps2controller.h"
|
||||
|
||||
// Constants used for key decoding
|
||||
const unsigned char BREAK_BIT = 0x80;
|
||||
const unsigned char PREFIX_1 = 0xe0;
|
||||
const unsigned char PREFIX_2 = 0xe1;
|
||||
|
||||
Key KeyDecoder::decode(unsigned char code) {
|
||||
Key key = modifier;
|
||||
|
||||
// All keys that are introduced by the MF II keyboard (compared to the older
|
||||
// AT keyboard) always send a prefix value as first byte.
|
||||
if (code == PREFIX_1 || code == PREFIX_2) {
|
||||
prefix = code;
|
||||
} else {
|
||||
// Releasing a key is, for us, only important for the modifier keys such as
|
||||
// SHIFT, CTRL and ALT, For other, non-modifier keys, we ignore the break
|
||||
// code.
|
||||
bool pressed = (code & BREAK_BIT) == 0;
|
||||
|
||||
// A key's break code is identical to its make code with an additionally set
|
||||
// BREAK_BIT
|
||||
Key::Scancode scancode = static_cast<Key::Scancode>(code & (~BREAK_BIT));
|
||||
|
||||
// We ignore "new" special keys, such as the Windows key
|
||||
if (scancode < Key::Scancode::KEYS) {
|
||||
// save state
|
||||
status[scancode] = pressed;
|
||||
|
||||
// Take a closer look at modifier make and break events
|
||||
bool isModifier = true;
|
||||
switch (scancode) {
|
||||
// both shifts are handled equally
|
||||
case Key::Scancode::KEY_LEFT_SHIFT:
|
||||
case Key::Scancode::KEY_RIGHT_SHIFT:
|
||||
modifier.shift = pressed;
|
||||
break;
|
||||
|
||||
case Key::Scancode::KEY_LEFT_ALT:
|
||||
if (prefix == PREFIX_1) {
|
||||
modifier.alt_right = pressed;
|
||||
} else {
|
||||
modifier.alt_left = pressed;
|
||||
}
|
||||
break;
|
||||
|
||||
case Key::Scancode::KEY_LEFT_CTRL:
|
||||
if (prefix == PREFIX_1) {
|
||||
modifier.ctrl_right = pressed;
|
||||
} else {
|
||||
modifier.ctrl_left = pressed;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
isModifier = false;
|
||||
}
|
||||
|
||||
// For keys other than modifiers, we only care about the make code
|
||||
if (pressed && !isModifier) {
|
||||
switch (scancode) {
|
||||
case Key::Scancode::KEY_CAPS_LOCK:
|
||||
modifier.caps_lock ^= 1;
|
||||
setLed(PS2Controller::LED_CAPS_LOCK, modifier.caps_lock);
|
||||
break;
|
||||
|
||||
case Key::Scancode::KEY_SCROLL_LOCK:
|
||||
modifier.scroll_lock ^= 1;
|
||||
setLed(PS2Controller::LED_SCROLL_LOCK, modifier.scroll_lock);
|
||||
break;
|
||||
|
||||
case Key::Scancode::KEY_NUM_LOCK: // Can be both NumLock and pause
|
||||
// On old keyboards, the pause functionality was only accessible by
|
||||
// pressing Ctrl+NumLock. Modern MF-II keyboards therefore send
|
||||
// exactly this code combination when the pause key was pressed.
|
||||
// Normally, the pause key does not provide an ASCII code, but we
|
||||
// check that anyway. In either case, we're now done decoding.
|
||||
if (modifier.ctrl_left) { // pause key
|
||||
key.scancode = scancode;
|
||||
} else { // NumLock
|
||||
modifier.num_lock ^= 1;
|
||||
setLed(PS2Controller::LED_NUM_LOCK, modifier.num_lock);
|
||||
}
|
||||
break;
|
||||
|
||||
// Special case scan code 53: This code is used by both the minus key
|
||||
// on the main keyboard and the division key on the number block. When
|
||||
// the division key was pressed, we adjust the scancode accordingly.
|
||||
case Key::Scancode::KEY_SLASH:
|
||||
if (prefix == PREFIX_1) {
|
||||
key.scancode = Key::Scancode::KEY_DIV;
|
||||
key.shift = true;
|
||||
} else {
|
||||
key.scancode = scancode;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
key.scancode = scancode;
|
||||
|
||||
// When NumLock is enabled and a key on the keypad was pressed, we
|
||||
// want return the ASCII and scan codes of the corresponding
|
||||
// numerical key instead of the arrow keys. The keys on the cursor
|
||||
// block (prefix == PREFIX_1), however, should remain usable.
|
||||
// Therefore, as a little hack, we deactivate the NumLock for these
|
||||
// keys.
|
||||
if (modifier.num_lock && prefix == PREFIX_1) {
|
||||
key.num_lock = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The prefix is only valid for the immediately following code, which was
|
||||
// just handled.
|
||||
prefix = 0;
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
/*! \file
|
||||
* \brief \ref KeyDecoder decodes a keystroke to the corresponding \ref Key
|
||||
* object
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "../object/key.h"
|
||||
#include "../types.h"
|
||||
|
||||
/*! \brief Decoder for \ref ps2keyboardset1 "keyboard codes" received from the
|
||||
* \ref PS2Controller
|
||||
* \ingroup io
|
||||
*
|
||||
* Extracts the make and break codes, modifier and scan codes from the pressed
|
||||
* key.
|
||||
*/
|
||||
class KeyDecoder {
|
||||
unsigned char prefix; ///< Prefix byte for keys
|
||||
Key modifier; ///< activated modifier keys (e.g., caps lock)
|
||||
|
||||
public:
|
||||
/*! \brief Current state (pressed or released) of all keys.
|
||||
*/
|
||||
bool status[Key::Scancode::KEYS];
|
||||
|
||||
/*! \brief Default constructor
|
||||
*/
|
||||
KeyDecoder() {}
|
||||
|
||||
/*! \brief Interprets the \ref ps2keyboardset1 "make and break codes"
|
||||
* received from the keyboard and derives the corresponding scan code and
|
||||
* further information about other pressed keys, such as \key{shift} and
|
||||
* \key{ctrl}.
|
||||
*
|
||||
* \param code Byte from Keyboard to decode
|
||||
* \return Pressed key (\ref Key::valid returns `false` if the key is not yet
|
||||
* complete)
|
||||
*/
|
||||
Key decode(unsigned char code);
|
||||
};
|
||||
@ -0,0 +1,130 @@
|
||||
#include "ps2controller.h"
|
||||
|
||||
#include "../arch/core_interrupt.h"
|
||||
#include "../arch/ioport.h"
|
||||
#include "../compiler/fix.h"
|
||||
#include "../debug/output.h"
|
||||
#include "keydecoder.h"
|
||||
|
||||
namespace PS2Controller {
|
||||
|
||||
// I/O Ports of the PS2 Controller
|
||||
static const IOPort ctrl_port(
|
||||
0x64); ///< Access status- (read) and command (write) register
|
||||
static const IOPort data_port(0x60); ///< Access PS/2 device [keyboard] output-
|
||||
///< (read) and input (write) buffer
|
||||
/* The buffers are used to communicate with the controller or the connected
|
||||
* PS/2 devices alike:
|
||||
* - For the output buffer, the controller decides to which PS/2 device the
|
||||
* data gets forwarded to -- by default it is the primary PS/2 device
|
||||
* (keyboard).
|
||||
* - The source device from which the data was gathered can be determined using
|
||||
* the status flag (\ref IS_MOUSE).
|
||||
*
|
||||
* Please also note, that the naming of the buffer may be a bit contra-intuitive
|
||||
* since it is the perspective of the PS/2 controller due to historical reasons.
|
||||
*/
|
||||
|
||||
// Key decoder (stores the state of the modifier keys)
|
||||
static KeyDecoder key_decoder;
|
||||
|
||||
// To store the current state of the Keyboard LEDs
|
||||
static uint8_t leds = 0;
|
||||
|
||||
/*! \brief Flags in the PS/2 controller status register
|
||||
*/
|
||||
enum Status {
|
||||
HAS_OUTPUT = 1 << 0, ///< Output buffer non-empty?
|
||||
INPUT_PENDING = 1 << 1, ///< Is input buffer full?
|
||||
SYSTEM_FLAG = 1 << 2, ///< set on soft reset, cleared on power up
|
||||
IS_COMMAND = 1 << 3, ///< Is command Byte? (otherwise data)
|
||||
IS_MOUSE = 1 << 5, ///< Mouse output has data
|
||||
TIMEOUT_ERROR = 1 << 6, ///< Timeout error
|
||||
PARITY_ERROR = 1 << 7 ///< Parity error
|
||||
};
|
||||
|
||||
/*! \brief Commands to be send to the Keyboard
|
||||
*/
|
||||
enum KeyboardCommand : uint8_t {
|
||||
KEYBOARD_SET_LED =
|
||||
0xed, ///< Set the LED (according to the following parameter byte)
|
||||
KEYBOARD_SEND_ECHO = 0xee, ///< Send an echo packet
|
||||
KEYBOARD_SET_SPEED = 0xf3, ///< Set the repeat rate (according to the
|
||||
///< following parameter byte)
|
||||
KEYBOARD_ENABLE = 0xf4, ///< Enable Keyboard
|
||||
KEYBOARD_DISABLE = 0xf5, ///< Disable Keyboard
|
||||
KEYBOARD_SET_DEFAULT = 0xf6, ///< Load defaults
|
||||
};
|
||||
|
||||
/*! \brief Replies
|
||||
*/
|
||||
enum Reply {
|
||||
ACK = 0xfa, ///< Acknowledgement
|
||||
RESEND = 0xfe, ///< Request to resend (not required to implement)
|
||||
ECHO = 0xee ///< Echo answer
|
||||
};
|
||||
|
||||
/*! \brief Commands for the PS/2 Controller
|
||||
*
|
||||
* These commands are processed by the controller and *not* send to
|
||||
* keyboard/mouse. They have to be written into the command register.
|
||||
*/
|
||||
enum ControllerCommand {
|
||||
CONTROLLER_GET_COMMAND_BYTE = 0x20, ///< Read Command Byte of PS/2 Controller
|
||||
CONTROLLER_SET_COMMAND_BYTE =
|
||||
0x60, ///< Write Command Byte of PS/2 Controller
|
||||
CONTROLLER_MOUSE_DISABLE = 0xa7, ///< Disable mouse interface
|
||||
CONTROLLER_MOUSE_ENABLE = 0xa8, ///< Enable mouse interface
|
||||
CONTROLLER_KEYBOARD_DISABLE = 0xad, ///< Disable keyboard interface
|
||||
CONTROLLER_KEYBOARD_ENABLE = 0xae, ///< Enable keyboard interface
|
||||
CONTROLLER_SEND_TO_MOUSE = 0xd4, ///< Send parameter to mouse device
|
||||
};
|
||||
|
||||
/*! \brief Send a command or data to a connected PS/2 device
|
||||
*
|
||||
* The value must only be written into the input buffer after the previously
|
||||
* written values have been fetched (\ref INPUT_PENDING in the status register).
|
||||
*
|
||||
* \todo(11) Implement method
|
||||
*
|
||||
* \param value data to be sent
|
||||
*/
|
||||
[[maybe_unused]] static void sendData(uint8_t value) {
|
||||
// TODO: You have to implement this method
|
||||
(void)value;
|
||||
}
|
||||
|
||||
void init() {
|
||||
// Switch all LEDs off (on many PCs NumLock is turned on after power up)
|
||||
setLed(LED_CAPS_LOCK, false);
|
||||
setLed(LED_SCROLL_LOCK, false);
|
||||
setLed(LED_NUM_LOCK, false);
|
||||
|
||||
// Set to maximum speed & minimum delay
|
||||
setRepeatRate(SPEED_30_0CPS, DELAY_250MS);
|
||||
}
|
||||
|
||||
bool fetch(Key &pressed) {
|
||||
// TODO: You have to implement this method
|
||||
(void)pressed;
|
||||
return false;
|
||||
}
|
||||
|
||||
void setRepeatRate(Speed speed, Delay delay) {
|
||||
(void)speed;
|
||||
(void)delay;
|
||||
}
|
||||
|
||||
void setLed(enum LED led, bool on) {
|
||||
if (on) {
|
||||
leds |= led;
|
||||
} else {
|
||||
leds &= ~led;
|
||||
}
|
||||
sendData(KEYBOARD_SET_LED); // Command for the Keyboard
|
||||
sendData(leds); // Parameter
|
||||
}
|
||||
|
||||
void drainBuffer() {}
|
||||
|
||||
} // namespace PS2Controller
|
||||
@ -0,0 +1,153 @@
|
||||
/*! \file
|
||||
* \brief \ref PS2Controller "PS/2 Controller" (Intel 8042, also known as
|
||||
* Keyboard Controller)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "../object/key.h"
|
||||
#include "../types.h"
|
||||
|
||||
/*! \brief PS/2 Controller
|
||||
* \ingroup io
|
||||
*
|
||||
* Initializes the PS/2 devices (Keyboard and optional Mouse), and
|
||||
* determines both the scan code and ASCII character of a pressed key from the
|
||||
* transmitted make and break codes using the \ref KeyDecoder.
|
||||
*
|
||||
* \note This controller is also known as Intel 8042 (nowadays integrated in
|
||||
* the mainboard) or *Keyboard Controller*.
|
||||
* But to avoid confusion with the actual Keyboard and since we use the
|
||||
* PS/2-compatible mode to support the Mouse as well, the name
|
||||
* PS/2 Controller was chosen for the sake of simplicity.
|
||||
*
|
||||
* \note Since modern PCs sometimes don't have an PS/2 connector, USB keyboards
|
||||
* and mice are emulated as PS/2 device with USB Legacy Support.
|
||||
*/
|
||||
namespace PS2Controller {
|
||||
/*! \brief Initialization of connected devices
|
||||
*
|
||||
* All status LEDs of the keyboard are switched off and the repetition rate is
|
||||
* set to maximum speed.
|
||||
*
|
||||
* Later the \ref IOAPIC is configured to receive corresponding interrupts.
|
||||
*
|
||||
* \note The keyboard interrupts should be configured as \ref IOAPIC::LEVEL
|
||||
* "level triggered". According to the standard we would have to check the
|
||||
* corresponding entry in
|
||||
* \ref ACPI::MADS::Interrupt_Source_Override and use these values. Most
|
||||
* likely this would suggest an \ref IOAPIC::EDGE "edge-triggered mode" -- which
|
||||
* would work as well. However, using a \ref IOAPIC::LEVEL "level-triggered
|
||||
* mode" is more forgiving because it resends the interrupt request even if an
|
||||
* interrupt was lost (e.g. the required handling, retrieving the buffer entry,
|
||||
* was not performed).
|
||||
*
|
||||
* \todo(12) Register with \ref IOAPIC
|
||||
*/
|
||||
void init();
|
||||
|
||||
/*! \brief Retrieve the keyboard event
|
||||
*
|
||||
* Retrieves make and brake events from the keyboard.
|
||||
* If a valid (non special) key was pressed, the scan code is determined
|
||||
* using \ref KeyDecoder::decode into a \ref Key object.
|
||||
* Events on special keys like \key{Shift}, \key{Alt}, \key{CapsLock} etc. are
|
||||
* stored (in \ref KeyDecoder) and applied on subsequent keystrokes, while no
|
||||
* valid key is retrieved.
|
||||
*
|
||||
* Mouse events are ignored.
|
||||
*
|
||||
* \todo(11) Implement Method
|
||||
*
|
||||
* \todo(12) Adjust method (unless it is already non-blocking)
|
||||
*
|
||||
* \param pressed Reference to an object which will contain the pressed \ref Key
|
||||
* on success
|
||||
* \return `true` if a valid key was decoded
|
||||
*/
|
||||
bool fetch(Key &pressed);
|
||||
|
||||
/*! \brief Delay before the keyboard starts repeating sending a pressed key
|
||||
*/
|
||||
enum Delay {
|
||||
DELAY_250MS = 0, ///< Delay of 0.25s
|
||||
DELAY_500MS = 1, ///< Delay of 0.5s
|
||||
DELAY_750MS = 2, ///< Delay of 0.75s
|
||||
DELAY_1000MS = 3 ///< Delay of 1s
|
||||
};
|
||||
|
||||
/*! \brief Repeat Rate of Characters
|
||||
*
|
||||
* \see \ref ps2keyboard
|
||||
*/
|
||||
enum Speed {
|
||||
SPEED_30_0CPS = 0x00, ///< 30 characters per second
|
||||
SPEED_26_7CPS = 0x01, ///< 26.7 characters per second
|
||||
SPEED_24_0CPS = 0x02, ///< 24 characters per second
|
||||
SPEED_21_8CPS = 0x03, ///< 12.8 characters per second
|
||||
SPEED_20_7CPS = 0x04, ///< 20.7 characters per second
|
||||
SPEED_18_5CPS = 0x05, ///< 18.5 characters per second
|
||||
SPEED_17_1CPS = 0x06, ///< 17.1 characters per second
|
||||
SPEED_16_0CPS = 0x07, ///< 16 characters per second
|
||||
SPEED_15_0CPS = 0x08, ///< 15 characters per second
|
||||
SPEED_13_3CPS = 0x09, ///< 13.3 characters per second
|
||||
SPEED_12_0CPS = 0x0a, ///< 12 characters per second
|
||||
SPEED_10_9CPS = 0x0b, ///< 10.9 characters per second
|
||||
SPEED_10_0CPS = 0x0c, ///< 10 characters per second
|
||||
SPEED_09_2CPS = 0x0d, ///< 9.2 characters per second
|
||||
SPEED_08_6CPS = 0x0e, ///< 8.6 characters per second
|
||||
SPEED_08_0CPS = 0x0f, ///< 8 characters per second
|
||||
SPEED_07_5CPS = 0x10, ///< 7.5 characters per second
|
||||
SPEED_06_7CPS = 0x11, ///< 6.7 characters per second
|
||||
SPEED_06_0CPS = 0x12, ///< 6 characters per second
|
||||
SPEED_05_5CPS = 0x13, ///< 5.5 characters per second
|
||||
SPEED_05_0CPS = 0x14, ///< 5 characters per second
|
||||
SPEED_04_6CPS = 0x15, ///< 4.6 characters per second
|
||||
SPEED_04_3CPS = 0x16, ///< 4.3 characters per second
|
||||
SPEED_04_0CPS = 0x17, ///< 4 characters per second
|
||||
SPEED_03_7CPS = 0x18, ///< 3.7 characters per second
|
||||
SPEED_03_3CPS = 0x19, ///< 3.3 characters per second
|
||||
SPEED_03_0CPS = 0x1a, ///< 3 characters per second
|
||||
SPEED_02_7CPS = 0x1b, ///< 2.7 characters per second
|
||||
SPEED_02_5CPS = 0x1c, ///< 2.5 characters per second
|
||||
SPEED_02_3CPS = 0x1d, ///< 2.3 characters per second
|
||||
SPEED_02_1CPS = 0x1e, ///< 2.1 characters per second
|
||||
SPEED_02_0CPS = 0x1f, ///< 2 characters per second
|
||||
};
|
||||
|
||||
/*! \brief Configure the repeat rate of the keyboard
|
||||
*
|
||||
* \param delay configures how long a key must be pressed before the repetition
|
||||
* begins.
|
||||
* \param speed determines how fast the key codes should follow each other.
|
||||
* Valid values are between `0` (30 characters per second) and
|
||||
* `31` (2 characters per second).
|
||||
*/
|
||||
void setRepeatRate(Speed speed, Delay delay);
|
||||
|
||||
/*! \brief Keyboard LEDs
|
||||
*/
|
||||
enum LED {
|
||||
LED_SCROLL_LOCK = 1 << 0, ///< Scroll Lock
|
||||
LED_NUM_LOCK = 1 << 1, ///< Num Lock
|
||||
LED_CAPS_LOCK = 1 << 2, ///< Caps Lock
|
||||
};
|
||||
|
||||
/*! \brief Enable or disable a keyboard LED
|
||||
*
|
||||
* \param led LED to enable or disable
|
||||
* \param on `true` will enable the specified LED, `false` disable
|
||||
*/
|
||||
void setLed(enum LED led, bool on);
|
||||
|
||||
/*! \brief Empties the keyboard buffer.
|
||||
*
|
||||
* The keyboard may not send any interrupts if the buffer is not empty.
|
||||
* To prevent unhandled keystrokes (for example during boot) the buffer
|
||||
* should be emptied once right before allowing keyboard interrupts
|
||||
* (even if keystrokes might be lost).
|
||||
*
|
||||
* \todo(12) Implement method
|
||||
*/
|
||||
void drainBuffer();
|
||||
|
||||
} // namespace PS2Controller
|
||||
@ -0,0 +1,30 @@
|
||||
#include "serialstream.h"
|
||||
|
||||
SerialStream::SerialStream(ComPort port, BaudRate baud_rate, DataBits data_bits,
|
||||
StopBits stop_bits, Parity parity) {
|
||||
(void)port;
|
||||
(void)baud_rate;
|
||||
(void)data_bits;
|
||||
(void)stop_bits;
|
||||
(void)parity;
|
||||
}
|
||||
|
||||
void SerialStream::flush() {}
|
||||
|
||||
void SerialStream::setForeground(Color c) { (void)c; }
|
||||
|
||||
void SerialStream::setBackground(Color c) { (void)c; }
|
||||
|
||||
void SerialStream::setAttribute(Attrib a) { (void)a; }
|
||||
|
||||
void SerialStream::reset() {}
|
||||
|
||||
void SerialStream::setPos(int x, int y) {
|
||||
(void)x;
|
||||
(void)y;
|
||||
}
|
||||
|
||||
void SerialStream::print(char* str, int length) {
|
||||
(void)str;
|
||||
(void)length;
|
||||
}
|
||||
@ -0,0 +1,143 @@
|
||||
/*! \file
|
||||
* \brief \ref Serial \ref SerialStream "output stream"
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "../arch/serial.h"
|
||||
#include "../object/outputstream.h"
|
||||
#include "../types.h"
|
||||
|
||||
/*! \brief Console (VT100 compatible) via \ref Serial interface.
|
||||
* \ingroup io
|
||||
*
|
||||
* This class allows to connect a VT100-compatible display terminal via
|
||||
* the serial interface.
|
||||
*
|
||||
* The utility 'screen' can be used to attach a terminal to an interface
|
||||
* at a specified connection speed: `screen /dev/ttyS0 115200`
|
||||
*
|
||||
* Color and position can be adjusted with the help of
|
||||
* [escape
|
||||
* codes](http://web.archive.org/web/20181008150037/http://www.termsys.demon.co.uk/vtansi.htm).
|
||||
*/
|
||||
|
||||
class SerialStream : public OutputStream, public Serial {
|
||||
public:
|
||||
/*! \brief Attributes
|
||||
* can be used to influence the display of the output.
|
||||
*
|
||||
* \note The attributes might not be supported or have a different effect
|
||||
* depending on the terminal emulator!
|
||||
*/
|
||||
enum Attrib {
|
||||
RESET = 0, ///< Turn off character attributes
|
||||
BRIGHT = 1, ///< Bold
|
||||
DIM = 2, ///< Low intensity (dimmed)
|
||||
UNDERSCORE = 4, ///< Underline
|
||||
BLINK = 5, ///< Blink (slow)
|
||||
REVERSE = 7, ///< Swap fore & background
|
||||
HIDDEN = 8, ///< Concealed
|
||||
};
|
||||
|
||||
/*! \brief Color codes
|
||||
*
|
||||
* Default VT100 supports eight colors for both foreground and background
|
||||
* (later versions 256 [8 bit] and even true color [32 bit]).
|
||||
* The actual color is affected by the attributes and can look significantly
|
||||
* different depending on the terminal emulator.
|
||||
*/
|
||||
enum Color {
|
||||
BLACK = 0,
|
||||
RED = 1,
|
||||
GREEN = 2,
|
||||
YELLOW = 3,
|
||||
BLUE = 4,
|
||||
MAGENTA = 5,
|
||||
CYAN = 6,
|
||||
WHITE = 7
|
||||
};
|
||||
|
||||
/*! \brief Constructor for the VT100-compatible console
|
||||
*
|
||||
* Sets up the serial connection as well
|
||||
*
|
||||
* \todo(11) Implement Method
|
||||
*/
|
||||
explicit SerialStream(ComPort port = COM1, BaudRate baud_rate = BAUD_115200,
|
||||
DataBits data_bits = DATA_8BIT,
|
||||
StopBits stop_bits = STOP_1BIT,
|
||||
Parity parity = PARITY_NONE);
|
||||
|
||||
/*! \brief Method to output the buffer contents of the base class \ref
|
||||
* Stringbuffer
|
||||
*
|
||||
* The method is automatically called when the buffer is full,
|
||||
* but can also be called explicitly to force output of the current buffer.
|
||||
*
|
||||
* \todo(11) Implement Method
|
||||
*/
|
||||
void flush() override;
|
||||
|
||||
/*! \brief Change foreground color (for subsequent output)
|
||||
*
|
||||
* \todo(11) Implement Method
|
||||
*
|
||||
* \param c Color
|
||||
*/
|
||||
void setForeground(Color c);
|
||||
|
||||
/*! \brief Change background color (for subsequent output)
|
||||
*
|
||||
* \todo(11) Implement Method
|
||||
*
|
||||
* \param c Color
|
||||
*/
|
||||
void setBackground(Color c);
|
||||
|
||||
/*! \brief Change text attribute (for subsequent output)
|
||||
*
|
||||
* \todo(11) Implement Method
|
||||
*
|
||||
* \param a Attribute
|
||||
*/
|
||||
void setAttribute(Attrib a);
|
||||
|
||||
/*! \brief Reset terminal
|
||||
*
|
||||
* Clear screen, place cursor at the beginning and reset colors
|
||||
* and attributes to the default value.
|
||||
*
|
||||
* \todo(11) Implement Method
|
||||
*/
|
||||
void reset();
|
||||
|
||||
/*! \brief Set the cursor position
|
||||
*
|
||||
* \param x Column in window
|
||||
* \param y Row in window
|
||||
*
|
||||
* \todo(11) Implement Method
|
||||
*/
|
||||
void setPos(int x, int y);
|
||||
|
||||
/*! \brief Display multiple characters in the window starting at the current
|
||||
* cursor position
|
||||
*
|
||||
* This method can be used to output a string, starting at the current cursor
|
||||
* position. Since the string does not need to contain a '\0' termination
|
||||
* (as it is usually the case in C), the parameter `length` is required to
|
||||
* specify the number of characters in the string.
|
||||
*
|
||||
* The text is displayed using the previously configured
|
||||
* \ref setAttribute() "attributes", \ref setForeground() "fore-"
|
||||
* and \ref setBackground "background" color.
|
||||
*
|
||||
* A line break will occur wherever the character `\n` is inserted
|
||||
* in the text to be output (for compatibility reasons a `\r` is
|
||||
* automatically appended).
|
||||
*
|
||||
* \param str String to output
|
||||
* \param length length of string
|
||||
*/
|
||||
void print(char* str, int length);
|
||||
};
|
||||
@ -0,0 +1,12 @@
|
||||
#include "textstream.h"
|
||||
|
||||
TextStream::TextStream(unsigned from_col, unsigned to_col, unsigned from_row,
|
||||
unsigned to_row, bool use_cursor) {
|
||||
(void)from_col;
|
||||
(void)to_col;
|
||||
(void)from_row;
|
||||
(void)to_row;
|
||||
(void)use_cursor;
|
||||
}
|
||||
|
||||
void TextStream::flush() {}
|
||||
@ -0,0 +1,41 @@
|
||||
/*! \file
|
||||
* \brief \ref TextStream outputs text onto the screen in \ref CGA
|
||||
*/
|
||||
|
||||
/*! \defgroup io I/O subsystem
|
||||
* \brief The input/output subsystem
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "../types.h"
|
||||
|
||||
/*! \brief Output text (form different data type sources) on screen in text
|
||||
* mode
|
||||
* \ingroup io
|
||||
*
|
||||
* Allows the output of different data types as strings on the \ref CGA
|
||||
* screen of a PC.
|
||||
* To achieve this, \ref TextStream is derived from both \ref OutputStream and
|
||||
* \ref TextWindow and only implements the method \ref TextStream::flush().
|
||||
* Further formatting or special effects are implemented in \ref TextWindow.
|
||||
*/
|
||||
class TextStream {
|
||||
// Prevent copies and assignments
|
||||
TextStream(const TextStream&) = delete;
|
||||
TextStream& operator=(const TextStream&) = delete;
|
||||
|
||||
public:
|
||||
/// \copydoc TextWindow::TextWindow(unsigned,unsigned,unsigned,unsigned,bool)
|
||||
TextStream(unsigned from_col, unsigned to_col, unsigned from_row,
|
||||
unsigned to_row, bool use_cursor = false);
|
||||
|
||||
/*! \brief Output the buffer contents of the base class \ref Stringbuffer
|
||||
*
|
||||
* The method is automatically called when the buffer is full,
|
||||
* but can also be called explicitly to force output of the current buffer.
|
||||
*
|
||||
*
|
||||
* \todo(11) Implement method
|
||||
*/
|
||||
void flush();
|
||||
};
|
||||
@ -0,0 +1,12 @@
|
||||
#include "epilogues.h"
|
||||
|
||||
#include "guard.h"
|
||||
|
||||
namespace Epilogues {
|
||||
|
||||
void keyboard(Vault& g) { (void)g; }
|
||||
|
||||
void timer(Vault& g) { (void)g; }
|
||||
|
||||
void assassin(Vault& g) { (void)g; }
|
||||
}; // namespace Epilogues
|
||||
@ -0,0 +1,50 @@
|
||||
/*! \file
|
||||
* \brief XXX: Write summary
|
||||
*/
|
||||
#pragma once
|
||||
#include "../types.h"
|
||||
|
||||
struct Vault;
|
||||
|
||||
/*! \brief A handler function for an epilogue.
|
||||
* \ingroup interrupts
|
||||
*
|
||||
* It receives the vault directly, because it is executed on level 1/2 (by the
|
||||
* \ref Guard) .
|
||||
*
|
||||
* \note Since it does only receive one parameter, other data must be passed
|
||||
* in a different way.
|
||||
*/
|
||||
using Epilogue = void (*)(Vault&);
|
||||
|
||||
namespace Epilogues {
|
||||
|
||||
/*!
|
||||
* @brief The keyboard epilogue.
|
||||
*
|
||||
* Handle the keyboard Key that has been fetched during the prologue.
|
||||
*
|
||||
* \todo(13) print the stored character
|
||||
* \todo(15) Store the key to the keyboard buffer for user threads. Wake user
|
||||
* threads waiting for a key using the key semaphore.
|
||||
*
|
||||
* @param g
|
||||
*/
|
||||
void keyboard(Vault& g);
|
||||
|
||||
/*!
|
||||
* @brief Timer epilogue
|
||||
* \todo(15) Preemptively reschedule threads
|
||||
* \todo(16) Check the bellringer
|
||||
* \todo(17) Refresh screen with fixed FPS rate
|
||||
* @param g
|
||||
*/
|
||||
void timer(Vault& g);
|
||||
|
||||
/*! \brief Examine the `dying flag` of the current thread and reschedule if
|
||||
* it is set.
|
||||
*
|
||||
* \todo(15) Implement the rescheduling (in \MPStuBS only)
|
||||
*/
|
||||
void assassin(Vault& g);
|
||||
}; // namespace Epilogues
|
||||
@ -0,0 +1,30 @@
|
||||
#include "guard.h"
|
||||
|
||||
#include "../arch/core.h"
|
||||
#include "../debug/output.h"
|
||||
#include "../object/bbuffer.h"
|
||||
#include "../sync/ticketlock.h"
|
||||
#include "epilogues.h"
|
||||
|
||||
#define FOR_CURRENT_CORE [Core::getID()]
|
||||
|
||||
//! \brief The protected data for the epilogue level
|
||||
static Vault global_vault;
|
||||
|
||||
// lists of pending epilogues
|
||||
static BBuffer<Epilogue, 32> epilogue_queue[Core::MAX] = {};
|
||||
// Big Kernel Lock (BKL) for the epilogue level
|
||||
constinit Ticketlock global_lock;
|
||||
constinit bool epi_flag[Core::MAX] = {false};
|
||||
|
||||
Vault::Vault() {}
|
||||
|
||||
Guarded::~Guarded() { Guard::leave(); }
|
||||
|
||||
Guarded Guard::enter() { while (true); }
|
||||
|
||||
void Guard::leave() {}
|
||||
|
||||
void Guard::relay(Epilogue handler) { (void)handler; }
|
||||
|
||||
const Vault &Guard::unsafeConstAccess() { return global_vault; }
|
||||
@ -0,0 +1,121 @@
|
||||
/*! \file
|
||||
* \brief \ref Guard synchronizes access to epilogue level
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "../object/bbuffer.h"
|
||||
#include "../object/key.h"
|
||||
#include "../types.h"
|
||||
#include "epilogues.h"
|
||||
|
||||
//! \brief The epilogue vault contains the protected data for the epilogue level
|
||||
struct Vault {
|
||||
Vault();
|
||||
// no copy
|
||||
Vault(const Vault&) = delete;
|
||||
Vault& operator=(const Vault&) = delete;
|
||||
};
|
||||
|
||||
/*! \brief Lock guard that provides access to the epilogue \ref Vault
|
||||
*
|
||||
* This object automatically unlocks the \ref Guard when it goes out of scope.
|
||||
*/
|
||||
class Guarded {
|
||||
public:
|
||||
//! This constructor should only be used by the \ref Guard
|
||||
explicit Guarded(Vault& vault) : _vault(vault) {}
|
||||
//! Leave the critical section
|
||||
~Guarded();
|
||||
|
||||
//! Access the epilogue vault
|
||||
Vault& vault() { return _vault; }
|
||||
const Vault& vault() const { return _vault; }
|
||||
|
||||
// no copy
|
||||
Guarded(const Guarded&) = delete;
|
||||
Guarded& operator=(const Guarded&) = delete;
|
||||
|
||||
private:
|
||||
Vault& _vault;
|
||||
};
|
||||
|
||||
/*! \brief Synchronizes the kernel with interrupts using the Prologue/Epilogue
|
||||
* Model \ingroup interrupts
|
||||
*
|
||||
* The Guard is used to synchronize between "normal" core activities (currently
|
||||
* just the text output, later system calls) and interrupt handling routines.
|
||||
* For this purpose, \ref Guard has to contain one ore more \ref BBuffer
|
||||
* "queues", in which \ref Epilogue functions can be added. This is necessary if
|
||||
* the critical section is occupied at the time when an interrupt occurs, and
|
||||
* the
|
||||
* \ref Epilogue cannot be executed immediately. The queued epilogues are
|
||||
* processed when leaving the critical section.
|
||||
*
|
||||
* **Hints:**
|
||||
* - The epilogue queue is a central data structure, whose consistency
|
||||
* must be ensured. The implementation provided by the \ref BBuffer is not
|
||||
* entirely safe against concurrency. You need to disable
|
||||
* interrupts during operations on the buffer.
|
||||
* - In \MPStuBS, you need a separate epilogue queue for each core,
|
||||
* in which each processor serializes *its* epilogues. However, epilogues
|
||||
* on different cores could then be executed in parallel, since the
|
||||
* critical section is managed separately on a per-core base. This must be
|
||||
* prevented by using a global \ref Ticketlock to avoid concurrent
|
||||
* execution of epilogues -- there must never be more than one epilogue
|
||||
* active on the whole system at the same time!<br>
|
||||
* *Please note:* This [giant lock](https://en.wikipedia.org/wiki/Giant_lock)
|
||||
* (synchronizing all cores) should not be confused with the (core-specific)
|
||||
* flag variable that marks only the entry to the epilogue level on the
|
||||
* corresponding core!
|
||||
* - Interrupts should be disabled for as short as possible. Due to this
|
||||
* reason, the prologue/epilogue model allows epilogues to be interrupted
|
||||
* by prologues. This means that interrupts should be
|
||||
* \ref Core::Interrupt::enable "enabled" again before the epilogue is
|
||||
* executed (this includes notifying the APIC about the
|
||||
* \ref LAPIC::endOfInterrupt() "End-Of-Interrupt")
|
||||
*/
|
||||
namespace Guard {
|
||||
|
||||
/*! \brief Entering the critical section from level 0.
|
||||
*
|
||||
* Entering the critical section has to be handled differently depending on
|
||||
* the system: In a single-core system it is sufficient to mark the entry
|
||||
* by just setting a flag variable (since only one control flow can enter
|
||||
* the critical section at the same time). However, as soon as there are
|
||||
* multiple cores, this is no longer the case. If a core wants to enter the
|
||||
* critical section while *another* core is already in there, it should
|
||||
* (actively) wait in this method until the critical area is released again.
|
||||
*
|
||||
* \todo(13) Implement Method
|
||||
*/
|
||||
Guarded enter();
|
||||
|
||||
/*! \brief Leaving the critical section.
|
||||
*
|
||||
* Leaves the critical section and processes all remaining (enqueued) epilogues.
|
||||
* This may only be called while in level 1/2 after calling \ref enter().
|
||||
*
|
||||
* Note: Usually, this method is called by the destructor of the \ref
|
||||
* Guarded.
|
||||
*
|
||||
* \todo(13) Implement Method
|
||||
*/
|
||||
void leave();
|
||||
|
||||
/*! \brief A prologue wants its epilogue to be processed (entering from level
|
||||
* 1).
|
||||
*
|
||||
* This method is called by the interrupt handlers.
|
||||
* Whether this is done immediately or the epilogue just enqueued to the
|
||||
* epilogue queue depends on whether the critical section on *this* Core is
|
||||
* accessible or not.
|
||||
*
|
||||
* \todo(13) Implement Method
|
||||
*/
|
||||
void relay(Epilogue handler);
|
||||
|
||||
/*! \brief Access the epilogue vault without taking the lock.
|
||||
* Beware race conditions!
|
||||
*/
|
||||
const Vault& unsafeConstAccess();
|
||||
} // namespace Guard
|
||||
@ -0,0 +1,14 @@
|
||||
[SECTION .text]
|
||||
[EXTERN handle_keyboard]
|
||||
[GLOBAL handle_keyboard_asm]
|
||||
|
||||
; entry point for an interrupt to trigger a kernelpanic
|
||||
;
|
||||
align 16
|
||||
handle_keyboard_asm:
|
||||
; The interrupt may be triggered asynchronously, therefore the whole context
|
||||
; has to be saved and restored, or the interrupted code might not be able to
|
||||
; continue. The C++ compiler will only generates code to preserve
|
||||
; non-scratch registers in the high-level interrupt handler -- the scratch
|
||||
; registers have to be saved (and restored later) manually!
|
||||
; TODO(12): Implement the context save and restore for the keyboard interrupt
|
||||
@ -0,0 +1,102 @@
|
||||
#include "handlers.h"
|
||||
|
||||
#include "../arch/core_cr.h"
|
||||
#include "../arch/idt.h"
|
||||
#include "../arch/lapic.h"
|
||||
#include "../arch/system.h"
|
||||
#include "../debug/kernelpanic.h"
|
||||
#include "../debug/output.h"
|
||||
|
||||
void printContext(const InterruptContext *context) {
|
||||
DBG << "ip: " << hex << context->cs << ':' << context->ip
|
||||
<< " sp: " << context->ss << ':' << context->sp << " flags" << bin
|
||||
<< context->flags << endl;
|
||||
}
|
||||
|
||||
[[gnu::interrupt]] void handle_invalid_opcode(InterruptContext *context) {
|
||||
DBG << "Invalid opcode encoutered" << endl;
|
||||
printContext(context);
|
||||
kernelpanic("Invalid opcode!");
|
||||
}
|
||||
|
||||
[[gnu::interrupt]] void handle_double_fault(InterruptContext *context,
|
||||
uint64_t error) {
|
||||
(void)error;
|
||||
DBG << "Double fault encoutered" << endl;
|
||||
printContext(context);
|
||||
kernelpanic("Double fault!");
|
||||
}
|
||||
|
||||
[[gnu::interrupt]] void handle_invalid_tss(InterruptContext *context,
|
||||
uint64_t error) {
|
||||
DBG << "Invalid tss encoutered. Offending selector idx: " << dec << error
|
||||
<< endl;
|
||||
printContext(context);
|
||||
kernelpanic("Invalid TSS!");
|
||||
}
|
||||
|
||||
[[gnu::interrupt]] void handle_general_protection_fault(
|
||||
InterruptContext *context, uint64_t error) {
|
||||
DBG << "General protection fault encoutered. Error code: " << dec << error
|
||||
<< endl;
|
||||
printContext(context);
|
||||
kernelpanic("General protection fault!");
|
||||
}
|
||||
|
||||
enum PAGE_FAULT_ERROR {
|
||||
PF_ERR_PRESENT = 0x1,
|
||||
PF_ERR_WRITE = 0x2,
|
||||
PF_ERR_USER = 0x4,
|
||||
PF_ERR_RESERVED = 0x8,
|
||||
PF_ERR_IFETCH = 0x10,
|
||||
};
|
||||
|
||||
[[gnu::interrupt]] void handle_page_fault(InterruptContext *context,
|
||||
uint64_t error) {
|
||||
(void)error;
|
||||
DBG << "Page fault encoutered at linear address " << hex
|
||||
<< Core::CR<2>::read() << endl
|
||||
<< (error & PF_ERR_PRESENT ? "present" : "non-present") << " page|"
|
||||
<< (error & PF_ERR_WRITE ? "write" : "read") << " access|"
|
||||
<< (error & PF_ERR_USER ? "user" : "supervisor") << "|"
|
||||
<< (error & PF_ERR_RESERVED ? "reserved bit int pte" : "") << "|"
|
||||
<< (error & PF_ERR_IFETCH ? "instrution" : "data") << " fetch|" << endl;
|
||||
printContext(context);
|
||||
kernelpanic("Page fault!");
|
||||
}
|
||||
|
||||
void handle_keyboard() {}
|
||||
|
||||
[[gnu::interrupt]] void handle_panic(InterruptContext *context) {
|
||||
(void)context;
|
||||
}
|
||||
|
||||
[[gnu::interrupt]] void handle_timer(InterruptContext *context) {
|
||||
(void)context;
|
||||
}
|
||||
|
||||
[[gnu::interrupt]] void handle_assassin(InterruptContext *context) {
|
||||
(void)context;
|
||||
}
|
||||
[[gnu::interrupt]] void handle_wakeup(InterruptContext *context) {
|
||||
(void)context;
|
||||
}
|
||||
|
||||
void initInterruptHandlers() {
|
||||
// Some handlers that are useful for debugging
|
||||
IDT::set(Core::Interrupt::Vector::INVALID_OPCODE,
|
||||
IDT::InterruptDescriptor::Returning(handle_invalid_opcode));
|
||||
IDT::set(Core::Interrupt::Vector::DOUBLE_FAULT,
|
||||
IDT::InterruptDescriptor::DivergingWithError(handle_double_fault));
|
||||
IDT::set(Core::Interrupt::Vector::INVALID_TSS,
|
||||
IDT::InterruptDescriptor::ReturningWithError(handle_invalid_tss));
|
||||
IDT::set(Core::Interrupt::Vector::GENERAL_PROTECTION_FAULT,
|
||||
IDT::InterruptDescriptor::ReturningWithError(
|
||||
handle_general_protection_fault));
|
||||
IDT::set(Core::Interrupt::Vector::PAGE_FAULT,
|
||||
IDT::InterruptDescriptor::ReturningWithError(handle_page_fault));
|
||||
|
||||
// TODO: Add more handlers here
|
||||
// Load the idt pointer
|
||||
IDT::load();
|
||||
}
|
||||
@ -0,0 +1,109 @@
|
||||
/*! \file All interrupts need to start somewhere. This file contains the entry
|
||||
* points for all interrupts handled by StuBS.
|
||||
* \brief The Interrupt Subsystem
|
||||
* \defgroup interrupts Interrupt Handling
|
||||
*/
|
||||
#pragma once
|
||||
#include "../types.h"
|
||||
|
||||
/*! \brief Initialize the IDT.
|
||||
*
|
||||
* The interrupt subsystem of StubBS contains all functionality to accept
|
||||
* interrupts from the hardware and process them.
|
||||
* In later exercises the interrupts will enable applications to
|
||||
* execute core functionality (system calls).
|
||||
* The entry point for the interrupt subsystem is the function
|
||||
* 'interrupt_entry_VECTOR' (in `interrupt/handler.asm`).
|
||||
*
|
||||
* \todo(12) Register your own interrupt handlers
|
||||
*/
|
||||
void initInterruptHandlers();
|
||||
|
||||
struct InterruptContext;
|
||||
|
||||
/*!
|
||||
* @brief Helper function for printf-debugging the InterruptContext
|
||||
*/
|
||||
void printContext(const InterruptContext *context);
|
||||
|
||||
/*!
|
||||
* @brief An interrupt handler for the INVALID_OPCODE trap
|
||||
*/
|
||||
[[gnu::interrupt]] void handle_invalid_opcode(InterruptContext *context);
|
||||
/*!
|
||||
* @brief A double fault occurs when another exception occurs during exception
|
||||
* handling.
|
||||
*
|
||||
* In this case, the OS cannot recover anymore. This can happen e.g.
|
||||
* during page fault handling.
|
||||
*/
|
||||
[[gnu::interrupt]] void handle_double_fault(InterruptContext *context,
|
||||
uint64_t error);
|
||||
/*!
|
||||
* @brief If the task state segment is configured incorrectly, the kernel cannot
|
||||
* switch the privilege levels during interrupts.
|
||||
*/
|
||||
[[gnu::interrupt]] void handle_invalid_tss(InterruptContext *context,
|
||||
uint64_t error);
|
||||
/*!
|
||||
* @brief When the CPU tried to execute an unprivileged opcode or exceeds
|
||||
* segmentation bounds, the GPF exception is raised.
|
||||
*/
|
||||
[[gnu::interrupt]] void handle_general_protection_fault(
|
||||
InterruptContext *context, uint64_t error);
|
||||
/*!
|
||||
* @brief With paging enabled, an invalid access to a memory page causes a page
|
||||
* fault.
|
||||
*/
|
||||
[[gnu::interrupt]] void handle_page_fault(InterruptContext *context,
|
||||
uint64_t error);
|
||||
|
||||
extern "C" { // disable C++ name mangling for asm function
|
||||
|
||||
/*! \brief Assembly interrupt handler for the keyboard.
|
||||
*
|
||||
* On keyboard interrupt, the register state is saved to and restored from the
|
||||
* stack. This function wraps the handle_keyboard C-function.
|
||||
*
|
||||
* \todo(12) Implement in assembly
|
||||
*/
|
||||
[[gnu::interrupt]] void handle_keyboard_asm(InterruptContext *context);
|
||||
|
||||
/*! \brief Higher-level Interrupt handler for the keyboard.
|
||||
*
|
||||
* On keyboard interrupt, the PS2-Controller may contain a valid Key that has to
|
||||
* be fetched.
|
||||
*
|
||||
* \todo(12) Fetch a single key
|
||||
* \todo(13) Extend to use the Prologue-Epilogue pattern
|
||||
*/
|
||||
void handle_keyboard();
|
||||
}
|
||||
|
||||
/*! \brief handle_panic
|
||||
*
|
||||
* \todo(12) Trigger a kernel panic
|
||||
*/
|
||||
[[gnu::interrupt]] void handle_panic(InterruptContext *context);
|
||||
|
||||
/*! \brief handle_timer
|
||||
*
|
||||
* \todo(15) Handle the timer interrupt
|
||||
*/
|
||||
[[gnu::interrupt]] void handle_timer(InterruptContext *context);
|
||||
|
||||
/*! \brief handle_assassin
|
||||
*
|
||||
* Handler for the assassin IPI, i.e. a thread shall be killed.
|
||||
*
|
||||
* \todo(15) Handle the assassin interrupt (in \MPStuBS only)
|
||||
*/
|
||||
[[gnu::interrupt]] void handle_assassin(InterruptContext *context);
|
||||
|
||||
/*! \brief handle_wakeup
|
||||
*
|
||||
* In Multicore systems, an IPI is used to wake a sleeping core.
|
||||
*
|
||||
* \todo(16) Handle the wakeup interrupt (in \MPStuBS only)
|
||||
*/
|
||||
[[gnu::interrupt]] void handle_wakeup(InterruptContext *context);
|
||||
@ -0,0 +1,28 @@
|
||||
|
||||
#include "arch/lapic.h"
|
||||
#include "boot/startup_ap.h"
|
||||
#include "debug/output.h"
|
||||
|
||||
// Main function
|
||||
// (the bootstrap processor starts here)}
|
||||
extern "C" int main() {
|
||||
unsigned int numCPUs = Core::count();
|
||||
DBG_VERBOSE << "Number of CPUs: " << numCPUs << endl;
|
||||
|
||||
/* Start application processors
|
||||
* To avoid unexpected behaviour, make sure that interrupts are not
|
||||
* enabled before the APs are booted. Otherwise it might interfere with the
|
||||
* Startup IPIs or even block devices like keyboard because of a missing EOI
|
||||
*/
|
||||
ApplicationProcessor::boot();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Main function for application processors
|
||||
extern "C" int main_ap() {
|
||||
DBG_VERBOSE << "CPU core " << static_cast<int>(Core::getID()) << " / LAPIC "
|
||||
<< static_cast<int>(LAPIC::getID()) << " in main_ap()" << endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env sh
|
||||
## This repo should not directly contain a flake.nix, to avoid it being automatically copied to the (locally) world-readable Nix store.
|
||||
exec nix develop path:"$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P )"/utils "$@"
|
||||
@ -0,0 +1,62 @@
|
||||
// vim: set noet ts=4 sw=4:
|
||||
|
||||
/*! \file
|
||||
* \brief Contains a \ref BBuffer "bounded buffer"
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "../types.h"
|
||||
|
||||
/*! \brief The class BBuffer implements a bounded buffer, that is a circular
|
||||
* buffer with a fixed capacity.
|
||||
*
|
||||
* \tparam T the type of data to be stored
|
||||
* \tparam CAP the buffers capacity (must be greater than 1)
|
||||
*/
|
||||
template <typename T, unsigned CAP>
|
||||
class BBuffer {
|
||||
static_assert(CAP > 1, "BBuffer of size 1 is unsupported.");
|
||||
// Prevent copies and assignments
|
||||
BBuffer(const BBuffer&) = delete;
|
||||
BBuffer& operator=(const BBuffer&) = delete;
|
||||
|
||||
private:
|
||||
T data[CAP];
|
||||
volatile unsigned in;
|
||||
volatile unsigned out;
|
||||
|
||||
public:
|
||||
/*! \brief Constructor that initialized an empty buffer.
|
||||
*/
|
||||
BBuffer() : in(0), out(0) {}
|
||||
|
||||
/*! \brief Add an element to the buffer.
|
||||
* \param val The element to be added.
|
||||
* \return `false` if the buffer is full and no element can be added; `true`
|
||||
* otherwise.
|
||||
*/
|
||||
bool produce(T val) {
|
||||
unsigned nextin = (in + 1) % CAP;
|
||||
if (nextin != out) {
|
||||
data[in] = val;
|
||||
in = nextin;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*! \brief Remove an element from the buffer.
|
||||
* \param val Output parameter that receives the next element. If there is
|
||||
* (currently) no next element, `val` will not be modified.
|
||||
* \return `false` if the buffer was empty; `true` if the buffer was
|
||||
* not empty and an element was written to val.
|
||||
*/
|
||||
bool consume(T& val) {
|
||||
if (in != out) {
|
||||
val = data[out];
|
||||
out = (out + 1) % CAP;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,118 @@
|
||||
#include "key.h"
|
||||
|
||||
// Character table for scan codes for US keyboards
|
||||
static struct {
|
||||
const unsigned char normal, // Character without modifiers
|
||||
shift, // Character with pressed Shift, Capslock, or in Numpad
|
||||
alt; // Character with pressed Alt key
|
||||
} ascii_tab[Key::Scancode::KEYS] = {
|
||||
{0, 0, 0}, // KEY_INVALID
|
||||
{0, 0, 0}, // KEY_ESCAPE
|
||||
{'1', '!', 0}, // KEY_1
|
||||
{'2', '"', 253}, // KEY_2
|
||||
{'3', 21, 0}, // KEY_3
|
||||
{'4', '$', 0}, // KEY_4
|
||||
{'5', '%', 0}, // KEY_5
|
||||
{'6', '&', 0}, // KEY_6
|
||||
{'7', '/', '{'}, // KEY_7
|
||||
{'8', '(', '['}, // KEY_8
|
||||
{'9', ')', ']'}, // KEY_9
|
||||
{'0', '=', '}'}, // KEY_0
|
||||
{225, '?', '\\'}, // KEY_DASH
|
||||
{39, 96, 0}, // KEY_EQUAL
|
||||
{'\b', 0, 0}, // KEY_BACKSPACE
|
||||
{0, 0, 0}, // KEY_TAB
|
||||
{'q', 'Q', '@'}, // KEY_Q
|
||||
{'w', 'W', 0}, // KEY_W
|
||||
{'e', 'E', 0}, // KEY_E
|
||||
{'r', 'R', 0}, // KEY_R
|
||||
{'t', 'T', 0}, // KEY_T
|
||||
{'z', 'Z', 0}, // KEY_Y
|
||||
{'u', 'U', 0}, // KEY_U
|
||||
{'i', 'I', 0}, // KEY_I
|
||||
{'o', 'O', 0}, // KEY_O
|
||||
{'p', 'P', 0}, // KEY_P
|
||||
{129, 154, 0}, // KEY_OPEN_BRACKET
|
||||
{'+', '*', '~'}, // KEY_CLOSE_BRACKET
|
||||
{'\n', 0, 0}, // KEY_ENTER
|
||||
{0, 0, 0}, // KEY_LEFT_CTRL
|
||||
{'a', 'A', 0}, // KEY_A
|
||||
{'s', 'S', 0}, // KEY_S
|
||||
{'d', 'D', 0}, // KEY_D
|
||||
{'f', 'F', 0}, // KEY_F
|
||||
{'g', 'G', 0}, // KEY_G
|
||||
{'h', 'H', 0}, // KEY_H
|
||||
{'j', 'J', 0}, // KEY_J
|
||||
{'k', 'K', 0}, // KEY_K
|
||||
{'l', 'L', 0}, // KEY_L
|
||||
{148, 153, 0}, // KEY_SEMICOLON
|
||||
{132, 142, 0}, // KEY_APOSTROPH
|
||||
{'^', 248, 0}, // KEY_GRAVE_ACCENT
|
||||
{0, 0, 0}, // KEY_LEFT_SHIFT
|
||||
{'#', 39, 0}, // KEY_BACKSLASH
|
||||
{'y', 'Y', 0}, // KEY_Z
|
||||
{'x', 'X', 0}, // KEY_X
|
||||
{'c', 'C', 0}, // KEY_C
|
||||
{'v', 'V', 0}, // KEY_V
|
||||
{'b', 'B', 0}, // KEY_B
|
||||
{'n', 'N', 0}, // KEY_N
|
||||
{'m', 'M', 230}, // KEY_M
|
||||
{',', ';', 0}, // KEY_COMMA
|
||||
{'.', ':', 0}, // KEY_PERIOD
|
||||
{'-', '_', 0}, // KEY_SLASH
|
||||
{0, 0, 0}, // KEY_RIGHT_SHIFT
|
||||
{'*', '*', 0}, // KEY_KP_STAR
|
||||
{0, 0, 0}, // KEY_LEFT_ALT
|
||||
{' ', ' ', 0}, // KEY_SPACEBAR
|
||||
{0, 0, 0}, // KEY_CAPS_LOCK
|
||||
{0, 0, 0}, // KEY_F1
|
||||
{0, 0, 0}, // KEY_F2
|
||||
{0, 0, 0}, // KEY_F3
|
||||
{0, 0, 0}, // KEY_F4
|
||||
{0, 0, 0}, // KEY_F5
|
||||
{0, 0, 0}, // KEY_F6
|
||||
{0, 0, 0}, // KEY_F7
|
||||
{0, 0, 0}, // KEY_F8
|
||||
{0, 0, 0}, // KEY_F9
|
||||
{0, 0, 0}, // KEY_F10
|
||||
{0, 0, 0}, // KEY_NUM_LOCK
|
||||
{0, 0, 0}, // KEY_SCROLL_LOCK
|
||||
{0, '7', 0}, // KEY_KP_7
|
||||
{0, '8', 0}, // KEY_KP_8
|
||||
{0, '9', 0}, // KEY_KP_9
|
||||
{'-', '-', 0}, // KEY_KP_DASH
|
||||
{0, '4', 0}, // KEY_KP_4
|
||||
{0, '5', 0}, // KEY_KP_5
|
||||
{0, '6', 0}, // KEY_KP_6
|
||||
{'+', '+', 0}, // KEY_KP_PLUS
|
||||
{0, '1', 0}, // KEY_KP_1
|
||||
{0, '2', 0}, // KEY_KP_2
|
||||
{0, '3', 0}, // KEY_KP_3
|
||||
{0, '0', 0}, // KEY_KP_0
|
||||
{127, ',', 0}, // KEY_KP_PERIOD
|
||||
{0, 0, 0}, // KEY_SYSREQ
|
||||
{0, 0, 0}, // KEY_EUROPE_2
|
||||
{'<', '>', '|'}, // KEY_F11
|
||||
{0, 0, 0}, // KEY_F12
|
||||
{0, 0, 0}, // KEY_KP_EQUAL
|
||||
};
|
||||
|
||||
unsigned char Key::ascii() const {
|
||||
// Select the correct table depending on the modifier bits.
|
||||
// For the sake of simplicity, Shift and NumLock have precedence over Alt.
|
||||
// The Ctrl modifier does not have a distinct table.
|
||||
|
||||
if (!valid()) {
|
||||
return '\0';
|
||||
} else if (shift ||
|
||||
(caps_lock && ((scancode >= KEY_Q && scancode <= KEY_P) ||
|
||||
(scancode >= KEY_A && scancode <= KEY_L) ||
|
||||
(scancode >= KEY_Z && scancode <= KEY_M))) ||
|
||||
(num_lock && scancode >= KEY_KP_7 && scancode <= KEY_KP_PERIOD)) {
|
||||
return ascii_tab[scancode].shift;
|
||||
} else if (alt()) {
|
||||
return ascii_tab[scancode].alt;
|
||||
} else {
|
||||
return ascii_tab[scancode].normal;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,165 @@
|
||||
/*! \file
|
||||
* \brief \ref Key, an abstraction for handling pressed keys and their
|
||||
* modifiers
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "../types.h"
|
||||
|
||||
/*! \brief Class that abstracts a key, made up of the scan code and the modifier
|
||||
* bits.
|
||||
*/
|
||||
struct Key {
|
||||
/*! \brief The keys' scan codes (code 1)
|
||||
*/
|
||||
enum Scancode : uint8_t {
|
||||
// Invalid scan code
|
||||
KEY_INVALID = 0,
|
||||
|
||||
// "real" valid scan codes
|
||||
KEY_ESCAPE,
|
||||
KEY_1,
|
||||
KEY_2,
|
||||
KEY_3,
|
||||
KEY_4,
|
||||
KEY_5,
|
||||
KEY_6,
|
||||
KEY_7,
|
||||
KEY_8,
|
||||
KEY_9,
|
||||
KEY_0,
|
||||
KEY_DASH,
|
||||
KEY_EQUAL,
|
||||
KEY_BACKSPACE,
|
||||
KEY_TAB,
|
||||
KEY_Q,
|
||||
KEY_W,
|
||||
KEY_E,
|
||||
KEY_R,
|
||||
KEY_T,
|
||||
KEY_Y,
|
||||
KEY_U,
|
||||
KEY_I,
|
||||
KEY_O,
|
||||
KEY_P,
|
||||
KEY_OPEN_BRACKET,
|
||||
KEY_CLOSE_BRACKET,
|
||||
KEY_ENTER,
|
||||
KEY_LEFT_CTRL,
|
||||
KEY_A,
|
||||
KEY_S,
|
||||
KEY_D,
|
||||
KEY_F,
|
||||
KEY_G,
|
||||
KEY_H,
|
||||
KEY_J,
|
||||
KEY_K,
|
||||
KEY_L,
|
||||
KEY_SEMICOLON,
|
||||
KEY_APOSTROPH,
|
||||
KEY_GRAVE_ACCENT,
|
||||
KEY_LEFT_SHIFT,
|
||||
KEY_BACKSLASH,
|
||||
KEY_Z,
|
||||
KEY_X,
|
||||
KEY_C,
|
||||
KEY_V,
|
||||
KEY_B,
|
||||
KEY_N,
|
||||
KEY_M,
|
||||
KEY_COMMA,
|
||||
KEY_PERIOD,
|
||||
KEY_SLASH,
|
||||
KEY_RIGHT_SHIFT,
|
||||
KEY_KP_STAR,
|
||||
KEY_LEFT_ALT,
|
||||
KEY_SPACEBAR,
|
||||
KEY_CAPS_LOCK,
|
||||
KEY_F1,
|
||||
KEY_F2,
|
||||
KEY_F3,
|
||||
KEY_F4,
|
||||
KEY_F5,
|
||||
KEY_F6,
|
||||
KEY_F7,
|
||||
KEY_F8,
|
||||
KEY_F9,
|
||||
KEY_F10,
|
||||
KEY_NUM_LOCK,
|
||||
KEY_SCROLL_LOCK,
|
||||
KEY_KP_7,
|
||||
KEY_KP_8,
|
||||
KEY_KP_9,
|
||||
KEY_KP_DASH,
|
||||
KEY_KP_4,
|
||||
KEY_KP_5,
|
||||
KEY_KP_6,
|
||||
KEY_KP_PLUS,
|
||||
KEY_KP_1,
|
||||
KEY_KP_2,
|
||||
KEY_KP_3,
|
||||
KEY_KP_0,
|
||||
KEY_KP_PERIOD,
|
||||
KEY_SYSREQ,
|
||||
KEY_EUROPE_2,
|
||||
KEY_F11,
|
||||
KEY_F12,
|
||||
KEY_KP_EQUAL,
|
||||
|
||||
// Number of keys (excluding aliases below)
|
||||
KEYS,
|
||||
|
||||
// aliases
|
||||
KEY_DIV = KEY_7,
|
||||
KEY_DEL = KEY_KP_PERIOD,
|
||||
KEY_UP = KEY_KP_8,
|
||||
KEY_DOWN = KEY_KP_2,
|
||||
KEY_LEFT = KEY_KP_4,
|
||||
KEY_RIGHT = KEY_KP_6,
|
||||
};
|
||||
|
||||
Scancode scancode;
|
||||
|
||||
// bit masks for the modifier keys
|
||||
bool shift : 1, alt_left : 1, alt_right : 1, ctrl_left : 1, ctrl_right : 1,
|
||||
caps_lock : 1, num_lock : 1, scroll_lock : 1;
|
||||
|
||||
/*! \brief Default constructor: Instantiates an invalid key by setting ASCII,
|
||||
* scan code, and modifier bits to 0
|
||||
*/
|
||||
Key()
|
||||
: scancode(KEY_INVALID),
|
||||
shift(false),
|
||||
alt_left(false),
|
||||
alt_right(false),
|
||||
ctrl_left(false),
|
||||
ctrl_right(false),
|
||||
caps_lock(false),
|
||||
num_lock(false),
|
||||
scroll_lock(false) {}
|
||||
|
||||
/*! \brief Invalid keys have a scancode = 0
|
||||
* \return Checks whether a key is valid.
|
||||
*/
|
||||
bool valid() const { return scancode != KEY_INVALID && scancode < KEYS; }
|
||||
|
||||
/*! \brief Marks the key as invalid by setting the scan code to 0.
|
||||
*
|
||||
*/
|
||||
void invalidate() { scancode = KEY_INVALID; }
|
||||
|
||||
/*! \brief Get the key's ASCII value
|
||||
* \return the key's ASCII value
|
||||
*/
|
||||
unsigned char ascii() const;
|
||||
|
||||
/*! \brief Indicates whether the ALT modifier is set
|
||||
* \return `true` if ALT key was pressed during key press
|
||||
*/
|
||||
bool alt() const { return alt_left || alt_right; }
|
||||
|
||||
/*! \brief Indicates whether the CTRL modifier is set
|
||||
* \return `true` if CTRL key was pressed during key press
|
||||
*/
|
||||
bool ctrl() const { return ctrl_left || ctrl_right; }
|
||||
};
|
||||
@ -0,0 +1,181 @@
|
||||
#include "outputstream.h"
|
||||
|
||||
// operator <<: Converts the value in given data type to a string
|
||||
|
||||
// Print a single character (trivial)
|
||||
OutputStream& OutputStream::operator<<(char c) {
|
||||
put(c);
|
||||
return *this;
|
||||
}
|
||||
|
||||
OutputStream& OutputStream::operator<<(unsigned char c) {
|
||||
return *this << static_cast<char>(c);
|
||||
}
|
||||
|
||||
// Printing a null-terminated string
|
||||
OutputStream& OutputStream::operator<<(const char* string) {
|
||||
while ((*string) != '\0') {
|
||||
put(*string);
|
||||
string++;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
OutputStream& OutputStream::operator<<(bool b) {
|
||||
return *this << (b ? "true" : "false");
|
||||
}
|
||||
|
||||
// Print integral numbers in number system base.
|
||||
// All signed types are promoted to long long,
|
||||
// all unsigned types to unsigned long long.
|
||||
|
||||
OutputStream& OutputStream::operator<<(short ival) {
|
||||
return *this << static_cast<long long>(ival);
|
||||
}
|
||||
|
||||
OutputStream& OutputStream::operator<<(unsigned short ival) {
|
||||
return *this << static_cast<unsigned long long>(ival);
|
||||
}
|
||||
|
||||
OutputStream& OutputStream::operator<<(int ival) {
|
||||
return *this << static_cast<long long>(ival);
|
||||
}
|
||||
|
||||
OutputStream& OutputStream::operator<<(unsigned int ival) {
|
||||
return *this << static_cast<unsigned long long>(ival);
|
||||
}
|
||||
|
||||
OutputStream& OutputStream::operator<<(long ival) {
|
||||
return *this << static_cast<long long>(ival);
|
||||
}
|
||||
|
||||
OutputStream& OutputStream::operator<<(unsigned long ival) {
|
||||
return *this << static_cast<unsigned long long>(ival);
|
||||
}
|
||||
|
||||
// Print a signed , integral number.
|
||||
OutputStream& OutputStream::operator<<(long long ival) {
|
||||
/* Print '-' if number is negative
|
||||
*
|
||||
* In case ival is equal to LONG_LONG_MIN (0x8000000000000000), this
|
||||
* multiplication with -1 will overflow and, as for all signed overflows,
|
||||
* is not specified in C/C++. Thus, this operation will only work when
|
||||
* the system uses two's complement:
|
||||
* ~(0x8000000000000000) + 1 = 0x7fffffffffffffff + 1 = 0x8000000000000000
|
||||
*
|
||||
* When casting 0x8000000000000000 to unsigned long long, the value will
|
||||
* be (correctly) interpreted as -(LONG_LONG_MIN).
|
||||
*
|
||||
* A solution conforming (more) to the standard could be:
|
||||
* if ((ival < 0) && (base == 10)) {
|
||||
* put('-');
|
||||
* if (ival == LONG_LONG_MIN) {
|
||||
* return *this << static_cast<unsigned long long>(LONG_LONG_MAX -
|
||||
* (LONG_LONG_MAX + LONG_LONG_MIN)); } else { return *this <<
|
||||
* static_cast<unsigned long long>(-ival);
|
||||
* }
|
||||
* (However it introduces additional overhead)
|
||||
*/
|
||||
if ((ival < 0) && (base == 10)) {
|
||||
put('-');
|
||||
ival = -ival;
|
||||
}
|
||||
// Print the remaining positive number using the unsigned output
|
||||
return *this << static_cast<unsigned long long>(ival);
|
||||
}
|
||||
|
||||
// Print a unsigned, integral number.
|
||||
OutputStream& OutputStream::operator<<(unsigned long long ival) {
|
||||
if (base == 0) {
|
||||
base = 16;
|
||||
}
|
||||
|
||||
if (base == 2) {
|
||||
put('0');
|
||||
put('b');
|
||||
} else if (base == 8) {
|
||||
put('0'); // octal numbers are prefixed with 0
|
||||
} else if (base == 16) {
|
||||
put('0'); // hexadecimal numbers are prefixed with 0x
|
||||
put('x');
|
||||
}
|
||||
|
||||
// Determine the largest potency in the number system used, which is
|
||||
// still smaller than the number to be printed
|
||||
unsigned long long div;
|
||||
for (div = 1; ival / div >= static_cast<unsigned long long>(base);
|
||||
div *= base) {
|
||||
}
|
||||
|
||||
// print number char by char
|
||||
for (; div > 0; div /= static_cast<unsigned long long>(base)) {
|
||||
auto digit = ival / div;
|
||||
if (digit < 10) {
|
||||
put(static_cast<char>('0' + digit));
|
||||
} else {
|
||||
put(static_cast<char>('a' + digit - 10));
|
||||
}
|
||||
|
||||
ival %= div;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Print a pointer as hexadecimal number
|
||||
OutputStream& OutputStream::operator<<(const void* ptr) {
|
||||
int oldbase = base;
|
||||
base = 16;
|
||||
*this << reinterpret_cast<uintptr_t>(ptr);
|
||||
base = oldbase;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Calls one of the manipulator functions
|
||||
OutputStream& OutputStream::operator<<(OutputStream& (*f)(OutputStream&)) {
|
||||
return f(*this);
|
||||
}
|
||||
|
||||
/* STREAM MANIPULATORS
|
||||
*
|
||||
* The functions below take and return a reference to an OutputStream object
|
||||
* and are called by OutputStream& operator << (OutputStream& (*f)
|
||||
* (OutputStream&)); The purpose of theses manipulator functions is modifying
|
||||
* the behavior of the stream the are executed on, such as changing the number
|
||||
* system.
|
||||
*/
|
||||
|
||||
// flush: Explicit buffer flush
|
||||
OutputStream& flush(OutputStream& os) {
|
||||
os.flush();
|
||||
return os;
|
||||
}
|
||||
|
||||
// endl: Inserts a newline to the output
|
||||
OutputStream& endl(OutputStream& os) {
|
||||
os << '\n' << flush;
|
||||
return os;
|
||||
}
|
||||
|
||||
// bin: Selects the binary number system
|
||||
OutputStream& bin(OutputStream& os) {
|
||||
os.base = 2;
|
||||
return os;
|
||||
}
|
||||
|
||||
// oct: Selects the octal number system
|
||||
OutputStream& oct(OutputStream& os) {
|
||||
os.base = 8;
|
||||
return os;
|
||||
}
|
||||
|
||||
// dec: Selects the decimal number system
|
||||
OutputStream& dec(OutputStream& os) {
|
||||
os.base = 10;
|
||||
return os;
|
||||
}
|
||||
|
||||
// hex: Selects the hexadecimal number system
|
||||
OutputStream& hex(OutputStream& os) {
|
||||
os.base = 16;
|
||||
return os;
|
||||
}
|
||||
@ -0,0 +1,209 @@
|
||||
/*! \file
|
||||
* \brief This file contains the \ref OutputStream
|
||||
*
|
||||
* Along with the class OutputStream itself, this file contains definitions for
|
||||
* the manipulators \ref hex, \ref dec, \ref oct, and \ref bin, which are used
|
||||
* for changing the radix, and \ref endl for signaling the end of the current
|
||||
* line.
|
||||
* \ingroup io
|
||||
*
|
||||
* \par Manipulators
|
||||
* To simplify formatting text and numbers using the class OutputStream, we
|
||||
* define so-called manipulators. For example, the expression <tt>kout << "a = "
|
||||
* << dec << a << " is hexadecimal " << hex << a << endl;</tt> should, at first,
|
||||
* print the value stored in decimal and then in hexadecimal form, followed by a
|
||||
* line break. The intended properties can be realized by implementing \ref hex,
|
||||
* \ref dec, \ref oct, \ref bin, and \ref endl as functions (i.e., they are, in
|
||||
* particular, not methods of \ref OutputStream) that take (as first parameter)
|
||||
* and return a reference to an OutputStream object. When compiling the
|
||||
* expression shown above, the method <tt>OutputStream& OutputStream::operator<<
|
||||
* ((*f*) (OutputStream&))</tt> is chosen when one of the functions \ref hex,
|
||||
* \ref dec, \ref oct, \ref bin, or \ref endl is streamed into an \ref
|
||||
* OutputStream, which finally will execute the passed function.
|
||||
*
|
||||
* \note The term manipulator originates from the book
|
||||
* [The C++ Programming Language](http://www.stroustrup.com/4th.html)
|
||||
* by Bjarne Stroustrup. Refer to this book for further explanations.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "../types.h"
|
||||
#include "./stringbuffer.h"
|
||||
|
||||
/*! \brief The class OutputStream corresponds, essentially, to the class ostream
|
||||
* from the C++ IO-Stream library.
|
||||
*
|
||||
* As relying on the method \ref Stringbuffer::put() is quite cumbersome when
|
||||
* not only printing single characters, but numbers and whole strings, the
|
||||
* class OutputStream provides a convenient way of composing output of
|
||||
* variables of varying data types. Therefore, OutputStream implements shift
|
||||
* operators `operator<<`` for various data types (similar to those known from
|
||||
* the C++ IO-Stream library)
|
||||
*
|
||||
* For further convenience, OutputStream also allows printing integral numbers
|
||||
* in decimal, binary, octal, and hexadecimal format. Remember that, for
|
||||
* negative numbers, the sign is only printed when using the decimal number
|
||||
* system; for binary, octal, and hex, the number is printed as stored in the
|
||||
* machine word without interpreting the sign. For Intel CPUs, two's complement
|
||||
* is used for storing negative values, `-1`, for example, will print hex
|
||||
* `FFFFFFFF` and octal `37777777777`.
|
||||
*
|
||||
* OutputStream's public methods/operators all return a reference to the object
|
||||
* they are called on (i.e. `*this`). Returning `*this` allows chaining those
|
||||
* stream operators in a single expression, such as
|
||||
* <tt>kout << "a = " << a</tt>;
|
||||
*
|
||||
* At this point in time, OutputStream implements `operator<<`` for chars,
|
||||
* strings and whole numbers. An additional `operator<<` allows using
|
||||
* manipulators whose detailed description is given below.
|
||||
*/
|
||||
|
||||
class OutputStream : public Stringbuffer {
|
||||
OutputStream(const OutputStream&) = delete;
|
||||
OutputStream& operator=(const OutputStream&) = delete;
|
||||
|
||||
public:
|
||||
/*! \brief Number system used for printing integral numbers (one of 2,
|
||||
* 8, 10, or 16)
|
||||
*/
|
||||
int base;
|
||||
|
||||
/*! \brief Default constructor. Initial number system is decimal.
|
||||
*
|
||||
*/
|
||||
OutputStream() : base(10) {}
|
||||
|
||||
/*! \brief Destructor
|
||||
*/
|
||||
virtual ~OutputStream() {}
|
||||
|
||||
/*! \brief Clears the buffer.
|
||||
*
|
||||
* Pure virtual method that must be implemented by derived
|
||||
* (non-abstract) classes.
|
||||
* Formatting of the buffer contents can be implemented differently by
|
||||
* different derived classes
|
||||
*/
|
||||
virtual void flush() = 0;
|
||||
|
||||
/*! \brief Print a single character
|
||||
*
|
||||
* \param c Character to be printed
|
||||
* \return Reference to OutputStream os; allows operator chaining.
|
||||
*/
|
||||
OutputStream& operator<<(char c);
|
||||
|
||||
/*! \brief Print a single character
|
||||
* \note In C, there are no "characters" in that sense, but only
|
||||
* integers. A `char`, therefore, is a 8 bit number with the most
|
||||
* significant bit (optionally) representing a sign.
|
||||
* Depending on whether signed or not, the value ranges are [-128, 127]
|
||||
* or [0; 255]. For GCC, a `char` is a `signed char`.
|
||||
*
|
||||
* \param c Character to be printed
|
||||
* \return Reference to OutputStream os; allows operator chaining.
|
||||
*/
|
||||
OutputStream& operator<<(unsigned char c);
|
||||
|
||||
/*! \brief Printing a null-terminated string
|
||||
*
|
||||
* \param string String to be printed
|
||||
* \return Reference to OutputStream os; allows operator chaining.
|
||||
*/
|
||||
OutputStream& operator<<(const char* string);
|
||||
|
||||
/*! \brief Print a boolean value
|
||||
*
|
||||
* \param b Boolean to be printed
|
||||
* \return Reference to OutputStream os; allows operator chaining.
|
||||
*/
|
||||
OutputStream& operator<<(bool b);
|
||||
|
||||
/*! \brief Print an integral number in radix base
|
||||
*
|
||||
* \param ival Number to be printed
|
||||
* \return Reference to OutputStream os; allows operator chaining.
|
||||
*/
|
||||
OutputStream& operator<<(short ival);
|
||||
|
||||
/// \copydoc OutputStream::operator<<(short)
|
||||
OutputStream& operator<<(unsigned short ival);
|
||||
|
||||
/// \copydoc OutputStream::operator<<(short)
|
||||
OutputStream& operator<<(int ival);
|
||||
|
||||
/// \copydoc OutputStream::operator<<(short)
|
||||
OutputStream& operator<<(unsigned int ival);
|
||||
|
||||
/// \copydoc OutputStream::operator<<(short)
|
||||
OutputStream& operator<<(long ival);
|
||||
|
||||
/// \copydoc OutputStream::operator<<(short)
|
||||
OutputStream& operator<<(unsigned long ival);
|
||||
|
||||
/// \copydoc OutputStream::operator<<(short)
|
||||
OutputStream& operator<<(long long ival);
|
||||
|
||||
/// \copydoc OutputStream::operator<<(short)
|
||||
OutputStream& operator<<(unsigned long long ival);
|
||||
|
||||
/*! \brief Print a pointer as hexadecimal number
|
||||
*
|
||||
* \param ptr Pointer to be printed
|
||||
* \return Reference to OutputStream os; allows operator chaining.
|
||||
*/
|
||||
OutputStream& operator<<(const void* ptr);
|
||||
|
||||
/*! \brief Calls one of the manipulator functions.
|
||||
*
|
||||
* Method that calls the manipulator functions defined below, which
|
||||
* allow modifying the stream's behavior by, for instance, changing the
|
||||
* number system.
|
||||
*
|
||||
* \param f Manipulator function to be called
|
||||
* \return Reference to OutputStream os; allows operator chaining.
|
||||
*/
|
||||
OutputStream& operator<<(OutputStream& (*f)(OutputStream&));
|
||||
};
|
||||
|
||||
/*! \brief Enforces a buffer flush.
|
||||
*
|
||||
* \param os Reference to stream to be flushed.
|
||||
* \return Reference to OutputStream os; allows operator chaining.
|
||||
*/
|
||||
OutputStream& flush(OutputStream& os);
|
||||
|
||||
/*! \brief Prints a newline character to the stream and issues a buffer flush.
|
||||
*
|
||||
* \param os Reference to stream to be modified.
|
||||
* \return Reference to OutputStream os; allows operator chaining.
|
||||
*/
|
||||
OutputStream& endl(OutputStream& os);
|
||||
|
||||
/*! \brief Print subsequent numbers in binary form.
|
||||
*
|
||||
* \param os Reference to stream to be modified.
|
||||
* \return Reference to OutputStream os; allows operator chaining.
|
||||
*/
|
||||
OutputStream& bin(OutputStream& os);
|
||||
|
||||
/*! \brief Print subsequent numbers in octal form.
|
||||
*
|
||||
* \param os Reference to stream to be modified.
|
||||
* \return Reference to OutputStream os; allows operator chaining.
|
||||
*/
|
||||
OutputStream& oct(OutputStream& os);
|
||||
|
||||
/*! \brief Print subsequent numbers in decimal form.
|
||||
*
|
||||
* \param os Reference to stream to be modified.
|
||||
* \return Reference to OutputStream os; allows operator chaining.
|
||||
*/
|
||||
OutputStream& dec(OutputStream& os);
|
||||
|
||||
/*! \brief Print subsequent numbers in hex form.
|
||||
*
|
||||
* \param os Reference to stream to be modified.
|
||||
* \return Reference to OutputStream os; allows operator chaining.
|
||||
*/
|
||||
OutputStream& hex(OutputStream& os);
|
||||
@ -0,0 +1,293 @@
|
||||
/*! \file
|
||||
* \brief Templated \ref Queue for arbitrary objects.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "../arch/core.h"
|
||||
#include "../debug/assert.h"
|
||||
#include "../object/outputstream.h"
|
||||
#include "../types.h"
|
||||
|
||||
/*! \brief Templated Queue for arbitrary objects.
|
||||
*
|
||||
* Queue is implemented by a head-object (Queue<T>) and next-pointers embedded
|
||||
* in the queued objects. This Queue supports arrays of next-pointers by passing
|
||||
* an index into the constructor identifying the index into the next-pointer
|
||||
* array. By passing a different get_link function into the constructor, the
|
||||
* member name of the next-pointer array can be changed and objects can be
|
||||
* contained in different independent queues.
|
||||
*/
|
||||
template <class T>
|
||||
class Queue {
|
||||
/*! \brief Default get_link implementation returns a pointer to the
|
||||
* link_index'th element of the next-pointer array.
|
||||
* The function assumes a member named "queue_link" that stores the
|
||||
* next-pointer.
|
||||
*
|
||||
* If your object contains a queue_link member you can just ignore this
|
||||
* function and the get_link keyword argument of the constructor.
|
||||
*
|
||||
* \param[in] obj the object whose link should be accessed.
|
||||
* \param[in] link_index the index within the array.
|
||||
*
|
||||
* \return A pointer to the next-object pointer.
|
||||
*/
|
||||
static T** default_get_link(T& obj, unsigned link_index) {
|
||||
assert(link_index < sizeof(T::queue_link) / sizeof(void*));
|
||||
return &obj.queue_link[link_index];
|
||||
}
|
||||
/// Type definition for the get_link function
|
||||
typedef T** (*NextFunc)(T&, unsigned);
|
||||
|
||||
/// Queue-local index into the next-pointer array
|
||||
unsigned link_index;
|
||||
|
||||
/// Provides the same signature for single- and multi-core Queue
|
||||
T** get_link_wrapped(T& obj) { return get_link(obj, link_index); }
|
||||
|
||||
/// Function pointer to the get_link function, called whenever the
|
||||
/// next pointer array is referenced
|
||||
const NextFunc get_link;
|
||||
/// head points to the first element (the one returned on first dequeue).
|
||||
/// Can be nullptr if the queue is empty.
|
||||
T* head;
|
||||
/// tail points to the last element (the one last added).
|
||||
/// Is only valid if head != nullptr
|
||||
T* tail;
|
||||
|
||||
// Prevent copies and assignments
|
||||
Queue(const Queue&) = delete;
|
||||
Queue& operator=(const Queue&) = delete;
|
||||
|
||||
public:
|
||||
/*! \brief Minimal forward iterator
|
||||
* You can use this iterator to iterate the queue like a normal STL container.
|
||||
* It only supports forward iteration, since the queue is single linked.
|
||||
*/
|
||||
class Iterator {
|
||||
private:
|
||||
Queue<T>& queue;
|
||||
T* current;
|
||||
friend class Queue<T>;
|
||||
Iterator(Queue<T>& queue, T* current) : queue(queue), current(current) {}
|
||||
|
||||
public:
|
||||
Iterator operator+(unsigned num) {
|
||||
if (current == nullptr) {
|
||||
return *this;
|
||||
}
|
||||
T* temp = current;
|
||||
while (num--) {
|
||||
temp = queue.next(*temp);
|
||||
}
|
||||
return Iterator(queue, temp);
|
||||
}
|
||||
|
||||
// pre increment
|
||||
Iterator& operator++() {
|
||||
current = queue.next(*current);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// post increment
|
||||
Iterator operator++(int) {
|
||||
auto temp = Iterator(queue, current);
|
||||
current = queue.next(*current);
|
||||
return temp;
|
||||
}
|
||||
|
||||
T* operator*() { return current; }
|
||||
|
||||
bool operator==(const Iterator& other) { return current == other.current; }
|
||||
|
||||
bool operator!=(const Iterator& other) { return !(*this == other); }
|
||||
};
|
||||
|
||||
constexpr Queue(Queue&&) = default;
|
||||
|
||||
/*! \brief Constructor
|
||||
* \param[in] link_index denotes the index into the next-pointer array
|
||||
* to be used by this
|
||||
*queue-object
|
||||
* \param[in] get_link A function pointer to the get_link, i.e. a function
|
||||
* which returns a pointer to the
|
||||
*next-pointer of an element in the Queue.
|
||||
*/
|
||||
explicit Queue(unsigned link_index, NextFunc get_link = default_get_link)
|
||||
: link_index(link_index),
|
||||
get_link(get_link),
|
||||
head(nullptr),
|
||||
tail(nullptr) {}
|
||||
|
||||
/*! \brief Enqueues the provided item at the end of the queue. If the element
|
||||
* is already contained in the queue, false will be returned
|
||||
* \param[in] item element to be appended (enqueued).
|
||||
* \return false if the element already was enqueued (and nothing was done)
|
||||
* or not (and it is now enqueued, then true)
|
||||
*/
|
||||
bool enqueue(T& item) {
|
||||
T** nextptr = get_link_wrapped(item);
|
||||
if (*nextptr != nullptr || (head != nullptr && tail == &item)) {
|
||||
return false;
|
||||
}
|
||||
*nextptr = nullptr;
|
||||
|
||||
if (head == nullptr) {
|
||||
head = tail = &item;
|
||||
} else {
|
||||
assert(tail != nullptr);
|
||||
*get_link_wrapped(*tail) = &item;
|
||||
tail = &item;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/*! \brief insert a new element at the start of the queue
|
||||
* \param[in] item the new item to add
|
||||
* \return true if successful, false if item was already in the queue
|
||||
**/
|
||||
bool insertFirst(T& item) {
|
||||
T** nextptr = get_link_wrapped(item);
|
||||
if (*nextptr != nullptr || (head != nullptr && tail == &item)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (head == nullptr) {
|
||||
tail = &item;
|
||||
}
|
||||
*nextptr = head;
|
||||
head = &item;
|
||||
return true;
|
||||
}
|
||||
|
||||
/*! \brief Insert a new element item into the list after an element after.
|
||||
* Returns false if item is already in the/a list or after is not in this
|
||||
*list
|
||||
* \param[in] after the element after which the new one should be inserted
|
||||
* \param[in] item the new element to add
|
||||
* \return true if successful, false if item was in the list or after was not
|
||||
**/
|
||||
bool insertAfter(T& after, T& item) {
|
||||
// if queue is empty there is no after
|
||||
// and tail is not valid so we need to check head here
|
||||
if (head == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (&after == tail) {
|
||||
return enqueue(item);
|
||||
}
|
||||
T** nextptr = get_link_wrapped(item);
|
||||
// if item is already in the list return false
|
||||
if (*nextptr != nullptr || tail == &item) {
|
||||
return false;
|
||||
}
|
||||
|
||||
T** pnextptr = get_link_wrapped(after);
|
||||
// if after is NOT in the list, return false
|
||||
if (!(pnextptr != nullptr || tail == &after)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*nextptr = *pnextptr;
|
||||
*pnextptr = &item;
|
||||
return true;
|
||||
}
|
||||
|
||||
/*! \brief return the next element of a given one or nullptr if the end is
|
||||
*reached
|
||||
* \param[in] item the current item
|
||||
* \return the next element or nullptr if the end is reached or the item is
|
||||
*not in this list
|
||||
**/
|
||||
T* next(T& item) {
|
||||
T** nextptr = get_link_wrapped(item);
|
||||
// if item is already in the list return nullptr
|
||||
if (head == nullptr || (*nextptr == nullptr && tail != &item)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return *nextptr;
|
||||
}
|
||||
|
||||
/*! \brief Return whether or not the queue is empty
|
||||
* \return True if the queue is empty or false otherwise.
|
||||
*/
|
||||
bool is_empty() const { return (head == nullptr); }
|
||||
|
||||
/*! \brief Removes the first element in the queue and returns it.
|
||||
* \note Does not update the tail-pointer
|
||||
* \return Pointer to the removed item or `nullptr` if the queue was empty.
|
||||
*/
|
||||
T* dequeue() {
|
||||
T* out = head;
|
||||
if (head != nullptr) {
|
||||
T** nextptr = get_link_wrapped(*head);
|
||||
head = *nextptr;
|
||||
*nextptr = nullptr;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/*! \brief Removes a given element from the queue and returns that element,
|
||||
* or nullptr if it was not present
|
||||
* \return pointer to the removed element, or nullptr if not present
|
||||
*/
|
||||
T* remove(T* that) {
|
||||
if (!that) return nullptr;
|
||||
T* cur = head;
|
||||
T** next_link;
|
||||
|
||||
if (head == that) {
|
||||
head = *get_link_wrapped(*head);
|
||||
|
||||
*get_link_wrapped(*that) = nullptr;
|
||||
return that;
|
||||
}
|
||||
while (cur) {
|
||||
next_link = get_link_wrapped(*cur);
|
||||
if (*next_link == that) {
|
||||
*next_link = *get_link_wrapped(**next_link);
|
||||
|
||||
if (that == tail) {
|
||||
tail = cur;
|
||||
}
|
||||
|
||||
*get_link_wrapped(*that) = nullptr;
|
||||
return that;
|
||||
}
|
||||
cur = *next_link;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/// get an iterator to the first element
|
||||
Queue<T>::Iterator begin() { return Queue<T>::Iterator(*this, head); }
|
||||
|
||||
/// get an iterator that marks the end of list
|
||||
Queue<T>::Iterator end() { return Queue<T>::Iterator(*this, nullptr); }
|
||||
|
||||
/// get the first element of the queue
|
||||
T* first() { return head; }
|
||||
|
||||
/// get the last element of the queue
|
||||
T* last() { return (head == nullptr ? nullptr : tail); }
|
||||
};
|
||||
|
||||
/*! \brief Overload stream operator for list printing.
|
||||
*
|
||||
* With this a list can be printed. The elements itself are not printed, just
|
||||
* the pointer.
|
||||
*/
|
||||
template <class T>
|
||||
OutputStream& operator<<(OutputStream& os, Queue<T>& queue) {
|
||||
os << "{";
|
||||
for (typename Queue<T>::Iterator it = queue.begin(); it != queue.end();
|
||||
++it) {
|
||||
os << *it;
|
||||
if (it + 1 != queue.end()) {
|
||||
os << ", ";
|
||||
}
|
||||
}
|
||||
return os << "}";
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
#include "stringbuffer.h"
|
||||
|
||||
void Stringbuffer::put(char c) { (void)c; }
|
||||
@ -0,0 +1,71 @@
|
||||
/*! \file
|
||||
* \brief \ref Stringbuffer composes single characters into a buffer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "../types.h"
|
||||
|
||||
/*! \brief The class Stringbuffer composes single characters into a longer text
|
||||
* that can be processed on block.
|
||||
*
|
||||
* To make Stringbuffer as versatile as possible, the class does make
|
||||
* assumptions about neither the underlying hardware, nor the meaning of
|
||||
* "processing". When flush() is called (i.e., either on explicit request or
|
||||
* once the buffer is full). To be hardware independent, flush() is to be
|
||||
* implemented by the derived classes.
|
||||
*
|
||||
* \par Hints for Implementation
|
||||
* Use a buffer of fixed size for caching characters, which should be
|
||||
* accessible by derived classes.
|
||||
* Keep in mind that the derived implementation of flush() will need to know
|
||||
* about numbers of characters in the buffer.
|
||||
*
|
||||
* \par Notes
|
||||
* Reason for the existence of this class is that generating longer texts is
|
||||
* often implemented by assembly of small fragments (such as single characters
|
||||
* or numbers).
|
||||
* However, writing such small fragments directly to (for example) screen is
|
||||
* quite inefficient (e.g., due to the use of IO ports, syscalls, or locks) and
|
||||
* can be improved drastically by delaying the output step until the assembly
|
||||
* is finished (or the buffer runs full).
|
||||
*/
|
||||
class Stringbuffer {
|
||||
// Prevent copies and assignments
|
||||
Stringbuffer(const Stringbuffer&) = delete;
|
||||
Stringbuffer& operator=(const Stringbuffer&) = delete;
|
||||
|
||||
// All variables and methods are protected in this class,
|
||||
// as the derived classes need direct access to be buffer,
|
||||
// the constructor, the destructor, and the method put.
|
||||
// flush() is to be implemented either way and may be redefined
|
||||
// as public.
|
||||
|
||||
protected:
|
||||
/*! \brief Constructor; Marks the buffer as empty
|
||||
*/
|
||||
Stringbuffer() {}
|
||||
|
||||
/*! \brief Inserts a character into the buffer.
|
||||
*
|
||||
* Once the buffer is full, a call to flush() will be issued and
|
||||
* thereby clearing the buffer.
|
||||
*
|
||||
* \param c Char to be added
|
||||
*
|
||||
* \todo(11) Implement
|
||||
*/
|
||||
void put(char c);
|
||||
|
||||
/*! \brief Flush the buffer contents
|
||||
*
|
||||
* This method is to be defined in derived classes, as only those know
|
||||
* how to print characters.
|
||||
* flush() is required to reset the position pos.
|
||||
*/
|
||||
virtual void flush() = 0;
|
||||
|
||||
public:
|
||||
/*! \brief Destructor (nothing to do here)
|
||||
*/
|
||||
virtual ~Stringbuffer() {}
|
||||
};
|
||||
@ -0,0 +1,29 @@
|
||||
#include "bellringer.h"
|
||||
|
||||
#include "../interrupt/guard.h"
|
||||
#include "../thread/thread.h"
|
||||
|
||||
struct Bell {
|
||||
// link pointer to the next bell in the bellringers bell list
|
||||
Bell *queue_link[1] = {nullptr};
|
||||
|
||||
Thread *thread;
|
||||
size_t counter;
|
||||
};
|
||||
|
||||
Bell **Bellringer::bell_link(Bell &obj, unsigned link_index) {
|
||||
return &obj.queue_link[link_index];
|
||||
}
|
||||
|
||||
// check: Checks whether bells are running out of time and rings them if
|
||||
// necessary
|
||||
void Bellringer::check(Vault &vault) { (void)vault; }
|
||||
|
||||
// job: Give a bell to the bellringer & ring it when the specified time ran out.
|
||||
void Bellringer::sleep(Vault &vault, unsigned int ms) {
|
||||
(void)vault;
|
||||
(void)ms;
|
||||
}
|
||||
|
||||
// Are there bells in the queue?
|
||||
bool Bellringer::bellPending() const { return false; }
|
||||
@ -0,0 +1,69 @@
|
||||
/*! \file
|
||||
* \brief \ref Bellringer that manages and activates time-triggered activities.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "../object/queue.h"
|
||||
#include "../types.h"
|
||||
|
||||
struct Vault;
|
||||
struct Bell;
|
||||
|
||||
/*! \brief Manages and activates time-triggered activities.
|
||||
* \ingroup ipc
|
||||
*
|
||||
* The Bellringer is regularly activated and checks whether any of the bells
|
||||
* should ring. The bells are stored in a Queue<Bell> that is managed by the
|
||||
* Bellringer. A clever implementation avoids iterating through the whole list
|
||||
* for every iteration by keeping the bells sorted and storing delta times. This
|
||||
* approach leads to a complexity of O(1) for the method called by the timer
|
||||
* interrupt in case no bells need to be rung.
|
||||
*/
|
||||
class Bellringer {
|
||||
// Prevent copies and assignments
|
||||
Bellringer(const Bellringer&) = delete;
|
||||
Bellringer& operator=(const Bellringer&) = delete;
|
||||
|
||||
/*! \brief List of bells currently managed.
|
||||
*
|
||||
* This list contains non-expired bells enqueued by job().
|
||||
* These bells will be checked on every call to check().
|
||||
*
|
||||
* All elements that should be inserted into a Queue instance
|
||||
* are required to be derived from Queue<Bell>::Node.
|
||||
*/
|
||||
Queue<Bell> bells;
|
||||
|
||||
//! Link pointer for bells
|
||||
static Bell** bell_link(Bell& obj, unsigned link_index);
|
||||
|
||||
public:
|
||||
// constructor
|
||||
Bellringer() : bells(0, bell_link) {}
|
||||
|
||||
/*! \brief Checks whether there are bells to be rung.
|
||||
*
|
||||
* Every call to check elapses a tick. Once such a tick reduces a bells
|
||||
* remaining time to zero, the bell will be rung.
|
||||
*
|
||||
* \todo(16) Implement Method
|
||||
*/
|
||||
void check(Vault& vault);
|
||||
|
||||
/*! \brief Passes a `bell` to the bellringer to be rung after `ms`
|
||||
* milliseconds.
|
||||
* \param bell Bell that should be rung after `ms` milliseconds
|
||||
* \param ms number of milliseconds that should be waited before
|
||||
* ringing the bell
|
||||
*
|
||||
* \todo(16) Implement Method
|
||||
*/
|
||||
void sleep(Vault& vault, unsigned int ms);
|
||||
|
||||
/*! \brief Checks whether there are enqueued bells.
|
||||
* \return true if there are enqueued bells, false otherwise
|
||||
*
|
||||
* \todo(16) Implement Method
|
||||
*/
|
||||
bool bellPending() const;
|
||||
};
|
||||
@ -0,0 +1,16 @@
|
||||
#include "./semaphore.h"
|
||||
|
||||
#include "../interrupt/guard.h"
|
||||
#include "../thread/thread.h"
|
||||
|
||||
Semaphore::Semaphore(unsigned c) { (void)c; }
|
||||
|
||||
Thread **Semaphore::thread_link(Thread &obj, unsigned link_index) {
|
||||
(void)obj;
|
||||
(void)link_index;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Semaphore::p(Vault &vault) { (void)vault; }
|
||||
|
||||
void Semaphore::v(Vault &vault) { (void)vault; }
|
||||
@ -0,0 +1,57 @@
|
||||
#pragma once
|
||||
#include "../object/queue.h"
|
||||
#include "../types.h"
|
||||
|
||||
/*! \file
|
||||
* \brief \ref Semaphore for synchronization of threads.
|
||||
*/
|
||||
|
||||
/*!
|
||||
* \defgroup ipc Inter-Process Communication
|
||||
* \brief Communication between threads
|
||||
*/
|
||||
|
||||
// Forward declarations to break cyclic includes
|
||||
struct Vault;
|
||||
class Thread;
|
||||
|
||||
/*! \brief Semaphore used for synchronization of threads.
|
||||
* \ingroup ipc
|
||||
*
|
||||
* The class Semaphore implements the concept of counting semaphores.
|
||||
* The waiting list is provided by the base class Waitingroom.
|
||||
*/
|
||||
class Semaphore {
|
||||
// Prevent copies and assignments
|
||||
Semaphore(const Semaphore&) = delete;
|
||||
Semaphore& operator=(const Semaphore&) = delete;
|
||||
|
||||
static Thread** thread_link(Thread& obj, unsigned link_index);
|
||||
|
||||
public:
|
||||
/*! \brief Constructor; initialized the counter with provided value `c`
|
||||
* \param c Initial counter value
|
||||
*
|
||||
* \todo(16) Implement Constructor
|
||||
*/
|
||||
explicit Semaphore(unsigned c = 0);
|
||||
|
||||
/*! \brief Wait for access to the critical area.
|
||||
*
|
||||
* Enter/Wait operation: If the counter is greater than 0, then it is
|
||||
* decremented by one. Otherwise the calling thread will be enqueued
|
||||
* into the Waitingroom and marked as blocked.
|
||||
*
|
||||
* \todo(16) Implement Method
|
||||
*/
|
||||
void p(Vault& vault);
|
||||
|
||||
/*! \brief Leave the critical area.
|
||||
*
|
||||
* Leave operation: If there are threads in the Waitingroom, wake the
|
||||
* first one; otherwise increment the counter by one.
|
||||
*
|
||||
* \todo(16) Implement Method
|
||||
*/
|
||||
void v(Vault& vault);
|
||||
};
|
||||
@ -0,0 +1,60 @@
|
||||
/*! \file
|
||||
* \brief Contains the class Spinlock
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "../types.h"
|
||||
|
||||
/*! \brief Using Spinlocks, it is possible to serialize blocks of code
|
||||
* that might otherwise run in parallel on multiple CPU cores,
|
||||
* or be interleaved due to interrupts or scheduling.
|
||||
*
|
||||
* \ingroup sync
|
||||
*
|
||||
* Synchronization is implemented using a lock variable. Once a thread enters
|
||||
* the critical area, it sets the lock variable (to a non-zero value); when
|
||||
* this thread leaves the critical area, it resets the lock variable to zero.
|
||||
* Threads trying to enter an already locked critical area, actively wait,
|
||||
* continuously checking until the critical area is free again.
|
||||
*
|
||||
* Use the following two GCC intrinsics
|
||||
* - `bool __atomic_test_and_set(void *ptr, int memorder)`
|
||||
* - `void __atomic_clear (bool *ptr, int memorder)`
|
||||
*
|
||||
* These intrinsics are translated into atomic, architecture-specific
|
||||
* CPU instructions.
|
||||
*
|
||||
* If you want that things just work, choose __ATOMIC_SEQ_CST as memorder.
|
||||
* This is not the most efficient memory order but works reasonably well.
|
||||
*
|
||||
* <a
|
||||
* href="https://gcc.gnu.org/onlinedocs/gcc/_005f_005fatomic-Builtins.html">Atomic
|
||||
* Builtins in GCC manual</a>
|
||||
*/
|
||||
class Spinlock {
|
||||
// Prevent copies and assignments
|
||||
Spinlock(const Spinlock& copy) = delete;
|
||||
Spinlock& operator=(const Spinlock&) = delete;
|
||||
|
||||
public:
|
||||
/*! \brief Constructor; Initializes as unlocked.
|
||||
*
|
||||
* \todo(12) Complete Constructor (for \MPStuBS, or use \ref Ticketlock)
|
||||
*
|
||||
*/
|
||||
consteval Spinlock() {}
|
||||
|
||||
/*! \brief Enters the critical area. In case the area is already locked,
|
||||
* \ref lock() will actively wait until the area can be entered.
|
||||
*
|
||||
* \see \ref Core::pause()
|
||||
* \todo(12) Implement Method (for \MPStuBS, or use \ref Ticketlock)
|
||||
*/
|
||||
void lock() {}
|
||||
|
||||
/*! \brief Unblocks the critical area.
|
||||
*
|
||||
* \todo(12) Implement Method (for \MPStuBS, or use \ref Ticketlock)
|
||||
*/
|
||||
void unlock() {}
|
||||
};
|
||||
@ -0,0 +1,52 @@
|
||||
/*! \file
|
||||
* \brief Contains the class Ticketlock
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
/*! \brief Using Ticketlocks, it is possible to serialize blocks of code
|
||||
* that might otherwise run in parallel on multiple CPU cores,
|
||||
* or be interleaved due to interrupts or scheduling.
|
||||
*
|
||||
* \ingroup sync
|
||||
*
|
||||
* Synchronization is implemented using a lock and a ticket variable.
|
||||
* Once a thread tries to enter the critical area, it obtains a ticket by
|
||||
* atomically incrementing the ticket variable and waiting until the lock
|
||||
* counter reaches this ticket, if it is not there already.
|
||||
* When a thread leaves the critical area, it increments the lock variable by
|
||||
* one and thereby allows the next thread to enter the critical area.
|
||||
*
|
||||
* If you want that things just work, choose __ATOMIC_SEQ_CST as memorder.
|
||||
* This is not the most efficient memory order but works reasonably well.
|
||||
*
|
||||
* <a
|
||||
* href="https://gcc.gnu.org/onlinedocs/gcc/_005f_005fatomic-Builtins.html">Atomic
|
||||
* Builtins in GCC manual</a>
|
||||
*/
|
||||
class Ticketlock {
|
||||
// Prevent copies and assignments
|
||||
Ticketlock(const Ticketlock& copy) = delete;
|
||||
Ticketlock& operator=(const Ticketlock&) = delete;
|
||||
|
||||
public:
|
||||
/*! \brief Constructor
|
||||
*
|
||||
* \todo(12) Complete Constructor (for \MPStuBS)
|
||||
*/
|
||||
consteval Ticketlock() {}
|
||||
|
||||
/*! \brief Enters the critical area. In case the area is already locked,
|
||||
* \ref lock() will actively wait until the area can be entered.
|
||||
*
|
||||
* \see \ref Core::pause()
|
||||
* \todo(12) Implement Method (for \MPStuBS)
|
||||
*/
|
||||
void lock() {}
|
||||
|
||||
/*! \brief Unblocks the critical area.
|
||||
*
|
||||
* \todo(12) Implement Method (for \MPStuBS)
|
||||
*/
|
||||
void unlock() {}
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue