Handout
This commit is contained in:
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());
|
||||
};
|
||||
Reference in New Issue
Block a user