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