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
 |