aboutsummaryrefslogtreecommitdiff
path: root/arch/x86_64/src/context_switching/main.cpp
blob: 7449d8426ef4e8dbb5d86c6c7cc4a520803a4fd0 (plain)
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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
#include "arch/context_switching/main.hpp"

#include "arch/boot/pointers.hpp"
#include "arch/exception_handling/assert.hpp"
#include "arch/kernel/cpu/call.hpp"
#include "arch/kernel/cpu/control_register.hpp"
#include "arch/kernel/cpu/if.hpp"
#include "arch/kernel/cpu/msr.hpp"
#include "arch/kernel/cpu/segment_register.hpp"
#include "arch/kernel/cpu/tr.hpp"
#include "arch/video/vga/text.hpp"

namespace teachos::arch::context_switching
{
  namespace
  {
    auto constexpr IA32_STAR_ADDRESS = 0xC0000081;
    auto constexpr IA32_LSTAR_ADDRESS = 0xC0000082;
    auto constexpr IA32_FMASK_ADDRESS = 0xC0000084;

    constexpr interrupt_descriptor_table::segment_selector KERNEL_CODE_SEGMENT_SELECTOR{
        1U, interrupt_descriptor_table::segment_selector::REQUEST_LEVEL_KERNEL};
    constexpr interrupt_descriptor_table::segment_selector KERNEL_DATA_SEGMENT_SELECTOR{
        2U, interrupt_descriptor_table::segment_selector::REQUEST_LEVEL_KERNEL};
    constexpr kernel::cpu::far_pointer KERNEL_CODE_POINTER{&kernel::cpu::reload_data_segment_registers,
                                                           KERNEL_CODE_SEGMENT_SELECTOR};
    constexpr context_switching::interrupt_descriptor_table::segment_selector USER_CODE_SEGMENT_SELECTOR{
        3U, context_switching::interrupt_descriptor_table::segment_selector::REQUEST_LEVEL_USER};
    constexpr context_switching::interrupt_descriptor_table::segment_selector USER_DATA_SEGMENT_SELECTOR{
        4U, context_switching::interrupt_descriptor_table::segment_selector::REQUEST_LEVEL_USER};

    auto reload_gdtr() -> void
    {
      /*asm volatile("pushq $0x8\n\t"            // Push new CS
                   "lea 1f(%%rip), %%rax\n\t"  // Get address of label 1 into RAX
                   "pushq %%rax\n\t"           // Push return address
                   "lretq\n"                   // Far return (loads CS:RIP)
                   "1:\n\t"                    // Label to return to
                   :
                   :
                   : "rax", "memory");*/

      kernel::cpu::call(KERNEL_CODE_POINTER);
    }

    auto user_mode_main() -> void
    {
      video::vga::text::write("Successfully entered user mode!", video::vga::text::common_attributes::green_on_black);

      // RFLAGS is saved into R11, RIP of the next instruction into RCX
      // Required for SYSRETURN to know where to return too.
      // Additional state needs to be saved by calling convention:

      // Syscall Number: RAX, Return Value: RAX (0 indicating no error, and -1 indicating an error, use as a boolean)
      // Argument in this order (max 6. no argument on stack): RDI, RSI, RDX, R10, R8, R9
      // Not used registers: RBX, RSP, R12, R13, R14

      // Actual Source: https://man7.org/linux/man-pages/man2/syscall.2.html More cleare documentation:
      // https://sys.readthedocs.io/en/latest/doc/05_calling_system_calls.html
      uint64_t syscall_number = 60U;
      asm volatile("mov %[input], %%rax"
                   : /* no output from call */
                   : [input] "r"(syscall_number)
                   : "memory");
      asm volatile("syscall");

      // TODO: Reading RAX value does not work because the read itself changes the RAX value?!
      // asm volatile("mov %%rax, %[output]" : [output] "=r"(syscall_value));

      // TODO: Causes a general protection fault after the sysreturn?
      // If removed instead it will cause a general protection fault after leaving this main method to return to kernel
      // mode. But CS and SS are still configured for User mode.
      /*video::vga::text::write("Successfully made a SYSCALL and returned with SYSRETQ!",
                              video::vga::text::common_attributes::green_on_black);*/
    }

