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