ostd/arch/x86/trap/
gdt.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
// SPDX-License-Identifier: MPL-2.0

//! Configure the Global Descriptor Table (GDT).

use alloc::boxed::Box;

use x86_64::{
    instructions::tables::{lgdt, load_tss},
    registers::{
        model_specific::Star,
        segmentation::{Segment, CS},
    },
    structures::{
        gdt::{Descriptor, SegmentSelector},
        tss::TaskStateSegment,
        DescriptorTablePointer,
    },
    PrivilegeLevel, VirtAddr,
};

use crate::cpu::local::{CpuLocal, StaticCpuLocal};

use safety::safety;

/// Initializes and loads the GDT and TSS.
///
/// The caller should only call this method once in the boot context for each available processor.
/// This is not a safety requirement, however, because calling this method again will do nothing
/// more than load the GDT and TSS with the same contents.
///
#[safety {
    Memo("The caller must ensure that no preemption can occur during the method, otherwise we may accidentally load a wrong GDT and TSS that actually belongs to another CPU.")
}]
pub(super) unsafe fn init() {
    let tss_ptr = LOCAL_TSS.as_ptr();

    // FIXME: The segment limit in the descriptor created by `tss_segment_unchecked` does not
    // include the I/O port bitmap.

    // SAFETY: As a CPU-local variable, the TSS lives for `'static`.
    let tss_desc = unsafe { Descriptor::tss_segment_unchecked(tss_ptr) };
    let (tss0, tss1) = match tss_desc {
        Descriptor::SystemSegment(tss0, tss1) => (tss0, tss1),
        _ => unreachable!(),
    };

    // The kernel CS is considered a global invariant set by the boot GDT. This method is not
    // intended for switching to a new kernel CS.
    assert_eq!(CS::get_reg(), KERNEL_CS);

    // Allocate a new GDT with 8 entries.
    let gdt = Box::new([
        0, KCODE64, KDATA, /* UCODE32 (not used) */ 0, UDATA, UCODE64, tss0, tss1,
    ]);
    let gdt = &*Box::leak(gdt);
    assert_eq!(gdt[KERNEL_CS.index() as usize], KCODE64);
    assert_eq!(gdt[KERNEL_SS.index() as usize], KDATA);
    assert_eq!(gdt[USER_CS.index() as usize], UCODE64);
    assert_eq!(gdt[USER_SS.index() as usize], UDATA);

    // Load the new GDT.
    let gdtr = DescriptorTablePointer {
        limit: (core::mem::size_of_val(gdt) - 1) as u16,
        base: VirtAddr::new(gdt.as_ptr().addr() as u64),
    };
    // SAFETY: The GDT is valid to load because:
    //  - It lives for `'static`.
    //  - It contains correct entries at correct indexes: the kernel code/data segments, the user
    //    code/data segments, and the TSS segment.
    //  - Specifically, the TSS segment points to the CPU-local TSS of the current CPU.
    unsafe { lgdt(&gdtr) };

    // Load the TSS.
    let tss_sel = SegmentSelector::new(6, PrivilegeLevel::Ring0);
    assert_eq!(gdt[tss_sel.index() as usize], tss0);
    assert_eq!(gdt[(tss_sel.index() + 1) as usize], tss1);
    // SAFETY: The selector points to the TSS descriptors in the GDT.
    unsafe { load_tss(tss_sel) };

    // Set up the selectors for the `syscall` and `sysret` instructions.
    let sysret = SegmentSelector::new(3, PrivilegeLevel::Ring3);
    assert_eq!(gdt[(sysret.index() + 1) as usize], UDATA);
    assert_eq!(gdt[(sysret.index() + 2) as usize], UCODE64);
    let syscall = SegmentSelector::new(1, PrivilegeLevel::Ring0);
    assert_eq!(gdt[syscall.index() as usize], KCODE64);
    assert_eq!(gdt[(syscall.index() + 1) as usize], KDATA);
    // SAFETY: The selector points to correct kernel/user code/data descriptors in the GDT.
    unsafe { Star::write_raw(sysret.0, syscall.0) };
}

// The linker script makes sure that the `.cpu_local_tss` section is at the beginning of the area
// that stores CPU-local variables. This is important because `trap.S` and `syscall.S` will assume
// this and treat the beginning of the CPU-local area as a TSS for loading and saving the kernel
// stack!
//
// No other special initialization is required because the kernel stack information is stored in
// the TSS when we start the userspace program. See `syscall.S` for details.
#[link_section = ".cpu_local_tss"]
static LOCAL_TSS: StaticCpuLocal<TaskStateSegment> = {
    let tss = TaskStateSegment::new();
    // SAFETY: The `.cpu_local_tss` section is part of the CPU-local area.
    unsafe { CpuLocal::__new_static(tss) }
};

// Kernel code and data descriptors.
//
// These are the exact, unique values that satisfy the requirements of the `syscall` instruction.
// The Intel manual says: "It is the responsibility of OS software to ensure that the descriptors
// (in GDT or LDT) referenced by those selector values correspond to the fixed values loaded into
// the descriptor caches; the SYSCALL instruction does not ensure this correspondence."
pub(in crate::arch) const KCODE64: u64 = 0x00AF_9B00_0000_FFFF;
pub(in crate::arch) const KDATA: u64 = 0x00CF_9300_0000_FFFF;

// A 32-bit code descriptor that is used in the boot stage only. See `boot/bsp_boot.S`.
pub(in crate::arch) const KCODE32: u64 = 0x00CF_9B00_0000_FFFF;

// User code and data descriptors.
//
// These are the exact, unique values that satisfy the requirements of the `sysret` instruction.
// The Intel manual says: "It is the responsibility of OS software to ensure that the descriptors
// (in GDT or LDT) referenced by those selector values correspond to the fixed values loaded into
// the descriptor caches; the SYSRET instruction does not ensure this correspondence."
const UCODE64: u64 = 0x00AF_FB00_0000_FFFF;
const UDATA: u64 = 0x00CF_F300_0000_FFFF;

const KERNEL_CS: SegmentSelector = SegmentSelector::new(1, PrivilegeLevel::Ring0);
const KERNEL_SS: SegmentSelector = SegmentSelector::new(2, PrivilegeLevel::Ring0);

pub(super) const USER_CS: SegmentSelector = SegmentSelector::new(5, PrivilegeLevel::Ring3);
pub(super) const USER_SS: SegmentSelector = SegmentSelector::new(4, PrivilegeLevel::Ring3);