You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

211 lines
7.9 KiB
C++

/*! \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