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++
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
|