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

View File

@@ -0,0 +1,20 @@
#include "epilogues.h"
#include "../thread/thread.h"
#include "guard.h"
Key Epilogues::key;
void Epilogues::keyboard(Vault& v) {
if (v.keys.produce(key)) {
v.keys_sem.v(v);
}
key.invalidate();
}
void Epilogues::timer(Vault& v) {
// Let Bellringer check for waiting threads
v.bellringer.check(v);
// Reschedule on each tick
v.scheduler.resume();
}

View File

@@ -0,0 +1,42 @@
/*! \file
* \brief Abstraction for epilogue objects, basically function pointers.
* \ingroup interrupts
*/
#pragma once
#include "../object/key.h"
#include "../types.h"
struct Vault;
/*! \brief A handler function for an epilogue.
* \ingroup interrupts
*
* It receives the vault directly, because it is executed on level 1/2 (by the
* \ref Guard) .
*
* \note Since it does only receive one parameter, other data must be passed
* in a different way.
*/
using Epilogue = void (*)(Vault&);
namespace Epilogues {
/*!
* @brief The keyboard epilogue.
*
* Handle the keyboard Key that has been fetched during the prologue.
*
* threads waiting for a key using the key semaphore.
*
* @param v The vault.
*/
void keyboard(Vault& v);
extern Key key;
/*!
* @brief Timer epilogue
* @param v The vault.
*/
void timer(Vault& v);
}; // namespace Epilogues

114
kernel/interrupt/guard.cc Normal file
View File

@@ -0,0 +1,114 @@
/*! \file
* \brief Guard implementation
*/
#include "guard.h"
#include "../arch/cache.h"
#include "../arch/core.h"
#include "../debug/assert.h" // IWYU pragma: keep
#include "../debug/output.h"
#include "../object/bbuffer.h"
#include "epilogues.h"
//! \brief The protected data for the epilogue level
static Vault global_vault;
//! \brief State of the epilogue level (per core)
struct State {
//! \brief lists of pending epilogues
BBuffer<Epilogue, 32> epilogue_queue;
//! \brief indicates whether the epilogue level is entered
bool epi_flag = false;
} __attribute__((aligned(CACHE_LINE_SIZE)));
constinit State state = {};
static inline State &get_state() { return state; }
Vault::Vault() {}
Guarded::~Guarded() { Guard::leave(); }
Guarded Guard::enter() {
assert(!get_state().epi_flag);
// Mark entry of epilogue level on this core
get_state().epi_flag = true;
return Guarded(global_vault);
}
void Guard::leave() {
// Make sure that we've performed an enter before.
assert(get_state().epi_flag);
Vault &vault = global_vault;
bool status;
while (true) {
status = Core::Interrupt::disable();
// Get item from epilogue queue (if any)
Epilogue iter;
if (!get_state().epilogue_queue.consume(iter)) {
break;
}
// Interrupts shall be restored before processing the epilogue
Core::Interrupt::restore(status);
// Process epilogue
iter(vault);
}
// leave the epilogue level this core
get_state().epi_flag = false;
// Restore previous interrupt state
Core::Interrupt::restore(status);
}
void Guard::relay(Epilogue handler) {
if (get_state().epi_flag) {
// Enqueue epilogue
bool success = get_state().epilogue_queue.produce(handler);
if (!success) DBG << "Dropped epilogue!" << endl;
} else {
// Mark entry of epilogue level on this core
get_state().epi_flag = true;
// Enable interrupts (interrupt_handler should already have sent the ACK
// IRQ via LAPIC)
Core::Interrupt::enable();
// We are, at this point, in the prologue level (E1), so this is
// allowed:
Vault &vault = global_vault;
// Process epilogue
handler(vault);
// Make sure pending epilogues are executed
while (true) {
// Disable Interrupts while accessing the epilogue queue
Core::Interrupt::disable();
Epilogue iter;
if (!get_state().epilogue_queue.consume(iter)) {
break;
}
// Interrupts shall be enabled before processing the epilogue
Core::Interrupt::enable();
// Process epilogue
iter(vault);
}
Core::Interrupt::disable();
// leave the epilogue level this core
get_state().epi_flag = false;
}
}
const Vault &Guard::unsafeConstAccess() { return global_vault; }

132
kernel/interrupt/guard.h Normal file
View File

