Handout
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
.build*
|
||||||
|
/build*
|
||||||
16
CPPLINT.cfg
Normal file
16
CPPLINT.cfg
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# StuBS Coding Style Checker
|
||||||
|
#
|
||||||
|
# Wir orientieren uns grob an den Google C++ Style Guide ( http://google.github.io/styleguide/cppguide.html )
|
||||||
|
# mit primär folgenden Änderungen/Anpassungen:
|
||||||
|
#
|
||||||
|
# - Tabs statt Leerzeichen. Spart Bytes ;)
|
||||||
|
# - Zeilenlänge ist 120
|
||||||
|
# - Keine Angaben zum Copyright
|
||||||
|
# - Aufgrund des Aufgabenbuildsystems sind neue / leere Zeilen leider nicht immer vermeidbar
|
||||||
|
#
|
||||||
|
# Zum Prüfen empfiehlt sich beispielsweise das Pythonscript CPPLINT.py ( https://github.com/cpplint/cpplint )
|
||||||
|
# welches mit dieser Konfigurationsdatei arbeiten kann.
|
||||||
|
#
|
||||||
|
set noparent
|
||||||
|
filter=-whitespace/tab,-legal/copyright,-runtime/int,-runtime/threadsafe_fn,-readability/todo,-build/include_subdir,-runtime/references,-build/include_what_you_use,-whitespace/blank_line,-build/include,-whitespace/end_of_line,-whitespace/indent
|
||||||
|
linelength=120
|
||||||
13
LICENSE
Normal file
13
LICENSE
Normal file
@@ -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.
|
||||||
62
Makefile
Normal file
62
Makefile
Normal file
@@ -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)" $@
|
||||||
42
README.md
Normal file
42
README.md
Normal file
@@ -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
|
||||||
144
arch/acpi.cc
Normal file
144
arch/acpi.cc
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
#include "acpi.h"
|
||||||
|
|
||||||
|
#include "../debug/output.h"
|
||||||
|
|
||||||
|
#pragma GCC diagnostic push
|
||||||
|
#pragma GCC diagnostic ignored "-Warray-bounds"
|
||||||
|
|
||||||
|
namespace ACPI {
|
||||||
|
|
||||||
|
static RSDP *rsdp = 0;
|
||||||
|
static RSDT *rsdt = 0;
|
||||||
|
static XSDT *xsdt = 0;
|
||||||
|
|
||||||
|
const char *RSDP_SIGNATURE = "RSD PTR ";
|
||||||
|
|
||||||
|
static int checksum(const void *pos, unsigned len) {
|
||||||
|
const uint8_t *mem = reinterpret_cast<const uint8_t *>(pos);
|
||||||
|
uint8_t sum = 0;
|
||||||
|
for (unsigned i = 0; i < len; i++) {
|
||||||
|
sum += mem[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const RSDP *findRSDP(const void *pos, unsigned len) {
|
||||||
|
/* since the RSDP is 16-Byte aligned, we only need to check
|
||||||
|
every second 64bit memory block */
|
||||||
|
for (unsigned block = 0; block < len / 8; block += 2) {
|
||||||
|
const uint64_t *mem = reinterpret_cast<const uint64_t *>(pos) + block;
|
||||||
|
if (*mem == *reinterpret_cast<const uint64_t *>(RSDP_SIGNATURE)) {
|
||||||
|
const RSDP *rsdp = reinterpret_cast<const RSDP *>(mem);
|
||||||
|
/* ACPI Specification Revision 4.0a: 5.2.5.3*/
|
||||||
|
if ((rsdp->revision == 0 && checksum(mem, 20) == 0) ||
|
||||||
|
(rsdp->length > 20 && checksum(mem, rsdp->length) == 0)) {
|
||||||
|
return rsdp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool init() {
|
||||||
|
/* ACPI Specification Revision 4.0a:
|
||||||
|
* 5.2.5.1 Finding the RSDP on IA-PC Systems
|
||||||
|
* OSPM finds the Root System Description Pointer (RSDP) structure by
|
||||||
|
* searching physical memory ranges on 16-byte boundaries for a valid
|
||||||
|
* Root System Description Pointer structure signature and checksum
|
||||||
|
* match as follows:
|
||||||
|
* * The first 1 KB of the Extended BIOS Data Area (EBDA). For EISA or
|
||||||
|
* MCA systems, the EBDA can be found in the two-byte location 40:0Eh
|
||||||
|
* on the BIOS data area.
|
||||||
|
* * The BIOS read-only memory space between 0E0000h and 0FFFFFh.
|
||||||
|
*/
|
||||||
|
const uintptr_t ebda =
|
||||||
|
static_cast<uintptr_t>(*reinterpret_cast<uint32_t *>(0x40e));
|
||||||
|
const RSDP *rsdp = findRSDP(reinterpret_cast<void *>(ebda), 1024);
|
||||||
|
if (rsdp == nullptr) {
|
||||||
|
rsdp = findRSDP(reinterpret_cast<void *>(0xe0000), 0xfffff - 0xe0000);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rsdp == nullptr) {
|
||||||
|
DBG_VERBOSE << "No ACPI!" << endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
rsdt = reinterpret_cast<RSDT *>(static_cast<uintptr_t>(rsdp->rsdtaddress));
|
||||||
|
|
||||||
|
/* If the XSDT is present we must use it; see:
|
||||||
|
* ACPI Specification Revision 4.0a:
|
||||||
|
* "An ACPI-compatible OS must use the XSDT if present."
|
||||||
|
*/
|
||||||
|
if (rsdp->revision != 0 && rsdp->length >= 36) {
|
||||||
|
xsdt = reinterpret_cast<XSDT *>(rsdp->xsdtaddress);
|
||||||
|
}
|
||||||
|
DBG_VERBOSE << "ACPI revision " << rsdp->revision << endl;
|
||||||
|
for (unsigned i = 0; i != count(); ++i) {
|
||||||
|
SDTH *sdt = get(i);
|
||||||
|
if (sdt != nullptr) {
|
||||||
|
char *c = reinterpret_cast<char *>(&sdt->signature);
|
||||||
|
DBG_VERBOSE << i << ". " << c[0] << c[1] << c[2] << c[3] << " @ "
|
||||||
|
<< reinterpret_cast<void *>(sdt) << endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned count() {
|
||||||
|
if (xsdt != nullptr) {
|
||||||
|
return (xsdt->length - 36) / 8;
|
||||||
|
} else if (rsdt != nullptr) {
|
||||||
|
return (rsdt->length - 36) / 4;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SDTH *get(unsigned num) {
|
||||||
|
if (xsdt != nullptr) {
|
||||||
|
SDTH *entry = reinterpret_cast<SDTH *>(xsdt->entries[num]);
|
||||||
|
if (checksum(entry, entry->length) == 0) {
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
} else if (rsdt != nullptr) {
|
||||||
|
SDTH *entry =
|
||||||
|
reinterpret_cast<SDTH *>(static_cast<uintptr_t>(rsdt->entries[num]));
|
||||||
|
if (checksum(entry, entry->length) == 0) {
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDTH *get(char a, char b, char c, char d) {
|
||||||
|
union {
|
||||||
|
char signature[4];
|
||||||
|
uint32_t value;
|
||||||
|
};
|
||||||
|
signature[0] = a;
|
||||||
|
signature[1] = b;
|
||||||
|
signature[2] = c;
|
||||||
|
signature[3] = d;
|
||||||
|
|
||||||
|
if (xsdt != nullptr) {
|
||||||
|
for (unsigned i = 0; i < count(); i++) {
|
||||||
|
SDTH *entry = reinterpret_cast<SDTH *>(xsdt->entries[i]);
|
||||||
|
if (entry->signature == value && checksum(entry, entry->length) == 0) {
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (rsdt != nullptr) {
|
||||||
|
for (unsigned i = 0; i < count(); i++) {
|
||||||
|
SDTH *entry =
|
||||||
|
reinterpret_cast<SDTH *>(static_cast<uintptr_t>(rsdt->entries[i]));
|
||||||
|
if (entry->signature == value && checksum(entry, entry->length) == 0) {
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int revision() { return rsdp != nullptr ? rsdp->revision : -1; }
|
||||||
|
|
||||||
|
} // namespace ACPI
|
||||||
270
arch/acpi.h
Normal file
270
arch/acpi.h
Normal file
@@ -0,0 +1,270 @@
|
|||||||
|
/*! \file
|
||||||
|
* \brief Structs and methods related to the \ref ACPI "Advanced Configuration
|
||||||
|
* and Power Interface (ACPI)""
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "../types.h"
|
||||||
|
|
||||||
|
/*! \brief Abstracts the ACPI standard that provides interfaces for hardware
|
||||||
|
* detection, device configuration, and energy management.
|
||||||
|
* \ingroup io
|
||||||
|
*
|
||||||
|
* ACPI is the successor to APM (Advanced Power Management), aiming to give the
|
||||||
|
* operating system more control over the hardware. This extended control, for
|
||||||
|
* instance, enables the operating system to assign a particular amount of
|
||||||
|
* energy to every device (e.g., by disabling a device or changing to standby
|
||||||
|
* mode). For this purpose, BIOS and chipset provide a set of tables that
|
||||||
|
* describe the system and its components and provide routines the OS can call.
|
||||||
|
* These tables contain details about the system, such as the number of CPU
|
||||||
|
* cores and the LAPIC/IOAPIC, which are determined during system boot.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace ACPI {
|
||||||
|
|
||||||
|
/*! \brief Root System Description Pointer (RSDP)
|
||||||
|
*
|
||||||
|
* The first step to using ACPI is finding the RSDP that is used to find the
|
||||||
|
* RSDT / XSDT, which themselves contain pointers to even more tables.
|
||||||
|
*
|
||||||
|
* On UEFI systems, the RSDP can be found in the EFI_SYSTEM_TABLE; for non-UEFI
|
||||||
|
* systems we have to search for the signature 'RSD PTR ' in the EBDA (Extended
|
||||||
|
* Bios Data Area) or in the memory area up to `FFFFFh`.
|
||||||
|
*
|
||||||
|
* \see [ACPI-Specification 5.2.5.3; Root System Description Pointer (RSDP)
|
||||||
|
* Structure](acpi.pdf#page=161)
|
||||||
|
*/
|
||||||
|
|
||||||
|
struct RSDP {
|
||||||
|
char signature[8]; /* must exactly be equal to 'RSD PTR ' */
|
||||||
|
uint8_t checksum;
|
||||||
|
char oemid[6];
|
||||||
|
uint8_t revision; /* specifies the ACPI version */
|
||||||
|
uint32_t rsdtaddress; /* physical address of the RSDT */
|
||||||
|
uint32_t length;
|
||||||
|
uint64_t xsdtaddress; /* physical address of the XSDT */
|
||||||
|
uint8_t extended_checksum;
|
||||||
|
uint8_t reserved[3];
|
||||||
|
} __attribute__((packed));
|
||||||
|
|
||||||
|
/*! \brief System Description Table Header (SDTH)
|
||||||
|
*
|
||||||
|
* All System Description Tables (e.g., the RSDT) contain the same entries at
|
||||||
|
* the very beginning of the structure, which are abstracted in the SDTH.
|
||||||
|
*
|
||||||
|
* \see [ACPI-Specification 5.2.6; System Description Table
|
||||||
|
* Header](acpi.pdf#page=162)
|
||||||
|
*/
|
||||||
|
struct SDTH {
|
||||||
|
uint32_t signature; /* table id */
|
||||||
|
uint32_t length;
|
||||||
|
uint8_t revision;
|
||||||
|
uint8_t checksum;
|
||||||
|
char oemid[6];
|
||||||
|
char oem_table_id[8];
|
||||||
|
uint32_t oem_revision;
|
||||||
|
uint32_t creator_id;
|
||||||
|
uint32_t creator_revision;
|
||||||
|
|
||||||
|
/* \brief Helper method
|
||||||
|
* \return Pointer to the end of the table
|
||||||
|
*/
|
||||||
|
void *end() { return reinterpret_cast<uint8_t *>(this) + length; }
|
||||||
|
} __attribute__((packed));
|
||||||
|
|
||||||
|
/*! \brief Root System Description Table (RSDT)
|
||||||
|
*
|
||||||
|
* The RSDT can be found in the RSDP. The RSDT contains physical addresses of
|
||||||
|
* all other System Description Tables, for example the MADT.
|
||||||
|
*
|
||||||
|
* \see [ACPI-Specification 5.2.7; Root System Description Table
|
||||||
|
* (RSDT)](acpi.pdf#page=167)
|
||||||
|
*/
|
||||||
|
|
||||||
|
struct RSDT : SDTH {
|
||||||
|
uint32_t entries[];
|
||||||
|
} __attribute__((packed));
|
||||||
|
|
||||||
|
/*! \brief Extended System Description Table (XSDT)
|
||||||
|
*
|
||||||
|
* Like RSDT, but contains 64-bit instead of 32-bit addresses.
|
||||||
|
*
|
||||||
|
* \see [ACPI-Specification 5.2.8; Extended System Description Table
|
||||||
|
* (XSDT)](acpi.pdf#page=168)
|
||||||
|
*/
|
||||||
|
|
||||||
|
struct XSDT : SDTH {
|
||||||
|
uint64_t entries[];
|
||||||
|
} __attribute__((packed));
|
||||||
|
|
||||||
|
/*! \brief Helper structure
|
||||||
|
*
|
||||||
|
* Is used for accessing the substructures present in SRAT / MADT.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
struct SubHeader {
|
||||||
|
uint8_t type;
|
||||||
|
uint8_t length;
|
||||||
|
|
||||||
|
/* Method to traverse multiple substructures */
|
||||||
|
SubHeader *next() {
|
||||||
|
return reinterpret_cast<SubHeader *>(reinterpret_cast<uint8_t *>(this) +
|
||||||
|
length);
|
||||||
|
}
|
||||||
|
} __attribute__((packed));
|
||||||
|
|
||||||
|
/*! \brief Multiple APIC Description Table (MADT)
|
||||||
|
*
|
||||||
|
* Describes all interrupt controllers present within the system. Is used to
|
||||||
|
* obtain the IDs of the APICs, along with the number of available processor
|
||||||
|
* cores.
|
||||||
|
*
|
||||||
|
* \see [ACPI-Specification 5.2.12; Multiple APIC Description Table
|
||||||
|
* (MADT)](acpi.pdf#page=193)
|
||||||
|
*/
|
||||||
|
struct MADT : SDTH {
|
||||||
|
uint32_t local_apic_address;
|
||||||
|
uint32_t flags_pcat_compat : 1, flags_reserved : 31;
|
||||||
|
|
||||||
|
/* method to access the first subheader */
|
||||||
|
SubHeader *first() {
|
||||||
|
return reinterpret_cast<SubHeader *>(reinterpret_cast<uint8_t *>(this) +
|
||||||
|
sizeof(MADT));
|
||||||
|
}
|
||||||
|
} __attribute__((packed));
|
||||||
|
|
||||||
|
enum class AddressSpace : uint8_t {
|
||||||
|
MEMORY = 0x0,
|
||||||
|
IO = 0x1,
|
||||||
|
};
|
||||||
|
|
||||||
|
/*! \brief ACPI address format
|
||||||
|
*
|
||||||
|
* The ACPI standard defines its own address format that is able to handle
|
||||||
|
* addresses both in memory address space, as well as IO-port address space.
|
||||||
|
*/
|
||||||
|
struct Address {
|
||||||
|
AddressSpace address_space;
|
||||||
|
uint8_t register_bit_width;
|
||||||
|
uint8_t register_bit_offset;
|
||||||
|
uint8_t reserved;
|
||||||
|
uint64_t address;
|
||||||
|
} __attribute__((packed));
|
||||||
|
|
||||||
|
// Multiple APIC Definition Structure
|
||||||
|
namespace MADS {
|
||||||
|
enum Type {
|
||||||
|
Type_LAPIC = 0,
|
||||||
|
Type_IOAPIC = 1,
|
||||||
|
Type_Interrupt_Source_Override = 2,
|
||||||
|
Type_LAPIC_Address_Override = 5,
|
||||||
|
};
|
||||||
|
|
||||||
|
/*! \brief Processor Local APIC (LAPIC) Structure
|
||||||
|
*
|
||||||
|
* Represents a physical processor along with its local interrupt controller.
|
||||||
|
* The MADT contains a LAPIC structure for every processor available in the
|
||||||
|
* system.
|
||||||
|
*
|
||||||
|
* \see [ACPI-Specification 5.2.12.2; Processor Local APIC
|
||||||
|
* Structure](acpi.pdf#page=195)
|
||||||
|
*/
|
||||||
|
struct LAPIC : SubHeader {
|
||||||
|
uint8_t acpi_processor_id;
|
||||||
|
uint8_t apic_id;
|
||||||
|
uint32_t flags_enabled : 1, flags_reserved : 31; /* must be 0 */
|
||||||
|
} __attribute__((packed));
|
||||||
|
|
||||||
|
/*! \brief I/O APIC Structure
|
||||||
|
*
|
||||||
|
* Represents an I/O-APIC.
|
||||||
|
* The MADT contains an IOAPIC structure for every I/O APIC present in the
|
||||||
|
* system.
|
||||||
|
*
|
||||||
|
* \see [ACPI-Specification 5.2.12.3; I/O APIC Structure](acpi.pdf#page=196)
|
||||||
|
*/
|
||||||
|
|
||||||
|
struct IOAPIC : SubHeader {
|
||||||
|
uint8_t ioapic_id;
|
||||||
|
uint8_t reserved;
|
||||||
|
uint32_t ioapic_address;
|
||||||
|
uint32_t global_system_interrupt_base;
|
||||||
|
} __attribute__((packed));
|
||||||
|
|
||||||
|
/*! \brief Interrupt Source Override Structure
|
||||||
|
*
|
||||||
|
* Is required to describe differences between the IA-PC standard interrupt
|
||||||
|
* definition and the actual hardware implementation.
|
||||||
|
*
|
||||||
|
* \see [ACPI-Specification 5.2.12.5; Interrupt Source Override
|
||||||
|
* Structure](acpi.pdf#page=197)
|
||||||
|
*/
|
||||||
|
struct Interrupt_Source_Override : SubHeader {
|
||||||
|
uint8_t bus;
|
||||||
|
uint8_t source;
|
||||||
|
uint32_t global_system_interrupt;
|
||||||
|
uint16_t flags_polarity : 2, flags_trigger_mode : 2,
|
||||||
|
flags_reserved : 12; /* must be 0 */
|
||||||
|
} __attribute__((packed));
|
||||||
|
|
||||||
|
/*! \brief Local APIC Address Override Structure
|
||||||
|
*
|
||||||
|
* Support for 64-bit systems is achieved by replacing the 32-bit physical LAPIC
|
||||||
|
* address stored in the MADT with the corresponding 64-bit address.
|
||||||
|
*
|
||||||
|
* \see [ACPI-Specification 5.2.12.8; Local APIC Address Override
|
||||||
|
* Structure](acpi.pdf#page=199)
|
||||||
|
*/
|
||||||
|
|
||||||
|
struct LAPIC_Address_Override : SubHeader {
|
||||||
|
uint16_t reserved;
|
||||||
|
union {
|
||||||
|
uint64_t lapic_address;
|
||||||
|
struct {
|
||||||
|
uint32_t lapic_address_low;
|
||||||
|
uint32_t lapic_address_high;
|
||||||
|
} __attribute__((packed));
|
||||||
|
};
|
||||||
|
} __attribute__((packed));
|
||||||
|
|
||||||
|
} // namespace MADS
|
||||||
|
|
||||||
|
/*! \brief Initialize the ACPI description table
|
||||||
|
*
|
||||||
|
* Searches physical memory ranges o 16-byte boundaries for a valid Root System
|
||||||
|
* Description Pointer (RSDP) structure signature and checksum. If present, the
|
||||||
|
* superseding Extended System Description Table (XSDT) is used.
|
||||||
|
*
|
||||||
|
* \see [ACPI-Specification 5.2.5 Root System Description Pointer
|
||||||
|
* (RSDP)](acpi.pdf#page=160)
|
||||||
|
* \see [ACPI-Specification 5.2.8 Extended System Description Table
|
||||||
|
* (XSDT)](acpi.pdf#page=168)
|
||||||
|
*/
|
||||||
|
bool init();
|
||||||
|
|
||||||
|
/*! \brief Number of entries in the description table
|
||||||
|
*/
|
||||||
|
unsigned count();
|
||||||
|
|
||||||
|
/*! \brief Get entry of description table by index
|
||||||
|
*
|
||||||
|
* \param num index in description table
|
||||||
|
* \return Pointer to corresponding entry or `nullptr` if not available
|
||||||
|
*/
|
||||||
|
SDTH *get(unsigned num);
|
||||||
|
|
||||||
|
/*! \brief Get entry of description table by four character identifier
|
||||||
|
*
|
||||||
|
* \param a first character of identifier
|
||||||
|
* \param b second character of identifier
|
||||||
|
* \param c third character of identifier
|
||||||
|
* \param d forth and last character of identifier
|
||||||
|
* \return Pointer to corresponding entry or `nullptr` if not available
|
||||||
|
*/
|
||||||
|
SDTH *get(char a, char b, char c, char d);
|
||||||
|
|
||||||
|
/*! \brief Retrieve the revision from the Root System Description Pointer (RSDP)
|
||||||
|
*/
|
||||||
|
int revision();
|
||||||
|
|
||||||
|
} // namespace ACPI
|
||||||
150
arch/apic.cc
Normal file
150
arch/apic.cc
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
#include "apic.h"
|
||||||
|
|
||||||
|
#include "../debug/assert.h"
|
||||||
|
#include "../debug/output.h"
|
||||||
|
#include "acpi.h"
|
||||||
|
#include "core.h"
|
||||||
|
#include "ioport.h"
|
||||||
|
#include "lapic_registers.h"
|
||||||
|
|
||||||
|
namespace APIC {
|
||||||
|
|
||||||
|
static struct {
|
||||||
|
uint32_t id;
|
||||||
|
uintptr_t address;
|
||||||
|
uint32_t interrupt_base;
|
||||||
|
} ioapic;
|
||||||
|
|
||||||
|
static uint8_t slot_map[16];
|
||||||
|
|
||||||
|
static uint8_t lapic_id[Core::MAX];
|
||||||
|
static unsigned lapics = 0;
|
||||||
|
|
||||||
|
bool init() {
|
||||||
|
// get Multiple APIC Definition Table (MADT) from ACPI
|
||||||
|
ACPI::MADT* madt = static_cast<ACPI::MADT*>(ACPI::get('A', 'P', 'I', 'C'));
|
||||||
|
if (madt == 0) {
|
||||||
|
DBG_VERBOSE << "ERROR: no MADT found in ACPI" << endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// read the local APIC address
|
||||||
|
LAPIC::base_address = static_cast<uintptr_t>(madt->local_apic_address);
|
||||||
|
DBG_VERBOSE << "LAPIC Address "
|
||||||
|
<< reinterpret_cast<void*>(
|
||||||
|
static_cast<uintptr_t>(madt->local_apic_address))
|
||||||
|
<< endl;
|
||||||
|
|
||||||
|
// PC/AT compatibility mode
|
||||||
|
if (madt->flags_pcat_compat != 0) {
|
||||||
|
// The APIC operating mode is set to compatible PIC mode - we have to change
|
||||||
|
// it.
|
||||||
|
DBG_VERBOSE << "PIC comp mode, disabling PICs." << endl;
|
||||||
|
|
||||||
|
// Select Interrupt Mode Control Register (IMCR)
|
||||||
|
// (this register will only exist if hardware supports the PIC mode)
|
||||||
|
IOPort reg(0x22);
|
||||||
|
reg.outb(0x70);
|
||||||
|
// disable PIC mode, use APIC
|
||||||
|
IOPort imcr(0x23);
|
||||||
|
imcr.outb(0x01);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set default mapping of external interrupt slots (might be overwritten
|
||||||
|
// below)
|
||||||
|
for (unsigned i = 0; i < sizeof(slot_map) / sizeof(slot_map[0]); i++) {
|
||||||
|
slot_map[i] = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize invalid lapic_ids
|
||||||
|
for (unsigned i = 0; i < Core::MAX; i++) {
|
||||||
|
lapic_id[i] = INVALID_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset numbers, store apic data into arrays
|
||||||
|
for (ACPI::SubHeader* mads = madt->first(); mads < madt->end();
|
||||||
|
mads = mads->next()) {
|
||||||
|
switch (mads->type) {
|
||||||
|
case ACPI::MADS::Type_LAPIC: {
|
||||||
|
ACPI::MADS::LAPIC* mads_lapic = static_cast<ACPI::MADS::LAPIC*>(mads);
|
||||||
|
if (mads_lapic->flags_enabled == 0) {
|
||||||
|
DBG_VERBOSE << "Detected disabled LAPIC with ID "
|
||||||
|
<< static_cast<unsigned>(mads_lapic->apic_id) << endl;
|
||||||
|
} else if (lapics >= Core::MAX) {
|
||||||
|
DBG_VERBOSE << "Got more LAPICs than Core::MAX" << endl;
|
||||||
|
} else if (mads_lapic->apic_id == INVALID_ID) {
|
||||||
|
DBG_VERBOSE << "Got invalid APIC ID" << endl;
|
||||||
|
} else {
|
||||||
|
DBG_VERBOSE << "Detected LAPIC with ID "
|
||||||
|
<< static_cast<unsigned>(mads_lapic->apic_id) << endl;
|
||||||
|
lapic_id[lapics++] = mads_lapic->apic_id;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ACPI::MADS::Type_IOAPIC: {
|
||||||
|
ACPI::MADS::IOAPIC* mads_ioapic =
|
||||||
|
static_cast<ACPI::MADS::IOAPIC*>(mads);
|
||||||
|
DBG_VERBOSE << "Detected IO APIC with ID "
|
||||||
|
<< static_cast<unsigned>(mads_ioapic->ioapic_id)
|
||||||
|
<< " / Base "
|
||||||
|
<< reinterpret_cast<void*>(static_cast<uintptr_t>(
|
||||||
|
mads_ioapic->global_system_interrupt_base))
|
||||||
|
<< endl;
|
||||||
|
if (mads_ioapic->global_system_interrupt_base > 23) {
|
||||||
|
DBG_VERBOSE << "Ignoring IOAPIC since we currently only support one."
|
||||||
|
<< endl;
|
||||||
|
} else {
|
||||||
|
ioapic.id = mads_ioapic->ioapic_id;
|
||||||
|
ioapic.address = static_cast<uintptr_t>(mads_ioapic->ioapic_address);
|
||||||
|
ioapic.interrupt_base = mads_ioapic->global_system_interrupt_base;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ACPI::MADS::Type_Interrupt_Source_Override: {
|
||||||
|
ACPI::MADS::Interrupt_Source_Override* mads_iso =
|
||||||
|
static_cast<ACPI::MADS::Interrupt_Source_Override*>(mads);
|
||||||
|
if (mads_iso->bus == 0) {
|
||||||
|
DBG_VERBOSE << "Overriding Interrupt Source "
|
||||||
|
<< static_cast<unsigned>(mads_iso->source) << " with "
|
||||||
|
<< mads_iso->global_system_interrupt << endl;
|
||||||
|
if (mads_iso->source < sizeof(slot_map) / sizeof(slot_map[0])) {
|
||||||
|
slot_map[mads_iso->source] = mads_iso->global_system_interrupt;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
DBG_VERBOSE << "Override for bus " << mads_iso->bus
|
||||||
|
<< " != ISA. Does not conform to ACPI." << endl;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ACPI::MADS::Type_LAPIC_Address_Override: {
|
||||||
|
ACPI::MADS::LAPIC_Address_Override* mads_lao =
|
||||||
|
static_cast<ACPI::MADS::LAPIC_Address_Override*>(mads);
|
||||||
|
LAPIC::base_address =
|
||||||
|
static_cast<uintptr_t>(mads_lao->lapic_address_low);
|
||||||
|
DBG_VERBOSE << "Overriding LAPIC address with "
|
||||||
|
<< reinterpret_cast<void*>(
|
||||||
|
static_cast<uintptr_t>(mads_lao->lapic_address))
|
||||||
|
<< endl;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t getIOAPICSlot(APIC::Device device) { return slot_map[device]; }
|
||||||
|
|
||||||
|
uintptr_t getIOAPICAddress() { return ioapic.address; }
|
||||||
|
|
||||||
|
uint8_t getIOAPICID() { return ioapic.id; }
|
||||||
|
|
||||||
|
uint8_t getLogicalAPICID(uint8_t core) {
|
||||||
|
return core < Core::MAX ? (1 << core) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t getLAPICID(uint8_t core) {
|
||||||
|
assert(core < Core::MAX);
|
||||||
|
return lapic_id[core];
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace APIC
|
||||||
82
arch/apic.h
Normal file
82
arch/apic.h
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
/*! \file
|
||||||
|
* \brief Gather system information from the \ref ACPI about the \ref APIC
|
||||||
|
* "Advanced Programmable Interrupt Controller (APIC)"
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "../types.h"
|
||||||
|
|
||||||
|
/*! \brief Information about the (extended) Advanced Programmable Interrupt
|
||||||
|
* Controller
|
||||||
|
*/
|
||||||
|
namespace APIC {
|
||||||
|
/*! \brief Historic order of interrupt lines (PIC)
|
||||||
|
*/
|
||||||
|
enum Device {
|
||||||
|
TIMER = 0, ///< Programmable Interrupt Timer (\ref PIT)
|
||||||
|
KEYBOARD = 1, ///< Keyboard
|
||||||
|
COM1 = 4, ///< First serial interface
|
||||||
|
COM2 = 3, ///< Second serial interface
|
||||||
|
COM3 = 4, ///< Third serial interface (shared with COM1)
|
||||||
|
COM4 = 3, ///< Forth serial interface (shared with COM2)
|
||||||
|
FLOPPY = 6, ///< Floppy device
|
||||||
|
LPT1 = 7, ///< Printer
|
||||||
|
REALTIMECLOCK = 8, ///< Real time clock
|
||||||
|
PS2MOUSE = 12, ///< Mouse
|
||||||
|
IDE1 = 14, ///< First hard disk
|
||||||
|
IDE2 = 15 ///< Second hard disk
|
||||||
|
};
|
||||||
|
|
||||||
|
/*! \brief Invalid APIC ID
|
||||||
|
*
|
||||||
|
* The highest address is reserved according to xAPIC specification
|
||||||
|
*/
|
||||||
|
const uint8_t INVALID_ID = 0xff;
|
||||||
|
|
||||||
|
/*! \brief Executes system detection
|
||||||
|
*
|
||||||
|
* Searches and evaluates the APIC entries in the \ref ACPI table.
|
||||||
|
* This function recognizes a possibly existing multicore system.
|
||||||
|
* After successful detection, the number of available CPUs (which is equal
|
||||||
|
* to the number of \ref LAPIC "local APICs") ) can be queried
|
||||||
|
* using the method \ref Core::count().
|
||||||
|
*
|
||||||
|
* \note Called by \ref kernel_init() on BSP
|
||||||
|
*
|
||||||
|
* \return `true` if detection of the APIC entries was successful
|
||||||
|
*/
|
||||||
|
bool init();
|
||||||
|
|
||||||
|
/*! \brief Queries the I/O-APIC address determined during system boot
|
||||||
|
*
|
||||||
|
* \return Base address of the (first & only supported) I/O APIC
|
||||||
|
*/
|
||||||
|
uintptr_t getIOAPICAddress();
|
||||||
|
|
||||||
|
/*! \brief Queries of ID of the I/O-APIC determined during system boot
|
||||||
|
*
|
||||||
|
* \return Identification of the (first & only supported) I/O APIC
|
||||||
|
*/
|
||||||
|
uint8_t getIOAPICID();
|
||||||
|
|
||||||
|
/*! \brief Returns the pin number the \p device is connected to.
|
||||||
|
*/
|
||||||
|
uint8_t getIOAPICSlot(APIC::Device device);
|
||||||
|
|
||||||
|
/*! \brief Returns the logical ID of the Local APIC passed for \a core.
|
||||||
|
*
|
||||||
|
* The LAPIC's logical ID is set (by StuBS) during boot such that exactly one
|
||||||
|
* bit is set per CPU core. For core 0, bit 0 is set in its ID, while core 1 has
|
||||||
|
* bit 1 set, etc.
|
||||||
|
*
|
||||||
|
* \param core The queried CPU core
|
||||||
|
*/
|
||||||
|
uint8_t getLogicalAPICID(uint8_t core);
|
||||||
|
|
||||||
|
/*! \brief Get the Local APIC ID of a CPU
|
||||||
|
* \param core Query CPU core number
|
||||||
|
* \return LAPIC ID of CPU or INVALID_ID if invalid CPU ID
|
||||||
|
*/
|
||||||
|
uint8_t getLAPICID(uint8_t core);
|
||||||
|
|
||||||
|
} // namespace APIC
|
||||||
22
arch/cache.h
Normal file
22
arch/cache.h
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
/*! \file
|
||||||
|
* \brief Helper for cache alignment
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../debug/assert.h"
|
||||||
|
|
||||||
|
// Helper for aligning to cache line (to prevent false sharing)
|
||||||
|
#ifndef CACHE_LINE_SIZE
|
||||||
|
#define CACHE_LINE_SIZE 64
|
||||||
|
#endif
|
||||||
|
#define cache_aligned alignas(CACHE_LINE_SIZE)
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \def assert_cache_aligned(TYPE)
|
||||||
|
* \brief Compile time check of cache alignment
|
||||||
|
* \param TYPE data type to check
|
||||||
|
*/
|
||||||
|
#define assert_cache_aligned(TYPE) \
|
||||||
|
static_assert(sizeof(TYPE) % CACHE_LINE_SIZE == 0, \
|
||||||
|
STRINGIFY(TYPE) "Not aligned on cache boundary")
|
||||||
21
arch/cga.cc
Normal file
21
arch/cga.cc
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#include "cga.h"
|
||||||
|
|
||||||
|
namespace CGA {
|
||||||
|
|
||||||
|
void setCursor(unsigned abs_x, unsigned abs_y) {
|
||||||
|
(void)abs_x;
|
||||||
|
(void)abs_y;
|
||||||
|
}
|
||||||
|
|
||||||
|
void getCursor(unsigned& abs_x, unsigned& abs_y) {
|
||||||
|
(void)abs_x;
|
||||||
|
(void)abs_y;
|
||||||
|
}
|
||||||
|
|
||||||
|
void show(unsigned abs_x, unsigned abs_y, char character, Attribute attrib) {
|
||||||
|
(void)abs_x;
|
||||||
|
(void)abs_y;
|
||||||
|
(void)character;
|
||||||
|
(void)attrib;
|
||||||
|
}
|
||||||
|
}; // namespace CGA
|
||||||
138
arch/cga.h
Normal file
138
arch/cga.h
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
/*! \file
|
||||||
|
* \brief \ref CGA provides a basic interface to display a character in
|
||||||
|
* VGA-compatible text mode
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "../types.h"
|
||||||
|
|
||||||
|
/*! \brief Basic operations in the VGA-compatible text mode
|
||||||
|
* \ingroup io
|
||||||
|
*
|
||||||
|
* This namespace provides an interface to access the screen in text mode
|
||||||
|
* (also known as CGA mode), with access directly on the hardware
|
||||||
|
* level, i.e. the video memory and the I/O ports of the graphics
|
||||||
|
* card.
|
||||||
|
*/
|
||||||
|
namespace CGA {
|
||||||
|
constexpr unsigned ROWS = 25; ///< Visible rows in text mode
|
||||||
|
constexpr unsigned COLUMNS = 80; ///< Visible columns in text mode
|
||||||
|
|
||||||
|
/*! \brief CGA color palette
|
||||||
|
*
|
||||||
|
* Colors for the attribute byte.
|
||||||
|
* All 16 colors can be used for the foreground while the background colors
|
||||||
|
* are limited to the first eight (from`BLACK` to `LIGHT_GREY`)
|
||||||
|
*/
|
||||||
|
enum Color {
|
||||||
|
BLACK, ///< Black (fore- and background)
|
||||||
|
BLUE, ///< Blue (fore- and background)
|
||||||
|
GREEN, ///< Green (fore- and background)
|
||||||
|
CYAN, ///< Cyan (fore- and background)
|
||||||
|
RED, ///< Red (fore- and background)
|
||||||
|
MAGENTA, ///< Magenta (fore- and background)
|
||||||
|
BROWN, ///< Brown (fore- and background)
|
||||||
|
LIGHT_GREY, ///< Light grey (fore- and background)
|
||||||
|
DARK_GREY, ///< Dark grey (foreground only)
|
||||||
|
LIGHT_BLUE, ///< Light blue (foreground only)
|
||||||
|
LIGHT_GREEN, ///< Light green (foreground only)
|
||||||
|
LIGHT_CYAN, ///< Light cyan (foreground only)
|
||||||
|
LIGHT_RED, ///< Light red (foreground only)
|
||||||
|
LIGHT_MAGENTA, ///< Light magenta (foreground only)
|
||||||
|
YELLOW, ///< Yellow (foreground only)
|
||||||
|
WHITE ///< White (foreground only)
|
||||||
|
};
|
||||||
|
|
||||||
|
/*! \brief Structure of a character attribute
|
||||||
|
* consists of 4 bit fore- and 3 bit background color, and a single blink bit.
|
||||||
|
*
|
||||||
|
* [Bit fields](https://en.cppreference.com/w/cpp/language/bit_field) can
|
||||||
|
* notably simplify the access and code readability.
|
||||||
|
*
|
||||||
|
* \note [Type punning](https://en.wikipedia.org/wiki/Type_punning#Use_of_union)
|
||||||
|
* is indeed undefined behavior in C++. However, *gcc* explicitly allows
|
||||||
|
* this construct as a [language extension](https://gcc.gnu.org/bugs/#nonbugs).
|
||||||
|
* Some compilers ([other than
|
||||||
|
* gcc](https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html#Type%2Dpunning)
|
||||||
|
* might allow this feature only by disabling strict aliasing
|
||||||
|
* (`-fno-strict-aliasing`). In \StuBS we use this feature extensively due to
|
||||||
|
* the improved code readability.
|
||||||
|
*
|
||||||
|
* \todo(11) Fill in the bitfield
|
||||||
|
*/
|
||||||
|
union Attribute {
|
||||||
|
struct {
|
||||||
|
uint8_t todo : 8;
|
||||||
|
} __attribute__((packed));
|
||||||
|
uint8_t value; ///< combined value
|
||||||
|
|
||||||
|
/*! \brief Attribute constructor (with default values)
|
||||||
|
*
|
||||||
|
* \todo(11) Complete constructor
|
||||||
|
*
|
||||||
|
* \param foreground Foreground color (Default: \ref LIGHT_GREY)
|
||||||
|
* \param background Background color (Default: \ref BLACK)
|
||||||
|
* \param blink Blink if `true` (default: no blinking)
|
||||||
|
*/
|
||||||
|
explicit Attribute(Color foreground = LIGHT_GREY, Color background = BLACK,
|
||||||
|
bool blink = false) { // NOLINT
|
||||||
|
(void)foreground;
|
||||||
|
(void)background;
|
||||||
|
(void)blink;
|
||||||
|
}
|
||||||
|
} __attribute__((packed)); // prevent padding by the compiler
|
||||||
|
|
||||||
|
/*! \brief Set the keyboard hardware cursor to absolute screen position
|
||||||
|
*
|
||||||
|
* \todo(11) Implement the method using \ref IOPort
|
||||||
|
*
|
||||||
|
* \param abs_x absolute column of the keyboard hardware cursor
|
||||||
|
* \param abs_y absolute row of the keyboard hardware cursor
|
||||||
|
*/
|
||||||
|
void setCursor(unsigned abs_x, unsigned abs_y);
|
||||||
|
|
||||||
|
/*! \brief Retrieve the keyboard hardware cursor position on screen
|
||||||
|
*
|
||||||
|
* \todo(11) Implement the method using the \ref IOPort
|
||||||
|
*
|
||||||
|
* \param abs_x absolute column of the keyboard hardware cursor
|
||||||
|
* \param abs_y absolute row of the keyboard hardware cursor
|
||||||
|
*/
|
||||||
|
void getCursor(unsigned& abs_x, unsigned& abs_y);
|
||||||
|
|
||||||
|
/*! \brief Basic output of a character at a specific position on the screen.
|
||||||
|
*
|
||||||
|
* This method outputs the given character at the absolute screen position
|
||||||
|
* (`x`, `y`) with the specified color attribute.
|
||||||
|
*
|
||||||
|
* The position (`0`,`0`) indicates the upper left corner of the screen.
|
||||||
|
* The attribute defines characteristics such as background color,
|
||||||
|
* foreground color and blinking.
|
||||||
|
*
|
||||||
|
* \param abs_x Column (`abs_x` < \ref COLUMNS) in which the character should be
|
||||||
|
* displayed
|
||||||
|
* \param abs_y Row (`abs_y` < \ref ROWS) in which the character should be
|
||||||
|
* displayed
|
||||||
|
* \param character Character to be displayed
|
||||||
|
* \param attrib Attribute with color settings
|
||||||
|
*
|
||||||
|
* \todo(11) Implement the method
|
||||||
|
*/
|
||||||
|
void show(unsigned abs_x, unsigned abs_y, char character,
|
||||||
|
Attribute attrib = Attribute());
|
||||||
|
|
||||||
|
/*! \brief Structure for a cell in text mode
|
||||||
|
*
|
||||||
|
* Consisting of two bytes, character and attribute
|
||||||
|
*/
|
||||||
|
struct Cell {
|
||||||
|
char character;
|
||||||
|
Attribute attribute;
|
||||||
|
Cell(char character, Attribute attribute)
|
||||||
|
: character(character), attribute(attribute) {}
|
||||||
|
} __attribute__((packed));
|
||||||
|
|
||||||
|
/*! \brief Base address for linear text buffer in video memory
|
||||||
|
*/
|
||||||
|
Cell* const TEXT_BUFFER_BASE = nullptr;
|
||||||
|
}; // namespace CGA
|
||||||
61
arch/cmos.cc
Normal file
61
arch/cmos.cc
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
#include "cmos.h"
|
||||||
|
|
||||||
|
#include "core_interrupt.h"
|
||||||
|
#include "ioport.h"
|
||||||
|
|
||||||
|
namespace CMOS {
|
||||||
|
static IOPort address(0x70);
|
||||||
|
static IOPort data(0x71);
|
||||||
|
|
||||||
|
namespace NMI {
|
||||||
|
static const uint8_t mask = 0x80;
|
||||||
|
// Cache NMI to speed things up
|
||||||
|
static bool disabled = false;
|
||||||
|
|
||||||
|
void enable() {
|
||||||
|
bool status = Core::Interrupt::disable();
|
||||||
|
uint8_t value = address.inb();
|
||||||
|
value &= ~mask;
|
||||||
|
address.outb(value);
|
||||||
|
Core::Interrupt::restore(status);
|
||||||
|
disabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void disable() {
|
||||||
|
bool status = Core::Interrupt::disable();
|
||||||
|
uint8_t value = address.inb();
|
||||||
|
value |= mask;
|
||||||
|
address.outb(value);
|
||||||
|
Core::Interrupt::restore(status);
|
||||||
|
disabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isEnabled() {
|
||||||
|
disabled = (address.inb() & mask) != 0;
|
||||||
|
return !disabled;
|
||||||
|
}
|
||||||
|
} // namespace NMI
|
||||||
|
|
||||||
|
static void setAddress(enum Register reg) {
|
||||||
|
uint8_t value = reg;
|
||||||
|
// The highest bit controls the Non Maskable Interrupt
|
||||||
|
// so we don't want to accidentally change it.
|
||||||
|
if (NMI::disabled) {
|
||||||
|
value |= NMI::mask;
|
||||||
|
} else {
|
||||||
|
value &= ~NMI::mask;
|
||||||
|
}
|
||||||
|
address.outb(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t read(enum Register reg) {
|
||||||
|
setAddress(reg);
|
||||||
|
return data.inb();
|
||||||
|
}
|
||||||
|
|
||||||
|
void write(enum Register reg, uint8_t value) {
|
||||||
|
setAddress(reg);
|
||||||
|
data.outb(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace CMOS
|
||||||
46
arch/cmos.h
Normal file
46
arch/cmos.h
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
/*! \file
|
||||||
|
* \brief Controlling the \ref CMOS "complementary metal oxide semiconductor
|
||||||
|
* (CMOS)"
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "../types.h"
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \defgroup CMOS CMOS
|
||||||
|
* \brief complementary metal oxide semiconductor (CMOS)
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*! \brief CMOS
|
||||||
|
* \ingroup CMOS
|
||||||
|
*/
|
||||||
|
namespace CMOS {
|
||||||
|
|
||||||
|
enum Register {
|
||||||
|
REG_SECOND = 0x0, ///< RTC
|
||||||
|
REG_ALARM_SECOND = 0x1, ///< RTC
|
||||||
|
REG_MINUTE = 0x2, ///< RTC
|
||||||
|
REG_ALARM_MINUTE = 0x3, ///< RTC
|
||||||
|
REG_HOUR = 0x4, ///< RTC
|
||||||
|
REG_ALARM_HOUR = 0x5, ///< RTC
|
||||||
|
REG_WEEKDAY = 0x6, ///< RTC
|
||||||
|
REG_DAYOFMONTH = 0x7, ///< RTC
|
||||||
|
REG_MONTH = 0x8, ///< RTC
|
||||||
|
REG_YEAR = 0x9, ///< RTC
|
||||||
|
REG_STATUS_A = 0xa, ///< RTC
|
||||||
|
REG_STATUS_B = 0xb, ///< RTC
|
||||||
|
REG_STATUS_C = 0xc, ///< RTC
|
||||||
|
REG_STATUS_D = 0xd, ///< RTC
|
||||||
|
REG_STATUS_DIAGNOSE = 0xe,
|
||||||
|
REG_STATUS_SHUTDOWN = 0xf
|
||||||
|
};
|
||||||
|
|
||||||
|
uint8_t read(enum Register reg);
|
||||||
|
void write(enum Register reg, uint8_t value);
|
||||||
|
|
||||||
|
namespace NMI {
|
||||||
|
void enable();
|
||||||
|
void disable();
|
||||||
|
bool isEnabled();
|
||||||
|
} // namespace NMI
|
||||||
|
} // namespace CMOS
|
||||||
20
arch/context.asm
Normal file
20
arch/context.asm
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
[SECTION .text]
|
||||||
|
[GLOBAL context_switch]
|
||||||
|
[GLOBAL context_launch]
|
||||||
|
[GLOBAL fake_systemv_abi]
|
||||||
|
|
||||||
|
; context_switch saves the registers in the current context structure
|
||||||
|
; and populates the registers from the the next context.
|
||||||
|
align 16
|
||||||
|
context_switch:
|
||||||
|
|
||||||
|
; context_launch populates the register set from the next context structure.
|
||||||
|
; It does not save the current registers.
|
||||||
|
align 16 ; When only one parameter is used for `align`, it will use NOP
|
||||||
|
context_launch:
|
||||||
|
|
||||||
|
; fake_systemv_abi is used to populate the volatile argument registers used by the systemv abi (rdi, rsi, ...)
|
||||||
|
; with values from the non-volatile registers saved within the thread context (r15, r14, ...)
|
||||||
|
align 16
|
||||||
|
fake_systemv_abi:
|
||||||
|
|
||||||
9
arch/context.cc
Normal file
9
arch/context.cc
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
#include "context.h"
|
||||||
|
|
||||||
|
void prepareContext(void* tos, Context& context, void (*kickoff)(void*),
|
||||||
|
void* param1) {
|
||||||
|
(void)tos;
|
||||||
|
(void)context;
|
||||||
|
(void)kickoff;
|
||||||
|
(void)param1;
|
||||||
|
}
|
||||||
118
arch/context.h
Normal file
118
arch/context.h
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
/*! \file
|
||||||
|
* \brief Functionality required for \ref context_switch "context switching"
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*! \defgroup context Context Switch
|
||||||
|
* \brief Low-Level functionality required for context switching
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "../types.h"
|
||||||
|
|
||||||
|
/*! \brief Structure for saving the CPU context when switching coroutines.
|
||||||
|
* \ingroup context
|
||||||
|
*/
|
||||||
|
struct Context {
|
||||||
|
uintptr_t rbx; ///< RBX of the thread
|
||||||
|
uintptr_t rbp; ///< RBP of the thread
|
||||||
|
uintptr_t r12; ///< R12 of the thread
|
||||||
|
uintptr_t r13; ///< R13 of the thread
|
||||||
|
uintptr_t r14; ///< R14 of the thread
|
||||||
|
uintptr_t r15; ///< R15 of the thread
|
||||||
|
void* rsp; ///< Current stack pointer of the thread
|
||||||
|
} __attribute__((packed));
|
||||||
|
|
||||||
|
/*! \brief Prepares a context for its first activation.
|
||||||
|
*
|
||||||
|
* \ingroup context
|
||||||
|
*
|
||||||
|
* On first activation (during *some* context switch), the execution of a
|
||||||
|
* thread should start at its entry point (typically an implementation of \ref
|
||||||
|
* Thread::kickoff).
|
||||||
|
*
|
||||||
|
* For this, we have to prepare the thread context such that \ref
|
||||||
|
* context_switch and \ref context_launch can work with it.
|
||||||
|
*
|
||||||
|
* Just pushing the entry point onto the stack as a return address is not
|
||||||
|
* sufficient, however.
|
||||||
|
* \ref Thread::kickoff requires a pointer to the current thread as a
|
||||||
|
* parameter, which we also have to transfer. According to the 64 bit systemv
|
||||||
|
* calling convention, parameters are passed via the volatile registers `rdi,
|
||||||
|
* rsi, rcx, rdx, r8, r9`. But theses are never set during the intial context
|
||||||
|
* switch (why?). Therefore we pass the parameter using the non-volatile
|
||||||
|
* register `r15` and use a trampoline function as the actual entry point. See
|
||||||
|
* \ref fake_systemv_abi for details.
|
||||||
|
*
|
||||||
|
* `prepareContext()` can be implemented in the high-level programming language
|
||||||
|
* C++ (in file `context.cc`).
|
||||||
|
*
|
||||||
|
* \param tos Pointer to the top of stack (= address of first byte beyond
|
||||||
|
* the memory reserved for the stack)
|
||||||
|
* \param context Reference to the Context structure to be filled
|
||||||
|
* \param kickoff Pointer to the \ref Thread::kickoff function
|
||||||
|
* \param param1 first parameter for \ref Thread::kickoff function
|
||||||
|
*/
|
||||||
|
/*!
|
||||||
|
* \todo(14) Implement Function (and helper functions, if required)
|
||||||
|
*/
|
||||||
|
void prepareContext(void* tos, Context& context, void (*kickoff)(void*),
|
||||||
|
void* param1 = nullptr);
|
||||||
|
|
||||||
|
/*! \brief Executes the context switch.
|
||||||
|
*
|
||||||
|
* \ingroup context
|
||||||
|
*
|
||||||
|
* For a clean context switch, the current register values must be stored in
|
||||||
|
* the given context struct. Subsequently, these values must be restored
|
||||||
|
* accordingly from the `next` context struct.
|
||||||
|
*
|
||||||
|
* This function must be implemented in assembler in the file `context.asm`
|
||||||
|
* (why?). It must be declared as `extern "C"`, so assembler functions are not
|
||||||
|
* C++ name mangled.
|
||||||
|
*
|
||||||
|
* \param next Pointer to the structure that the next context will be read
|
||||||
|
* from
|
||||||
|
* \param current Pointer to the structure that the current context will be
|
||||||
|
* stored in
|
||||||
|
*
|
||||||
|
* \todo(14) Implement Method
|
||||||
|
*/
|
||||||
|
extern "C" void context_switch(Context* next, Context* current);
|
||||||
|
|
||||||
|
/*! \brief Launch context switching.
|
||||||
|
*
|
||||||
|
* To start context switching, the current context (from the boot-routines) is
|
||||||
|
* thrown away and the prepared register values within the given `next` context
|
||||||
|
* replace it.
|
||||||
|
*
|
||||||
|
* This function must be implemented in assembler in the file `context.asm`
|
||||||
|
* (why?). It must be declared as `extern "C"`, so assembler functions are not
|
||||||
|
* C++ name mangled.
|
||||||
|
*
|
||||||
|
* \ingroup context
|
||||||
|
*
|
||||||
|
* \param next Pointer to the structure that the next context will be read
|
||||||
|
* from
|
||||||
|
*
|
||||||
|
* \todo(14) Implement Method
|
||||||
|
*/
|
||||||
|
extern "C" void context_launch(Context* next);
|
||||||
|
|
||||||
|
/*! \brief Fakes a systemv abi call.
|
||||||
|
*
|
||||||
|
* When a thread is first started, only non-volatile registers are "restored"
|
||||||
|
* from our prepared context (which is where we stored our \ref Thread::kickoff
|
||||||
|
* parameters). However, the 64 bit calling convention (systemv) dictates that
|
||||||
|
* parameters are passed via the volatile registers `rdi, rsi, rcx, rdx, r8,
|
||||||
|
* r9`. In order to call a C++ function, we have to transfer our parameters from
|
||||||
|
* the non-volatile registers (e.g. `r15, ...`) to the correct volatile ones
|
||||||
|
* (`rdi, ...`).
|
||||||
|
*
|
||||||
|
* This function must be implemented in assembler in the file `context.asm`
|
||||||
|
* (why?). It must be declared as `extern "C"`, so assembler functions are not
|
||||||
|
* C++ name mangled.
|
||||||
|
* \ingroup context
|
||||||
|
*
|
||||||
|
* \todo(14) Implement Method
|
||||||
|
*/
|
||||||
|
extern "C" void fake_systemv_abi();
|
||||||
73
arch/core.cc
Normal file
73
arch/core.cc
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
#include "core.h"
|
||||||
|
|
||||||
|
#include "apic.h"
|
||||||
|
#include "lapic.h"
|
||||||
|
|
||||||
|
/*! \brief Initial size of CPU core stacks
|
||||||
|
*
|
||||||
|
* Used during startup in `boot/startup.asm`
|
||||||
|
*/
|
||||||
|
extern "C" const unsigned long CPU_CORE_STACK_SIZE = 4096;
|
||||||
|
|
||||||
|
/*! \brief Reserved memory for CPU core stacks
|
||||||
|
*/
|
||||||
|
alignas(
|
||||||
|
16) static unsigned char cpu_core_stack[Core::MAX * CPU_CORE_STACK_SIZE];
|
||||||
|
|
||||||
|
/*! \brief Pointer to stack memory
|
||||||
|
*
|
||||||
|
* Incremented during startup of each core (bootstrap and application
|
||||||
|
* processors) in `boot/startup.asm`
|
||||||
|
*/
|
||||||
|
unsigned char* cpu_core_stack_pointer = cpu_core_stack;
|
||||||
|
|
||||||
|
namespace Core {
|
||||||
|
|
||||||
|
static unsigned cores = 0; ///< Number of available CPU cores
|
||||||
|
static volatile unsigned
|
||||||
|
core_id[255]; ///< Lookup table for CPU core IDs with LAPIC ID as index
|
||||||
|
|
||||||
|
static unsigned online_cores = 0; ///< Number of currently online CPU cores
|
||||||
|
static bool online_core[Core::MAX]; ///< Lookup table for online CPU cores with
|
||||||
|
///< CPU core ID as index
|
||||||
|
|
||||||
|
void init() {
|
||||||
|
// Increment number of online CPU cores
|
||||||
|
if (__atomic_fetch_add(&online_cores, 1, __ATOMIC_RELAXED) == 0) {
|
||||||
|
// Fill Lookup table
|
||||||
|
for (unsigned i = 0; i < Core::MAX; i++) {
|
||||||
|
uint8_t lapic_id = APIC::getLAPICID(i);
|
||||||
|
if (lapic_id < APIC::INVALID_ID) { // ignore invalid LAPICs
|
||||||
|
core_id[lapic_id] = i;
|
||||||
|
cores++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get CPU ID
|
||||||
|
uint8_t cpu = getID();
|
||||||
|
|
||||||
|
// initialize local APIC with logical APIC ID
|
||||||
|
LAPIC::init(APIC::getLogicalAPICID(cpu));
|
||||||
|
|
||||||
|
// set current CPU online
|
||||||
|
online_core[cpu] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void exit() {
|
||||||
|
// CPU core offline
|
||||||
|
online_core[getID()] = false;
|
||||||
|
__atomic_fetch_sub(&online_cores, 1, __ATOMIC_RELAXED);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned getID() { return core_id[LAPIC::getID()]; }
|
||||||
|
|
||||||
|
unsigned count() { return cores; }
|
||||||
|
|
||||||
|
unsigned countOnline() { return online_cores; }
|
||||||
|
|
||||||
|
bool isOnline(uint8_t core_id) {
|
||||||
|
return core_id > Core::MAX ? false : online_core[core_id];
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Core
|
||||||
117
arch/core.h
Normal file
117
arch/core.h
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
/*! \file
|
||||||
|
* \brief Access to internals of a CPU \ref Core
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*! \defgroup sync CPU Synchronization
|
||||||
|
*
|
||||||
|
* The synchronization module houses functions useful for orchestrating multiple
|
||||||
|
* processors and their activities. Synchronisation, in this case, means
|
||||||
|
* handling the resource contention between multiple participants, running on
|
||||||
|
* either the same or different cores.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "../types.h"
|
||||||
|
#include "core_cr.h"
|
||||||
|
#include "core_interrupt.h"
|
||||||
|
#include "core_msr.h"
|
||||||
|
|
||||||
|
/*! \brief Implements an abstraction for CPU internals.
|
||||||
|
*
|
||||||
|
* These internals include functions to \ref Core::Interrupt "allow or deny
|
||||||
|
* interrupts", access \ref Core::CR "control registers".
|
||||||
|
*/
|
||||||
|
namespace Core {
|
||||||
|
|
||||||
|
/*! \brief Maximum number of supported CPUs
|
||||||
|
*/
|
||||||
|
constexpr unsigned MAX = 8;
|
||||||
|
|
||||||
|
/*! \brief Get the ID of the current CPU core
|
||||||
|
* using \ref LAPIC::getID() with an internal lookup table.
|
||||||
|
*
|
||||||
|
* \return ID of current Core (a number between 0 and \ref Core::MAX)
|
||||||
|
*/
|
||||||
|
unsigned getID();
|
||||||
|
|
||||||
|
/*! \brief Initialize this CPU core
|
||||||
|
*
|
||||||
|
* Mark this core as *online* and setup the cores \ref LAPIC by assigning it a
|
||||||
|
* unique \ref APIC::getLogicalAPICID() "logical APIC ID"
|
||||||
|
*
|
||||||
|
* \note Should only be called from \ref kernel_init() during startup.
|
||||||
|
*/
|
||||||
|
void init();
|
||||||
|
|
||||||
|
/*! \brief Deinitialize this CPU core
|
||||||
|
*
|
||||||
|
* Mark this Core as *offline*
|
||||||
|
*
|
||||||
|
* \note Should only be called from \ref kernel_init() after returning from
|
||||||
|
* `main()` or `main_ap()`.
|
||||||
|
*/
|
||||||
|
void exit();
|
||||||
|
|
||||||
|
/*! \brief Get number of available CPU cores
|
||||||
|
*
|
||||||
|
* \return total number of cores
|
||||||
|
*/
|
||||||
|
unsigned count();
|
||||||
|
|
||||||
|
/*! \brief Get number of successfully started (and currently active) CPU cores
|
||||||
|
*
|
||||||
|
* \return total number of online cores
|
||||||
|
*/
|
||||||
|
unsigned countOnline();
|
||||||
|
|
||||||
|
/*! \brief Check if CPU core is currently active
|
||||||
|
* \param core_id ID of the CPU core
|
||||||
|
* \return `true` if successfully started and is currently active
|
||||||
|
*/
|
||||||
|
bool isOnline(uint8_t core_id);
|
||||||
|
|
||||||
|
/*! \brief Gives the core a hint that it is executing a spinloop and should
|
||||||
|
* sleep "shortly"
|
||||||
|
*
|
||||||
|
* Improves the over-all performance when executing a spinloop by waiting a
|
||||||
|
* short moment reduce the load on the memory.
|
||||||
|
*
|
||||||
|
* \see [ISDMv2, Chapter 4. PAUSE - Spin Loop
|
||||||
|
* Hint](intel_manual_vol2.pdf#page=887)
|
||||||
|
*/
|
||||||
|
inline void pause() { asm volatile("pause\n\t" : : : "memory"); }
|
||||||
|
|
||||||
|
/*! \brief Halt the CPU core until the next interrupt.
|
||||||
|
*
|
||||||
|
* Halts the current CPU core such that it will wake up on the next interrupt.
|
||||||
|
* Internally, this function first enables the interrupts via `sti` and then
|
||||||
|
* halts the core using `hlt`. Halted cores can only be woken by interrupts. The
|
||||||
|
* effect of `sti` is delayed by one instruction, making the sequence `sti hlt`
|
||||||
|
* atomic (if interrupts were disabled previously).
|
||||||
|
*
|
||||||
|
* \see [ISDMv2, Chapter 4. STI - Set Interrupt
|
||||||
|
* Flag](intel_manual_vol2.pdf#page=1297)
|
||||||
|
* \see [ISDMv2, Chapter 3. HLT - Halt](intel_manual_vol2.pdf#page=539)
|
||||||
|
*/
|
||||||
|
inline void idle() { asm volatile("sti\n\t hlt\n\t" : : : "memory"); }
|
||||||
|
|
||||||
|
/*! \brief Permanently halts the core.
|
||||||
|
*
|
||||||
|
* Permanently halts the current CPU core. Internally, this function first
|
||||||
|
* disables the interrupts via `cli` and then halts the CPU core using `hlt`. As
|
||||||
|
* halted CPU cores can only be woken by interrupts, it is guaranteed that this
|
||||||
|
* core will be halted until the next reboot. The execution of die never
|
||||||
|
* returns. On multicore systems, only the executing CPU core will be halted
|
||||||
|
* permanently, other cores will continue execution.
|
||||||
|
*
|
||||||
|
* \see [ISDMv2, Chapter 3. CLI - Clear Interrupt
|
||||||
|
* Flag](intel_manual_vol2.pdf#page=245)
|
||||||
|
* \see [ISDMv2, Chapter 3. HLT - Halt](intel_manual_vol2.pdf#page=539)
|
||||||
|
*/
|
||||||
|
[[noreturn]] inline void die() {
|
||||||
|
while (true) {
|
||||||
|
asm volatile("cli\n\t hlt\n\t" : : : "memory");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Core
|
||||||
83
arch/core_cr.h
Normal file
83
arch/core_cr.h
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
/*! \file
|
||||||
|
* \brief Access to \ref Core::CR "Control Register" of a \ref Core "CPU core"
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "../types.h"
|
||||||
|
|
||||||
|
namespace Core {
|
||||||
|
/*! \brief Control Register 0
|
||||||
|
*
|
||||||
|
* \see [ISDMv3, 2.5 Control Registers](intel_manual_vol3.pdf#page=74)
|
||||||
|
*/
|
||||||
|
enum CR0 {
|
||||||
|
CR0_PE = 1 << 0, ///< Protected Mode enabled
|
||||||
|
CR0_MP = 1 << 1, ///< Monitor co-processor
|
||||||
|
CR0_EM = 1 << 2, ///< Emulation (no x87 floating-point unit present)
|
||||||
|
CR0_TS = 1 << 3, ///< Task switched
|
||||||
|
CR0_ET = 1 << 4, ///< Extension type
|
||||||
|
CR0_NE = 1 << 15, ///< Numeric error
|
||||||
|
CR0_WP = 1 << 16, ///< Write protect
|
||||||
|
CR0_AM = 1 << 18, ///< Alignment mask
|
||||||
|
CR0_NW = 1 << 29, ///< Not-write through caching
|
||||||
|
CR0_CD = 1 << 30, ///< Cache disable
|
||||||
|
CR0_PG = 1 << 31, ///< Paging
|
||||||
|
};
|
||||||
|
|
||||||
|
/*! \brief Control Register 4
|
||||||
|
*
|
||||||
|
* \see [ISDMv3, 2.5 Control Registers](intel_manual_vol3.pdf#page=77)
|
||||||
|
*/
|
||||||
|
enum CR4 {
|
||||||
|
CR4_VME = 1 << 0, ///< Virtual 8086 Mode Extensions
|
||||||
|
CR4_PVI = 1 << 1, ///< Protected-mode Virtual Interrupts
|
||||||
|
CR4_TSD = 1 << 2, ///< Time Stamp Disable
|
||||||
|
CR4_DE = 1 << 3, ///< Debugging Extensions
|
||||||
|
CR4_PSE = 1 << 4, ///< Page Size Extension
|
||||||
|
CR4_PAE = 1 << 5, ///< Physical Address Extension
|
||||||
|
CR4_MCE = 1 << 6, ///< Machine Check Exception
|
||||||
|
CR4_PGE = 1 << 7, ///< Page Global Enabled
|
||||||
|
CR4_PCE = 1 << 8, ///< Performance-Monitoring Counter enable
|
||||||
|
CR4_OSFXSR =
|
||||||
|
1 << 9, ///< Operating system support for FXSAVE and FXRSTOR instructions
|
||||||
|
CR4_OSXMMEXCPT = 1 << 10, ///< Operating System Support for Unmasked SIMD
|
||||||
|
///< Floating-Point Exceptions
|
||||||
|
CR4_UMIP = 1 << 11, ///< User-Mode Instruction Prevention
|
||||||
|
CR4_VMXE = 1 << 13, ///< Virtual Machine Extensions Enable
|
||||||
|
CR4_SMXE = 1 << 14, ///< Safer Mode Extensions Enable
|
||||||
|
CR4_FSGSBASE = 1 << 16, ///< Enables the instructions RDFSBASE, RDGSBASE,
|
||||||
|
///< WRFSBASE, and WRGSBASE.
|
||||||
|
CR4_PCIDE = 1 << 17, ///< PCID Enable
|
||||||
|
CR4_OSXSAVE = 1 << 18, ///< XSAVE and Processor Extended States Enable
|
||||||
|
CR4_SMEP = 1 << 20, ///< Supervisor Mode Execution Protection Enable
|
||||||
|
CR4_SMAP = 1 << 21, ///< Supervisor Mode Access Prevention Enable
|
||||||
|
CR4_PKE = 1 << 22, ///< Protection Key Enable
|
||||||
|
};
|
||||||
|
|
||||||
|
/*! \brief Access to the Control Register
|
||||||
|
*
|
||||||
|
* \see [ISDMv3, 2.5 Control Registers](intel_manual_vol3.pdf#page=73)
|
||||||
|
* \tparam id Control Register to access
|
||||||
|
*/
|
||||||
|
template <uint8_t id>
|
||||||
|
class CR {
|
||||||
|
public:
|
||||||
|
/*! \brief Read the value of the current Control Register
|
||||||
|
*
|
||||||
|
* \return Value stored in the CR
|
||||||
|
*/
|
||||||
|
inline static uintptr_t read(void) {
|
||||||
|
uintptr_t val;
|
||||||
|
asm volatile("mov %%cr%c1, %0" : "=r"(val) : "n"(id));
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! \brief Write a value into the current Control Register
|
||||||
|
*
|
||||||
|
* \param value Value to write into the CR
|
||||||
|
*/
|
||||||
|
inline static void write(uintptr_t value) {
|
||||||
|
asm volatile("mov %0, %%cr%c1" : : "r"(value), "n"(id));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} // namespace Core
|
||||||
141
arch/core_interrupt.h
Normal file
141
arch/core_interrupt.h
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
/*! \file
|
||||||
|
* \brief \ref Core::Interrupt "Interrupt control" and \ref
|
||||||
|
* Core::Interrupt::Vector "interrupt vector list"
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "../types.h"
|
||||||
|
|
||||||
|
namespace Core {
|
||||||
|
/*! \brief Exception and Interrupt control
|
||||||
|
*
|
||||||
|
* \see [ISDMv3, Chapter 6 Interrupt and Exception
|
||||||
|
* Handling](intel_manual_vol3.pdf#page=185)
|
||||||
|
*/
|
||||||
|
namespace Interrupt {
|
||||||
|
|
||||||
|
/*! \brief Bit in `FLAGS` register corresponding to the current interrupt state
|
||||||
|
*/
|
||||||
|
constexpr uintptr_t FLAG_ENABLE = 1 << 9;
|
||||||
|
|
||||||
|
/*! \brief List of used interrupt vectors.
|
||||||
|
*
|
||||||
|
* The exception vectors from `0` to `31` are reserved for traps, faults and
|
||||||
|
* aborts. Their behavior is different for each exception, some push an *error
|
||||||
|
* code*, some are not recoverable.
|
||||||
|
*
|
||||||
|
* The vectors from `32` to `255` are user defined interrupts.
|
||||||
|
*
|
||||||
|
* \see [ISDMv3, 6.15 Exception and Interrupt
|
||||||
|
* Reference](intel_manual_vol3.pdf#page=203)
|
||||||
|
* \todo(12) Add Keyboard and Panic vector numbers
|
||||||
|
*/
|
||||||
|
enum Vector {
|
||||||
|
// Predefined Exceptions
|
||||||
|
DIVISON_BY_ZERO =
|
||||||
|
0, ///< Divide-by-zero Error (at a `DIV`/`IDIV` instruction)
|
||||||
|
DEBUG = 1, ///< Debug exception
|
||||||
|
NON_MASKABLE_INTERRUPT = 2, ///< Non Maskable Interrupt
|
||||||
|
BREAKPOINT = 3, ///< Breakpoint exception (used for debugging)
|
||||||
|
OVERFLOW = 4, ///< Overflow exception (at `INTO` instruction)
|
||||||
|
BOUND_RANGE_EXCEEDED = 5, ///< Bound Range Exceeded (at `BOUND` instruction)
|
||||||
|
INVALID_OPCODE = 6, ///< Opcode at Instruction Pointer is invalid (you
|
||||||
|
///< probably shouldn't be here)
|
||||||
|
DEVICE_NOT_AVAILABLE =
|
||||||
|
7, ///< FPU/MMX/SSE instruction but corresponding extension not activated
|
||||||
|
DOUBLE_FAULT = 8, ///< Exception occurred while trying to call
|
||||||
|
///< exception/interrupt handler
|
||||||
|
// Coprocessor Segment Overrun (Legacy)
|
||||||
|
INVALID_TSS =
|
||||||
|
10, ///< Invalid Task State Segment selector (see error code for index)
|
||||||
|
SEGMENT_NOT_PRESENT =
|
||||||
|
11, ///< Segment not available (see error code for selector index)
|
||||||
|
STACK_SEGMENT_FAULT = 12, ///< Stack segment not available or invalid (see
|
||||||
|
///< error code for selector index)
|
||||||
|
GENERAL_PROTECTION_FAULT =
|
||||||
|
13, ///< Operation not allowed (see error code for selector index)
|
||||||
|
PAGE_FAULT = 14, ///< Operation on Page (r/w/x) not allowed for current
|
||||||
|
///< privilege (error code + `cr2`)
|
||||||
|
// reserved
|
||||||
|
FLOATING_POINT_EXCEPTION = 16, ///< x87 FPU error (at `WAIT`/`FWAIT`),
|
||||||
|
///< accidentally \ref Core::CR0_NE set?
|
||||||
|
ALIGNMENT_CHECK = 17, ///< Unaligned memory access in userspace (Exception
|
||||||
|
///< activated by \ref Core::CR0_AM)
|
||||||
|
MACHINE_CHECK = 18, ///< Model specific exception
|
||||||
|
SIMD_FP_EXCEPTION =
|
||||||
|
19, ///< SSE/MMX error (if \ref Core::CR4_OSXMMEXCPT activated)
|
||||||
|
SECURITY_EXCEPTION = 31,
|
||||||
|
|
||||||
|
// Interrupts
|
||||||
|
};
|
||||||
|
constexpr size_t VECTORS = 256;
|
||||||
|
|
||||||
|
/*! \brief Check if interrupts are enabled on this CPU
|
||||||
|
*
|
||||||
|
* This is done by pushing the `FLAGS` register onto stack,
|
||||||
|
* reading it into a register and checking the corresponding bit.
|
||||||
|
*
|
||||||
|
* \return `true` if enabled, `false` if disabled
|
||||||
|
*/
|
||||||
|
inline bool isEnabled() {
|
||||||
|
uintptr_t out;
|
||||||
|
asm volatile(
|
||||||
|
"pushf\n\t"
|
||||||
|
"pop %0\n\t"
|
||||||
|
: "=r"(out)
|
||||||
|
:
|
||||||
|
: "memory");
|
||||||
|
return (out & FLAG_ENABLE) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! \brief Allow interrupts
|
||||||
|
*
|
||||||
|
* Enables interrupt handling by executing the instruction `sti`.
|
||||||
|
* Since this instruction is delayed by one cycle, an subsequent `nop` is
|
||||||
|
* executed (to ensure deterministic behavior, independent from the compiler
|
||||||
|
* generated code)
|
||||||
|
*
|
||||||
|
* A pending interrupt (i.e., those arriving while interrupts were disabled)
|
||||||
|
* will be delivered after re-enabling interrupts.
|
||||||
|
*
|
||||||
|
* \see [ISDMv2, Chapter 4. STI - Set Interrupt
|
||||||
|
* Flag](intel_manual_vol2.pdf#page=1297)
|
||||||
|
*/
|
||||||
|
inline void enable() { asm volatile("sti\n\t nop\n\t" : : : "memory"); }
|
||||||
|
|
||||||
|
/*! \brief Forbid interrupts
|
||||||
|
*
|
||||||
|
* Prevents interrupt handling by executing the instruction `cli`.
|
||||||
|
* Will return the previous interrupt state.
|
||||||
|
* \return `true` if interrupts were enabled at the time of executing this
|
||||||
|
* function, `false` if they were already disabled.
|
||||||
|
*
|
||||||
|
* \see [ISDMv2, Chapter 3. CLI - Ckear Interrupt
|
||||||
|
* Flag](intel_manual_vol2.pdf#page=245)
|
||||||
|
*/
|
||||||
|
inline bool disable() {
|
||||||
|
bool enabled = isEnabled();
|
||||||
|
asm volatile("cli\n\t" : : : "memory");
|
||||||
|
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! \brief Restore interrupt
|
||||||
|
*
|
||||||
|
* Restore the interrupt state to the state prior to calling \ref disable() by
|
||||||
|
* using its return value.
|
||||||
|
*
|
||||||
|
* \note This function will never disable interrupts, even if val is false!
|
||||||
|
* This function is designed to allow nested disabling and restoring of
|
||||||
|
* the interrupt state.
|
||||||
|
*
|
||||||
|
* \param val if set to `true`, interrupts will be enabled; nothing will happen
|
||||||
|
* on false.
|
||||||
|
*/
|
||||||
|
inline void restore(bool val) {
|
||||||
|
if (val) {
|
||||||
|
enable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace Interrupt
|
||||||
|
} // namespace Core
|
||||||
103
arch/core_msr.h
Normal file
103
arch/core_msr.h
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
/*! \file
|
||||||
|
* \brief \ref Core::MSRs "Identifiers" for \ref Core::MSR "Model-Specific
|
||||||
|
* Register"
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "../types.h"
|
||||||
|
|
||||||
|
namespace Core {
|
||||||
|
/*! \brief Model-Specific Register Identifiers
|
||||||
|
*
|
||||||
|
* Selection of useful identifiers.
|
||||||
|
*
|
||||||
|
* \see [ISDMv4](intel_manual_vol4.pdf)
|
||||||
|
*/
|
||||||
|
enum MSRs {
|
||||||
|
MSR_PLATFORM_INFO =
|
||||||
|
0xce, ///< Platform information including bus frequency (Intel)
|
||||||
|
MSR_TSC_DEADLINE = 0x6e0, ///< Register for \ref LAPIC::Timer Deadline mode
|
||||||
|
// Fast system calls
|
||||||
|
// XXX: Remove if we don't do fast syscalls
|
||||||
|
MSR_EFER =
|
||||||
|
0xC0000080, ///< Extended Feature Enable Register, \see Core::MSR_EFER
|
||||||
|
MSR_STAR = 0xC0000081, ///< eip (protected mode), ring 0 and 3 segment bases
|
||||||
|
MSR_LSTAR = 0xC0000082, ///< rip (long mode)
|
||||||
|
MSR_SFMASK = 0xC0000084, ///< lower 32 bit: flag mask, if bit is set
|
||||||
|
///< corresponding rflag is cleared through syscall
|
||||||
|
|
||||||
|
// CPU local variables
|
||||||
|
MSR_FS_BASE = 0xC0000100,
|
||||||
|
MSR_GS_BASE = 0xC0000101, ///< Current GS base pointer
|
||||||
|
MSR_SHADOW_GS_BASE = 0xC0000102, ///< Usually called `MSR_KERNEL_GS_BASE` but
|
||||||
|
///< this is misleading
|
||||||
|
};
|
||||||
|
|
||||||
|
/* \brief Important bits in Extended Feature Enable Register (EFER)
|
||||||
|
*
|
||||||
|
* \see [ISDMv3, 2.2.1 Extended Feature Enable
|
||||||
|
* Register](intel_manual_vol3.pdf#page=69)
|
||||||
|
* \see [AAPMv2, 3.1.7 Extended Feature Enable
|
||||||
|
* Register](amd64_manual_vol2.pdf#page=107)
|
||||||
|
*/
|
||||||
|
enum MSR_EFER {
|
||||||
|
MSR_EFER_SCE = 1 << 0, ///< System Call Extensions
|
||||||
|
MSR_EFER_LME = 1 << 8, ///< Long mode enable
|
||||||
|
MSR_EFER_LMA = 1 << 10, ///< Long mode active
|
||||||
|
MSR_EFER_NXE = 1 << 11, ///< No-Execute Enable
|
||||||
|
MSR_EFER_SVME = 1 << 12, ///< Secure Virtual Machine Enable
|
||||||
|
MSR_EFER_LMSLE = 1 << 13, ///< Long Mode Segment Limit Enable
|
||||||
|
MSR_EFER_FFXSR = 1 << 14, ///< Fast `FXSAVE`/`FXRSTOR` instruction
|
||||||
|
MSR_EFER_TCE = 1 << 15, ///< Translation Cache Extension
|
||||||
|
};
|
||||||
|
|
||||||
|
/*! \brief Access to the Model-Specific Register (MSR)
|
||||||
|
*
|
||||||
|
* \see [ISDMv3, 9.4 Model-Specific Registers
|
||||||
|
* (MSRs)](intel_manual_vol3.pdf#page=319)
|
||||||
|
* \see [ISDMv4](intel_manual_vol4.pdf)
|
||||||
|
* \tparam id ID of the Model-Specific Register to access
|
||||||
|
*/
|
||||||
|
template <enum MSRs id>
|
||||||
|
class MSR {
|
||||||
|
/*! \brief Helper to access low and high bits of a 64 bit value
|
||||||
|
* \internal
|
||||||
|
*/
|
||||||
|
union uint64_parts {
|
||||||
|
struct {
|
||||||
|
uint32_t low;
|
||||||
|
uint32_t high;
|
||||||
|
} __attribute__((packed));
|
||||||
|
uint64_t value;
|
||||||
|
|
||||||
|
explicit uint64_parts(uint64_t value = 0) : value(value) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
/*! \brief Read the value of the current MSR
|
||||||
|
*
|
||||||
|
* \return Value stored in the MSR
|
||||||
|
*
|
||||||
|
* \see [ISDMv2, Chapter 4. RDMSR - Read from Model Specific
|
||||||
|
* Register](intel_manual_vol2.pdf#page=1186)
|
||||||
|
*/
|
||||||
|
static inline uint64_t read() {
|
||||||
|
uint64_parts p;
|
||||||
|
asm volatile("rdmsr \n\t" : "=a"(p.low), "=d"(p.high) : "c"(id));
|
||||||
|
return p.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! \brief Write a value into the current MSR
|
||||||
|
*
|
||||||
|
* \param value Value to write into the MSR
|
||||||
|
*
|
||||||
|
* \see [ISDMv2, Chapter 5. WRMSR - Write to Model Specific
|
||||||
|
* Register](intel_manual_vol2.pdf#page=1912)
|
||||||
|
*/
|
||||||
|
static inline void write(uint64_t value) {
|
||||||
|
uint64_parts p(value);
|
||||||
|
asm volatile("wrmsr \n\t" : : "c"(id), "a"(p.low), "d"(p.high));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Core
|
||||||
201
arch/cpuid.h
Normal file
201
arch/cpuid.h
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
/*! \file
|
||||||
|
* \brief \ref CPUID queries information about the processor
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
#include "../types.h"
|
||||||
|
|
||||||
|
/*! \brief Query information about the processor
|
||||||
|
*
|
||||||
|
* \note This is an interface to the `cpuid` instruction, which can return
|
||||||
|
* information about the processor. It should therefor **not** be confused with
|
||||||
|
* functionality to
|
||||||
|
* \ref Core::getID() "retrieve the ID of the current CPU (core)"!
|
||||||
|
*/
|
||||||
|
namespace CPUID {
|
||||||
|
|
||||||
|
/*! \brief Structure for register values returned by `cpuid` instruction
|
||||||
|
*/
|
||||||
|
union Reg {
|
||||||
|
struct {
|
||||||
|
uint32_t ebx, edx, ecx, eax;
|
||||||
|
};
|
||||||
|
char value[16];
|
||||||
|
};
|
||||||
|
|
||||||
|
enum Function {
|
||||||
|
HIGHEST_FUNCTION_PARAMETER = 0x0, ///< Maximum Input Value for Basic CPUID
|
||||||
|
///< Information (in register `eax`)
|
||||||
|
MANUFACTURER_ID = 0x0, ///< CPU String (in register `ebx`, `ecx` and `edx`)
|
||||||
|
PROCESSOR_INFO = 0x1, ///< Version Information like Type, Family, Model (in
|
||||||
|
///< register `eax`)
|
||||||
|
FEATURE_BITS = 0x1, ///< Feature Information (in register `ecx` and `edx`)
|
||||||
|
CACHE_INFORMATION = 0x2, ///< Cache and TLB Information
|
||||||
|
PROCESSOR_SERIAL_NUMBER = 0x3, ///< deprecated
|
||||||
|
HIGHEST_EXTENDED_FUNCTION =
|
||||||
|
0x80000000, ///< Maximum Input Value for Extended Function CPUID (in
|
||||||
|
///< register `eax`)
|
||||||
|
EXTENDED_PROCESSOR_INFO = 0x80000001, ///< Extended Processor Signature and
|
||||||
|
///< Feature Bits (in register `eax`)
|
||||||
|
EXTENDED_FEATURE_BITS = 0x80000001, ///< Extended Feature Information (in
|
||||||
|
///< register `ecx` and `edx`)
|
||||||
|
PROCESSOR_BRAND_STRING_1 = 0x80000002, ///< Processor Brand String (1/3)
|
||||||
|
PROCESSOR_BRAND_STRING_2 = 0x80000003, ///< Processor Brand String (2/3)
|
||||||
|
PROCESSOR_BRAND_STRING_3 = 0x80000004, ///< Processor Brand String (3/3)
|
||||||
|
ADVANCED_POWER_MANAGEMENT = 0x80000007, ///< Advanced Power Management (with
|
||||||
|
///< Invariant TSC in register `edx`)
|
||||||
|
ADDRESS_SIZES =
|
||||||
|
0x80000008, ///< Linear/Physical Address size (in register `eax`)
|
||||||
|
};
|
||||||
|
|
||||||
|
/*! \brief Get CPU identification and feature information
|
||||||
|
*
|
||||||
|
* \param eax Requested feature
|
||||||
|
* \return Register values filled by instruction `cpuid` for the requested
|
||||||
|
* feature
|
||||||
|
*
|
||||||
|
* \see [ISDMv2, Chapter 3. CPUID - CPU
|
||||||
|
* Identification](intel_manual_vol2.pdf#page=292)
|
||||||
|
*/
|
||||||
|
inline Reg get(Function eax) {
|
||||||
|
Reg r;
|
||||||
|
asm volatile("cpuid \n\t"
|
||||||
|
: "=a"(r.eax), "=b"(r.ebx), "=c"(r.ecx), "=d"(r.edx)
|
||||||
|
: "0"(eax));
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum FeatureECX {
|
||||||
|
FEATURE_SSE3 = 1 << 0, ///< Prescott New Instructions-SSE3 (PNI)
|
||||||
|
FEATURE_PCLMUL = 1 << 1, ///< Carry-less Multiplication
|
||||||
|
FEATURE_DTES64 = 1 << 2, ///< 64-bit debug store (edx bit 21)
|
||||||
|
FEATURE_MONITOR = 1 << 3, ///< MONITOR and MWAIT instructions (SSE3)
|
||||||
|
FEATURE_DS_CPL = 1 << 4, ///< CPL qualified debug store
|
||||||
|
FEATURE_VMX = 1 << 5, ///< Virtual Machine eXtensions
|
||||||
|
FEATURE_SMX = 1 << 6, ///< Safer Mode Extensions (LaGrande)
|
||||||
|
FEATURE_EST = 1 << 7, ///< Enhanced SpeedStep
|
||||||
|
FEATURE_TM2 = 1 << 8, ///< Thermal Monitor 2
|
||||||
|
FEATURE_SSSE3 = 1 << 9, ///< Supplemental SSE3 instructions
|
||||||
|
FEATURE_CID = 1 << 10, ///< L1 Context ID
|
||||||
|
FEATURE_SDBG = 1 << 11, ///< Silicon Debug interface
|
||||||
|
FEATURE_FMA = 1 << 12, ///< Fused multiply-add (FMA3)
|
||||||
|
FEATURE_CX16 = 1 << 13, ///< CMPXCHG16B instruction
|
||||||
|
FEATURE_ETPRD = 1 << 14, ///< Can disable sending task priority messages
|
||||||
|
FEATURE_PDCM = 1 << 15, ///< Perfmon & debug capability
|
||||||
|
FEATURE_PCIDE = 1 << 17, ///< Process context identifiers (CR4 bit 17)
|
||||||
|
FEATURE_DCA = 1 << 18, ///< Direct cache access for DMA writes
|
||||||
|
FEATURE_SSE4_1 = 1 << 19, ///< SSE4.1 instructions
|
||||||
|
FEATURE_SSE4_2 = 1 << 20, ///< SSE4.2 instructions
|
||||||
|
FEATURE_X2APIC = 1 << 21, ///< x2APIC
|
||||||
|
FEATURE_MOVBE = 1 << 22, ///< MOVBE instruction (big-endian)
|
||||||
|
FEATURE_POPCNT = 1 << 23, ///< POPCNT instruction
|
||||||
|
FEATURE_TSC_DEADLINE =
|
||||||
|
1
|
||||||
|
<< 24, ///< APIC implements one-shot operation using a TSC deadline value
|
||||||
|
FEATURE_AES = 1 << 25, ///< AES instruction set
|
||||||
|
FEATURE_XSAVE = 1 << 26, ///< XSAVE, XRESTOR, XSETBV, XGETBV
|
||||||
|
FEATURE_OSXSAVE = 1 << 27, ///< XSAVE enabled by OS
|
||||||
|
FEATURE_AVX = 1 << 28, ///< Advanced Vector Extensions
|
||||||
|
FEATURE_F16C = 1 << 29, ///< F16C (half-precision) FP feature
|
||||||
|
FEATURE_RDRND =
|
||||||
|
1 << 30, ///< RDRAND (on-chip random number generator) feature
|
||||||
|
FEATURE_HYPERVISOR =
|
||||||
|
1 << 31 ///< Hypervisor present (always zero on physical CPUs)
|
||||||
|
};
|
||||||
|
|
||||||
|
enum FeatureEDX {
|
||||||
|
FEATURE_FPU = 1 << 0, ///< Onboard x87 FPU
|
||||||
|
FEATURE_VME =
|
||||||
|
1 << 1, ///< Virtual 8086 mode extensions (such as VIF, VIP, PIV)
|
||||||
|
FEATURE_DE = 1 << 2, ///< Debugging extensions (CR4 bit 3)
|
||||||
|
FEATURE_PSE = 1 << 3, ///< Page Size Extension
|
||||||
|
FEATURE_TSC = 1 << 4, ///< Time Stamp Counter
|
||||||
|
FEATURE_MSR = 1 << 5, ///< Model-specific registers
|
||||||
|
FEATURE_PAE = 1 << 6, ///< Physical Address Extension
|
||||||
|
FEATURE_MCE = 1 << 7, ///< Machine Check Exception
|
||||||
|
FEATURE_CX8 = 1 << 8, ///< CMPXCHG8 (compare-and-swap) instruction
|
||||||
|
FEATURE_APIC =
|
||||||
|
1 << 9, ///< Onboard Advanced Programmable Interrupt Controller
|
||||||
|
FEATURE_SEP = 1 << 11, ///< SYSENTER and SYSEXIT instructions
|
||||||
|
FEATURE_MTRR = 1 << 12, ///< Memory Type Range Registers
|
||||||
|
FEATURE_PGE = 1 << 13, ///< Page Global Enable bit in CR4
|
||||||
|
FEATURE_MCA = 1 << 14, ///< Machine check architecture
|
||||||
|
FEATURE_CMOV = 1 << 15, ///< Conditional move and FCMOV instructions
|
||||||
|
FEATURE_PAT = 1 << 16, ///< Page Attribute Table
|
||||||
|
FEATURE_PSE36 = 1 << 17, ///< 36-bit page size extension
|
||||||
|
FEATURE_PSN = 1 << 18, ///< Processor Serial Number
|
||||||
|
FEATURE_CLF = 1 << 19, ///< CLFLUSH instruction (SSE2)
|
||||||
|
FEATURE_DTES = 1 << 21, ///< Debug store: save trace of executed jumps
|
||||||
|
FEATURE_ACPI = 1 << 22, ///< Onboard thermal control MSRs for ACPI
|
||||||
|
FEATURE_MMX = 1 << 23, ///< MMX instructions
|
||||||
|
FEATURE_FXSR = 1 << 24, ///< FXSAVE, FXRESTOR instructions, CR4 bit 9
|
||||||
|
FEATURE_SSE = 1 << 25, ///< SSE instructions (a.k.a. Katmai New Instructions)
|
||||||
|
FEATURE_SSE2 = 1 << 26, ///< SSE2 instructions
|
||||||
|
FEATURE_SS = 1 << 27, ///< CPU cache implements self-snoop
|
||||||
|
FEATURE_HTT = 1 << 28, ///< Hyper-threading
|
||||||
|
FEATURE_TM1 = 1 << 29, ///< Thermal monitor automatically limits temperature
|
||||||
|
FEATURE_IA64 = 1 << 30, ///< IA64 processor emulating x86
|
||||||
|
FEATURE_PBE = 1 << 31 ///< Pending Break Enable (PBE# pin) wakeup capability
|
||||||
|
};
|
||||||
|
|
||||||
|
enum ExtendedFeatureEDX {
|
||||||
|
EXTENDED_FEATURE_FPU = 1 << 0, ///< Onboard x87 FPU
|
||||||
|
EXTENDED_FEATURE_VME =
|
||||||
|
1 << 1, ///< Virtual 8086 mode extensions (such as VIF, VIP, PIV)
|
||||||
|
EXTENDED_FEATURE_DE = 1 << 2, ///< Debugging extensions (CR4 bit 3)
|
||||||
|
EXTENDED_FEATURE_PSE = 1 << 3, ///< Page Size Extension
|
||||||
|
EXTENDED_FEATURE_TSC = 1 << 4, ///< Time Stamp Counter
|
||||||
|
EXTENDED_FEATURE_MSR = 1 << 5, ///< Model-specific registers
|
||||||
|
EXTENDED_FEATURE_PAE = 1 << 6, ///< Physical Address Extension
|
||||||
|
EXTENDED_FEATURE_MCE = 1 << 7, ///< Machine Check Exception
|
||||||
|
EXTENDED_FEATURE_CX8 = 1 << 8, ///< CMPXCHG8 (compare-and-swap) instruction
|
||||||
|
EXTENDED_FEATURE_APIC =
|
||||||
|
1 << 9, ///< Onboard Advanced Programmable Interrupt Controller
|
||||||
|
EXTENDED_FEATURE_SYSCALL = 1 << 11, ///< SYSCALL and SYSRET instructions
|
||||||
|
EXTENDED_FEATURE_MTRR = 1 << 12, ///< Memory Type Range Registers
|
||||||
|
EXTENDED_FEATURE_PGE = 1 << 13, ///< Page Global Enable bit in CR4
|
||||||
|
EXTENDED_FEATURE_MCA = 1 << 14, ///< Machine check architecture
|
||||||
|
EXTENDED_FEATURE_CMOV = 1 << 15, ///< Conditional move and FCMOV instructions
|
||||||
|
EXTENDED_FEATURE_PAT = 1 << 16, ///< Page Attribute Table
|
||||||
|
EXTENDED_FEATURE_PSE36 = 1 << 17, ///< 36-bit page size extension
|
||||||
|
EXTENDED_FEATURE_MP = 1 << 19, ///< Multiprocessor Capable
|
||||||
|
EXTENDED_FEATURE_NX = 1 << 20, ///< Non-executable bit
|
||||||
|
EXTENDED_FEATURE_MMXEXT = 1 << 22, ///< extended MMX instructions
|
||||||
|
EXTENDED_FEATURE_MMX = 1 << 23, ///< MMX instructions
|
||||||
|
EXTENDED_FEATURE_FXSR = 1
|
||||||
|
<< 24, ///< FXSAVE, FXRESTOR instructions, CR4 bit 9
|
||||||
|
EXTENDED_FEATURE_FXSR_OPT = 1 << 25, ///< FXSAVE, FXRESTOR optimizations
|
||||||
|
EXTENDED_FEATURE_PDPE1GB = 1 << 26, ///< Gibibyte Pages
|
||||||
|
EXTENDED_FEATURE_RDTSCP = 1 << 27, ///< CPU cache implements self-snoop
|
||||||
|
EXTENDED_FEATURE_LM = 1 << 29, ///< Long Mode (x64)
|
||||||
|
EXTENDED_FEATURE_3DNOWEXT = 1 << 30, ///< Extended 3DNow! instructions
|
||||||
|
EXTENDED_FEATURE_3DNOW = 1 << 31 ///< 3DNow! instructions
|
||||||
|
};
|
||||||
|
|
||||||
|
/*! \brief Check if feature is provided by this system
|
||||||
|
*
|
||||||
|
* \param feature Feature to test
|
||||||
|
* \return `true` if available, `false` otherwise
|
||||||
|
*/
|
||||||
|
inline bool has(enum FeatureECX feature) {
|
||||||
|
return (get(FEATURE_BITS).ecx & feature) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! \brief Check if feature is provided by this system
|
||||||
|
*
|
||||||
|
* \param feature Feature to test
|
||||||
|
* \return `true` if available, `false` otherwise
|
||||||
|
*/
|
||||||
|
inline bool has(enum FeatureEDX feature) {
|
||||||
|
return (get(FEATURE_BITS).edx & feature) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! \brief Check if feature is provided by this system
|
||||||
|
*
|
||||||
|
* \param feature Extended feature to test
|
||||||
|
* \return `true` if available, `false` if either feature or extended features
|
||||||
|
* are unavailable
|
||||||
|
*/
|
||||||
|
inline bool has(enum ExtendedFeatureEDX feature) {
|
||||||
|
return (get(EXTENDED_FEATURE_BITS).edx & feature) != 0;
|
||||||
|
}
|
||||||
|
} // namespace CPUID
|
||||||
36
arch/gdt.cc
Normal file
36
arch/gdt.cc
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
#include "gdt.h"
|
||||||
|
|
||||||
|
#include "core.h"
|
||||||
|
|
||||||
|
namespace GDT {
|
||||||
|
|
||||||
|
// The static 32-bit Global Descriptor Table (GDT)
|
||||||
|
alignas(16) constinit SegmentDescriptor protected_mode[] = {
|
||||||
|
// Null descriptor
|
||||||
|
{},
|
||||||
|
|
||||||
|
// Global code segment von 0-4GB
|
||||||
|
SegmentDescriptor::Segment(0, UINT32_MAX, true, 0, SIZE_32BIT),
|
||||||
|
|
||||||
|
// Global data segment von 0-4GB
|
||||||
|
SegmentDescriptor::Segment(0, UINT32_MAX, false, 0, SIZE_32BIT),
|
||||||
|
};
|
||||||
|
extern "C" constexpr Pointer gdt_protected_mode_pointer(protected_mode);
|
||||||
|
|
||||||
|
// The static 64-bit Global Descriptor Table (GDT)
|
||||||
|
// \see [ISDMv3 3.2.4 Segmentation in IA-32e
|
||||||
|
// Mode](intel_manual_vol3.pdf#page=91)
|
||||||
|
alignas(16) constinit SegmentDescriptor long_mode[] = {
|
||||||
|
// Null descriptor
|
||||||
|
SegmentDescriptor::Null(),
|
||||||
|
|
||||||
|
// Global code segment
|
||||||
|
SegmentDescriptor::Segment64(true, 0),
|
||||||
|
|
||||||
|
// Global data segment
|
||||||
|
SegmentDescriptor::Segment64(false, 0),
|
||||||
|
|
||||||
|
};
|
||||||
|
extern "C" constexpr Pointer gdt_long_mode_pointer(long_mode);
|
||||||
|
|
||||||
|
} // namespace GDT
|
||||||
199
arch/gdt.h
Normal file
199
arch/gdt.h
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
/*! \file
|
||||||
|
* \brief The \ref GDT "Global Descriptor Table (GDT)".
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "../types.h"
|
||||||
|
|
||||||
|
/*! \brief Abstracts the GDT that, primarily, contains descriptors to memory
|
||||||
|
* segments.
|
||||||
|
* \ingroup memory
|
||||||
|
*
|
||||||
|
* The GDT is a table that primarily contains segment descriptors. Segment
|
||||||
|
* descriptors has a size of 8 Bytes and contains the size, position, access
|
||||||
|
* rights, and purpose of such a segment. Unlike the LDT, the GDT is shared
|
||||||
|
* between all processes and may contain TSS and LDT descriptors. For the
|
||||||
|
* kernel, the first entry is required to be a null descriptor and the code and
|
||||||
|
* data segments. To support user-mode processes, additional TSS, code, and data
|
||||||
|
* segments for ring 3 must be added.
|
||||||
|
*
|
||||||
|
* The base address and size of the GDT are written to the GDTR register during
|
||||||
|
* boot (via. `lgdt`).
|
||||||
|
*
|
||||||
|
* \see [ISDMv3, 2.4.1; Global Descriptor Table Register
|
||||||
|
* (GDTR)](intel_manual_vol3.pdf#page=72)
|
||||||
|
* \see [ISDMv3, 3.5.1; Segment
|
||||||
|
* Descriptor Tables](intel_manual_vol3.pdf#page=99)
|
||||||
|
*/
|
||||||
|
namespace GDT {
|
||||||
|
|
||||||
|
enum Segments {
|
||||||
|
SEGMENT_NULL = 0,
|
||||||
|
SEGMENT_KERNEL_CODE,
|
||||||
|
SEGMENT_KERNEL_DATA,
|
||||||
|
};
|
||||||
|
|
||||||
|
/*! \brief Unit of the segment limit
|
||||||
|
*/
|
||||||
|
enum Granularity {
|
||||||
|
GRANULARITY_BYTES = 0, ///< Segment limit in Bytes
|
||||||
|
GRANULARITY_4KBLOCK = 1 ///< Segment limit in blocks of 4 Kilobytes
|
||||||
|
};
|
||||||
|
|
||||||
|
/*! \brief Descriptor type */
|
||||||
|
enum DescriptorType {
|
||||||
|
DESCRIPTOR_SYSTEM = 0, ///< entry is a system segment
|
||||||
|
DESCRIPTOR_CODEDATA = 1, ///< entry is a code/data segment
|
||||||
|
};
|
||||||
|
|
||||||
|
/*! \brief Address width
|
||||||
|
*/
|
||||||
|
enum Size {
|
||||||
|
SIZE_16BIT = 0, ///< 16-bit (D/B = 0, L = 0)
|
||||||
|
SIZE_32BIT = 2, ///< 32-bit (D/B = 1, L = 0)
|
||||||
|
SIZE_64BIT = 1, ///< 64-bit (D/B = 0, L = 1)
|
||||||
|
};
|
||||||
|
|
||||||
|
/*! \brief Type flags for used descriptor types
|
||||||
|
*/
|
||||||
|
enum TypeFlags {
|
||||||
|
TYPE_DATA_RW = 0b0010ull, ///< Data rw, not expanding down
|
||||||
|
TYPE_CODE_RX = 0b1010ull, ///< Code rx, non-conforming
|
||||||
|
};
|
||||||
|
|
||||||
|
/*! \brief Describes the structure of segment descriptors
|
||||||
|
*
|
||||||
|
* A data structure that contains size, position, access rights, and purpose of
|
||||||
|
* any segment. Segment descriptors are used in both the GDT, as well as in
|
||||||
|
* LDTs.
|
||||||
|
*
|
||||||
|
* \see [ISDMv3, 3.4.5; Segment Descriptors](intel_manual_vol3.pdf#page=95)
|
||||||
|
* \see [AAPMv2, 4.7 Legacy Segment Descriptors](amd64_manual_vol2.pdf#page=132)
|
||||||
|
* \see [AAPMv2, 4.8 Long-Mode Segment
|
||||||
|
* Descriptors](amd64_manual_vol2.pdf#page=140)
|
||||||
|
*/
|
||||||
|
union SegmentDescriptor {
|
||||||
|
// Universally valid values (shared across all segment types)
|
||||||
|
struct {
|
||||||
|
uint64_t limit_low : 16; ///< Least-significant bits of segment size
|
||||||
|
///< (influenced by granularity!)
|
||||||
|
uint64_t base_low : 24; ///< Least-significant bits of base address
|
||||||
|
uint64_t
|
||||||
|
type : 4; ///< Meaning of those 4 bits depends on descriptor_type below
|
||||||
|
DescriptorType descriptor_type : 1; ///< Descriptor type (influences the
|
||||||
|
///< meaning of the 3 bits above)
|
||||||
|
uint64_t privilege_level : 2; ///< Ring for this segment
|
||||||
|
bool present : 1; ///< Entry is valid iff set to `true`
|
||||||
|
uint64_t limit_high : 4; ///< Most-significant bits of segment size
|
||||||
|
bool available : 1; ///< Bit which can be used for other purposes (in
|
||||||
|
///< software)
|
||||||
|
uint64_t custom : 2; ///< Meaning of those 2 bits relate to descriptor_type
|
||||||
|
///< and type
|
||||||
|
Granularity
|
||||||
|
granularity : 1; ///< Unit used as granularity for the segment limit
|
||||||
|
uint64_t base_high : 8; ///< most-significant bits of base address
|
||||||
|
} __attribute__((packed));
|
||||||
|
|
||||||
|
uint64_t value; ///!< Merged value
|
||||||
|
|
||||||
|
/*! \brief Explicitly constructs a null descriptor.
|
||||||
|
*/
|
||||||
|
consteval static SegmentDescriptor Null() {
|
||||||
|
return SegmentDescriptor{
|
||||||
|
.value = 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! \brief Constructs a code/data segment descriptor.
|
||||||
|
* \param base Base Address of segment
|
||||||
|
* \param limit Size of segment
|
||||||
|
* \param code Code or data segment
|
||||||
|
* \param ring Privilege level
|
||||||
|
* \param size Address width
|
||||||
|
*/
|
||||||
|
consteval static SegmentDescriptor Segment(uintptr_t base, uint32_t limit,
|
||||||
|
bool code, uint64_t ring,
|
||||||
|
Size size) {
|
||||||
|
return SegmentDescriptor{
|
||||||
|
.limit_low = limit >> (limit > 0xFFFFF ? 12 : 0) & 0xFFFF,
|
||||||
|
.base_low = base & 0xFFFFFF,
|
||||||
|
.type = code ? TYPE_CODE_RX : TYPE_DATA_RW,
|
||||||
|
.descriptor_type = DESCRIPTOR_CODEDATA,
|
||||||
|
.privilege_level = ring,
|
||||||
|
.present = true,
|
||||||
|
.limit_high = (limit > 0xFFFFF ? (limit >> 28) : (limit >> 16)) & 0xF,
|
||||||
|
.available = false,
|
||||||
|
.custom = size,
|
||||||
|
.granularity =
|
||||||
|
limit > 0xFFFFF ? GRANULARITY_4KBLOCK : GRANULARITY_BYTES,
|
||||||
|
.base_high = (base >> 24) & 0xFF,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! \brief Constructs a 64bit code/data segment descriptor.
|
||||||
|
* \param code Code or data segment
|
||||||
|
* \param ring Privilege level
|
||||||
|
*/
|
||||||
|
consteval static SegmentDescriptor Segment64(bool code, int ring) {
|
||||||
|
return SegmentDescriptor::Segment(0, 0, code, ring, SIZE_64BIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
} __attribute__((packed));
|
||||||
|
|
||||||
|
static_assert(sizeof(SegmentDescriptor) == 8,
|
||||||
|
"GDT::SegmentDescriptor has wrong size");
|
||||||
|
|
||||||
|
/*! \brief Structure that describes a GDT Pointer (aka GDT Descriptor)
|
||||||
|
*
|
||||||
|
* It contains both the length (in bytes) of the GDT (minus 1 byte) and the
|
||||||
|
* pointer to the GDT. The pointer to the GDT can be loaded using the
|
||||||
|
* instruction `lgdt`.
|
||||||
|
*
|
||||||
|
* \note As Intel uses little endian for representing multi-byte values, the
|
||||||
|
* GDT::Pointer structure can be used for 16, 32, and 64 bit descriptor tables:
|
||||||
|
* \verbatim
|
||||||
|
* | 16 bit | 16 bit | 16 bit | 16 bit | 16 bit |
|
||||||
|
* +--------+---------------------------------------+
|
||||||
|
* Pointer | limit | base (up to 64 bit) |
|
||||||
|
* +--------+---------+---------+---------+---------+
|
||||||
|
* | used for 16 bit | ignored... |
|
||||||
|
* | used for 32 bit | ignored... |
|
||||||
|
* | used for 64 bit |
|
||||||
|
* \endverbatim
|
||||||
|
*
|
||||||
|
* \see [ISDMv3, Figure 2-6; Memory Management
|
||||||
|
* Registers](intel_manual_vol3.pdf#page=72)
|
||||||
|
*/
|
||||||
|
struct Pointer {
|
||||||
|
uint16_t limit; //!< GDT size in bytes (minus 1 byte)
|
||||||
|
void* base; //!< GDT base address
|
||||||
|
|
||||||
|
/*! \brief Constructor (automatic length)
|
||||||
|
* \param desc Array of GDT segment descriptors -- must be defined in the same
|
||||||
|
* module!
|
||||||
|
*/
|
||||||
|
template <typename T, size_t LEN>
|
||||||
|
explicit constexpr Pointer(const T (&desc)[LEN])
|
||||||
|
: limit(LEN * sizeof(T) - 1), base(const_cast<T*>(desc)) {}
|
||||||
|
|
||||||
|
/*! \brief Constructor
|
||||||
|
* \param desc Address of the GDT segment descriptors
|
||||||
|
* \param len Number of entries
|
||||||
|
*/
|
||||||
|
consteval Pointer(void* desc, size_t len)
|
||||||
|
: limit(len * sizeof(SegmentDescriptor) - 1), base(desc) {}
|
||||||
|
|
||||||
|
/*! \brief Set an address
|
||||||
|
* \note On change, `lgdt` must be executed again
|
||||||
|
* \param desc Address of the GDT segment descriptors
|
||||||
|
* \param len Number of entries
|
||||||
|
*/
|
||||||
|
constexpr void set(void* desc, size_t len) {
|
||||||
|
limit = len * sizeof(SegmentDescriptor) - 1;
|
||||||
|
base = desc;
|
||||||
|
}
|
||||||
|
} __attribute__((packed));
|
||||||
|
|
||||||
|
static_assert(sizeof(Pointer) == 10, "GDT::Pointer has wrong size");
|
||||||
|
|
||||||
|
} // namespace GDT
|
||||||
35
arch/idt.cc
Normal file
35
arch/idt.cc
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
#include "idt.h"
|
||||||
|
|
||||||
|
#include "core_interrupt.h"
|
||||||
|
|
||||||
|
namespace IDT {
|
||||||
|
|
||||||
|
// Interrupt Descriptor Table, 8 Byte aligned
|
||||||
|
constinit struct InterruptDescriptor idt[256] = {};
|
||||||
|
|
||||||
|
// Struct used for loading (the address of) the Interrupt Descriptor Table into
|
||||||
|
// the IDT-Register
|
||||||
|
struct Register {
|
||||||
|
uint16_t limit; // Address of the last valid byte (relative to base)
|
||||||
|
struct InterruptDescriptor* base;
|
||||||
|
explicit Register(uint8_t max = 255) {
|
||||||
|
limit = (max + static_cast<uint16_t>(1)) * sizeof(InterruptDescriptor) - 1;
|
||||||
|
base = idt;
|
||||||
|
}
|
||||||
|
} __attribute__((packed));
|
||||||
|
|
||||||
|
static_assert(sizeof(InterruptDescriptor) == 16,
|
||||||
|
"IDT::InterruptDescriptor has wrong size");
|
||||||
|
static_assert(sizeof(Register) == 10, "IDT::Register has wrong size");
|
||||||
|
static_assert(alignof(decltype(idt)) % 8 == 0, "IDT must be 8 byte aligned!");
|
||||||
|
|
||||||
|
void load() {
|
||||||
|
// Create structure required for writing to idtr and load via lidt
|
||||||
|
Register idtr(Core::Interrupt::VECTORS - 1);
|
||||||
|
asm volatile("lidt %0\n\t" ::"m"(idtr));
|
||||||
|
}
|
||||||
|
|
||||||
|
void set(Core::Interrupt::Vector vector, InterruptDescriptor descriptor) {
|
||||||
|
idt[(uint8_t)vector] = descriptor;
|
||||||
|
}
|
||||||
|
} // namespace IDT
|
||||||
210
arch/idt.h
Normal file
210
arch/idt.h
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
/*! \file
|
||||||
|
* \brief \ref IDT "Interrupt Descriptor Table (IDT)" containing the entry
|
||||||
|
* points for interrupt handling.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "../types.h"
|
||||||
|
#include "core_interrupt.h"
|
||||||
|
|
||||||
|
/*! \brief "Interrupt Descriptor Table (IDT)
|
||||||
|
* \ingroup interrupts
|
||||||
|
*
|
||||||
|
* \see [ISDMv3 6.14 Exception and Interrupt Handling in 64-bit
|
||||||
|
* Mode](intel_manual_vol3.pdf#page=200)
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*! \brief Preserved interrupt context
|
||||||
|
*
|
||||||
|
* After an interrupt was triggered, the core first saves the basic context
|
||||||
|
* (current code- & stack segment, instruction & stack pointer and the status
|
||||||
|
* flags register) and looks up the handling function for the vector using the
|
||||||
|
* \ref IDT. No other registers are saved or restored automatically. It is the
|
||||||
|
* handlers (our) job to save and restore all modified registers. However, most
|
||||||
|
* handlers in StuBS are implemented directly in C++ utilizing the `interrupt`
|
||||||
|
* attribute: The compiler treats all modified registers as callee-saved, which
|
||||||
|
* saves us a lot of work, but prevents us from knowing the exact contents of
|
||||||
|
* each regiser (we don't know if/when the compiler modified it). `interrupt`
|
||||||
|
* functions receive up to two parameters: A pointer to this /ref
|
||||||
|
* InterruptContext and, depending on the interrupt, an error code, which is
|
||||||
|
* also pushed onto the stack by the CPU. Contrary to "normal" functions, the
|
||||||
|
* compiler will return using the `iret` instruction.
|
||||||
|
*/
|
||||||
|
struct InterruptContext {
|
||||||
|
// Context saved by CPU
|
||||||
|
// uintptr_t error_code; ///< Error Code
|
||||||
|
uintptr_t ip; ///< Instruction Pointer (at interrupt)
|
||||||
|
uintptr_t cs : 16; ///< Code segment (in case of a ring switch it is the
|
||||||
|
///< segment of the user mode)
|
||||||
|
uintptr_t : 0; ///< Alignment (due to 16 bit code segment)
|
||||||
|
uintptr_t flags; ///< Status flags register
|
||||||
|
uintptr_t sp; ///< Stack pointer (at interrupt)
|
||||||
|
uintptr_t ss : 16; ///< Stack segment (in case of a ring switch it is the
|
||||||
|
///< segment of the user mode)
|
||||||
|
uintptr_t : 0; ///< Alignment (due to 16 bit stack segment)
|
||||||
|
} __attribute__((packed));
|
||||||
|
static_assert(sizeof(InterruptContext) == 5 * 8,
|
||||||
|
"InterruptContext has wrong size");
|
||||||
|
|
||||||
|
namespace IDT {
|
||||||
|
/*! \brief Gate types
|
||||||
|
*
|
||||||
|
* \see [ISDMv3 3.5 System Descriptor Types](intel_manual_vol3.pdf#page=99)
|
||||||
|
*/
|
||||||
|
enum Gate {
|
||||||
|
GATE_INT = 0x6, ///< Interrupt Gate (CPU disables interrupts unpon entry)
|
||||||
|
GATE_TRAP = 0x7, ///< Trap Gate (interrupts remain enabled unpon entry)
|
||||||
|
};
|
||||||
|
|
||||||
|
/*! \brief Segment type
|
||||||
|
*
|
||||||
|
* \see [ISDMv3 3.5 System Descriptor Types](intel_manual_vol3.pdf#page=99)
|
||||||
|
*/
|
||||||
|
enum GateSize {
|
||||||
|
GATE_SIZE_16 = 0, ///< 16 bit
|
||||||
|
GATE_SIZE_32 = 1, ///< 32 bit
|
||||||
|
GATE_SIZE_64 = 1, ///< 64 bit
|
||||||
|
};
|
||||||
|
|
||||||
|
/*! \brief Descriptor Privilege Level
|
||||||
|
*/
|
||||||
|
enum DPL {
|
||||||
|
DPL_KERNEL = 0, ///< Ring 0 / Kernel mode
|
||||||
|
/* DPLs 1 and 2 are unused */
|
||||||
|
DPL_USER = 3, ///< Ring 3 / User mode
|
||||||
|
};
|
||||||
|
|
||||||
|
/*! \brief Interrupt handler that returns after execution (trap/fault).
|
||||||
|
*/
|
||||||
|
using ReturningHandler = void (*)(InterruptContext*);
|
||||||
|
|
||||||
|
/*! \brief Interrupt handler that returns after execution (trap/fault) and
|
||||||
|
* receives an error code.
|
||||||
|
*/
|
||||||
|
using ReturningHandlerWithError = void (*)(InterruptContext*, uint64_t);
|
||||||
|
|
||||||
|
/*! \brief Interrupt handler that does **not** return after execution (abort).
|
||||||
|
*/
|
||||||
|
using DivergingHandler = void (*)(InterruptContext*);
|
||||||
|
|
||||||
|
/*! \brief Interrupt handler that does **not** return after execution (abort)
|
||||||
|
* and receives an error code.
|
||||||
|
*/
|
||||||
|
using DivergingHandlerWithError = void (*)(InterruptContext*, uint64_t);
|
||||||
|
|
||||||
|
/*! \brief Interrupt Descriptor stored in the Interrupt-Descriptor Table (IDT)
|
||||||
|
*/
|
||||||
|
struct alignas(8) InterruptDescriptor {
|
||||||
|
uint16_t address_low; ///< lower interrupt function offset
|
||||||
|
uint16_t selector; ///< code segment selector in GDT or LDT
|
||||||
|
union {
|
||||||
|
struct {
|
||||||
|
uint8_t ist : 3; ///< Interrupt Stack Index
|
||||||
|
uint8_t : 5; ///< unused, has to be 0
|
||||||
|
Gate type : 3; ///< gate type
|
||||||
|
GateSize size : 1; ///< gate size
|
||||||
|
uint8_t : 1; ///< unused, has to be 0
|
||||||
|
DPL dpl : 2; ///< descriptor privilege level
|
||||||
|
bool present : 1; ///< present: 1 for interrupts
|
||||||
|
} __attribute__((packed));
|
||||||
|
uint16_t flags;
|
||||||
|
};
|
||||||
|
uint64_t address_high : 48; ///< higher interrupt function offset
|
||||||
|
uint64_t : 0; ///< fill until aligned with 64 bit
|
||||||
|
|
||||||
|
/*! \brief Create a non-present interrupt descriptor.
|
||||||
|
*/
|
||||||
|
InterruptDescriptor() = default;
|
||||||
|
|
||||||
|
/*! \brief Create an interrupt descriptor.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* \param handler Entry point for interrupt handling
|
||||||
|
* \param ist Stack index from the \ref TaskStateSegment for the interrupt
|
||||||
|
* handler. Set to 0 to use current stack.
|
||||||
|
* \param dpl Permissions required for enter this interrupt handler
|
||||||
|
* (kernel- or user space)
|
||||||
|
*/
|
||||||
|
InterruptDescriptor(uintptr_t handler, uint8_t ist, DPL dpl)
|
||||||
|
: address_low(handler & 0xffff),
|
||||||
|
selector(8), // XXX: This should come from `Segments`
|
||||||
|
ist(ist),
|
||||||
|
type(GATE_INT),
|
||||||
|
size(GATE_SIZE_64),
|
||||||
|
dpl(dpl),
|
||||||
|
present(true),
|
||||||
|
address_high((handler >> 16) & 0xffffffffffff) {}
|
||||||
|
|
||||||
|
/*! \brief Create an interrupt descriptor for a handler that does return
|
||||||
|
* (trap/fault).
|
||||||
|
*
|
||||||
|
* \param handler Entry point for interrupt handling
|
||||||
|
* \param ist Stack index from the \ref TaskStateSegment for the interrupt
|
||||||
|
* handler. Set to 0 to use current stack.
|
||||||
|
* \param dpl Permissions required for enter this interrupt handler
|
||||||
|
* (kernel- or user space)
|
||||||
|
*/
|
||||||
|
static InterruptDescriptor Returning(ReturningHandler handler,
|
||||||
|
uint8_t ist = 0, DPL dpl = DPL_KERNEL) {
|
||||||
|
return {reinterpret_cast<uintptr_t>(handler), ist, dpl};
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! \brief Create an interrupt descriptor for a handler that does return
|
||||||
|
* (traps/fault) and receives an error code.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* \param handler Entry point for interrupt handling
|
||||||
|
* \param ist Stack index from the \ref TaskStateSegment for the interrupt
|
||||||
|
* handler. Set to 0 to use current stack.
|
||||||
|
* \param dpl Permissions required for enter this interrupt handler
|
||||||
|
* (kernel- or user space)
|
||||||
|
*/
|
||||||
|
static InterruptDescriptor ReturningWithError(
|
||||||
|
ReturningHandlerWithError handler, uint8_t ist = 0,
|
||||||
|
DPL dpl = DPL_KERNEL) {
|
||||||
|
return {reinterpret_cast<uintptr_t>(handler), ist, dpl};
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! \brief Create an interrupt descriptor for a handler that does **not**
|
||||||
|
* return (abort).
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* \param handler Entry point for interrupt handling
|
||||||
|
* \param ist Stack index from the \ref TaskStateSegment for the interrupt
|
||||||
|
* handler. Set to 0 to use current stack.
|
||||||
|
* \param dpl Permissions required for enter this interrupt handler
|
||||||
|
* (kernel- or user space)
|
||||||
|
*/
|
||||||
|
static InterruptDescriptor Diverging(DivergingHandler handler,
|
||||||
|
uint8_t ist = 0, DPL dpl = DPL_KERNEL) {
|
||||||
|
return {reinterpret_cast<uintptr_t>(handler), ist, dpl};
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! \brief Create an interrupt descriptor for a handler that does **not**
|
||||||
|
* return (abort) and receives an error code.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* \param handler Entry point for interrupt handling
|
||||||
|
* \param ist Stack index from the \ref TaskStateSegment for the interrupt
|
||||||
|
* handler. Set to 0 to use current stack.
|
||||||
|
* \param dpl Permissions required for enter this interrupt handler
|
||||||
|
* (kernel- or user space)
|
||||||
|
*/
|
||||||
|
static InterruptDescriptor DivergingWithError(
|
||||||
|
DivergingHandlerWithError handler, uint8_t ist = 0,
|
||||||
|
DPL dpl = DPL_KERNEL) {
|
||||||
|
return {reinterpret_cast<uintptr_t>(handler), ist, dpl};
|
||||||
|
}
|
||||||
|
|
||||||
|
} __attribute__((packed));
|
||||||
|
static_assert(sizeof(InterruptDescriptor) == 16,
|
||||||
|
"IDT::InterruptDescriptor has wrong size");
|
||||||
|
|
||||||
|
/*! \brief Load the IDT's address and size into the IDT-Register via `idtr`.
|
||||||
|
*/
|
||||||
|
void load();
|
||||||
|
|
||||||
|
/*! \brief Set the idt entry for the given interrupt vector.
|
||||||
|
*/
|
||||||
|
void set(Core::Interrupt::Vector vector, InterruptDescriptor descriptor);
|
||||||
|
} // namespace IDT
|
||||||
44
arch/ioapic.cc
Normal file
44
arch/ioapic.cc
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
#include "ioapic.h"
|
||||||
|
|
||||||
|
namespace IOAPIC {
|
||||||
|
/*! \brief IOAPIC registers memory mapped into the CPU's address space.
|
||||||
|
*
|
||||||
|
* Access to the actual IOAPIC registers can be obtained by performing the
|
||||||
|
* following steps:
|
||||||
|
* 1. Write the number of the IOAPIC register to the address stored in
|
||||||
|
* `IOREGSEL_REG`
|
||||||
|
* 2. Read the value from / write the value to the address referred to by
|
||||||
|
* `IOWIN_REG`.
|
||||||
|
*
|
||||||
|
* \see [IO-APIC manual](intel_ioapic.pdf#page=8)
|
||||||
|
*/
|
||||||
|
volatile Index *IOREGSEL_REG = reinterpret_cast<volatile Index *>(0xfec00000);
|
||||||
|
/// \copydoc IOREGSEL_REG
|
||||||
|
volatile Register *IOWIN_REG =
|
||||||
|
reinterpret_cast<volatile Register *>(0xfec00010);
|
||||||
|
|
||||||
|
// IOAPIC manual, p. 8
|
||||||
|
const Index IOAPICID_IDX = 0x00;
|
||||||
|
const Index IOREDTBL_IDX = 0x10;
|
||||||
|
|
||||||
|
const uint8_t slot_max = 24;
|
||||||
|
|
||||||
|
void init() {}
|
||||||
|
|
||||||
|
void config(uint8_t slot, Core::Interrupt::Vector vector,
|
||||||
|
TriggerMode trigger_mode, Polarity polarity) {
|
||||||
|
(void)slot;
|
||||||
|
(void)vector;
|
||||||
|
(void)trigger_mode;
|
||||||
|
(void)polarity;
|
||||||
|
}
|
||||||
|
|
||||||
|
void allow(uint8_t slot) { (void)slot; }
|
||||||
|
|
||||||
|
void forbid(uint8_t slot) { (void)slot; }
|
||||||
|
|
||||||
|
bool status(uint8_t slot) {
|
||||||
|
(void)slot;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} // namespace IOAPIC
|
||||||
82
arch/ioapic.h
Normal file
82
arch/ioapic.h
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
/*! \file
|
||||||
|
* \brief \ref IOAPIC abstracts the access to the I/O \ref APIC
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "../types.h"
|
||||||
|
#include "core_interrupt.h"
|
||||||
|
#include "ioapic_registers.h"
|
||||||
|
|
||||||
|
/*! \brief Abstraction of the I/O APIC that is used for management of external
|
||||||
|
* interrupts.
|
||||||
|
* \ingroup interrupts
|
||||||
|
*
|
||||||
|
* The I/O APIC's Core component is the IO-redirection table. This table is
|
||||||
|
* used to configure a flexible mapping between the interrupt number and the
|
||||||
|
* external interruption. Entries within this table have a width of 64 bit. For
|
||||||
|
* convenience, the union \ref IOAPIC::RedirectionTableEntry should be used for
|
||||||
|
* modifying these tables (see file `ioapic_registers.h` for details).
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace IOAPIC {
|
||||||
|
/*! \brief Initializes the I/O APIC.
|
||||||
|
*
|
||||||
|
* This function will initialize the I/O APIC by initializing the
|
||||||
|
* IO-redirection table with sane default values. The default interrupt-vector
|
||||||
|
* number is chosen such that, in case the interrupt is issued, the panic
|
||||||
|
* handler is executed. In the beginning, all external interrupts are disabled
|
||||||
|
* within the I/O APIC. Apart from the redirection table, the `APICID` (read
|
||||||
|
* from the system description tables during boot) needs to be written to the
|
||||||
|
* `IOAPICID` register (see \ref APIC::getIOAPICID() )
|
||||||
|
*
|
||||||
|
* \todo(12) Implement Function
|
||||||
|
*/
|
||||||
|
void init();
|
||||||
|
|
||||||
|
/*! \brief Creates a mapping between an interrupt vector and an external
|
||||||
|
interrupt.
|
||||||
|
*
|
||||||
|
* \param slot Number of the slot (i.e., the external interrupt) to
|
||||||
|
configure.
|
||||||
|
* \param vector Number of the interrupt vector that will be issued for
|
||||||
|
the external interrupt.
|
||||||
|
* \param trigger_mode Edge or level triggered interrupt signaling
|
||||||
|
(level-triggered interrupts required for the optional serial interface)
|
||||||
|
* \param polarity Polarity of the interrupt signaling (active high or
|
||||||
|
active low)
|
||||||
|
*
|
||||||
|
* \todo(12) Implement Function
|
||||||
|
*/
|
||||||
|
void config(uint8_t slot, Core::Interrupt::Vector vector,
|
||||||
|
TriggerMode trigger_mode = TriggerMode::EDGE,
|
||||||
|
Polarity polarity = Polarity::HIGH);
|
||||||
|
|
||||||
|
/*! \brief Enables the redirection of particular external interrupts to the
|
||||||
|
* CPU(s).
|
||||||
|
*
|
||||||
|
* To fully enable interrupt handling, the interrupts must be enabled for every
|
||||||
|
* CPU (e.g., by calling
|
||||||
|
* \ref Core::Interrupt::enable() in main).
|
||||||
|
* \todo(12) Do that somewhere appropriate.
|
||||||
|
*
|
||||||
|
* \param slot Number of the external interrupt that should be enabled.
|
||||||
|
*
|
||||||
|
* \todo(12) Implement Function
|
||||||
|
*/
|
||||||
|
void allow(uint8_t slot);
|
||||||
|
|
||||||
|
/*! \brief Selectively masks external interrupts by slot number.
|
||||||
|
* \param slot Slot number of the interrupt to be disabled.
|
||||||
|
*
|
||||||
|
* \todo(12) Implement Function
|
||||||
|
*/
|
||||||
|
void forbid(uint8_t slot);
|
||||||
|
|
||||||
|
/*! \brief Check whether an external interrupt source is masked.
|
||||||
|
* \param slot Slot number of the interrupt to be checked.
|
||||||
|
* \return Returns `true` iff the interrupt is unmasked, `false` otherwise
|
||||||
|
*
|
||||||
|
* \todo(12) Implement Function
|
||||||
|
*/
|
||||||
|
bool status(uint8_t slot);
|
||||||
|
} // namespace IOAPIC
|
||||||
227
arch/ioapic_registers.h
Normal file
227
arch/ioapic_registers.h
Normal file
@@ -0,0 +1,227 @@
|
|||||||
|
/*! \file
|
||||||
|
* \brief Helper structures for interacting with the \ref IOAPIC "I/O APIC".
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "../types.h"
|
||||||
|
|
||||||
|
namespace IOAPIC {
|
||||||
|
typedef uint32_t Index;
|
||||||
|
typedef uint32_t Register;
|
||||||
|
|
||||||
|
extern volatile Index *IOREGSEL_REG;
|
||||||
|
extern volatile Register *IOWIN_REG;
|
||||||
|
|
||||||
|
/*! \brief I/O APIC Identification
|
||||||
|
*
|
||||||
|
* The IOAPICID register is register number 0x0. The I/O APIC's ID will be read
|
||||||
|
* from the system configuration tables (provided by the BIOS) during boot. The
|
||||||
|
* number can be queried by calling \ref APIC::getIOAPICID(). During
|
||||||
|
* initialization, this number must be written to the IOAPICID register.
|
||||||
|
*
|
||||||
|
* \see [IO-APIC manual](intel_ioapic.pdf#page=9), page 9
|
||||||
|
*/
|
||||||
|
union Identification {
|
||||||
|
struct {
|
||||||
|
uint32_t : 24, ///< Reserved, do not modify
|
||||||
|
id : 4, ///< I/O APIC Identification
|
||||||
|
: 4; ///< Reserved, do not modify
|
||||||
|
};
|
||||||
|
Register value;
|
||||||
|
explicit Identification(Register value) : value(value) {}
|
||||||
|
} __attribute__((packed));
|
||||||
|
static_assert(sizeof(Identification) == 4,
|
||||||
|
"IOAPIC Identification has wrong size");
|
||||||
|
|
||||||
|
/*! \brief Delivery mode specifies the type of interrupt sent to the CPU. */
|
||||||
|
enum DeliveryMode {
|
||||||
|
FIXED = 0, ///< "ordinary" interrupt; send to ALL cores listed in the
|
||||||
|
///< destination bit mask
|
||||||
|
LOWEST_PRIORITY = 1, ///< "ordinary" interrupt; send to the lowest priority
|
||||||
|
///< core from destination mask
|
||||||
|
SMI = 2, ///< System Management Interrupt; vector number required to be 0
|
||||||
|
// Reserved
|
||||||
|
NMI = 4, ///< Non-Maskable Interrupt, vector number ignored, only edge
|
||||||
|
///< triggered
|
||||||
|
INIT = 5, ///< Initialization interrupt (always treated as edge triggered)
|
||||||
|
// Reserved
|
||||||
|
EXTERN_INT = 7 ///< external interrupt (only edge triggered)
|
||||||
|
};
|
||||||
|
|
||||||
|
/*! \brief Way of interpreting the value written to the destination field. */
|
||||||
|
enum DestinationMode {
|
||||||
|
PHYSICAL = 0, ///< Destination contains the physical destination APIC ID
|
||||||
|
LOGICAL = 1 ///< Destination contains a mask of logical APIC IDs
|
||||||
|
};
|
||||||
|
|
||||||
|
/*! \brief Interrupt polarity for the redirection-table entry */
|
||||||
|
enum Polarity {
|
||||||
|
HIGH = 0, ///< active high
|
||||||
|
LOW = 1 ///< active low
|
||||||
|
};
|
||||||
|
|
||||||
|
/*! \brief Trigger mode */
|
||||||
|
enum TriggerMode {
|
||||||
|
EDGE = 0, ///< edge triggered
|
||||||
|
LEVEL = 1 ///< level triggered
|
||||||
|
};
|
||||||
|
|
||||||
|
/*! \brief Interrupt state */
|
||||||
|
enum DeliveryStatus {
|
||||||
|
IDLE = 0, ///< No activity for this interrupt
|
||||||
|
SEND_PENDING =
|
||||||
|
1 ///< Interrupt will be sent as soon as the bus / LAPIC is ready
|
||||||
|
};
|
||||||
|
|
||||||
|
/*! \brief Interrupt masking */
|
||||||
|
enum InterruptMask {
|
||||||
|
UNMASKED = 0, ///< Redirection-table entry is active (non-masked)
|
||||||
|
MASKED = 1 ///< Redirection-table entry is inactive (masked)
|
||||||
|
};
|
||||||
|
|
||||||
|
/*! \brief Entry in the redirection table.
|
||||||
|
*
|
||||||
|
* The redirection table begins with I/O APIC register `0x10` and ends at
|
||||||
|
* `0x3f`.
|
||||||
|
*
|
||||||
|
* Each entry has a size of 64 bit, equaling two I/O APIC registers.
|
||||||
|
* For instance, entry 0 is stored in registers `0x10` and `0x11`, in which the
|
||||||
|
* low-order 32 bit (equals \ref value_low) and high-order 32 bit (equals \ref
|
||||||
|
* value_high) need to be stored.
|
||||||
|
*
|
||||||
|
* The union defined below provides an overlay allowing convenient modification
|
||||||
|
* of individual bits, while the 32-bit values \ref value_low and \ref
|
||||||
|
* value_high can be used for writing to the I/O APIC registers.
|
||||||
|
*
|
||||||
|
* \note [Type punning](https://en.wikipedia.org/wiki/Type_punning#Use_of_union)
|
||||||
|
* is indeed undefined behavior in C++. However, *gcc* explicitly allows
|
||||||
|
* this construct as a [language extension](https://gcc.gnu.org/bugs/#nonbugs).
|
||||||
|
* Some compilers ([other than
|
||||||
|
* gcc](https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html#Type%2Dpunning)
|
||||||
|
* might allow this feature only by disabling strict aliasing
|
||||||
|
* (`-fno-strict-aliasing`). In \StuBS we use this feature extensively due to
|
||||||
|
* the improved code readability.
|
||||||
|
*
|
||||||
|
* \see [IO-APIC manual](intel_ioapic.pdf#page=11), page 11-13
|
||||||
|
*/
|
||||||
|
union RedirectionTableEntry {
|
||||||
|
// @cond ANONYMOUS_STRUCT
|
||||||
|
struct {
|
||||||
|
// @endcond
|
||||||
|
|
||||||
|
/*! \brief Interrupt vector in the \ref IDT "Interrupt Descriptor Table
|
||||||
|
* (IDT)" will be activated when the corresponding external interrupt
|
||||||
|
* triggers.
|
||||||
|
*/
|
||||||
|
uint64_t vector : 8;
|
||||||
|
|
||||||
|
/*! \brief The delivery mode denotes the way the interrupts will be
|
||||||
|
* delivered to the local CPU cores, respectively to their local APICs.
|
||||||
|
*
|
||||||
|
* For StuBS, we use \ref LOWEST_PRIORITY, as all CPU cores have the same
|
||||||
|
* priority and we want to distribute interrupts evenly among them.
|
||||||
|
* It, however, is not guaranteed that this method of load balancing will
|
||||||
|
* work on every system.
|
||||||
|
*/
|
||||||
|
DeliveryMode delivery_mode : 3;
|
||||||
|
|
||||||
|
/*! \brief The destination mode defines how the value stored in \ref
|
||||||
|
* destination will be interpreted.
|
||||||
|
*
|
||||||
|
* For StuBS, we use \ref LOGICAL
|
||||||
|
*/
|
||||||
|
DestinationMode destination_mode : 1;
|
||||||
|
|
||||||
|
/*! \brief Delivery status holds the current status of interrupt delivery.
|
||||||
|
*
|
||||||
|
* \note This field is read only; write accesses to this field will be
|
||||||
|
* ignored.
|
||||||
|
*/
|
||||||
|
DeliveryStatus delivery_status : 1;
|
||||||
|
|
||||||
|
/*! \brief The polarity denotes when an interrupt should be issued.
|
||||||
|
*
|
||||||
|
* For StuBS, we usually use \ref HIGH (i.e., when the interrupt line is,
|
||||||
|
* logically, `1`).
|
||||||
|
*/
|
||||||
|
Polarity polarity : 1;
|
||||||
|
|
||||||
|
/*! \brief The remote IRR bit indicates whether the local APIC(s) accept the
|
||||||
|
* level interrupt.
|
||||||
|
*
|
||||||
|
* Once the LAPIC sends an \ref LAPIC::endOfInterrupt "End Of Interrupt
|
||||||
|
* (EOI)", this bit is reset to `0`.
|
||||||
|
*
|
||||||
|
* \note This field is read only and is only meaningful for level-triggered
|
||||||
|
* interrupts.
|
||||||
|
*/
|
||||||
|
uint64_t remote_irr : 1;
|
||||||
|
|
||||||
|
/*! \brief The trigger mode states whether the interrupt signaling is level
|
||||||
|
* or edge triggered.
|
||||||
|
*
|
||||||
|
* StuBS uses \ref EDGE for the Timer, the Keybaord and (optional) serial
|
||||||
|
* interface need \ref LEVEL
|
||||||
|
*/
|
||||||
|
TriggerMode trigger_mode : 1;
|
||||||
|
|
||||||
|
/*! \brief Mask or unmask interrupts for a particular, external source.
|
||||||
|
*
|
||||||
|
* The interrupt mask denotes whether interrupts should be
|
||||||
|
* accepted/unmasked (value \ref UNMASKED) or ignored/masked (value \ref
|
||||||
|
* MASKED).
|
||||||
|
*/
|
||||||
|
InterruptMask interrupt_mask : 1;
|
||||||
|
|
||||||
|
/*! \brief Reserved, do not modify. */
|
||||||
|
uint64_t : 39;
|
||||||
|
|
||||||
|
/*! \brief Interrupt destination.
|
||||||
|
*
|
||||||
|
* The meaning of destination depends on the destination mode:
|
||||||
|
* For the logical destination mode, destination holds a bit mask made up
|
||||||
|
* of the cores that are candidates for receiving the interrupt. In the
|
||||||
|
* single-core case, this value is `1`, in the multi-core case, the `n`
|
||||||
|
* low-order bits needs to be set (with `n` being the number of CPU cores,
|
||||||
|
* see \ref Core::count() ). Setting the `n` low-order bits marks all
|
||||||
|
* available cores as candidates for receiving interrupts and thereby
|
||||||
|
* balancing the number of interrupts between the cores.
|
||||||
|
*
|
||||||
|
* \note This form of load balancing depends on the hardware's behavior and
|
||||||
|
* may not work on all systems in the same fashion. Most notably, in QEMU
|
||||||
|
* all interrupts are sent to the BSP (core 0).
|
||||||
|
*/
|
||||||
|
uint64_t destination : 8;
|
||||||
|
|
||||||
|
// @cond ANONYMOUS_STRUCT
|
||||||
|
} __attribute__((packed));
|
||||||
|
// @endcond
|
||||||
|
|
||||||
|
// @cond ANONYMOUS_STRUCT
|
||||||
|
struct {
|
||||||
|
// @endcond
|
||||||
|
|
||||||
|
Register value_low; ///< Low-order 32 bits (for the register with the
|
||||||
|
///< smaller index)
|
||||||
|
Register value_high; ///< High-order 32 bits (for the register with the
|
||||||
|
///< higher index)
|
||||||
|
// @cond ANONYMOUS_STRUCT
|
||||||
|
} __attribute__((packed));
|
||||||
|
// @endcond
|
||||||
|
|
||||||
|
/*! \brief Constructor for an redirection-table entry
|
||||||
|
*
|
||||||
|
* Every entry in the redirection table represents an external source of
|
||||||
|
* interrupts and has a size of 64 bits. Due to the I/O APIC registers being
|
||||||
|
* only 32 bits wide, the constructor takes two 32 bit values.
|
||||||
|
*
|
||||||
|
* \param value_low First, low-order 32 bit value
|
||||||
|
* \param value_high Second, high-order 32 bit value
|
||||||
|
*/
|
||||||
|
RedirectionTableEntry(Register value_low, Register value_high)
|
||||||
|
: value_low(value_low), value_high(value_high) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
static_assert(sizeof(RedirectionTableEntry) == 8,
|
||||||
|
"IOAPIC::RedirectionTableEntry has wrong size");
|
||||||
|
} // namespace IOAPIC
|
||||||
63
arch/ioport.h
Normal file
63
arch/ioport.h
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
/*! \file
|
||||||
|
* \brief \ref IOPort provides access to the x86 IO address space
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "../types.h"
|
||||||
|
|
||||||
|
/*! \brief Abstracts access to the I/O address space
|
||||||
|
*
|
||||||
|
* x86 PCs have a separated I/O address space that is accessible only via the
|
||||||
|
* machine instructions `in` and `out`. An IOPort object encapsulates the
|
||||||
|
* corresponding address in the I/O address space and can be used for byte or
|
||||||
|
* word-wise reading or writing.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class IOPort {
|
||||||
|
/*! \brief Address in I/O address space
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
uint16_t address;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/*! \brief Constructor
|
||||||
|
* \param addr Address from the I/O address space
|
||||||
|
*/
|
||||||
|
explicit constexpr IOPort(uint16_t addr) : address(addr) {}
|
||||||
|
|
||||||
|
/*! \brief Write one byte to the I/O port
|
||||||
|
* \param val The value to be written
|
||||||
|
*/
|
||||||
|
void outb(uint8_t val) const {
|
||||||
|
asm volatile("out %%al, %%dx\n\t" : : "a"(val), "d"(address) :);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! \brief Write one word (2 bytes) to the I/O port
|
||||||
|
* \param val The value to be written
|
||||||
|
*/
|
||||||
|
void outw(uint16_t val) const {
|
||||||
|
asm volatile("out %%ax, %%dx\n\t" : : "a"(val), "d"(address) :);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! \brief Read one byte from the I/O port
|
||||||
|
* \return Read byte
|
||||||
|
*/
|
||||||
|
uint8_t inb() const {
|
||||||
|
uint8_t out = 0;
|
||||||
|
|
||||||
|
asm volatile("in %%dx, %%al\n\t" : "=a"(out) : "d"(address) :);
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! \brief Read one word (2 bytes) from the I/O port
|
||||||
|
* \return Read word (2 bytes)
|
||||||
|
*/
|
||||||
|
uint16_t inw() const {
|
||||||
|
uint16_t out = 0;
|
||||||
|
|
||||||
|
asm volatile("inw %%dx, %%ax\n\t" : "=a"(out) : "d"(address) :);
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
};
|
||||||
190
arch/lapic.cc
Normal file
190
arch/lapic.cc
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
#include "lapic.h"
|
||||||
|
|
||||||
|
#include "lapic_registers.h"
|
||||||
|
|
||||||
|
namespace LAPIC {
|
||||||
|
|
||||||
|
/*! \brief Base Address
|
||||||
|
* used with offset to access memory mapped registers
|
||||||
|
*/
|
||||||
|
volatile uintptr_t base_address = 0xfee00000;
|
||||||
|
|
||||||
|
Register read(Index idx) {
|
||||||
|
return *reinterpret_cast<volatile Register *>(base_address + idx);
|
||||||
|
}
|
||||||
|
void write(Index idx, Register value) {
|
||||||
|
*reinterpret_cast<volatile Register *>(base_address + idx) = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! \brief Local APIC ID (for Pentium 4 and newer)
|
||||||
|
*
|
||||||
|
* Is assigned automatically during boot and should not be changed.
|
||||||
|
*
|
||||||
|
* \see [ISDMv3, 10.4.6 Local APIC ID](intel_manual_vol3.pdf#page=371)
|
||||||
|
*/
|
||||||
|
union IdentificationRegister {
|
||||||
|
struct {
|
||||||
|
uint32_t : 24, ///< (reserved)
|
||||||
|
apic_id : 8; ///< APIC ID
|
||||||
|
};
|
||||||
|
Register value;
|
||||||
|
|
||||||
|
IdentificationRegister() : value(read(Index::IDENTIFICATION)) {}
|
||||||
|
} __attribute__((packed));
|
||||||
|
|
||||||
|
/*! \brief Local APIC Version
|
||||||
|
*
|
||||||
|
* \see [ISDMv3 10.4.8 Local APIC Version
|
||||||
|
* Register](intel_manual_vol3.pdf#page=373)
|
||||||
|
*/
|
||||||
|
union VersionRegister {
|
||||||
|
struct {
|
||||||
|
uint32_t
|
||||||
|
version : 8, ///< 0x14 for P4 and Xeon, 0x15 for more recent hardware
|
||||||
|
: 8, ///< (reserved)
|
||||||
|
max_lvt_entry : 8, ///< Maximum number of local vector entries
|
||||||
|
suppress_eoi_broadcast : 1, ///< Support for suppressing EOI broadcasts
|
||||||
|
: 7; ///< (reserved)
|
||||||
|
};
|
||||||
|
Register value;
|
||||||
|
|
||||||
|
VersionRegister() : value(read(Index::VERSION)) {}
|
||||||
|
} __attribute__((packed));
|
||||||
|
|
||||||
|
/*! \brief Logical Destination Register
|
||||||
|
* \see [ISDMv3 10.6.2.2 Logical Destination
|
||||||
|
* Mode](intel_manual_vol3.pdf#page=385)
|
||||||
|
*/
|
||||||
|
union LogicalDestinationRegister {
|
||||||
|
struct {
|
||||||
|
uint32_t : 24, ///< (reserved)
|
||||||
|
lapic_id : 8; ///< Logical APIC ID
|
||||||
|
};
|
||||||
|
Register value;
|
||||||
|
|
||||||
|
LogicalDestinationRegister() : value(read(Index::LOGICAL_DESTINATION)) {}
|
||||||
|
~LogicalDestinationRegister() { write(Index::LOGICAL_DESTINATION, value); }
|
||||||
|
} __attribute__((packed));
|
||||||
|
|
||||||
|
enum Model { CLUSTER = 0x0, FLAT = 0xf };
|
||||||
|
|
||||||
|
/*! \brief Destination Format Register
|
||||||
|
*
|
||||||
|
* \see [ISDMv3 10.6.2.2 Logical Destination
|
||||||
|
* Mode](intel_manual_vol3.pdf#page=385)
|
||||||
|
*/
|
||||||
|
union DestinationFormatRegister {
|
||||||
|
struct {
|
||||||
|
uint32_t : 28; ///< (reserved)
|
||||||
|
Model model : 4; ///< Model (Flat vs. Cluster)
|
||||||
|
};
|
||||||
|
Register value;
|
||||||
|
DestinationFormatRegister() : value(read(Index::DESTINATION_FORMAT)) {}
|
||||||
|
~DestinationFormatRegister() { write(Index::DESTINATION_FORMAT, value); }
|
||||||
|
} __attribute__((packed));
|
||||||
|
|
||||||
|
/*! \brief Task Priority Register
|
||||||
|
*
|
||||||
|
* \see [ISDMv3 10.8.3.1 Task and Processor
|
||||||
|
* Priorities](intel_manual_vol3.pdf#page=391)
|
||||||
|
*/
|
||||||
|
union TaskPriorityRegister {
|
||||||
|
struct {
|
||||||
|
uint32_t task_prio_sub : 4, ///< Task Priority Sub-Class
|
||||||
|
task_prio : 4, ///< Task Priority
|
||||||
|
: 24; ///< (reserved)
|
||||||
|
};
|
||||||
|
Register value;
|
||||||
|
TaskPriorityRegister() : value(read(Index::TASK_PRIORITY)) {}
|
||||||
|
~TaskPriorityRegister() { write(Index::TASK_PRIORITY, value); }
|
||||||
|
} __attribute__((packed));
|
||||||
|
|
||||||
|
/*! \brief APIC Software Status for Spurious Interrupt Vector */
|
||||||
|
enum APICSoftware {
|
||||||
|
APIC_DISABLED = 0,
|
||||||
|
APIC_ENABLED = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
/*! \brief Focus Processor Checking for Spurious Interrupt Vector */
|
||||||
|
enum FocusProcessorChecking {
|
||||||
|
CHECKING_ENABLED = 0,
|
||||||
|
CHECKING_DISABLED = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
/*! \brief Suppress End-Of-Interrupt-Broadcast for Spurious Interrupt Vector */
|
||||||
|
enum SuppressEOIBroadcast {
|
||||||
|
BROADCAST = 0,
|
||||||
|
SUPPRESS_BROADCAST = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
/*! \brief Spurious Interrupt Vector Register
|
||||||
|
*
|
||||||
|
* \see [ISDMv3 10.9 Spurious Interrupt](intel_manual_vol3.pdf#page=394)
|
||||||
|
*/
|
||||||
|
union SpuriousInterruptVectorRegister {
|
||||||
|
struct {
|
||||||
|
uint32_t spurious_vector : 8; ///< Spurious Vector
|
||||||
|
APICSoftware apic_software : 1; ///< APIC Software Enable/Disable
|
||||||
|
FocusProcessorChecking
|
||||||
|
focus_processor_checking : 1; ///< Focus Processor Checking
|
||||||
|
uint32_t reserved_1 : 2;
|
||||||
|
SuppressEOIBroadcast eoi_broadcast_suppression : 1;
|
||||||
|
uint32_t reserved : 19;
|
||||||
|
};
|
||||||
|
Register value;
|
||||||
|
|
||||||
|
SpuriousInterruptVectorRegister()
|
||||||
|
: value(read(Index::SPURIOUS_INTERRUPT_VECTOR)) {}
|
||||||
|
~SpuriousInterruptVectorRegister() {
|
||||||
|
write(Index::SPURIOUS_INTERRUPT_VECTOR, value);
|
||||||
|
}
|
||||||
|
} __attribute__((packed));
|
||||||
|
static_assert(sizeof(SpuriousInterruptVectorRegister) == 4,
|
||||||
|
"LAPIC Spurious Interrupt Vector has wrong size");
|
||||||
|
|
||||||
|
uint8_t getID() {
|
||||||
|
IdentificationRegister ir;
|
||||||
|
return ir.apic_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t getLogicalID() {
|
||||||
|
LogicalDestinationRegister ldr;
|
||||||
|
return ldr.lapic_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t getVersion() {
|
||||||
|
VersionRegister vr;
|
||||||
|
return vr.version;
|
||||||
|
}
|
||||||
|
|
||||||
|
void init(uint8_t logical_id) {
|
||||||
|
// reset logical destination ID
|
||||||
|
// can be set using setLogicalLAPICID()
|
||||||
|
LogicalDestinationRegister ldr;
|
||||||
|
ldr.lapic_id = logical_id;
|
||||||
|
|
||||||
|
// set task priority to 0 -> accept all interrupts
|
||||||
|
TaskPriorityRegister tpr;
|
||||||
|
tpr.task_prio = 0;
|
||||||
|
tpr.task_prio_sub = 0;
|
||||||
|
|
||||||
|
// set flat delivery mode
|
||||||
|
DestinationFormatRegister dfr;
|
||||||
|
dfr.model = Model::FLAT;
|
||||||
|
|
||||||
|
// use 255 as spurious vector, enable APIC and disable focus processor
|
||||||
|
SpuriousInterruptVectorRegister sivr;
|
||||||
|
sivr.spurious_vector = 0xff;
|
||||||
|
sivr.apic_software = APICSoftware::APIC_ENABLED;
|
||||||
|
sivr.focus_processor_checking = FocusProcessorChecking::CHECKING_DISABLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
void endOfInterrupt() {
|
||||||
|
// dummy read
|
||||||
|
read(SPURIOUS_INTERRUPT_VECTOR);
|
||||||
|
|
||||||
|
// signal end of interrupt
|
||||||
|
write(EOI, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace LAPIC
|
||||||
199
arch/lapic.h
Normal file
199
arch/lapic.h
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
/*! \file
|
||||||
|
* \brief \ref LAPIC abstracts access to the Local \ref APIC
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "../types.h"
|
||||||
|
|
||||||
|
/*! \brief Abstracts the local APIC (which is integrated into every CPU core)
|
||||||
|
* \ingroup interrupts
|
||||||
|
*
|
||||||
|
* In modern (x86) PCs, every CPU core has its own Local APIC (LAPIC). The
|
||||||
|
* LAPIC is the link between the local CPU core and the I/O APIC (that takes
|
||||||
|
* care about external interrupt sources. Interrupt messages received by the
|
||||||
|
* LAPIC will be passed to the corresponding CPU core and trigger the interrupt
|
||||||
|
* handler on this core.
|
||||||
|
*
|
||||||
|
* \see [ISDMv3 10.4 Local APIC](intel_manual_vol3.pdf#page=366)
|
||||||
|
*/
|
||||||
|
namespace LAPIC {
|
||||||
|
/*! \brief Initialized the local APIC of the calling CPU core and sets the
|
||||||
|
* logical LAPIC ID in the LDR register
|
||||||
|
* \param logical_id APIC ID to be set
|
||||||
|
*/
|
||||||
|
void init(uint8_t logical_id);
|
||||||
|
|
||||||
|
/*! \brief Signalize EOI (End of interrupt)
|
||||||
|
*
|
||||||
|
* Signals to the LAPIC that the handling of the current interrupt finished.
|
||||||
|
* This function must be called at the end of interrupt handling before ireting.
|
||||||
|
*/
|
||||||
|
void endOfInterrupt();
|
||||||
|
|
||||||
|
/*! \brief Get the ID of the current core's LAPIC
|
||||||
|
* \return LAPIC ID
|
||||||
|
*/
|
||||||
|
uint8_t getID();
|
||||||
|
|
||||||
|
/*! \brief Get the Logical ID of the current core's LAPIC
|
||||||
|
* \return Logical ID
|
||||||
|
*/
|
||||||
|
uint8_t getLogicalID();
|
||||||
|
|
||||||
|
/*! \brief Set the Logical ID of the current core's LAPIC
|
||||||
|
* \param id new Logical ID
|
||||||
|
*/
|
||||||
|
void setLogicalID(uint8_t id);
|
||||||
|
|
||||||
|
/*! \brief Get version number of local APIC
|
||||||
|
* \return version number
|
||||||
|
*/
|
||||||
|
uint8_t getVersion();
|
||||||
|
|
||||||
|
/*! \brief Inter-Processor Interrupts
|
||||||
|
*
|
||||||
|
* For multi-core systems, the LAPIC enables sending messages (Inter-Processor
|
||||||
|
* Interrupts, IPIs) to other CPU cores and receiving those sent from other
|
||||||
|
* cores.
|
||||||
|
*
|
||||||
|
* \see [ISDMv3 10.6 Issuing Interprocessor
|
||||||
|
* Interrupts](intel_manual_vol3.pdf#page=380)
|
||||||
|
*/
|
||||||
|
namespace IPI {
|
||||||
|
|
||||||
|
/*! \brief Check if the previously sent IPI has reached its destination.
|
||||||
|
*
|
||||||
|
* \return `true` if the previous IPI was accepted from its target processor,
|
||||||
|
* otherwise `false`
|
||||||
|
*/
|
||||||
|
bool isDelivered();
|
||||||
|
|
||||||
|
/*! \brief Send an Inter-Processor Interrupt (IPI)
|
||||||
|
* \param destination ID of the target processor (use APIC::getLAPICID(core) )
|
||||||
|
* \param vector Interrupt vector number to be triggered
|
||||||
|
*/
|
||||||
|
void send(uint8_t destination, uint8_t vector);
|
||||||
|
|
||||||
|
/*! \brief Send an Inter-Processor Interrupt (IPI) to a group of processors
|
||||||
|
* \param logical_destination Mask containing the logical APIC IDs of the target
|
||||||
|
* processors (use APIC::getLogicalLAPICID())
|
||||||
|
* \param vector Interrupt vector number to be triggered
|
||||||
|
*/
|
||||||
|
void sendGroup(uint8_t logical_destination, uint8_t vector);
|
||||||
|
|
||||||
|
/*! \brief Send an Inter-Processor Interrupt (IPI) to all processors (including
|
||||||
|
* self)
|
||||||
|
* \param vector Interrupt vector number to be triggered
|
||||||
|
*/
|
||||||
|
void sendAll(uint8_t vector);
|
||||||
|
|
||||||
|
/*! \brief Send an Inter-Processor Interrupt (IPI) to all other processors (all
|
||||||
|
* but self)
|
||||||
|
* \param vector Interrupt vector number to be triggered
|
||||||
|
*/
|
||||||
|
void sendOthers(uint8_t vector);
|
||||||
|
|
||||||
|
/*! \brief Send an INIT request IPI to all other processors
|
||||||
|
*
|
||||||
|
* \note Only required for startup
|
||||||
|
*
|
||||||
|
* \param assert if `true` send an INIT,
|
||||||
|
* on `false` send an INIT Level De-assert
|
||||||
|
*/
|
||||||
|
void sendInit(bool assert = true);
|
||||||
|
|
||||||
|
/*! \brief Send an Startup IPI to all other processors
|
||||||
|
*
|
||||||
|
* \note Only required for startup
|
||||||
|
*
|
||||||
|
* \param vector Pointer to a startup routine
|
||||||
|
*/
|
||||||
|
void sendStartup(uint8_t vector);
|
||||||
|
|
||||||
|
} // namespace IPI
|
||||||
|
|
||||||
|
/*! \brief Local Timer (for each LAPIC / CPU)
|
||||||
|
*
|
||||||
|
* \see [ISDMv3 10.5.4 APIC Timer](intel_manual_vol3.pdf#page=378)
|
||||||
|
*/
|
||||||
|
namespace Timer {
|
||||||
|
|
||||||
|
/*! \brief Determines the \ref LAPIC::Timer frequency.
|
||||||
|
*
|
||||||
|
* This function will calculate the number of LAPIC-timer ticks passing in the
|
||||||
|
* course of one millisecond. To do so, this function will rely on PIT timer
|
||||||
|
* functionality and measure the tick delta between start and end of waiting for
|
||||||
|
* a predefined period.
|
||||||
|
*
|
||||||
|
* For measurement, the LAPIC-timer single-shot mode (without interrupts) is
|
||||||
|
* used; after measurement, the timer is disabled again.
|
||||||
|
*
|
||||||
|
* \note The timer is counting towards zero.
|
||||||
|
*
|
||||||
|
* \return Number of LAPIC-timer ticks per millisecond
|
||||||
|
*
|
||||||
|
* \todo(15) Implement Method
|
||||||
|
*/
|
||||||
|
uint32_t ticks(void);
|
||||||
|
|
||||||
|
/*! \brief Set the \ref LAPIC::Timer.
|
||||||
|
* \param counter Initial counter value; decremented on every LAPIC timer tick
|
||||||
|
* \param divide Divider (power of 2, i.e., 1 2 4 8 16 32...) used as
|
||||||
|
* prescaler between bus frequency and LAPIC timer frequency: `LAPIC timer
|
||||||
|
* frequency = divide * bus frequency`. `divide` is a numerical parameter, the
|
||||||
|
* conversion to the corresponding bit mask is done internally by calling
|
||||||
|
* getClockDiv().
|
||||||
|
* \param vector Interrupt vector number to be triggered on counter expiry
|
||||||
|
* \param periodic If set, the interrupt will be issued periodically
|
||||||
|
* \param masked If set, interrupts on counter expiry are suppressed
|
||||||
|
*
|
||||||
|
* \todo(15) Implement Method
|
||||||
|
*/
|
||||||
|
void set(uint32_t counter, uint8_t divide, uint8_t vector, bool periodic,
|
||||||
|
bool masked = false);
|
||||||
|
|
||||||
|
/*! \brief Setup the \ref LAPIC::Timer.
|
||||||
|
*
|
||||||
|
* Initializes the \ref LAPIC::Timer
|
||||||
|
* in such a way that regular interrupts are triggered approx. every `us`
|
||||||
|
* microseconds when \ref LAPIC::Timer:::activate() is called.
|
||||||
|
* For this purpose, a suitable timer divisor is determined
|
||||||
|
* based on the timer frequency determined with \ref LAPIC::Timer::ticks().
|
||||||
|
* This timer divisor has to be as small as possible, but large enough to
|
||||||
|
* prevent the 32bit counter from overflowing.
|
||||||
|
*
|
||||||
|
* \param us Desired interrupt interval in microseconds.
|
||||||
|
* \return Indicates if the interval could be set.
|
||||||
|
*
|
||||||
|
* \todo(15) Implement Method
|
||||||
|
*/
|
||||||
|
bool setup(uint32_t us);
|
||||||
|
|
||||||
|
/*! \brief Retrieve the interrupt interval set during \ref LAPIC::Timer::setup()
|
||||||
|
*
|
||||||
|
* \return Interval in microseconds
|
||||||
|
*
|
||||||
|
* \todo(15) Implement method
|
||||||
|
*/
|
||||||
|
uint32_t interval();
|
||||||
|
|
||||||
|
/*! \brief Activate the timer on this core.
|
||||||
|
*
|
||||||
|
* The core local timer starts with the interval previously configured in
|
||||||
|
* \ref LAPIC::Timer::setup(). To get timer interrupts on all cores, this method
|
||||||
|
* must be called once per core (however, it is sufficient to call \ref
|
||||||
|
* LAPIC::Timer::setup() only once since the APIC-Bus frequency is the same on
|
||||||
|
* each core).
|
||||||
|
*
|
||||||
|
* \todo(15) Implement method
|
||||||
|
*/
|
||||||
|
void activate();
|
||||||
|
|
||||||
|
/*! \brief Set the LAPIC-timer interrupt mask
|
||||||
|
* \param masked If set, interrupts are suppressed on counter expiry.
|
||||||
|
*
|
||||||
|
* \todo(16) Implement for tick-less kernel
|
||||||
|
*/
|
||||||
|
void setMasked(bool masked);
|
||||||
|
} // namespace Timer
|
||||||
|
} // namespace LAPIC
|
||||||
244
arch/lapic_ipi.cc
Normal file
244
arch/lapic_ipi.cc
Normal file
@@ -0,0 +1,244 @@
|
|||||||
|
#include "lapic_registers.h"
|
||||||
|
|
||||||
|
namespace LAPIC {
|
||||||
|
namespace IPI {
|
||||||
|
|
||||||
|
/*! \brief Delivery mode specifies the type of interrupt sent to the CPU. */
|
||||||
|
enum DeliveryMode {
|
||||||
|
FIXED = 0, ///< "ordinary" interrupt; send to ALL cores listed in the
|
||||||
|
///< destination bit mask
|
||||||
|
LOWEST_PRIORITY = 1, ///< "ordinary" interrupt; send to the lowest priority
|
||||||
|
///< core from destination mask
|
||||||
|
SMI = 2, ///< System Management Interrupt; vector number required to be 0
|
||||||
|
// Reserved
|
||||||
|
NMI = 4, ///< Non-Maskable Interrupt, vector number ignored, only edge
|
||||||
|
///< triggered
|
||||||
|
INIT = 5, ///< Initialization interrupt (always treated as edge triggered)
|
||||||
|
INIT_LEVEL_DEASSERT = 5, ///< Synchronization interrupt
|
||||||
|
STARTUP = 6, ///< Dedicated Startup-Interrupt (SIPI)
|
||||||
|
// Reserved
|
||||||
|
};
|
||||||
|
|
||||||
|
/*! \brief Way of interpreting the value written to the destination field. */
|
||||||
|
enum DestinationMode {
|
||||||
|
PHYSICAL = 0, ///< Destination contains the physical destination APIC ID
|
||||||
|
LOGICAL = 1 ///< Destination contains a mask of logical APIC IDs
|
||||||
|
};
|
||||||
|
|
||||||
|
/*! \brief Interrupt state */
|
||||||
|
enum DeliveryStatus {
|
||||||
|
IDLE = 0, ///< No activity for this interrupt
|
||||||
|
SEND_PENDING =
|
||||||
|
1 ///< Interrupt will be sent as soon as the bus / LAPIC is ready
|
||||||
|
};
|
||||||
|
|
||||||
|
/*! \brief Interrupt level */
|
||||||
|
enum Level {
|
||||||
|
DEASSERT = 0, ///< Must be zero when DeliveryMode::INIT_LEVEL_DEASSERT
|
||||||
|
ASSERT = 1 ///< Must be one for all other delivery modes
|
||||||
|
};
|
||||||
|
|
||||||
|
/*! \brief Trigger mode for DeliveryMode::INIT_LEVEL_DEASSERT */
|
||||||
|
enum TriggerMode {
|
||||||
|
EDGE_TRIGGERED = 0, ///< edge triggered
|
||||||
|
LEVEL_TRIGGERED = 1 ///< level triggered
|
||||||
|
};
|
||||||
|
|
||||||
|
/*! \brief Shorthand for commonly used destinations */
|
||||||
|
enum DestinationShorthand {
|
||||||
|
NO_SHORTHAND = 0, ///< Use destination field instead of shorthand
|
||||||
|
SELF = 1, ///< Send IPI to self
|
||||||
|
ALL_INCLUDING_SELF = 2, ///< Send IPI to all including self
|
||||||
|
ALL_EXCLUDING_SELF = 3 ///< Send IPI to all except self
|
||||||
|
};
|
||||||
|
|
||||||
|
/*! \brief Interrupt mask */
|
||||||
|
enum InterruptMask {
|
||||||
|
UNMASKED = 0, ///< Interrupt entry is active (non-masked)
|
||||||
|
MASKED = 1 ///< Interrupt entry is deactivated (masked)
|
||||||
|
};
|
||||||
|
|
||||||
|
/*! \brief Interrupt Command
|
||||||
|
*
|
||||||
|
* \see [ISDMv3 10.6.1 Interrupt Command Register
|
||||||
|
* (ICR)](intel_manual_vol3.pdf#page=381)
|
||||||
|
*/
|
||||||
|
union InterruptCommand {
|
||||||
|
struct {
|
||||||
|
/*! \brief Interrupt vector in the \ref IDT "Interrupt Descriptor Table
|
||||||
|
* (IDT)" will be activated when the corresponding external interrupt
|
||||||
|
* triggers.
|
||||||
|
*//*! \brief Interrupt vector in the \ref IDT "Interrupt Descriptor Table (IDT)" will be
|
||||||
|
* activated when the corresponding external interrupt triggers.
|
||||||
|
*/
|
||||||
|
uint64_t vector : 8;
|
||||||
|
|
||||||
|
/*! \brief The delivery mode denotes the way the interrupts will be
|
||||||
|
* delivered to the local CPU cores, respectively to their local APICs.
|
||||||
|
*
|
||||||
|
* For StuBS, we use `DeliveryMode::LowestPriority`, as all CPU cores have
|
||||||
|
* the same priority and we want to distribute interrupts evenly among them.
|
||||||
|
* It, however, is not guaranteed that this method of load balancing will
|
||||||
|
* work on every system.
|
||||||
|
*/
|
||||||
|
enum DeliveryMode delivery_mode : 3;
|
||||||
|
|
||||||
|
/*! \brief The destination mode defines how the value stored in
|
||||||
|
* `destination` will be interpreted.
|
||||||
|
*
|
||||||
|
* For StuBS, we use `DestinationMode::Logical`.
|
||||||
|
*/
|
||||||
|
enum DestinationMode destination_mode : 1;
|
||||||
|
|
||||||
|
/*! \brief Delivery status holds the current status of interrupt delivery.
|
||||||
|
*
|
||||||
|
* \note This field is read only; write accesses to this field will be
|
||||||
|
* ignored.
|
||||||
|
*/
|
||||||
|
enum DeliveryStatus delivery_status : 1;
|
||||||
|
|
||||||
|
uint64_t : 1; ///< reserved
|
||||||
|
|
||||||
|
/*! \brief The polarity denotes when an interrupt should be issued.
|
||||||
|
*
|
||||||
|
* For StuBS, we use `Polarity::High` (i.e., when the interrupt line is,
|
||||||
|
* logically, 1).
|
||||||
|
*/
|
||||||
|
enum Level level : 1;
|
||||||
|
|
||||||
|
/*! \brief The trigger mode states whether the interrupt signaling is level
|
||||||
|
* or edge triggered.
|
||||||
|
*
|
||||||
|
* StuBS uses `TriggerMode::Edge` for Keyboard and Timer, the (optional)
|
||||||
|
* serial interface, however, needs `TriggerMode::Level`.
|
||||||
|
*/
|
||||||
|
enum TriggerMode trigger_mode : 1;
|
||||||
|
|
||||||
|
uint64_t : 2; ///< reserved
|
||||||
|
|
||||||
|
enum DestinationShorthand destination_shorthand : 2;
|
||||||
|
|
||||||
|
uint64_t : 36; ///< Reserved, do not modify
|
||||||
|
|
||||||
|
/*! \brief Interrupt destination.
|
||||||
|
*
|
||||||
|
* The meaning of destination depends on the destination mode:
|
||||||
|
* For the logical destination mode, destination holds a bit mask made up
|
||||||
|
* of the cores that are candidates for receiving the interrupt. In the
|
||||||
|
* single-core case, this value is `1`, in the multi-core case, the `n`
|
||||||
|
* low-order bits needs to be set (with `n` being the number of CPU cores,
|
||||||
|
* see \ref Core::count() ). Setting the `n` low-order bits marks all
|
||||||
|
* available cores as candidates for receiving interrupts and thereby
|
||||||
|
* balancing the number of interrupts between the cores.
|
||||||
|
*
|
||||||
|
* \note This form of load balancing depends on the hardware's behavior and
|
||||||
|
* may not work on all systems in the same fashion. Most notably, in QEMU
|
||||||
|
* all interrupts are sent to the BSP (core 0).
|
||||||
|
*/
|
||||||
|
uint64_t destination : 8;
|
||||||
|
} __attribute__((packed));
|
||||||
|
|
||||||
|
/*! \brief I/O redirection-table entry
|
||||||
|
*
|
||||||
|
* Every entry in the redirection table represents an external source of
|
||||||
|
* interrupts and has a size of 64 bits. Due to the I/O APIC registers being
|
||||||
|
* only 32 bits wide, the 64-bit value is split in two 32 bit values.
|
||||||
|
*/
|
||||||
|
struct {
|
||||||
|
Register value_low; ///< First, low-order register
|
||||||
|
Register value_high; ///< Second, high-order register
|
||||||
|
} __attribute__((packed));
|
||||||
|
|
||||||
|
/*! \brief Default constructor */
|
||||||
|
InterruptCommand() = default;
|
||||||
|
|
||||||
|
explicit InterruptCommand(
|
||||||
|
uint8_t destination, uint8_t vector = 0,
|
||||||
|
DestinationMode destination_mode = DestinationMode::PHYSICAL,
|
||||||
|
DeliveryMode delivery_mode = DeliveryMode::FIXED,
|
||||||
|
TriggerMode trigger_mode = TriggerMode::EDGE_TRIGGERED,
|
||||||
|
Level level = Level::ASSERT) {
|
||||||
|
readRegister();
|
||||||
|
this->vector = vector;
|
||||||
|
this->delivery_mode = delivery_mode;
|
||||||
|
this->destination_mode = destination_mode;
|
||||||
|
this->level = level;
|
||||||
|
this->trigger_mode = trigger_mode;
|
||||||
|
this->destination_shorthand = DestinationShorthand::NO_SHORTHAND;
|
||||||
|
this->destination = destination;
|
||||||
|
}
|
||||||
|
|
||||||
|
InterruptCommand(DestinationShorthand destination_shorthand, uint8_t vector,
|
||||||
|
DeliveryMode delivery_mode = DeliveryMode::FIXED,
|
||||||
|
TriggerMode trigger_mode = TriggerMode::EDGE_TRIGGERED,
|
||||||
|
Level level = Level::ASSERT) {
|
||||||
|
readRegister();
|
||||||
|
this->vector = vector;
|
||||||
|
this->delivery_mode = delivery_mode;
|
||||||
|
this->level = level;
|
||||||
|
this->trigger_mode = trigger_mode;
|
||||||
|
this->destination_shorthand = destination_shorthand;
|
||||||
|
this->destination = destination;
|
||||||
|
}
|
||||||
|
|
||||||
|
void send() const {
|
||||||
|
write(INTERRUPT_COMMAND_REGISTER_HIGH, value_high);
|
||||||
|
write(INTERRUPT_COMMAND_REGISTER_LOW, value_low);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isSendPending() {
|
||||||
|
value_low = read(INTERRUPT_COMMAND_REGISTER_LOW);
|
||||||
|
return delivery_status == DeliveryStatus::SEND_PENDING;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void readRegister() {
|
||||||
|
while (isSendPending()) {
|
||||||
|
}
|
||||||
|
value_high = read(INTERRUPT_COMMAND_REGISTER_HIGH);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
static_assert(sizeof(InterruptCommand) == 8,
|
||||||
|
"LAPIC Interrupt Command has wrong size");
|
||||||
|
|
||||||
|
bool isDelivered() {
|
||||||
|
InterruptCommand ic;
|
||||||
|
return !ic.isSendPending();
|
||||||
|
}
|
||||||
|
|
||||||
|
void send(uint8_t destination, uint8_t vector) {
|
||||||
|
InterruptCommand ic(destination, vector);
|
||||||
|
ic.send();
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendGroup(uint8_t logical_destination, uint8_t vector) {
|
||||||
|
InterruptCommand ic(logical_destination, vector, DestinationMode::LOGICAL);
|
||||||
|
ic.send();
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendAll(uint8_t vector) {
|
||||||
|
InterruptCommand ic(DestinationShorthand::ALL_INCLUDING_SELF, vector);
|
||||||
|
ic.send();
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendOthers(uint8_t vector) {
|
||||||
|
InterruptCommand ic(DestinationShorthand::ALL_EXCLUDING_SELF, vector);
|
||||||
|
ic.send();
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendInit(bool assert) {
|
||||||
|
LAPIC::IPI::InterruptCommand ic(
|
||||||
|
DestinationShorthand::ALL_EXCLUDING_SELF, 0, DeliveryMode::INIT,
|
||||||
|
assert ? TriggerMode::EDGE_TRIGGERED : TriggerMode::LEVEL_TRIGGERED,
|
||||||
|
assert ? Level::ASSERT : Level::DEASSERT);
|
||||||
|
ic.send();
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendStartup(uint8_t vector) {
|
||||||
|
InterruptCommand ic(DestinationShorthand::ALL_EXCLUDING_SELF, vector,
|
||||||
|
DeliveryMode::STARTUP);
|
||||||
|
ic.send();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace IPI
|
||||||
|
} // namespace LAPIC
|
||||||
54
arch/lapic_registers.h
Normal file
54
arch/lapic_registers.h
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
/*! \file
|
||||||
|
* \brief Structures and macros for accessing \ref LAPIC "the local APIC".
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "../types.h"
|
||||||
|
|
||||||
|
namespace LAPIC {
|
||||||
|
// Memory Mapped Base Address
|
||||||
|
extern volatile uintptr_t base_address;
|
||||||
|
|
||||||
|
typedef uint32_t Register;
|
||||||
|
|
||||||
|
/*! \brief Register Offset Index
|
||||||
|
*
|
||||||
|
* \see [ISDMv3 10.4.1 The Local APIC Block
|
||||||
|
* Diagram](intel_manual_vol3.pdf#page=368)
|
||||||
|
*/
|
||||||
|
enum Index : uint16_t {
|
||||||
|
IDENTIFICATION =
|
||||||
|
0x020, ///< Local APIC ID Register, RO (sometimes R/W). Do not change!
|
||||||
|
VERSION = 0x030, ///< Local APIC Version Register, RO
|
||||||
|
TASK_PRIORITY = 0x080, ///< Task Priority Register, R/W
|
||||||
|
EOI = 0x0b0, ///< EOI Register, WO
|
||||||
|
LOGICAL_DESTINATION = 0x0d0, ///< Logical Destination Register, R/W
|
||||||
|
DESTINATION_FORMAT =
|
||||||
|
0x0e0, ///< Destination Format Register, bits 0-27 RO, bits 28-31 R/W
|
||||||
|
SPURIOUS_INTERRUPT_VECTOR = 0x0f0, ///< Spurious Interrupt Vector Register,
|
||||||
|
///< bits 0-8 R/W, bits 9-1 R/W
|
||||||
|
INTERRUPT_COMMAND_REGISTER_LOW =
|
||||||
|
0x300, ///< Interrupt Command Register 1, R/W
|
||||||
|
INTERRUPT_COMMAND_REGISTER_HIGH =
|
||||||
|
0x310, ///< Interrupt Command Register 2, R/W
|
||||||
|
TIMER_CONTROL = 0x320, ///< LAPIC timer control register, R/W
|
||||||
|
TIMER_INITIAL_COUNTER = 0x380, ///< LAPIC timer initial counter register, R/W
|
||||||
|
TIMER_CURRENT_COUNTER = 0x390, ///< LAPIC timer current counter register, RO
|
||||||
|
TIMER_DIVIDE_CONFIGURATION =
|
||||||
|
0x3e0 ///< LAPIC timer divide configuration register, RW
|
||||||
|
};
|
||||||
|
|
||||||
|
/*! \brief Get value from APIC register
|
||||||
|
*
|
||||||
|
* \param idx Register Offset Index
|
||||||
|
* \return current value of register
|
||||||
|
*/
|
||||||
|
Register read(Index idx);
|
||||||
|
|
||||||
|
/*! \brief Write value to APIC register
|
||||||
|
*
|
||||||
|
* \param idx Register Offset Index
|
||||||
|
* \param value value to be written into register
|
||||||
|
*/
|
||||||
|
void write(Index idx, Register value);
|
||||||
|
} // namespace LAPIC
|
||||||
91
arch/lapic_timer.cc
Normal file
91
arch/lapic_timer.cc
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
#include "lapic.h"
|
||||||
|
#include "lapic_registers.h"
|
||||||
|
|
||||||
|
namespace LAPIC {
|
||||||
|
namespace Timer {
|
||||||
|
|
||||||
|
/*! \brief Timer Delivery Status */
|
||||||
|
enum DeliveryStatus { IDLE = 0, SEND_PENDING = 1 };
|
||||||
|
|
||||||
|
/*! \brief Timer Mode */
|
||||||
|
enum TimerMode {
|
||||||
|
ONE_SHOT = 0,
|
||||||
|
PERIODIC = 1,
|
||||||
|
DEADLINE = 2
|
||||||
|
// reserved
|
||||||
|
};
|
||||||
|
|
||||||
|
/*! \brief Timer Mask */
|
||||||
|
enum Mask { NOT_MASKED = 0, MASKED = 1 };
|
||||||
|
|
||||||
|
static const Register INVALID_DIV = 0xff;
|
||||||
|
|
||||||
|
/*! \brief LAPIC-Timer Control Register
|
||||||
|
*
|
||||||
|
* \see [ISDMv3 10.5.1 Local Vector Table](intel_manual_vol3.pdf#page=375)
|
||||||
|
*/
|
||||||
|
union ControlRegister {
|
||||||
|
struct {
|
||||||
|
uint32_t vector : 8; ///< Vector
|
||||||
|
uint32_t : 4;
|
||||||
|
DeliveryStatus delivery_status : 1; ///< Delivery Status (readonly)
|
||||||
|
uint32_t : 3;
|
||||||
|
Mask masked : 1; ///< Interrupt Mask (if set, interrupt will not trigger)
|
||||||
|
TimerMode timer_mode : 2; ///< Timer Mode
|
||||||
|
uint32_t : 13;
|
||||||
|
};
|
||||||
|
Register value;
|
||||||
|
} __attribute__((packed));
|
||||||
|
|
||||||
|
/*! \brief LAPIC timer divider table
|
||||||
|
*
|
||||||
|
* \see [ISDMv3 10.5.4 APIC Timer](intel_manual_vol3.pdf#page=378)
|
||||||
|
*/
|
||||||
|
static const Register div_masks[] = {
|
||||||
|
0xb, ///< divides by 1
|
||||||
|
0x0, ///< divides by 2
|
||||||
|
0x1, ///< divides by 4
|
||||||
|
0x2, ///< divides by 8
|
||||||
|
0x3, ///< divides by 16
|
||||||
|
0x8, ///< divides by 32
|
||||||
|
0x9, ///< divides by 64
|
||||||
|
0xa ///< divides by 128
|
||||||
|
};
|
||||||
|
|
||||||
|
/*! \brief Calculate the bit mask for the LAPIC-timer divider.
|
||||||
|
* \param div Divider, must be power of two: 1, 2, 4, 8, 16, 32, 64, 128
|
||||||
|
* \return Bit mask for LAPIC::setTimer() or `0xff` if `div` is invalid.
|
||||||
|
*/
|
||||||
|
Register getClockDiv(uint8_t div) {
|
||||||
|
(void)div;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t ticks(void) {
|
||||||
|
uint32_t ticks = 0; // ticks per millisecond
|
||||||
|
// Calculation (Assignment 5)
|
||||||
|
return ticks;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set(uint32_t counter, uint8_t divide, uint8_t vector, bool periodic,
|
||||||
|
bool masked) {
|
||||||
|
(void)counter;
|
||||||
|
(void)divide;
|
||||||
|
(void)vector;
|
||||||
|
(void)periodic;
|
||||||
|
(void)masked;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool setup(uint32_t us) {
|
||||||
|
(void)us;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t interval() { return 0; }
|
||||||
|
|
||||||
|
void activate() {}
|
||||||
|
|
||||||
|
void setMasked(bool masked) { (void)masked; }
|
||||||
|
|
||||||
|
} // namespace Timer
|
||||||
|
} // namespace LAPIC
|
||||||
63
arch/pic.cc
Normal file
63
arch/pic.cc
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
#include "pic.h"
|
||||||
|
|
||||||
|
#include "ioport.h"
|
||||||
|
|
||||||
|
namespace PIC {
|
||||||
|
|
||||||
|
void initialize() {
|
||||||
|
// Access primary & secondary PIC via two ports each
|
||||||
|
IOPort primary_port_a(0x20);
|
||||||
|
IOPort primary_port_b(0x21);
|
||||||
|
IOPort secondary_port_a(0xa0);
|
||||||
|
IOPort secondary_port_b(0xa1);
|
||||||
|
|
||||||
|
// Initialization Command Word 1 (ICW1)
|
||||||
|
// Basic PIC configuration, starting initialization
|
||||||
|
enum InitializationCommandWord1 {
|
||||||
|
ICW4_NEEDED = 1 << 0, // use Initialization Command Word 4
|
||||||
|
SINGLE_MODE = 1 << 1, // Single or multiple (cascade mode) 8259A
|
||||||
|
ADDRESS_INTERVAL_HALF =
|
||||||
|
1 << 2, // 4 or 8 bit interval between the interrupt vector locations
|
||||||
|
LEVEL_TRIGGERED = 1 << 3, // Level or edge triggered
|
||||||
|
ALWAYS_1 = 1 << 4,
|
||||||
|
};
|
||||||
|
const uint8_t icw1 = InitializationCommandWord1::ICW4_NEEDED |
|
||||||
|
InitializationCommandWord1::ALWAYS_1;
|
||||||
|
// ICW1 in port A (each)
|
||||||
|
primary_port_a.outb(icw1);
|
||||||
|
secondary_port_a.outb(icw1);
|
||||||
|
|
||||||
|
// Initialization Command Word 2 (ICW2):
|
||||||
|
// Configure interrupt vector base offset in port B
|
||||||
|
primary_port_b.outb(0x20); // Primary: IRQ Offset 32
|
||||||
|
secondary_port_b.outb(0x28); // Secondary: IRQ Offset 40
|
||||||
|
|
||||||
|
// Initialization Command Word 3 (ICW3):
|
||||||
|
// Configure pin on primary PIC connected to secondary PIC
|
||||||
|
const uint8_t pin = 2; // Secondary connected on primary pin 2
|
||||||
|
primary_port_b.outb(1 << pin); // Pin as bit mask for primary
|
||||||
|
secondary_port_b.outb(pin); // Pin as value (ID) for secondary
|
||||||
|
|
||||||
|
// Initialization Command Word 4 (ICW4)
|
||||||
|
// Basic PIC configuration, starting initialization
|
||||||
|
enum InitializationCommandWord4 {
|
||||||
|
MODE_8086 = 1 << 0, // 8086/8088 or 8085 mode
|
||||||
|
AUTO_EOI = 1 << 1, // Single or multiple (cascade mode) 8259A
|
||||||
|
BUFFER_PRIMARY = 1 << 2, // Primary or secondary buffering
|
||||||
|
BUFFERED_MODE =
|
||||||
|
1 << 3, // Enable or disable buffering (for primary or secondary above)
|
||||||
|
SPECIAL_FULLY_NESTED = 1 << 4 // Special or non special fully nested
|
||||||
|
};
|
||||||
|
const uint8_t icw4 = InitializationCommandWord4::MODE_8086 |
|
||||||
|
InitializationCommandWord4::AUTO_EOI;
|
||||||
|
// ICW3 in port B (each)
|
||||||
|
primary_port_b.outb(icw4);
|
||||||
|
secondary_port_b.outb(icw4);
|
||||||
|
|
||||||
|
// Operation Control Word 1 (OCW1):
|
||||||
|
// Disable (mask) all hardware interrupts on both legacy PICs (we'll use APIC)
|
||||||
|
secondary_port_b.outb(0xff);
|
||||||
|
primary_port_b.outb(0xff);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace PIC
|
||||||
18
arch/pic.h
Normal file
18
arch/pic.h
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
/*! \file
|
||||||
|
* \brief Handle (disable) the old Programmable Interrupt Controller (PIC)
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "../types.h"
|
||||||
|
|
||||||
|
/*! \brief The Programmable Interrupt Controller (PIC aka 8259A)
|
||||||
|
*/
|
||||||
|
namespace PIC {
|
||||||
|
|
||||||
|
/*! \brief Initialize the PICs (Programmable Interrupt Controller, 8259A),
|
||||||
|
* such that all 15 hardware interrupts are stored sequentially in the \ref IDT
|
||||||
|
* and the hardware interrupts are disabled (in favor of \ref APIC).
|
||||||
|
*/
|
||||||
|
void initialize();
|
||||||
|
|
||||||
|
} // namespace PIC
|
||||||
225
arch/pit.cc
Normal file
225
arch/pit.cc
Normal file
@@ -0,0 +1,225 @@
|
|||||||
|
#include "pit.h"
|
||||||
|
|
||||||
|
#include "core.h"
|
||||||
|
#include "ioport.h"
|
||||||
|
|
||||||
|
namespace PIT {
|
||||||
|
|
||||||
|
// we only use PIT channel 2
|
||||||
|
const uint8_t CHANNEL = 2;
|
||||||
|
static IOPort data(0x40 + CHANNEL);
|
||||||
|
|
||||||
|
/*! \brief Access mode
|
||||||
|
*/
|
||||||
|
enum AccessMode {
|
||||||
|
LATCH_COUNT_VALUE = 0,
|
||||||
|
LOW_BYTE_ONLY = 1,
|
||||||
|
HIGH_BYTE_ONLY = 2,
|
||||||
|
LOW_AND_HIGH_BYTE = 3
|
||||||
|
};
|
||||||
|
|
||||||
|
/*! \brief Operating Mode
|
||||||
|
*
|
||||||
|
* \warning Channel 2 is not able to send interrupts, however, the status bit
|
||||||
|
* will be set
|
||||||
|
*/
|
||||||
|
enum OperatingMode {
|
||||||
|
INTERRUPT_ON_TERMINAL_COUNT = 0,
|
||||||
|
PROGRAMMABLE_ONE_SHOT = 1,
|
||||||
|
RATE_GENERATOR = 2,
|
||||||
|
SQUARE_WAVE_GENERATOR = 3, ///< useful for the PC speaker
|
||||||
|
SOFTWARE_TRIGGERED_STROBE = 4,
|
||||||
|
HARDWARE_TRIGGERED_STROBE = 5
|
||||||
|
};
|
||||||
|
|
||||||
|
/*! \brief data format
|
||||||
|
*/
|
||||||
|
enum Format {
|
||||||
|
BINARY = 0,
|
||||||
|
BCD = 1 ///< Binary Coded Decimals
|
||||||
|
};
|
||||||
|
|
||||||
|
// Mode register (only writable)
|
||||||
|
static IOPort mode_register(0x43);
|
||||||
|
union Mode {
|
||||||
|
struct {
|
||||||
|
Format format : 1;
|
||||||
|
OperatingMode operating : 3;
|
||||||
|
AccessMode access : 2;
|
||||||
|
uint8_t channel : 2;
|
||||||
|
};
|
||||||
|
uint8_t value;
|
||||||
|
|
||||||
|
/*! \brief Constructor for mode, takes the numeric value */
|
||||||
|
explicit Mode(uint8_t value) : value(value) {}
|
||||||
|
|
||||||
|
/*! \brief Constructor for counting mode
|
||||||
|
* \param access Access mode to the 16-bit counter value
|
||||||
|
* \param operating Operating mode for the counter
|
||||||
|
* \param format Number format for the 16-bit counter values (binary or
|
||||||
|
* BCD)
|
||||||
|
*/
|
||||||
|
Mode(AccessMode access, OperatingMode operating, Format format)
|
||||||
|
: format(format),
|
||||||
|
operating(operating),
|
||||||
|
access(access),
|
||||||
|
channel(PIT::CHANNEL) {}
|
||||||
|
|
||||||
|
/*! \brief (Default) constructor for reading the counter value
|
||||||
|
*/
|
||||||
|
Mode() : value(0) { this->channel = PIT::CHANNEL; }
|
||||||
|
|
||||||
|
/*! \brief Write the value to the mode register
|
||||||
|
*/
|
||||||
|
void write() const { mode_register.outb(value); }
|
||||||
|
};
|
||||||
|
|
||||||
|
// The NMI Status and Control Register contains details about PIT counter 2
|
||||||
|
static IOPort controlRegister(0x61);
|
||||||
|
union Control {
|
||||||
|
/*! \brief I/O-port bitmap for the NMI Status and Control Register
|
||||||
|
* \note Over time, the meaning of the bits stored at I/O port 0x61 changed;
|
||||||
|
* don't get the structure confused with old documentation on the IBM PC XT
|
||||||
|
* platform.
|
||||||
|
* \see [Intel® I/O Controller Hub 7 (ICH7)
|
||||||
|
* Family](i-o-controller-hub-7-datasheet.pdf#page=415), page 415
|
||||||
|
*/
|
||||||
|
struct {
|
||||||
|
//! If enabled, the interrupt state will be visible at status_timer_counter2
|
||||||
|
uint8_t enable_timer_counter2 : 1;
|
||||||
|
uint8_t enable_speaker_data : 1; ///< If set, speaker output is equal to
|
||||||
|
///< status_timer_counter2
|
||||||
|
uint8_t enable_pci_serr : 1; ///< not important, do not modify
|
||||||
|
uint8_t enable_nmi_iochk : 1; ///< not important, do not modify
|
||||||
|
const uint8_t
|
||||||
|
refresh_cycle_toggle : 1; ///< not important, must be 0 on write
|
||||||
|
const uint8_t
|
||||||
|
status_timer_counter2 : 1; ///< will be set on timer expiration; must
|
||||||
|
///< be 0 on write
|
||||||
|
const uint8_t
|
||||||
|
status_iochk_nmi_source : 1; ///< not important, must be 0 on write
|
||||||
|
const uint8_t
|
||||||
|
status_serr_nmi_source : 1; ///< not important, must be 0 on write
|
||||||
|
};
|
||||||
|
uint8_t value;
|
||||||
|
|
||||||
|
/*! \brief Constructor
|
||||||
|
* \param value Numeric value for the control register
|
||||||
|
*/
|
||||||
|
explicit Control(uint8_t value) : value(value) {}
|
||||||
|
|
||||||
|
/*! \brief Default constructor
|
||||||
|
* Automatically reads the current contents from the control register.
|
||||||
|
*/
|
||||||
|
Control() : value(controlRegister.inb()) {}
|
||||||
|
|
||||||
|
/*! \brief Write the current state to the control register.
|
||||||
|
*/
|
||||||
|
void write() const { controlRegister.outb(value); }
|
||||||
|
};
|
||||||
|
|
||||||
|
// The base frequency is, due to historic reasons, 1.193182 MHz.
|
||||||
|
const uint64_t BASE_FREQUENCY = 1193182ULL;
|
||||||
|
|
||||||
|
bool set(uint16_t us) {
|
||||||
|
// Counter ticks for us
|
||||||
|
uint64_t counter = BASE_FREQUENCY * us / 1000000ULL;
|
||||||
|
|
||||||
|
// As the hardware counter has a size of 16 bit, we want to check whether the
|
||||||
|
// calculated counter value is too large ( > 54.9ms )
|
||||||
|
if (counter > 0xffff) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interrupt state should be readable in status register, but PC speaker
|
||||||
|
// should remain off
|
||||||
|
Control c;
|
||||||
|
c.enable_speaker_data = 0;
|
||||||
|
c.enable_timer_counter2 = 1;
|
||||||
|
c.write();
|
||||||
|
|
||||||
|
// Channel 2, 16-bit divisor, with mode 0 (interrupt) in binary format
|
||||||
|
Mode m(AccessMode::LOW_AND_HIGH_BYTE,
|
||||||
|
OperatingMode::INTERRUPT_ON_TERMINAL_COUNT, Format::BINARY);
|
||||||
|
m.write();
|
||||||
|
|
||||||
|
// Set the counter's start value
|
||||||
|
data.outb(counter & 0xff); // low
|
||||||
|
data.outb((counter >> 8) & 0xff); // high
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t get(void) {
|
||||||
|
// Set mode to reading
|
||||||
|
Mode m;
|
||||||
|
m.write();
|
||||||
|
|
||||||
|
// Read low and high
|
||||||
|
uint16_t value = data.inb();
|
||||||
|
value |= data.inb() << 8;
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isActive(void) {
|
||||||
|
Control c; // reads the current value from the control register
|
||||||
|
return c.enable_timer_counter2 == 1 && c.status_timer_counter2 == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool waitForTimeout(void) {
|
||||||
|
while (true) {
|
||||||
|
Control c; // reads the current value from the control register
|
||||||
|
if (c.enable_timer_counter2 == 0) {
|
||||||
|
return false;
|
||||||
|
} else if (c.status_timer_counter2 == 1) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
Core::pause();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool delay(uint16_t us) { return set(us) && waitForTimeout(); }
|
||||||
|
|
||||||
|
void pcspeaker(uint32_t freq) {
|
||||||
|
Control c;
|
||||||
|
if (freq == 0) {
|
||||||
|
disable();
|
||||||
|
} else {
|
||||||
|
// calculate frequency divider
|
||||||
|
uint64_t div = BASE_FREQUENCY / freq;
|
||||||
|
if (div > 0xffff) {
|
||||||
|
div = 0xffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if already configured
|
||||||
|
if (c.enable_speaker_data == 0) {
|
||||||
|
// if not, set mode
|
||||||
|
Mode m(AccessMode::LOW_AND_HIGH_BYTE,
|
||||||
|
OperatingMode::SQUARE_WAVE_GENERATOR, Format::BINARY);
|
||||||
|
m.write();
|
||||||
|
}
|
||||||
|
|
||||||
|
// write frequency divider
|
||||||
|
data.outb(div & 0xff);
|
||||||
|
data.outb((div >> 8) & 0xff);
|
||||||
|
|
||||||
|
// already configured? (second part to prevent playing a wrong sound)
|
||||||
|
if (c.enable_speaker_data == 0) {
|
||||||
|
// activate PC speaker
|
||||||
|
c.enable_speaker_data = 1;
|
||||||
|
c.enable_timer_counter2 = 1;
|
||||||
|
c.write();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void disable(void) {
|
||||||
|
Control c;
|
||||||
|
c.enable_speaker_data = 0;
|
||||||
|
c.enable_timer_counter2 = 0;
|
||||||
|
c.write();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace PIT
|
||||||
80
arch/pit.h
Normal file
80
arch/pit.h
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
/*! \file
|
||||||
|
* \brief The old/historical \ref PIT "Programmable Interval Timer (PIT)"
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "../types.h"
|
||||||
|
|
||||||
|
/*! \brief Abstraction of the historical Programmable Interval Timer (PIT).
|
||||||
|
*
|
||||||
|
* Historically, PCs had a Timer component of type 8253 or 8254, modern systems
|
||||||
|
* come with a compatible chip. Each of these chips provides three 16-bit wide
|
||||||
|
* counters ("channel"), each running at a frequency of 1.19318 MHz. The timer's
|
||||||
|
* counting speed is thereby independent from the CPU frequency.
|
||||||
|
*
|
||||||
|
* Traditionally, the first counter (channel 0) was used for triggering
|
||||||
|
* interrupts, the second one (channel 1) controlled the memory refresh, and the
|
||||||
|
* third counter (channel 2) was assigned to the PC speaker.
|
||||||
|
*
|
||||||
|
* As the PIT's frequency is fixed to a constant value of 1.19318 MHz, the PIT
|
||||||
|
* can be used for calibration. For this purpose, we use channel 2 only.
|
||||||
|
*
|
||||||
|
* \note Interrupts should be disabled while configuring the timer.
|
||||||
|
*/
|
||||||
|
namespace PIT {
|
||||||
|
|
||||||
|
/*! \brief Start timer
|
||||||
|
*
|
||||||
|
* Sets the channel 2 timer to the provided value and starts counting.
|
||||||
|
*
|
||||||
|
* \note The maximum waiting time is approx. 54,900 us (16 bit / 1.193 MHz).
|
||||||
|
* \param us Waiting time in us
|
||||||
|
* \return `true` if the counter is running; `false` if the waiting time
|
||||||
|
* exceeds the limits.
|
||||||
|
*/
|
||||||
|
bool set(uint16_t us);
|
||||||
|
|
||||||
|
/*! \brief Reads the current timer value
|
||||||
|
* \return Current timer value
|
||||||
|
*/
|
||||||
|
uint16_t get(void);
|
||||||
|
|
||||||
|
/*! \brief Check if the timer is running
|
||||||
|
* \return `true` if running, `false` otherwise
|
||||||
|
*/
|
||||||
|
bool isActive(void);
|
||||||
|
|
||||||
|
/*! \brief (Active) waiting for timeout
|
||||||
|
* \return `true` when timeout was successfully hit, `false` if the timer was
|
||||||
|
* not active prior to calling.
|
||||||
|
*/
|
||||||
|
bool waitForTimeout(void);
|
||||||
|
|
||||||
|
/*! \brief Set the timer and wait for timeout
|
||||||
|
* \note The maximum waiting time is approx. 54,900 us (16 bit / 1.193 MHz).
|
||||||
|
* \param us Waiting time in us
|
||||||
|
* \return `true` when waiting successfully terminated; `false` on error (e.g.,
|
||||||
|
* waiting time exceeds its limits)
|
||||||
|
*/
|
||||||
|
bool delay(uint16_t us);
|
||||||
|
|
||||||
|
/*! \brief Play a given frequency on the PC speaker.
|
||||||
|
*
|
||||||
|
* As the PC speaker is connected to PIT channel 2, the PIT can be used to play
|
||||||
|
* an acoustic signal. Playing sounds occupies the PIT, so it cannot be used for
|
||||||
|
* other purposes while playback.
|
||||||
|
*
|
||||||
|
* \note Not every PC has an activated PC speaker
|
||||||
|
* \note Qemu & KVM have to be launched with `-soundhw pcspk`.
|
||||||
|
* If you still cannot hear anything, try to set `QEMU_AUDIO_DRV` to
|
||||||
|
* `alsa` (by launching \StuBS with `QEMU_AUDIO_DRV=alsa make kvm`)
|
||||||
|
* \param freq Frequency (in Hz) of the sound to be played, or 0 to deactivate
|
||||||
|
* playback.
|
||||||
|
*/
|
||||||
|
void pcspeaker(uint32_t freq);
|
||||||
|
|
||||||
|
/*! \brief Deactivate the timer
|
||||||
|
*/
|
||||||
|
void disable(void);
|
||||||
|
|
||||||
|
} // namespace PIT
|
||||||
41
arch/serial.cc
Normal file
41
arch/serial.cc
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
#include "serial.h"
|
||||||
|
|
||||||
|
Serial::Serial(ComPort port, BaudRate baud_rate, DataBits data_bits,
|
||||||
|
StopBits stop_bits, Parity parity)
|
||||||
|
: port(port) {
|
||||||
|
// initialize FIFO mode, no irqs for sending, irq if first byte was received
|
||||||
|
|
||||||
|
// line control, select r/w of divisor latch register
|
||||||
|
writeReg(LINE_CONTROL_REGISTER, DIVISOR_LATCH_ACCESS_BIT);
|
||||||
|
|
||||||
|
// TODO: Implement here the correct handling of input arguments
|
||||||
|
(void)baud_rate;
|
||||||
|
(void)data_bits;
|
||||||
|
(void)stop_bits;
|
||||||
|
(void)parity;
|
||||||
|
|
||||||
|
// FIFO: Enable & clear buffers
|
||||||
|
writeReg(FIFO_CONTROL_REGISTER,
|
||||||
|
ENABLE_FIFO | CLEAR_RECEIVE_FIFO | CLEAR_TRANSMIT_FIFO);
|
||||||
|
|
||||||
|
// Modem Control: OUT2 (0000 1000) must be set for interrupt
|
||||||
|
writeReg(MODEM_CONTROL_REGISTER, OUT_2);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Serial::writeReg(RegisterIndex reg, char out) {
|
||||||
|
// TODO: Implement
|
||||||
|
(void)reg;
|
||||||
|
(void)out;
|
||||||
|
}
|
||||||
|
|
||||||
|
char Serial::readReg(RegisterIndex reg) {
|
||||||
|
// TODO: Implement
|
||||||
|
(void)reg;
|
||||||
|
return '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
int Serial::write(char out) {
|
||||||
|
// TODO: Implement
|
||||||
|
(void)out;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
205
arch/serial.h
Normal file
205
arch/serial.h
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
/*! \file
|
||||||
|
* \brief Communication via the \ref Serial interface (RS-232)
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "../types.h"
|
||||||
|
|
||||||
|
/*! \brief Serial interface.
|
||||||
|
* \ingroup io
|
||||||
|
*
|
||||||
|
* This class provides a serial interface (COM1 - COM4) for communication with
|
||||||
|
* the outside world.
|
||||||
|
*
|
||||||
|
* The first IBM PC used the external chip [8250
|
||||||
|
* UART](https://de.wikipedia.org/wiki/NSC_8250), whereas, in today's systems,
|
||||||
|
* this functionality is commonly integrated into the motherboard chipset, but
|
||||||
|
* remained compatible.
|
||||||
|
*
|
||||||
|
* \see [PC8250A Data Sheet](uart-8250a.pdf#page=11) (Registers on page 11)
|
||||||
|
* \see [PC16550D Data Sheet](uart-16550d.pdf#page=16) (Successor, for optional
|
||||||
|
* FIFO buffer, page 16)
|
||||||
|
*/
|
||||||
|
|
||||||
|
class Serial {
|
||||||
|
public:
|
||||||
|
/*! \brief COM-Port
|
||||||
|
*
|
||||||
|
* The serial interface and its hardware addresses. Modern desktop PCs have,
|
||||||
|
* at most, a single, physical COM-port (`COM1`)
|
||||||
|
*/
|
||||||
|
enum ComPort {
|
||||||
|
COM1 = 0x3f8,
|
||||||
|
COM2 = 0x2f8,
|
||||||
|
COM3 = 0x3e8,
|
||||||
|
COM4 = 0x2e8,
|
||||||
|
};
|
||||||
|
|
||||||
|
/*! \brief Transmission speed
|
||||||
|
*
|
||||||
|
* The unit Baud describes the transmission speed in number of symbols per
|
||||||
|
* seconds. 1 Baud therefore equals the transmission of 1 symbol per second.
|
||||||
|
* The possible Baud rates are whole-number dividers of the clock frequency
|
||||||
|
* of 115200 Hz..
|
||||||
|
*/
|
||||||
|
enum BaudRate {
|
||||||
|
BAUD_300 = 384,
|
||||||
|
BAUD_600 = 192,
|
||||||
|
BAUD_1200 = 96,
|
||||||
|
BAUD_2400 = 48,
|
||||||
|
BAUD_4800 = 24,
|
||||||
|
BAUD_9600 = 12,
|
||||||
|
BAUD_19200 = 6,
|
||||||
|
BAUD_38400 = 3,
|
||||||
|
BAUD_57600 = 2,
|
||||||
|
BAUD_115200 = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
/*! \brief Number of data bits per character */
|
||||||
|
enum DataBits : uint8_t {
|
||||||
|
DATA_5BIT = 0,
|
||||||
|
DATA_6BIT = 1,
|
||||||
|
DATA_7BIT = 2,
|
||||||
|
DATA_8BIT = 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
/*! \brief Number of stop bits per character */
|
||||||
|
enum StopBits : uint8_t {
|
||||||
|
STOP_1BIT = 0,
|
||||||
|
STOP_1_5BIT = 4,
|
||||||
|
STOP_2BIT = 4,
|
||||||
|
};
|
||||||
|
|
||||||
|
/*! \brief parity bit */
|
||||||
|
enum Parity : uint8_t {
|
||||||
|
PARITY_NONE = 0,
|
||||||
|
PARITY_ODD = 8,
|
||||||
|
PARITY_EVEN = 24,
|
||||||
|
PARITY_MARK = 40,
|
||||||
|
PARITY_SPACE = 56,
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
/*! \brief register index */
|
||||||
|
enum RegisterIndex {
|
||||||
|
// if Divisor Latch Access Bit [DLAB] = 0
|
||||||
|
RECEIVE_BUFFER_REGISTER = 0, ///< read only
|
||||||
|
TRANSMIT_BUFFER_REGISTER = 0, ///< write only
|
||||||
|
INTERRUPT_ENABLE_REGISTER = 1,
|
||||||
|
|
||||||
|
// if Divisor Latch Access Bit [DLAB] = 1
|
||||||
|
DIVISOR_LOW_REGISTER = 0,
|
||||||
|
DIVISOR_HIGH_REGISTER = 1,
|
||||||
|
|
||||||
|
// (irrespective from DLAB)
|
||||||
|
INTERRUPT_IDENT_REGISTER = 2, ///< read only
|
||||||
|
FIFO_CONTROL_REGISTER =
|
||||||
|
2, ///< write only -- 16550 and newer (esp. not 8250a)
|
||||||
|
LINE_CONTROL_REGISTER = 3, ///< highest-order bit is DLAB (see above)
|
||||||
|
MODEM_CONTROL_REGISTER = 4,
|
||||||
|
LINE_STATUS_REGISTER = 5,
|
||||||
|
MODEM_STATUS_REGISTER = 6
|
||||||
|
};
|
||||||
|
|
||||||
|
/*! \brief Mask for the respective register */
|
||||||
|
enum RegisterMask : uint8_t {
|
||||||
|
// Interrupt Enable Register
|
||||||
|
RECEIVED_DATA_AVAILABLE = 1 << 0,
|
||||||
|
TRANSMITTER_HOLDING_REGISTER_EMPTY = 1 << 1,
|
||||||
|
RECEIVER_LINE_STATUS = 1 << 2,
|
||||||
|
MODEM_STATUS = 1 << 3,
|
||||||
|
|
||||||
|
// Interrupt Ident Register
|
||||||
|
INTERRUPT_PENDING = 1 << 0, ///< 0 means interrupt pending
|
||||||
|
INTERRUPT_ID_0 = 1 << 1,
|
||||||
|
INTERRUPT_ID_1 = 1 << 2,
|
||||||
|
|
||||||
|
// FIFO Control Register
|
||||||
|
ENABLE_FIFO = 1 << 0, ///< 0 means disabled ^= conforming to 8250a
|
||||||
|
CLEAR_RECEIVE_FIFO = 1 << 1,
|
||||||
|
CLEAR_TRANSMIT_FIFO = 1 << 2,
|
||||||
|
DMA_MODE_SELECT = 1 << 3,
|
||||||
|
TRIGGER_RECEIVE = 1 << 6,
|
||||||
|
|
||||||
|
// Line Control Register
|
||||||
|
// bits per character: 5 6 7 8
|
||||||
|
WORD_LENGTH_SELECT_0 = 1 << 0, // Setting Select0: 0 1 0 1
|
||||||
|
WORD_LENGTH_SELECT_1 = 1 << 1, // Setting Select1: 0 0 1 1
|
||||||
|
NUMBER_OF_STOP_BITS = 1 << 2, // 0 ≙ one stop bit, 1 ≙ 1.5/2 stop bits
|
||||||
|
PARITY_ENABLE = 1 << 3,
|
||||||
|
EVEN_PARITY_SELECT = 1 << 4,
|
||||||
|
STICK_PARITY = 1 << 5,
|
||||||
|
SET_BREAK = 1 << 6,
|
||||||
|
DIVISOR_LATCH_ACCESS_BIT = 1 << 7, // DLAB
|
||||||
|
|
||||||
|
// Modem Control Register
|
||||||
|
DATA_TERMINAL_READY = 1 << 0,
|
||||||
|
REQUEST_TO_SEND = 1 << 1,
|
||||||
|
OUT_1 = 1 << 2,
|
||||||
|
OUT_2 = 1 << 3, // must be set for interrupts!
|
||||||
|
LOOP = 1 << 4,
|
||||||
|
|
||||||
|
// Line Status Register
|
||||||
|
DATA_READY = 1 << 0, // Set when there is a value in the receive buffer
|
||||||
|
OVERRUN_ERROR = 1 << 1,
|
||||||
|
PARITY_ERROR = 1 << 2,
|
||||||
|
FRAMING_ERROR = 1 << 3,
|
||||||
|
BREAK_INTERRUPT = 1 << 4,
|
||||||
|
TRANSMITTER_HOLDING_REGISTER = 1 << 5,
|
||||||
|
TRANSMITTER_EMPTY = 1 << 6, // Send buffer empty (ready to send)
|
||||||
|
|
||||||
|
// Modem Status Register
|
||||||
|
DELTA_CLEAR_TO_SEND = 1 << 0,
|
||||||
|
DELTA_DATA_SET_READY = 1 << 1,
|
||||||
|
TRAILING_EDGE_RING_INDICATOR = 1 << 2,
|
||||||
|
DELTA_DATA_CARRIER_DETECT = 1 << 3,
|
||||||
|
CLEAR_TO_SEND = 1 << 4,
|
||||||
|
DATA_SET_READY = 1 << 5,
|
||||||
|
RING_INDICATOR = 1 << 6,
|
||||||
|
DATA_CARRIER_DETECT = 1 << 7
|
||||||
|
};
|
||||||
|
|
||||||
|
/*! \brief Read value from register
|
||||||
|
*
|
||||||
|
* \todo(11) Implement Method
|
||||||
|
*
|
||||||
|
* \param reg Register index
|
||||||
|
* \return The value read from register
|
||||||
|
*/
|
||||||
|
char readReg(RegisterIndex reg);
|
||||||
|
|
||||||
|
/*! \brief Write value to register
|
||||||
|
*
|
||||||
|
* \todo(11) Implement Method
|
||||||
|
*
|
||||||
|
* \param reg Register index
|
||||||
|
* \param out value to be written
|
||||||
|
*/
|
||||||
|
void writeReg(RegisterIndex reg, char out);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/*! \brief Selected COM port */
|
||||||
|
const ComPort port;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/*! \brief Constructor
|
||||||
|
*
|
||||||
|
* Creates a Serial object that encapsulates the used COM port, as well as the
|
||||||
|
* parameters used for the serial connection. Default values are `8N1` (8 bit,
|
||||||
|
* no parity bit, one stop bit) with 115200 Baud using COM1.
|
||||||
|
*
|
||||||
|
* \todo(11) - Implement Constructor
|
||||||
|
*/
|
||||||
|
explicit Serial(ComPort port = COM1, BaudRate baud_rate = BAUD_115200,
|
||||||
|
DataBits data_bits = DATA_8BIT,
|
||||||
|
StopBits stop_bits = STOP_1BIT, Parity parity = PARITY_NONE);
|
||||||
|
|
||||||
|
/*! \brief Write one byte to the serial interface
|
||||||
|
*
|
||||||
|
* \todo(11) - Implement Method
|
||||||
|
*
|
||||||
|
* \param out Byte to be written
|
||||||
|
* \return Byte written (or `-1` if writing byte failed)
|
||||||
|
*/
|
||||||
|
int write(char out);
|
||||||
|
};
|
||||||
16
arch/system.cc
Normal file
16
arch/system.cc
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
#include "system.h"
|
||||||
|
|
||||||
|
#include "../debug/output.h"
|
||||||
|
#include "cmos.h"
|
||||||
|
#include "ioport.h"
|
||||||
|
|
||||||
|
namespace System {
|
||||||
|
|
||||||
|
void reboot() {
|
||||||
|
const IOPort system_control_port_a(0x92);
|
||||||
|
DBG_VERBOSE << "rebooting smp" << endl;
|
||||||
|
CMOS::write(CMOS::REG_STATUS_SHUTDOWN, 0);
|
||||||
|
system_control_port_a.outb(0x3);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace System
|
||||||
15
arch/system.h
Normal file
15
arch/system.h
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
/*! \file
|
||||||
|
* \brief General \ref System functionality (\ref System::reboot "reboot")
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "../types.h"
|
||||||
|
|
||||||
|
/*! \brief General System functions
|
||||||
|
*/
|
||||||
|
namespace System {
|
||||||
|
|
||||||
|
/*! \brief Perform a reboot
|
||||||
|
*/
|
||||||
|
void reboot();
|
||||||
|
} // namespace System
|
||||||
41
arch/textwindow.cc
Normal file
41
arch/textwindow.cc
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
#include "textwindow.h"
|
||||||
|
|
||||||
|
TextWindow::TextWindow(unsigned from_col, unsigned to_col, unsigned from_row,
|
||||||
|
unsigned to_row, bool use_cursor) {
|
||||||
|
(void)from_col;
|
||||||
|
(void)to_col;
|
||||||
|
(void)from_row;
|
||||||
|
(void)to_row;
|
||||||
|
(void)use_cursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextWindow::setPos(unsigned rel_x, unsigned rel_y) {
|
||||||
|
(void)rel_x;
|
||||||
|
(void)rel_y;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextWindow::getPos(unsigned& rel_x, unsigned& rel_y) const {
|
||||||
|
(void)rel_x;
|
||||||
|
(void)rel_y;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextWindow::setPos(int rel_x, int rel_y) {
|
||||||
|
(void)rel_x;
|
||||||
|
(void)rel_y;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextWindow::getPos(int& rel_x, int& rel_y) const {
|
||||||
|
(void)rel_x;
|
||||||
|
(void)rel_y;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextWindow::print(const char* str, size_t length, CGA::Attribute attrib) {
|
||||||
|
(void)str;
|
||||||
|
(void)length;
|
||||||
|
(void)attrib;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextWindow::reset(char character, CGA::Attribute attrib) {
|
||||||
|
(void)character;
|
||||||
|
(void)attrib;
|
||||||
|
}
|
||||||
134
arch/textwindow.h
Normal file
134
arch/textwindow.h
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
/*! \file
|
||||||
|
* \brief \ref TextWindow provides virtual output windows in text mode
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "../types.h"
|
||||||
|
#include "cga.h"
|
||||||
|
|
||||||
|
/*! \brief Virtual windows in text mode
|
||||||
|
* \ingroup io
|
||||||
|
*
|
||||||
|
* Outputs text on a part of the screen in \ref CGA,
|
||||||
|
* a window is defined in by position and size (with its own cursor).
|
||||||
|
*
|
||||||
|
* This allows to separate the output of the application from the debug output
|
||||||
|
* on the screen without having to synchronize.
|
||||||
|
*/
|
||||||
|
class TextWindow {
|
||||||
|
// Prevent copies and assignments
|
||||||
|
TextWindow(const TextWindow&) = delete;
|
||||||
|
TextWindow& operator=(const TextWindow&) = delete;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/*! \brief Constructor of a text window
|
||||||
|
*
|
||||||
|
* Creates a virtual, rectangular text window on the screen.
|
||||||
|
* The coordinates to construct the window are absolute positions in the
|
||||||
|
* \ref CGA screen.
|
||||||
|
*
|
||||||
|
* \note Overlapping windows are neither supported nor prevented -- better
|
||||||
|
* just try to avoid construction windows with overlapping coordinates!
|
||||||
|
*
|
||||||
|
* \warning Don't use the hardware cursor in more than one window!
|
||||||
|
*
|
||||||
|
* \param from_col Text Window starts in column `from_col`,
|
||||||
|
* the first (leftmost) possible column is `0`
|
||||||
|
* \param to_col Text Window extends to the right to column `to_col`
|
||||||
|
* (exclusive). This column has to be strictly greater than `from_col`, the
|
||||||
|
* maximum allowed value is \ref CGA::COLUMNS (rightmost)
|
||||||
|
* \param from_row Text Window starts in row `from_row`,
|
||||||
|
* the first possible (uppermost) row is `0`
|
||||||
|
* \param to_row Text Window extends down to row `to_row` (exclusive).
|
||||||
|
* This row has to be strictly greater than `from_row`,
|
||||||
|
* the maximum allowed value is \ref CGA::ROWS (bottom-most)
|
||||||
|
* \param use_cursor Specifies whether the hardware cursor (`true`) or a
|
||||||
|
* software cursor/variable (`false`) should be used to
|
||||||
|
* store the current position
|
||||||
|
*
|
||||||
|
* \todo(11) Implement constructor
|
||||||
|
*/
|
||||||
|
TextWindow(unsigned from_col, unsigned to_col, unsigned from_row,
|
||||||
|
unsigned to_row, bool use_cursor = false);
|
||||||
|
|
||||||
|
/*! \brief Set the cursor position in the window
|
||||||
|
*
|
||||||
|
* Depending on the constructor parameter `use_cursor` either the
|
||||||
|
* hardware cursor (and only the hardware cursor!) is used or the position
|
||||||
|
* is stored internally in the object.
|
||||||
|
*
|
||||||
|
* The coordinates are relative to the upper left starting position of
|
||||||
|
* the window.
|
||||||
|
*
|
||||||
|
* \param rel_x Column in window
|
||||||
|
* \param rel_y Row in window
|
||||||
|
* \todo(11) Implement method, use \ref CGA::setCursor() for the hardware
|
||||||
|
* cursor
|
||||||
|
*/
|
||||||
|
void setPos(unsigned rel_x, unsigned rel_y);
|
||||||
|
|
||||||
|
/*! \brief Set the cursor position in the window
|
||||||
|
*
|
||||||
|
* Depending on the constructor parameter `use_cursor` either the
|
||||||
|
* hardware cursor (and only the hardware cursor!) is used or the position
|
||||||
|
* is stored internally in the object.
|
||||||
|
*
|
||||||
|
* The coordinates are relative to the upper left starting position of
|
||||||
|
* the window.
|
||||||
|
* Negative coordinates are interpreted relative to the right and bottom
|
||||||
|
* border of the window.
|
||||||
|
*
|
||||||
|
* \todo(11) Implement this method (it can either use or replace
|
||||||
|
* \ref setPos(unsigned, unsigned))
|
||||||
|
*/
|
||||||
|
void setPos(int rel_x, int rel_y);
|
||||||
|
|
||||||
|
/*! \brief Get the current cursor position in the window
|
||||||
|
*
|
||||||
|
* Depending on the constructor parameter `use_cursor` either the
|
||||||
|
* hardware cursor (and only the hardware cursor!) is used or the position
|
||||||
|
* is retrieved from the internally stored object.
|
||||||
|
*
|
||||||
|
* \param rel_x Column in window
|
||||||
|
* \param rel_y Row in window
|
||||||
|
* \todo(11) Implement Method, use \ref CGA::getCursor() for the hardware
|
||||||
|
* cursor
|
||||||
|
*/
|
||||||
|
void getPos(unsigned& rel_x, unsigned& rel_y) const;
|
||||||
|
|
||||||
|
/// \copydoc TextWindow::getPos(unsigned&,unsigned&) const
|
||||||
|
void getPos(int& rel_x, int& rel_y) const;
|
||||||
|
|
||||||
|
/*! \brief Display multiple characters in the window
|
||||||
|
*
|
||||||
|
* Output a character string, starting at the current cursor position.
|
||||||
|
* Since the string does not need to contain a `\0` termination (unlike the
|
||||||
|
* common C string), a length parameter is required to specify the number
|
||||||
|
* of characters in the string.
|
||||||
|
* When the output is complete, the cursor is positioned after the last
|
||||||
|
* printed character.
|
||||||
|
* The same attributes (colors) are used for the entire text.
|
||||||
|
*
|
||||||
|
* If there is not enough space left at the end of the line,
|
||||||
|
* the output continues on the following line.
|
||||||
|
* As soon as the last window line is filled, the entire window area is
|
||||||
|
* moved up one line: The first line disappears, the bottom line is cleared.
|
||||||
|
*
|
||||||
|
* A line break also occurs whenever the character `\n` appears in the text.
|
||||||
|
*
|
||||||
|
* \param string Text to be printed
|
||||||
|
* \param length Length of text
|
||||||
|
* \param attrib Attribute for text
|
||||||
|
* \todo(11) Implement Method
|
||||||
|
*/
|
||||||
|
void print(const char* string, size_t length,
|
||||||
|
CGA::Attribute attrib = CGA::Attribute()); // NOLINT
|
||||||
|
|
||||||
|
/*! \brief Delete all contents in the window and reset the cursor.
|
||||||
|
*
|
||||||
|
* \param character Fill character
|
||||||
|
* \param attrib Attribute for fill character
|
||||||
|
* \todo(11) Implement Method
|
||||||
|
*/
|
||||||
|
void reset(char character = ' ', CGA::Attribute attrib = CGA::Attribute());
|
||||||
|
};
|
||||||
245
boot/longmode.asm
Normal file
245
boot/longmode.asm
Normal file
@@ -0,0 +1,245 @@
|
|||||||
|
; The stony path to Long Mode (64-bit)...
|
||||||
|
; ... begins in 32-bit Protected Mode
|
||||||
|
[BITS 32]
|
||||||
|
|
||||||
|
; Pointer to Long Mode Global Descriptor Table (GDT, 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
|
||||||
22
boot/multiboot/config.inc
Normal file
22
boot/multiboot/config.inc
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
; Magic Header, has to be present in Kernel to indicate Multiboot compliance
|
||||||
|
MULTIBOOT_HEADER_MAGIC_OS equ 0x1badb002
|
||||||
|
|
||||||
|
; Answer by the boot loader for Multiboot compliance, written in eax register
|
||||||
|
MULTIBOOT_HEADER_MAGIC_LOADER equ 0x2badb002
|
||||||
|
|
||||||
|
; Flags instructing the Multiboot compliant boot loader to setup the system
|
||||||
|
; according to your needs
|
||||||
|
MULTIBOOT_PAGE_ALIGN equ 1<<0 ; Align boot modules (initrds) at 4 KiB border
|
||||||
|
MULTIBOOT_MEMORY_INFO equ 1<<1 ; Request Memory Map information
|
||||||
|
MULTIBOOT_VIDEO_MODE equ 1<<2 ; Configure video mode
|
||||||
|
|
||||||
|
MULTIBOOT_HEADER_FLAGS equ 0
|
||||||
|
|
||||||
|
; Desired video mode (only considered if MULTIBOOT_VIDEO_MODE set)
|
||||||
|
; (boot loader will choose the best fitting mode, which might differ from the settings below)
|
||||||
|
MULTIBOOT_VIDEO_WIDTH equ 1280 ; Desired width
|
||||||
|
MULTIBOOT_VIDEO_HEIGHT equ 1024 ; Desired height
|
||||||
|
MULTIBOOT_VIDEO_BITDEPTH equ 32 ; Desired bit depth
|
||||||
|
|
||||||
|
; Checksum
|
||||||
|
MULTIBOOT_HEADER_CHKSUM equ -(MULTIBOOT_HEADER_MAGIC_OS + MULTIBOOT_HEADER_FLAGS)
|
||||||
167
boot/multiboot/data.cc
Normal file
167
boot/multiboot/data.cc
Normal file
@@ -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
|
||||||
230
boot/multiboot/data.h
Normal file
230
boot/multiboot/data.h
Normal file
@@ -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
|
||||||
33
boot/multiboot/header.asm
Normal file
33
boot/multiboot/header.asm
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
; The first 8192 bytes of the kernel binary must contain a header with
|
||||||
|
; predefined (and sometimes "magic") values according to the Multiboot standard.
|
||||||
|
; Based on these values, the boot loader decides whether and how to load the
|
||||||
|
; kernel -- which is compiled and linked into an ELF file.
|
||||||
|
; To make this possible with your StuBS kernel, the linker places the following
|
||||||
|
; entry `multiboot_header` at the very beginning of the file thanks to the
|
||||||
|
; linker script (located in compiler/sections.ld).
|
||||||
|
|
||||||
|
[SECTION .multiboot_header]
|
||||||
|
|
||||||
|
; Include configuration
|
||||||
|
%include 'boot/multiboot/config.inc'
|
||||||
|
|
||||||
|
; Multiboot Header
|
||||||
|
align 4
|
||||||
|
multiboot_header:
|
||||||
|
dd MULTIBOOT_HEADER_MAGIC_OS ; Magic Header Value
|
||||||
|
dd MULTIBOOT_HEADER_FLAGS ; Flags (affects following entries)
|
||||||
|
dd MULTIBOOT_HEADER_CHKSUM ; Header Checksum
|
||||||
|
|
||||||
|
; Following fields would have been required to be defined
|
||||||
|
; if flag A_OUT KLUDGE was set (but we don't need this)
|
||||||
|
dd 0 ; Header address
|
||||||
|
dd 0 ; Begin of load address
|
||||||
|
dd 0 ; end of load address
|
||||||
|
dd 0 ; end of bss segment
|
||||||
|
dd 0 ; address of entry function
|
||||||
|
|
||||||
|
; Following fields are required for video mode (flag MULTIBOOT_VIDEO_MODE)
|
||||||
|
dd 0 ; Mode: 0 = Graphic / 1 = Text
|
||||||
|
dd MULTIBOOT_VIDEO_WIDTH ; Width (pixels / columns)
|
||||||
|
dd MULTIBOOT_VIDEO_HEIGHT ; Height (pixels / rows)
|
||||||
|
dd MULTIBOOT_VIDEO_BITDEPTH ; color depth / number of colors
|
||||||
77
boot/startup.asm
Normal file
77
boot/startup.asm
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
; This is the actual entry point of the kernel.
|
||||||
|
; The switch into the 32-bit 'Protected Mode' has already been performed
|
||||||
|
; (by the boot loader).
|
||||||
|
; The assembly code just performs the absolute necessary steps (like setting up
|
||||||
|
; the stack) to be able to jump into the C++ code -- and continue further
|
||||||
|
; initialization in a (more) high-level language.
|
||||||
|
|
||||||
|
[BITS 32]
|
||||||
|
|
||||||
|
; External functions and variables
|
||||||
|
[EXTERN CPU_CORE_STACK_SIZE] ; Constant containing the initial stack size (per CPU core), see `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
|
||||||
73
boot/startup.cc
Normal file
73
boot/startup.cc
Normal file
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
50
boot/startup.h
Normal file
50
boot/startup.h
Normal file
@@ -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();
|
||||||
68
boot/startup_ap.asm
Normal file
68
boot/startup_ap.asm
Normal file
@@ -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
|
||||||
|
|
||||||
82
boot/startup_ap.cc
Normal file
82
boot/startup_ap.cc
Normal file
@@ -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
|
||||||
114
boot/startup_ap.h
Normal file
114
boot/startup_ap.h
Normal file
@@ -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!");
|
||||||
13
compiler/fix.h
Normal file
13
compiler/fix.h
Normal file
@@ -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
|
||||||
45
compiler/libc.cc
Normal file
45
compiler/libc.cc
Normal file
@@ -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;
|
||||||
|
}
|
||||||
23
compiler/libc.h
Normal file
23
compiler/libc.h
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
/*! \file
|
||||||
|
* \brief Initialization functions for global objects required by the compiler
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "../types.h"
|
||||||
|
|
||||||
|
/*! \brief C StartUp (CSU)
|
||||||
|
* required by the compiler and provided by the c standard library
|
||||||
|
*/
|
||||||
|
namespace CSU {
|
||||||
|
|
||||||
|
/*! \brief Call global constructors and initialization functions
|
||||||
|
* (this is usually done by __libc_csu_init)
|
||||||
|
*/
|
||||||
|
void initializer();
|
||||||
|
|
||||||
|
/*! \brief Call global destructors and finalizer functions
|
||||||
|
* (this is usually done by __libc_csu_fini)
|
||||||
|
*/
|
||||||
|
void finalizer();
|
||||||
|
|
||||||
|
} // namespace CSU
|
||||||
21
compiler/libcxx.cc
Normal file
21
compiler/libcxx.cc
Normal file
@@ -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) {
|
||||||
|
}
|
||||||
|
}
|
||||||
107
compiler/sections.ld
Normal file
107
compiler/sections.ld
Normal file
@@ -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")
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
||||||
14
debug/assert.cc
Normal file
14
debug/assert.cc
Normal file
@@ -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]]
|
||||||
|
}
|
||||||
75
debug/assert.h
Normal file
75
debug/assert.h
Normal file
@@ -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
|
||||||
44
debug/copystream.h
Normal file
44
debug/copystream.h
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
/*! \file
|
||||||
|
* \brief \ref CopyStream duplicates \ref OutputStream "output streams"
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "../object/outputstream.h"
|
||||||
|
#include "../types.h"
|
||||||
|
|
||||||
|
/*! \brief Duplicate all data passed by the stream operator to two \ref
|
||||||
|
* OutputStream "output streams"
|
||||||
|
* \ingroup io
|
||||||
|
*
|
||||||
|
* Can be used as replacement for any \ref OutputStream -- for example,
|
||||||
|
* forwarding the \ref DBG output simultaneously to screen (\ref TextStream) and
|
||||||
|
* serial console (\ref SerialStream).
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class CopyStream : public OutputStream {
|
||||||
|
/*! \brief First recipient
|
||||||
|
*/
|
||||||
|
OutputStream* first;
|
||||||
|
|
||||||
|
/*! \brief Second recipient
|
||||||
|
*/
|
||||||
|
OutputStream* second;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/*! \brief Constructor
|
||||||
|
*
|
||||||
|
* \param first First recipient for output passed to this object
|
||||||
|
* \param second Second recipient for output passed to this object
|
||||||
|
*/
|
||||||
|
CopyStream(OutputStream* first, OutputStream* second)
|
||||||
|
: first(first), second(second) {}
|
||||||
|
|
||||||
|
/*! \brief Redirect the buffer to both streams and flush them, too.
|
||||||
|
*/
|
||||||
|
void flush() override {
|
||||||
|
buffer[pos] = '\0'; // make sure buffer will only be printed until pos.
|
||||||
|
*first << buffer << ::flush;
|
||||||
|
*second << buffer << ::flush;
|
||||||
|
pos = 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
27
debug/kernelpanic.h
Normal file
27
debug/kernelpanic.h
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
// vim: set noet ts=4 sw=4:
|
||||||
|
|
||||||
|
/*! \file
|
||||||
|
* \brief Macro to print an error message and stop the current core.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "../types.h"
|
||||||
|
|
||||||
|
/*! \def kernelpanic
|
||||||
|
* \brief Print an error message in the debug window and \ref Core::die "stop
|
||||||
|
* the current core"
|
||||||
|
*
|
||||||
|
* \param MSG error message
|
||||||
|
* \ingroup debug
|
||||||
|
*/
|
||||||
|
#define kernelpanic(MSG) \
|
||||||
|
do { \
|
||||||
|
DBG << "PANIC: '" << (MSG) << "' in " << __func__ << " @ " << __FILE__ \
|
||||||
|
<< ":" << __LINE__ << ") - CPU stopped." << endl; \
|
||||||
|
Core::die(); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
// The includes are intentionally placed at the end, so the macro can be used
|
||||||
|
// inside those included files as well.
|
||||||
|
#include "../arch/core.h"
|
||||||
|
#include "./output.h"
|
||||||
4
debug/nullstream.cc
Normal file
4
debug/nullstream.cc
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
#include "nullstream.h"
|
||||||
|
|
||||||
|
// Instance
|
||||||
|
NullStream nullstream;
|
||||||
45
debug/nullstream.h
Normal file
45
debug/nullstream.h
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
/*! \file
|
||||||
|
* \brief \ref NullStream is a stream discarding everything
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "../object/outputstream.h"
|
||||||
|
#include "../types.h"
|
||||||
|
|
||||||
|
/*! \brief Ignore all data passed by the stream operator
|
||||||
|
* \ingroup io
|
||||||
|
*
|
||||||
|
* Can be used instead of the \ref OutputStream if (for debugging reasons) all
|
||||||
|
* output should be ignored, e.g. for \ref DBG_VERBOSE
|
||||||
|
*
|
||||||
|
* By using template programming, a single generic methods is sufficient
|
||||||
|
* (which simply discard everything).
|
||||||
|
*/
|
||||||
|
class NullStream {
|
||||||
|
/*! \brief Check if type is supported by output stream
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
auto check(T v, OutputStream* p = nullptr) -> decltype(*p << v, void()) {}
|
||||||
|
|
||||||
|
public:
|
||||||
|
/*! \brief Empty default constructor
|
||||||
|
*/
|
||||||
|
NullStream() {}
|
||||||
|
|
||||||
|
/*! \brief Generic stream operator for any data type
|
||||||
|
*
|
||||||
|
* Uses template meta programming for a generic & short solution
|
||||||
|
*
|
||||||
|
* \tparam T Type of data to ignore
|
||||||
|
* \param value data to be ignore
|
||||||
|
* \return Reference to the \ref NullStream object allowing concatenation of
|
||||||
|
* operators
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
NullStream& operator<<(T value) {
|
||||||
|
check(value);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
extern NullStream nullstream;
|
||||||
93
debug/output.h
Normal file
93
debug/output.h
Normal file
@@ -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];
|
||||||
122
device/keydecoder.cc
Normal file
122
device/keydecoder.cc
Normal file
@@ -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;
|
||||||
|
}
|
||||||
40
device/keydecoder.h
Normal file
40
device/keydecoder.h
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
/*! \file
|
||||||
|
* \brief \ref KeyDecoder decodes a keystroke to the corresponding \ref Key
|
||||||
|
* object
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "../object/key.h"
|
||||||
|
#include "../types.h"
|
||||||
|
|
||||||
|
/*! \brief Decoder for \ref ps2keyboardset1 "keyboard codes" received from the
|
||||||
|
* \ref PS2Controller
|
||||||
|
* \ingroup io
|
||||||
|
*
|
||||||
|
* Extracts the make and break codes, modifier and scan codes from the pressed
|
||||||
|
* key.
|
||||||
|
*/
|
||||||
|
class KeyDecoder {
|
||||||
|
unsigned char prefix; ///< Prefix byte for keys
|
||||||
|
Key modifier; ///< activated modifier keys (e.g., caps lock)
|
||||||
|
|
||||||
|
public:
|
||||||
|
/*! \brief Current state (pressed or released) of all keys.
|
||||||
|
*/
|
||||||
|
bool status[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);
|
||||||
|
};
|
||||||
130
device/ps2controller.cc
Normal file
130
device/ps2controller.cc
Normal file
@@ -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
|
||||||
153
device/ps2controller.h
Normal file
153
device/ps2controller.h
Normal file
@@ -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
|
||||||
30
device/serialstream.cc
Normal file
30
device/serialstream.cc
Normal file
@@ -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;
|
||||||
|
}
|
||||||
143
device/serialstream.h
Normal file
143
device/serialstream.h
Normal file
@@ -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);
|
||||||
|
};
|
||||||
12
device/textstream.cc
Normal file
12
device/textstream.cc
Normal file
@@ -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() {}
|
||||||
41
device/textstream.h
Normal file
41
device/textstream.h
Normal file
@@ -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();
|
||||||
|
};
|
||||||
12
interrupt/epilogues.cc
Normal file
12
interrupt/epilogues.cc
Normal file
@@ -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
|
||||||
50
interrupt/epilogues.h
Normal file
50
interrupt/epilogues.h
Normal file
@@ -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
|
||||||
30
interrupt/guard.cc
Normal file
30
interrupt/guard.cc
Normal file
@@ -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; }
|
||||||
121
interrupt/guard.h
Normal file
121
interrupt/guard.h
Normal file
@@ -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
|
||||||
14
interrupt/handlers.asm
Normal file
14
interrupt/handlers.asm
Normal file
@@ -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
|
||||||
102
interrupt/handlers.cc
Normal file
102
interrupt/handlers.cc
Normal file
@@ -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();
|
||||||
|
}
|
||||||
109
interrupt/handlers.h
Normal file
109
interrupt/handlers.h
Normal file
@@ -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);
|
||||||
28
main.cc
Normal file
28
main.cc
Normal file
@@ -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;
|
||||||
|
}
|
||||||
3
nix-develop.sh
Normal file
3
nix-develop.sh
Normal file
@@ -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 "$@"
|
||||||
62
object/bbuffer.h
Normal file
62
object/bbuffer.h
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
};
|
||||||
118
object/key.cc
Normal file
118
object/key.cc
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
165
object/key.h
Normal file
165
object/key.h
Normal file
@@ -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; }
|
||||||
|
};
|
||||||
181
object/outputstream.cc
Normal file
181
object/outputstream.cc
Normal file
@@ -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;
|
||||||
|
}
|
||||||
209
object/outputstream.h
Normal file
209
object/outputstream.h
Normal file
@@ -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);
|
||||||
293
object/queue.h
Normal file
293
object/queue.h
Normal file
@@ -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 << "}";
|
||||||
|
}
|
||||||
3
object/stringbuffer.cc
Normal file
3
object/stringbuffer.cc
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
#include "stringbuffer.h"
|
||||||
|
|
||||||
|
void Stringbuffer::put(char c) { (void)c; }
|
||||||
71
object/stringbuffer.h
Normal file
71
object/stringbuffer.h
Normal file
@@ -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() {}
|
||||||
|
};
|
||||||
29
sync/bellringer.cc
Normal file
29
sync/bellringer.cc
Normal file
@@ -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; }
|
||||||
69
sync/bellringer.h
Normal file
69
sync/bellringer.h
Normal file
@@ -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;
|
||||||
|
};
|
||||||
16
sync/semaphore.cc
Normal file
16
sync/semaphore.cc
Normal file
@@ -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; }
|
||||||
57
sync/semaphore.h
Normal file
57
sync/semaphore.h
Normal file
@@ -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);
|
||||||
|
};
|
||||||
60
sync/spinlock.h
Normal file
60
sync/spinlock.h
Normal file
@@ -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() {}
|
||||||
|
};
|
||||||
52
sync/ticketlock.h
Normal file
52
sync/ticketlock.h
Normal file
@@ -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
Reference in New Issue
Block a user