    auto syscall_handler() -> void
    {
      uint64_t syscall_number, arg_0, arg_1, arg_2, arg_3, arg_4, arg_5 = {};
      asm volatile("mov %%rax, %[output]" : [output] "=r"(syscall_number));
      asm volatile("mov %%rdi, %[output]" : [output] "=r"(arg_0));
      asm volatile("mov %%rsi, %[output]" : [output] "=r"(arg_1));
      asm volatile("mov %%rdx, %[output]" : [output] "=r"(arg_2));
      asm volatile("mov %%r10, %[output]" : [output] "=r"(arg_3));
      asm volatile("mov %%r8, %[output]" : [output] "=r"(arg_4));
      asm volatile("mov %%r9, %[output]" : [output] "=r"(arg_5));

      switch (syscall_number)
      {
        case 1:
          // Write VGA
          break;
        case 60U:
          // Exit
          break;
        default:
          break;
      }

      uint64_t result = 0U;
      asm volatile("mov %[input], %%rax"
                   : /* no output from call */
                   : [input] "r"(result)
                   : "memory");

      // Use SYSRET instead of SYSRETQ, because the latter would add 0x10 to bits [48:63] of IA32_STAR_ADDRESS for the
      // Code Segment. But only add 0x8 to bits [48:63] of IA32_STAR_ADDRESS for the Stack Segment, which means either
      // the Stack Segment or Code Segment is wrong. Whereas the former does not add 0x10 for the Code Segment and
      // therefore fixes the aformentioned issue.
      asm volatile("sysret");
    }

    auto enable_systemcall() -> void
    {
      uint64_t const syscall_function = reinterpret_cast<uint64_t>(syscall_handler);
      kernel::cpu::write_msr(IA32_LSTAR_ADDRESS, syscall_function);
      kernel::cpu::write_msr(IA32_FMASK_ADDRESS, 0U);

      uint64_t const kernel_cs = KERNEL_CODE_SEGMENT_SELECTOR;
      uint64_t const user_cs = USER_CODE_SEGMENT_SELECTOR;
      uint64_t const star_value = (kernel_cs << 32) | (user_cs << 48);
      kernel::cpu::write_msr(IA32_STAR_ADDRESS, star_value);

      kernel::cpu::set_efer_bit(kernel::cpu::efer_flags::SCE);
    }

  }  // namespace

  auto initialize_descriptor_tables() -> descriptor_tables
  {
    static bool initalized = false;

    if (!initalized)
    {
      kernel::cpu::clear_interrupt_flag();

      segment_descriptor_table::update_gdtr();
      interrupt_descriptor_table::update_interrupt_descriptor_table_register();

      reload_gdtr();
      segment_descriptor_table::update_tss_register();

      kernel::cpu::set_interrupt_flag();
      initalized = true;
    }

    descriptor_tables tables = {segment_descriptor_table::get_or_create_gdt(),
                                interrupt_descriptor_table::get_or_create_interrupt_descriptor_table()};
    return tables;
  }

  auto switch_to_user_mode() -> void
  {
    enable_systemcall();
    switch_context(USER_DATA_SEGMENT_SELECTOR, USER_CODE_SEGMENT_SELECTOR, user_mode_main);
  }

  auto switch_context(interrupt_descriptor_table::segment_selector data_segment,
                      interrupt_descriptor_table::segment_selector code_segment, void (*return_function)()) -> void
  {
    (void)initialize_descriptor_tables();
    kernel::cpu::set_data_segment_registers(data_segment);
    kernel::cpu::set_code_segment_register(data_segment, code_segment, reinterpret_cast<uint64_t>(return_function));
  }
}  // namespace teachos::arch::context_switching