blob: a6ebfe7cce91e114e44f12a3e187ed211bdb8556 [file] [log] [blame]
// 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;
}