summaryrefslogtreecommitdiff
path: root/boot/main.c
blob: 611c833a066005c3312653ab42a0a8d3bbe46017 (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
#include "kjarna.h"
#include <stdnoreturn.h>
#include <asm/x86_64/msr.h>
#include <bits/x86_64/msr.h>
#include <bits/x86_64/descriptor.h>
#include <libc/stdio.h>
#include <posix/sys/mman.h>

/*
 * Fake syscall mechanism -
 *
 * We have a GDT with entries for supervisory mode. These serve as the
 * entries to satisfy the requirements of the SYSCALL instruction, as
 * for some reason (possibly intentionally) the OVMF GDT is not laid out
 * in a way to make use of the SYSCALL instruction possible.
 *
 * This causes us to have to work inside of constraints during loading time
 * - All "user" mode execution entirely blocks interrupt processing. That
 *   means that "user" mode code must not execute "hlt", or the system will
 *   be locked.
 * - When (if?) user input is required, it is always buffered. It is possible
 *   to simulate unbuffered input, at the cost of one syscall per transfer
 *   from "kernel" side to "user" side. This will cause high input latency.
 *   We will not be running Quake in this environment.
 *
 * These constraints are probably fine, as the loading process only needs to 
 * open files, map memory, etc.
 */


void *stack_alloc(void **stack_pointer, size_t alloc_size)
{
	void *block = *(char **)stack_pointer -= alloc_size;

	// maintain alignment
	*(char **)stack_pointer -= alloc_size % sizeof(size_t);

	return block;
}

static struct segment_descriptor const fake_syscall_gdt[] =
{
	{ 0 },
	{ 0xffff, 0, 0, 0x9a, 0xaf, 0},
	{ 0xffff, 0, 0, 0x92, 0xcf, 0}
};

typedef uint64_t syscall_delegate_parameters[6];

struct syscall_context_return_state
{
	uint64_t rflags; // 8
	struct descriptor_table_register_long gdtr; // 24
	uint64_t return_ds; // 32
	uintptr_t return_rip; // 40
	uint64_t return_cs; // 48
};

struct syscall_context
{
	uint64_t pad; // 8
	uint64_t syscall_index; // 16
	syscall_delegate_parameters parameters; // 64
	struct syscall_context_return_state state;
};

typedef int64_t (syscall_delegate)(syscall_delegate_parameters params);

//constexpr int NR_SYSCALLS = 10;
//static syscall_delegate *syscall_handler_delegates[NR_SYSCALLS];

SYSV_ABI int64_t fake_syscall_handler(struct syscall_context *context)
{
	(void)context;
	return -1;
}

SYSV_ABI void fake_syscall_entry(void);
SYSV_ABI void fake_syscall_start(void *stack);

static void install_syscall_handler(void)
{
	union msr_lstar lstar = { (uintptr_t)fake_syscall_entry };
	union msr_star star = { { 0, 1 << 3, 1 << 3 | 3 } };

	msr_write(MSR_INDEX_LSTAR, lstar.value);
	msr_write(MSR_INDEX_STAR, star.value);

	uint64_t efer = msr_read(MSR_INDEX_EFER);
	efer |= 1;
	msr_write(MSR_INDEX_EFER, efer);
}

static void *create_fake_syscall_stack(void *entry_addr)
{
	constexpr size_t fake_syscall_stack_size = 0x20000; // 128KiB stack
	char *stack_pointer_base = mmap(nullptr, fake_syscall_stack_size, 0, 0, -1, 0);
	void *stack_pointer_head = stack_pointer_base + fake_syscall_stack_size;

	struct syscall_context *context = stack_alloc(&stack_pointer_head, sizeof(*context));

	context->state.rflags = 0;
	context->state.gdtr.base = (uintptr_t)&fake_syscall_gdt;
	context->state.gdtr.limit = sizeof(fake_syscall_gdt) - 1;
	context->state.return_ds = 16;
	context->state.return_cs = 8;
	context->state.return_rip = (uintptr_t)entry_addr;

	return stack_pointer_head;
}

int main(int argc, char **argv)
{
	(void)argc;
	(void)argv;

	struct kjarna_boot_image boot_image = get_boot_image();
	install_syscall_handler();
	void *fake_syscall_stack = create_fake_syscall_stack((void *)boot_image.entry);
	fake_syscall_start(fake_syscall_stack);
}