| // Copyright 2016 The Fuchsia Authors |
| // |
| // Use of this source code is governed by a MIT-style |
| // license that can be found in the LICENSE file or at |
| // https://opensource.org/licenses/MIT |
| |
| #include <assert.h> |
| #include <string.h> |
| #include <zircon/compiler.h> |
| #include <zircon/errors.h> |
| #include <zircon/types.h> |
| |
| #include <arch/ops.h> |
| #include <arch/x86.h> |
| #include <arch/x86/descriptor.h> |
| #include <arch/x86/feature.h> |
| #include <arch/x86/idt.h> |
| #include <arch/x86/interrupts.h> |
| #include <fbl/algorithm.h> |
| #include <kernel/mp.h> |
| #include <ktl/iterator.h> |
| #include <vm/pmm.h> |
| #include <vm/vm_aspace.h> |
| |
| #include <ktl/enforce.h> |
| |
| // The size of the `clac` instruction |
| #define CLAC_SIZE 3 |
| |
| // Early boot shared IDT structure |
| struct idt _idt_startup; |
| |
| // IDT after early boot |
| struct idt _idt __ALIGNED(PAGE_SIZE); |
| // Read-only remapping of the IDT |
| static struct idt* _idt_ro; |
| |
| extern uintptr_t const _isr_table[]; |
| |
| static inline void idt_set_segment_sel(struct idt_entry* entry, uint16_t sel) { |
| entry->w0 = (entry->w0 & 0x0000ffff) | (sel << 16); |
| } |
| |
| static inline void idt_set_offset(struct idt_entry* entry, uintptr_t offset) { |
| uint32_t low_16 = offset & 0xffff; |
| uint32_t mid_16 = (offset >> 16) & 0xffff; |
| entry->w0 = (entry->w0 & 0xffff0000) | low_16; |
| entry->w1 = (entry->w1 & 0x0000ffff) | (mid_16 << 16); |
| uint32_t high_32 = (uint32_t)(offset >> 32); |
| entry->w2 = high_32; |
| } |
| |
| static inline void idt_set_present(struct idt_entry* entry, bool present) { |
| entry->w1 = (entry->w1 & ~(1 << 15)) | ((!!present) << 15); |
| } |
| |
| static inline void idt_set_dpl(struct idt_entry* entry, enum idt_dpl dpl) { |
| ASSERT(dpl <= 3); |
| entry->w1 = (entry->w1 & ~(3 << 13)) | ((uint32_t)dpl << 13); |
| } |
| |
| static inline void idt_set_type(struct idt_entry* entry, enum idt_entry_type typ) { |
| entry->w1 = (entry->w1 & ~(0xf << 8)) | ((uint32_t)typ << 8); |
| } |
| |
| void idt_set_vector(struct idt* idt, uint8_t vec, uint16_t code_segment_sel, |
| uintptr_t entry_point_offset, enum idt_dpl dpl, enum idt_entry_type typ) { |
| struct idt_entry* entry = &idt->entries[vec]; |
| memset(entry, 0, sizeof(*entry)); |
| idt_set_segment_sel(entry, code_segment_sel); |
| idt_set_offset(entry, entry_point_offset); |
| idt_set_type(entry, typ); |
| idt_set_dpl(entry, dpl); |
| idt_set_present(entry, true); |
| } |
| |
| void idt_set_ist_index(struct idt* idt, uint8_t vec, uint8_t ist_idx) { |
| ASSERT(ist_idx < 8); |
| struct idt_entry* entry = &idt->entries[vec]; |
| entry->w1 = (entry->w1 & ~0x7) | ist_idx; |
| } |
| |
| void idt_setup(struct idt* idt) { |
| // If SMAP is not available, we need to skip past the CLAC instruction |
| // at the beginning of the ISR stubs. |
| int clac_shift = 0; |
| if (!g_x86_feature_has_smap) { |
| clac_shift += CLAC_SIZE; |
| } |
| |
| uint16_t sel; |
| enum idt_entry_type typ; |
| sel = CODE_64_SELECTOR; |
| typ = IDT_INTERRUPT_GATE64; |
| for (size_t i = 0; i < ktl::size(idt->entries); ++i) { |
| uintptr_t offset = _isr_table[i] + clac_shift; |
| enum idt_dpl dpl; |
| switch (i) { |
| case X86_INT_BREAKPOINT: |
| dpl = IDT_DPL3; |
| break; |
| default: |
| dpl = IDT_DPL0; |
| break; |
| } |
| idt_set_vector(idt, (uint8_t)i, sel, offset, dpl, typ); |
| } |
| } |
| |
| // Create a read-only remapping of the global IDT. |
| // This function is called on arch initialization before additional cpus |
| // started. It reloads the main processor IDT to be read-only. Each |
| // additional cpu will pick-up the read-only IDT by default. |
| // TODO(thgarnie): Move to C++ and non-compact VMAR for KASLR support. |
| void idt_setup_readonly(void) { |
| DEBUG_ASSERT(arch_curr_cpu_num() == 0); |
| DEBUG_ASSERT(mp_get_online_mask() == 1); |
| zx_status_t status = VmAspace::kernel_aspace()->AllocPhysical( |
| "idt_readonly", sizeof(_idt), (void**)&_idt_ro, PAGE_SIZE_SHIFT, vaddr_to_paddr(&_idt), |
| 0 /* vmm flags */, ARCH_MMU_FLAG_PERM_READ); |
| ASSERT(status == ZX_OK); |
| idt_load(_idt_ro); |
| } |
| |
| // Get the read-only IDT |
| struct idt* idt_get_readonly(void) { |
| ASSERT(_idt_ro != nullptr); |
| return _idt_ro; |
| } |