@@ -0,0 +1,132 @@
/*! \file
* \brief Guard synchronizes access to epilogue level
*/
#pragma once
#include "../device/graphics.h"
#include "../device/textstream.h"
#include "../object/bbuffer.h"
#include "../object/key.h"
#include "../sync/bellringer.h"
#include "../sync/semaphore.h"
#include "../thread/scheduler.h"
#include "../types.h"
#include "./epilogues.h"
//! \brief The epilogue vault contains the protected data for the epilogue level
struct Vault {
TextStream kout = TextStream(0, 80, 0, 17, true);
Scheduler scheduler;
Bellringer bellringer;
BBuffer<Key, 16> keys;
Semaphore keys_sem;
// Ignore this for now, this is for a bonus task
Graphics graphics;
Vault();
// no copy
Vault(const Vault&) = delete;
Vault& operator=(const Vault&) = delete;
};
/*! \brief Lock guard that provides access to the epilogue \ref Vault
*
* This object automatically unlocks the \ref Guard when it goes out of scope.
*/
class Guarded {
public:
//! This constructor should only be used by the \ref Guard
explicit Guarded(Vault& vault) : _vault(vault) {}
//! Leave the critical section
~Guarded();
//! Access the epilogue vault
Vault& vault() { return _vault; }
const Vault& vault() const { return _vault; }
// no copy
Guarded(const Guarded&) = delete;
Guarded& operator=(const Guarded&) = delete;
private:
Vault& _vault;
};
/*! \brief Synchronizes the kernel with interrupts using the Prologue/Epilogue
* Model \ingroup interrupts
*
* The Guard is used to synchronize between "normal" core activities (currently
* just the text output, later system calls) and interrupt handling routines.
* For this purpose, \ref Guard has to contain one ore more \ref BBuffer
* "queues", in which \ref Epilogue functions can be added. This is necessary if
* the critical section is occupied at the time when an interrupt occurs, and
* the
* \ref Epilogue cannot be executed immediately. The queued epilogues are
* processed when leaving the critical section.
*
* **Hints:**
* - The epilogue queue is a central data structure, whose consistency
* must be ensured. The implementation provided by the \ref BBuffer is not
* entirely safe against concurrency. You need to disable
* interrupts during operations on the buffer.
* - In \MPStuBS, you need a separate epilogue queue for each core,
* in which each processor serializes *its* epilogues. However, epilogues
* on different cores could then be executed in parallel, since the
* critical section is managed separately on a per-core base. This must be
* prevented by using a global \ref Ticketlock to avoid concurrent
* execution of epilogues -- there must never be more than one epilogue
* active on the whole system at the same time!<br>
* *Please note:* This [giant lock](https://en.wikipedia.org/wiki/Giant_lock)
* (synchronizing all cores) should not be confused with the (core-specific)
* flag variable that marks only the entry to the epilogue level on the
* corresponding core!
* - Interrupts should be disabled for as short as possible. Due to this
* reason, the prologue/epilogue model allows epilogues to be interrupted
* by prologues. This means that interrupts should be
* \ref Core::Interrupt::enable "enabled" again before the epilogue is
* executed (this includes notifying the APIC about the
* \ref LAPIC::endOfInterrupt() "End-Of-Interrupt")
*/
namespace Guard {
/*! \brief Entering the critical section from level 0.
*
* Entering the critical section has to be handled differently depending on
* the system: In a single-core system it is sufficient to mark the entry
* by just setting a flag variable (since only one control flow can enter
* the critical section at the same time). However, as soon as there are
* multiple cores, this is no longer the case. If a core wants to enter the
* critical section while *another* core is already in there, it should
* (actively) wait in this method until the critical area is released again.
*
*/
Guarded enter();
/*! \brief Leaving the critical section.
*
* Leaves the critical section and processes all remaining (enqueued) epilogues.
* This may only be called while in level 1/2 after calling \ref enter().
*
* Note: Usually, this method is called by the destructor of the \ref
* Guarded.
*
*/
void leave();
/*! \brief A prologue wants its epilogue to be processed (entering from level
* 1).
*
* This method is called by the interrupt handlers.
* Whether the passed epilogue is executed immediately or it just enqueued to
* the epilogue queue depends on whether the critical section on *this* Core is
* accessible or not.
*
*/
void relay(Epilogue handler);
/*! \brief Access the epilogue vault without taking the lock.
* Beware race conditions!
*/
const Vault& unsafeConstAccess();
} // namespace Guard

View File

@@ -0,0 +1,39 @@
[SECTION .text]
[EXTERN handle_keyboard]
[GLOBAL handle_keyboard_asm]
; entry point for an interrupt to trigger a kernelpanic
;
align 16
handle_keyboard_asm:
; The interrupt may be triggered asynchronously, therefore the whole context
; has to be saved and restored, or the interrupted code might not be able to
; continue. The C++ compiler will only generates code to preserve
; non-scratch registers in the high-level interrupt handler -- the scratch
; registers have to be saved (and restored later) manually!
push rax
push rcx
push rdx
push rsi
push rdi
push r8
push r9
push r10
push r11
; Clear direction flag for string operations
cld
; Call the high-level handler routine
call handle_keyboard
; Restore scratch registers
pop r11
pop r10
pop r9
pop r8
pop rdi
pop rsi
pop rdx
pop rcx
pop rax
iretq

View File

