summaryrefslogtreecommitdiff
path: root/boot/main.c
blob: d2172357bb3074053e8263d83971f4dfa3569cc3 (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
#include "kjarna.h"
#include <stdnoreturn.h>
#include <asm/x86_64/msr.h>
#include <bits/x86_64/msr.h>
#include <bits/x86_64/descriptor.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}
};

SYSV_ABI static void fake_syscall_handler(void)
{
	while (true);
}

static void *return_rsp;

SYSV_ABI void fake_syscall_entry(void);
SYSV_ABI void fake_syscall_return(void *target_rsp, void **return_rsp, SYSV_ABI void (*callback)());

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);
}

struct context_stack_frame
{
	struct descriptor_table_register_long lret_gdtr;
	uint64_t lret_ds;
	uint64_t lret_rip;
	uint64_t lret_cs;
};

static void enter_boot_image(struct kjarna_boot_image *image)
{
	size_t boot_stack_size = 0x10000;
	char *boot_image_stack = calloc(1, boot_stack_size);
	void *boot_image_stack_head = boot_image_stack + boot_stack_size;

	struct context_stack_frame *stack_frame = stack_alloc(&boot_image_stack_head, sizeof(*stack_frame));

	stack_frame->lret_cs = 8;
	stack_frame->lret_ds = 16;
	stack_frame->lret_rip = (uintptr_t)image->entry;

	stack_frame->lret_gdtr.limit = sizeof(fake_syscall_gdt) - 1;
	stack_frame->lret_gdtr.base = (uintptr_t)fake_syscall_gdt;

	
	fake_syscall_return(boot_image_stack_head, &return_rsp, fake_syscall_handler);

	while (true);
}

#include <libc/stdio.h>

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

	struct kjarna_boot_image boot_image = get_boot_image();
	install_syscall_handler();
	enter_boot_image(&boot_image);
}