1// Copyright 2016 The Fuchsia Authors 2// Copyright (c) 2009 Corey Tabaka 3// Copyright (c) 2016 Travis Geiselbrecht 4// 5// Use of this source code is governed by a MIT-style 6// license that can be found in the LICENSE file or at 7// https://opensource.org/licenses/MIT 8 9#include <arch/arch_ops.h> 10#include <arch/x86.h> 11#include <arch/x86/descriptor.h> 12#include <arch/x86/idt.h> 13#include <arch/x86/interrupts.h> 14#include <arch/x86/mp.h> 15#include <assert.h> 16#include <bits.h> 17#include <err.h> 18#include <kernel/mp.h> 19#include <string.h> 20#include <trace.h> 21#include <vm/fault.h> 22#include <vm/pmm.h> 23#include <vm/vm_aspace.h> 24#include <vm/vm_object_paged.h> 25#include <vm/vm_object_physical.h> 26#include <zircon/compiler.h> 27 28#define TSS_DESC_BUSY_BIT (1ull << 41) 29 30/* Temporary GDT defined in assembly is used during AP/BP setup process */ 31extern uint8_t _temp_gdt[]; 32extern uint8_t _temp_gdt_end[]; 33 34/* We create a new GDT after initialization is done and switch everyone to it */ 35static uintptr_t gdt = (uintptr_t)_temp_gdt; 36 37static void x86_tss_assign_ists(struct x86_percpu* percpu, tss_t* tss); 38 39struct task_desc { 40 uint64_t low; 41 uint64_t high; 42} __PACKED; 43 44void x86_initialize_percpu_tss(void) { 45 struct x86_percpu* percpu = x86_get_percpu(); 46 uint cpu_num = percpu->cpu_num; 47 tss_t* tss = &percpu->default_tss; 48 memset(tss, 0, sizeof(*tss)); 49 50 /* zeroed out TSS is okay for now */ 51 set_global_desc_64(TSS_SELECTOR(cpu_num), (uintptr_t)tss, sizeof(*tss) - 1, 1, 0, 0, SEG_TYPE_TSS, 0, 0); 52 53 x86_tss_assign_ists(percpu, tss); 54 55 tss->iomap_base = offsetof(tss_64_t, tss_bitmap); 56 // Need to have an extra byte at the end of the bitmap because it will always potentially read two bytes 57 tss->tss_bitmap[IO_BITMAP_BYTES] = 0xff; 58 59 x86_ltr(TSS_SELECTOR(cpu_num)); 60} 61 62static void x86_tss_assign_ists(struct x86_percpu* percpu, tss_t* tss) { 63 tss->ist1 = (uintptr_t)&percpu->interrupt_stacks[0] + PAGE_SIZE; 64 tss->ist2 = (uintptr_t)&percpu->interrupt_stacks[1] + PAGE_SIZE; 65 tss->ist3 = (uintptr_t)&percpu->interrupt_stacks[2] + PAGE_SIZE; 66} 67 68void x86_set_tss_sp(vaddr_t sp) { 69 tss_t* tss = &x86_get_percpu()->default_tss; 70 tss->rsp0 = sp; 71} 72 73void x86_clear_tss_busy(seg_sel_t sel) { 74 uint index = sel >> 3; 75 struct task_desc* desc = (struct task_desc*)(gdt + index * 8); 76 desc->low &= ~TSS_DESC_BUSY_BIT; 77} 78 79void set_global_desc_64(seg_sel_t sel, uint64_t base, uint32_t limit, 80 uint8_t present, uint8_t ring, uint8_t sys, 81 uint8_t type, uint8_t gran, uint8_t bits) { 82 // 64 bit descriptor structure 83 struct seg_desc_64 { 84 uint16_t limit_15_0; 85 uint16_t base_15_0; 86 uint8_t base_23_16; 87 88 uint8_t type : 4; 89 uint8_t s : 1; 90 uint8_t dpl : 2; 91 uint8_t p : 1; 92 93 uint8_t limit_19_16 : 4; 94 uint8_t avl : 1; 95 uint8_t reserved_0 : 1; 96 uint8_t d_b : 1; 97 uint8_t g : 1; 98 99 uint8_t base_31_24; 100 101 uint32_t base_63_32; 102 uint32_t reserved_sbz; 103 } __PACKED; 104 105 struct seg_desc_64 entry = {}; 106 107 entry.limit_15_0 = limit & 0x0000ffff; 108 entry.limit_19_16 = (limit & 0x000f0000) >> 16; 109 110 entry.base_15_0 = base & 0x0000ffff; 111 entry.base_23_16 = (base & 0x00ff0000) >> 16; 112 entry.base_31_24 = (base & 0xff000000) >> 24; 113 entry.base_63_32 = (uint32_t)(base >> 32); 114 115 entry.type = type & 0x0f; // segment type 116 entry.p = present != 0; // present 117 entry.dpl = ring & 0x03; // descriptor privilege level 118 entry.g = gran != 0; // granularity 119 entry.s = sys != 0; // system / non-system 120 entry.d_b = bits != 0; // 16 / 32 bit 121 122 // copy it into the appropriate entry 123 uint index = sel >> 3; 124 125 // for x86_64 index is still in units of 8 bytes into the gdt table 126 struct seg_desc_64* g = (struct seg_desc_64*)(gdt + index * 8); 127 *g = entry; 128} 129 130void gdt_setup() { 131 DEBUG_ASSERT(arch_curr_cpu_num() == 0); 132 DEBUG_ASSERT(mp_get_online_mask() == 1); 133 // Max GDT size is limited to 64K and we reserve the whole 64K area, but we map 134 // just enough pages to store GDT and leave the rest unmapped so all accesses 135 // beyond GDT last page are going to cause page fault. 136 // Why don't we just set a a proper limit value? That's because during VM exit 137 // on x86 architecture GDT limit is always set to 0xFFFF (see Intel SDM, Volume 138 // 3, 27.5.2 Loading Host Segment and Descriptor-Table Registers) and therefore 139 // requiring the hypervisor to restore GDT limit after VM exit using LGDT 140 // instruction which is a serializing instruction (see Intel SDM, Volume 3, 8.3 141 // Serializing Instructions). 142 uint32_t vmar_flags = VMAR_FLAG_CAN_MAP_SPECIFIC | VMAR_FLAG_CAN_MAP_READ | VMAR_FLAG_CAN_MAP_WRITE; 143 uint32_t mmu_flags = ARCH_MMU_FLAG_PERM_READ | ARCH_MMU_FLAG_PERM_WRITE; 144 145 size_t gdt_real_size = _temp_gdt_end - _temp_gdt; 146 size_t gdt_size = 0x10000; 147 148 fbl::RefPtr<VmObject> vmo; 149 zx_status_t status = VmObjectPaged::Create(PMM_ALLOC_FLAG_ANY, /*options*/0u, gdt_real_size, &vmo); 150 ASSERT(status == ZX_OK); 151 152 fbl::RefPtr<VmAddressRegion> vmar; 153 status = VmAspace::kernel_aspace()->RootVmar()->CreateSubVmar( 154 0, gdt_size, PAGE_SIZE_SHIFT, vmar_flags, "gdt_vmar", &vmar); 155 ASSERT(status == ZX_OK); 156 157 fbl::RefPtr<VmMapping> mapping; 158 status = vmar->CreateVmMapping( 159 /*mapping_offset*/0u, gdt_real_size, PAGE_SIZE_SHIFT, VMAR_FLAG_SPECIFIC, fbl::move(vmo), 160 /*vmo_offset*/0u, mmu_flags, "gdt", &mapping); 161 ASSERT(status == ZX_OK); 162 163 status = mapping->MapRange(0, gdt_real_size, /*commit*/true); 164 ASSERT(status == ZX_OK); 165 166 memcpy((void*)mapping->base(), _temp_gdt, gdt_real_size); 167 gdt = mapping->base(); 168 gdt_load(gdt_get()); 169} 170 171uintptr_t gdt_get(void) { 172 return gdt; 173} 174