aboutsummaryrefslogtreecommitdiff
path: root/arch/x86_64/src/context_switching/main.cpp
blob: 06c0810f40f1a4ddd785f53a2611934ef8ee96a0 (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
#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 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 { 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");

      // Reading RAX decrements value by one in 32-bit compatability mode it also crashes vga write, therfore use
      // SYSRETQ instead of SYSRET so we do not return into 32-bit compatability mode.
      asm volatile("mov %%rax, %[output]" : [output] "=r"(syscall_number));
      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");

      asm volatile("sysretq");
    }

    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 star_value = (kernel_cs << 32) | (kernel_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