@@ -0,0 +1,134 @@
#include "./handlers.h"
#include "../arch/core_cr.h"
#include "../arch/idt.h"
#include "../arch/lapic.h"
#include "../arch/system.h"
#include "../debug/kernelpanic.h"
#include "../debug/output.h"
#include "../device/ps2controller.h"
#include "./epilogues.h"
#include "./guard.h"
static void printContext(const InterruptContext *context) {
DBG << "ip: " << hex << context->cs << ':' << context->ip
<< " sp: " << context->ss << ':' << context->sp << " flags" << bin
<< context->flags << endl;
}
[[gnu::interrupt]] static void handle_invalid_opcode(
InterruptContext *context) {
DBG << "Invalid opcode encountered" << endl;
printContext(context);
kernelpanic("Invalid opcode!");
}
[[gnu::interrupt]] static void handle_double_fault(InterruptContext *context,
uint64_t error) {
DBG << "Double fault encountered. Error code: " << dec << error << endl;
printContext(context);
kernelpanic("Double fault!");
}
[[gnu::interrupt]] static void handle_invalid_tss(InterruptContext *context,
uint64_t error) {
DBG << "Invalid tss encountered. Offending selector idx: " << dec << error
<< endl;
printContext(context);
kernelpanic("Invalid TSS!");
}
[[gnu::interrupt]] static void handle_general_protection_fault(
InterruptContext *context, uint64_t error) {
DBG << "General protection fault encountered. Error code: " << dec << error
<< endl;
printContext(context);
kernelpanic("General protection fault!");
}
[[gnu::interrupt]] static void handle_page_fault(InterruptContext *context,
uint64_t error) {
DBG << "Page fault encountered at linear address " << hex
<< Core::CR<2>::read() << endl
<< PageFaultError(error) << endl;
printContext(context);
kernelpanic("Page fault!");
}
/*! \brief Assembly interrupt handler for the keyboard.
*
* On keyboard interrupt, the register state is saved to and restored from the
* stack. This function wraps the handle_keyboard C-function.
*
*/
extern "C" [[gnu::interrupt]] void handle_keyboard_asm(
InterruptContext *context);
/*! \brief Higher-level Interrupt handler for the keyboard.
*
* On keyboard interrupt, the PS2-Controller may contain a valid Key that has to
* be fetched.
*
*/
extern "C" void handle_keyboard() {
// DBG_VERBOSE << "Keyboard interrupt" << endl;
Key key;
if (PS2Controller::fetch(key)) {
// Ctrl-Alt-Delete should perform a reboot. This should still be done in
// prologue, to ensure that rebooting still works reliably in case of
// broken epilogues.
if (key.ctrl() && key.alt() && key.scancode == Key::Scancode::KEY_DEL) {
System::reboot();
}
// Check if the last key was processed by the epilogue
if (!Epilogues::key.valid()) {
// Make sure we can receive new interrupts
LAPIC::endOfInterrupt();
// Enqueue new epilogue
Epilogues::key = key;
Guard::relay(Epilogues::keyboard);
return;
}
}
LAPIC::endOfInterrupt();
}
[[maybe_unused, gnu::interrupt]] static void handle_panic(
InterruptContext *context) {
(void)context;
kernelpanic("Panic interrupt triggered!");
}
[[maybe_unused, gnu::interrupt]] static void handle_timer(
InterruptContext *context) {
// DBG_VERBOSE << "Timer interrupt" << endl;
(void)context;
LAPIC::endOfInterrupt();
Guard::relay(Epilogues::timer);
}
void initInterruptHandlers() {
// Some handlers that are useful for debugging
IDT::set(Core::Interrupt::Vector::INVALID_OPCODE,
IDT::InterruptDescriptor::Returning(handle_invalid_opcode));
IDT::set(Core::Interrupt::Vector::DOUBLE_FAULT,
IDT::InterruptDescriptor::DivergingWithError(handle_double_fault));
IDT::set(Core::Interrupt::Vector::INVALID_TSS,
IDT::InterruptDescriptor::ReturningWithError(handle_invalid_tss));
IDT::set(Core::Interrupt::Vector::GENERAL_PROTECTION_FAULT,
IDT::InterruptDescriptor::ReturningWithError(
handle_general_protection_fault));
IDT::set(Core::Interrupt::Vector::PAGE_FAULT,
IDT::InterruptDescriptor::ReturningWithError(handle_page_fault));
// TODO: Add more handlers here
IDT::set(Core::Interrupt::Vector::KEYBOARD,
IDT::InterruptDescriptor::Returning(handle_keyboard_asm));
IDT::set(Core::Interrupt::Vector::PANIC,
IDT::InterruptDescriptor::Returning(handle_panic));
IDT::set(Core::Interrupt::Vector::TIMER,
IDT::InterruptDescriptor::Returning(handle_timer));
// Load the idt pointer
IDT::load();
}

View File

@@ -0,0 +1,19 @@
/*! \file
* \brief All interrupts need to start somewhere. This file contains the entry
* points for all interrupts handled by StuBS.
* \defgroup interrupts Interrupt Handling
*/
#pragma once
#include "../types.h"
/*! \brief Initialize the IDT.
*
* The interrupt subsystem of StubBS contains all functionality to accept
* interrupts from the hardware and process them.
* In later exercises the interrupts will enable applications to
* execute core functionality (system calls).
* The entry point for the interrupt subsystem is the function
* 'interrupt_entry_VECTOR' (in `interrupt/handler.asm`).
*
*/
void initInterruptHandlers();