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