From 9db5b975f9ea2b942568eb57bbcce90a1c7420dc Mon Sep 17 00:00:00 2001 From: Pawel Dziepak Date: Tue, 6 May 2014 14:39:34 +0200 Subject: [PATCH] kernel/x86_64: rework of IDT handling code Similarly to previous patch regarding GDT this is mostly a rewrite of IDT handling code from C to C++. Thanks to constexpr IDT is now entirely generated at compile-time. --- .../private/kernel/arch/x86/64/descriptors.h | 33 ---- src/system/kernel/arch/x86/64/descriptors.cpp | 154 +++++++++++++----- 2 files changed, 116 insertions(+), 71 deletions(-) diff --git a/headers/private/kernel/arch/x86/64/descriptors.h b/headers/private/kernel/arch/x86/64/descriptors.h index 66512b314d..c5a783a611 100644 --- a/headers/private/kernel/arch/x86/64/descriptors.h +++ b/headers/private/kernel/arch/x86/64/descriptors.h @@ -39,21 +39,6 @@ struct segment_descriptor { uint32 base1 : 8; } _PACKED; -// Structure of an interrupt descriptor. -struct interrupt_descriptor { - uint32 base0 : 16; - uint32 sel : 16; - uint32 ist : 3; - uint32 unused1 : 5; - uint32 type : 4; - uint32 unused2 : 1; - uint32 dpl : 2; - uint32 present : 1; - uint32 base1 : 16; - uint32 base2 : 32; - uint32 reserved : 32; -} _PACKED; - struct tss { uint32 _reserved1; uint64 sp0; @@ -103,24 +88,6 @@ set_segment_descriptor(segment_descriptor* desc, uint8 type, uint8 dpl) } -static inline void -set_interrupt_descriptor(interrupt_descriptor* desc, uint64 addr, uint32 type, - uint16 seg, uint32 dpl, uint32 ist) -{ - desc->base0 = addr & 0xffff; - desc->base1 = (addr >> 16) & 0xffff; - desc->base2 = (addr >> 32) & 0xffffffff; - desc->sel = seg; - desc->ist = ist; - desc->type = type; - desc->dpl = dpl; - desc->present = 1; - desc->unused1 = 0; - desc->unused2 = 0; - desc->reserved = 0; -} - - #endif /* _ASSEMBLER */ #endif /* _KERNEL_ARCH_X86_64_DESCRIPTORS_H */ diff --git a/src/system/kernel/arch/x86/64/descriptors.cpp b/src/system/kernel/arch/x86/64/descriptors.cpp index 37e0ef29a1..993dfdf212 100644 --- a/src/system/kernel/arch/x86/64/descriptors.cpp +++ b/src/system/kernel/arch/x86/64/descriptors.cpp @@ -16,8 +16,20 @@ #include -#define IDT_GATES_COUNT 256 +template +struct GenerateTable : GenerateTable { +}; +template +struct GenerateTable { + GenerateTable() + : + fTable { Function(Index)... } + { + } + + T fTable[sizeof...(Index)]; +}; enum class DescriptorType : unsigned { DataWritable = 0x2, @@ -78,19 +90,61 @@ private: static constexpr unsigned kDescriptorCount = kFirstTSS + SMP_MAX_CPUS * 2; - alignas(sizeof(Descriptor)) Descriptor fTable[kDescriptorCount]; + alignas(uint64_t) Descriptor fTable[kDescriptorCount]; }; +enum class InterruptDescriptorType : unsigned { + Interrupt = 14, + Trap, +}; + +class [[gnu::packed]] InterruptDescriptor { +public: + constexpr InterruptDescriptor(uintptr_t isr, + unsigned ist, bool kernelOnly); + constexpr InterruptDescriptor(uintptr_t isr); + + static constexpr InterruptDescriptor Generate(unsigned index); + +private: + uint16_t fBase0; + uint16_t fSelector; + unsigned fIST :3; + unsigned fReserved0 :5; + unsigned fType :4; + unsigned fReserved1 :1; + unsigned fDPL :2; + unsigned fPresent :1; + uint16_t fBase1; + uint32_t fBase2; + uint32_t fReserved2; +}; + +class InterruptDescriptorTable { +public: + inline void Load() const; + + static constexpr unsigned kDescriptorCount = 256; + +private: + typedef GenerateTable TableType; + alignas(uint64_t) TableType fTable; +}; + +class InterruptServiceRoutine { + alignas(16) uint8_t fDummy[16]; +}; + +extern const InterruptServiceRoutine + isr_array[InterruptDescriptorTable::kDescriptorCount]; static GlobalDescriptorTable sGDT; - -static interrupt_descriptor sIDT[IDT_GATES_COUNT]; +static InterruptDescriptorTable sIDT; typedef void interrupt_handler_function(iframe* frame); -static const uint32 kInterruptHandlerTableSize = IDT_GATES_COUNT; -interrupt_handler_function* gInterruptHandlerTable[kInterruptHandlerTableSize]; - -extern uint8 isr_array[kInterruptHandlerTableSize][16]; +interrupt_handler_function* + gInterruptHandlerTable[InterruptDescriptorTable::kDescriptorCount]; constexpr bool @@ -198,18 +252,58 @@ GlobalDescriptorTable::SetTSS(unsigned cpu, const TSSDescriptor& tss) } -static inline void -load_idt() +constexpr +InterruptDescriptor::InterruptDescriptor(uintptr_t isr, unsigned ist, + bool kernelOnly) + : + fBase0(isr), + fSelector(KERNEL_CODE_SELECTOR), + fIST(ist), + fReserved0(0), + fType(static_cast(InterruptDescriptorType::Interrupt)), + fReserved1(0), + fDPL(kernelOnly ? 0 : 3), + fPresent(1), + fBase1(isr >> 16), + fBase2(isr >> 32), + fReserved2(0) { - struct { - uint16 limit; - void* address; - } _PACKED idtDescriptor = { - IDT_GATES_COUNT * sizeof(interrupt_descriptor) - 1, - sIDT + static_assert(sizeof(InterruptDescriptor) == sizeof(uint64_t) * 2, + "Invalid InterruptDescriptor size."); +} + + +constexpr +InterruptDescriptor::InterruptDescriptor(uintptr_t isr) + : + InterruptDescriptor(isr, 0, true) +{ +} + + +void +InterruptDescriptorTable::Load() const +{ + struct [[gnu::packed]] { + uint16_t fLimit; + const void* fAddress; + } gdtDescriptor = { + sizeof(fTable) - 1, + static_cast(fTable.fTable), }; - asm volatile("lidt %0" : : "m" (idtDescriptor)); + asm volatile("lidt %0" : : "m" (gdtDescriptor)); +} + + +constexpr InterruptDescriptor +InterruptDescriptor::Generate(unsigned index) +{ + return index == 3 + ? InterruptDescriptor(uintptr_t(isr_array + index), 0, false) + : (index == 8 + ? InterruptDescriptor(uintptr_t(isr_array + index), 1, true) + : InterruptDescriptor(uintptr_t(isr_array + index))); } @@ -258,38 +352,22 @@ x86_descriptors_preboot_init_percpu(kernel_args* args, int cpu) TSSDescriptor(uintptr_t(&gCPU[cpu].arch.tss), sizeof(struct tss))); TSSDescriptor::LoadTSS(tssIndex); - load_idt(); + new(&sIDT) InterruptDescriptorTable; + sIDT.Load(); } void x86_descriptors_init(kernel_args* args) { - // Fill out the IDT, pointing each entry to the corresponding entry in the - // ISR array created in arch_interrupts.S (see there to see how this works). - for(uint32 i = 0; i < kInterruptHandlerTableSize; i++) { - // x86_64 removes task gates, therefore we cannot use a separate TSS - // for the double fault exception. However, instead it adds a new stack - // switching mechanism, the IST. The IST is a table of stack addresses - // in the TSS. If the IST field of an interrupt descriptor is non-zero, - // the CPU will switch to the stack specified by that IST entry when - // handling that interrupt. So, we use IST entry 1 to store the double - // fault stack address (set up in x86_descriptors_init_post_vm()). - uint32 ist = (i == 8) ? 1 : 0; - - // Breakpoint exception can be raised from userland. - uint32 dpl = (i == 3) ? DPL_USER : DPL_KERNEL; - - set_interrupt_descriptor(&sIDT[i], (addr_t)&isr_array[i], - GATE_INTERRUPT, KERNEL_CODE_SELECTOR, dpl, ist); - } - // Initialize the interrupt handler table. interrupt_handler_function** table = gInterruptHandlerTable; for (uint32 i = 0; i < ARCH_INTERRUPT_BASE; i++) table[i] = x86_invalid_exception; - for (uint32 i = ARCH_INTERRUPT_BASE; i < kInterruptHandlerTableSize; i++) + for (uint32 i = ARCH_INTERRUPT_BASE; + i < InterruptDescriptorTable::kDescriptorCount; i++) { table[i] = x86_hardware_interrupt; + } table[0] = x86_unexpected_exception; // Divide Error Exception (#DE) table[1] = x86_handle_debug_exception; // Debug Exception (#DB)