Handout
This commit is contained in:
20
kernel/interrupt/epilogues.cc
Normal file
20
kernel/interrupt/epilogues.cc
Normal 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();
|
||||
}
|
||||
42
kernel/interrupt/epilogues.h
Normal file
42
kernel/interrupt/epilogues.h
Normal 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
114
kernel/interrupt/guard.cc
Normal 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
132
kernel/interrupt/guard.h
Normal 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
|
||||
39
kernel/interrupt/handlers.asm
Normal file
39
kernel/interrupt/handlers.asm
Normal 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
|
||||
134
kernel/interrupt/handlers.cc
Normal file
134
kernel/interrupt/handlers.cc
Normal 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();
|
||||
}
|
||||
19
kernel/interrupt/handlers.h
Normal file
19
kernel/interrupt/handlers.h
Normal 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();
|
||||
Reference in New Issue
Block a user