#include "kjarna.h" #include /* * 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 interrupts 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 lag. * 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. */ struct dtr64 { uint16_t limit; uint64_t base; } __attribute__((packed, aligned(16))); struct segment_descriptor { uint16_t limit0; uint16_t base0; uint8_t base16; uint8_t access; uint8_t flags_limit16; uint8_t base24; } __attribute__((packed, aligned(8))); union msr_star { struct { uint32_t target_eip; uint16_t syscall_selector_base; uint16_t sysret_selector_base; } fields; uint64_t value; }; union msr_lstar { void (*syscall_handler)(void); uint64_t value; }; 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 void msr_write(uint32_t index, uint64_t value) { uint32_t low = value & 0xffffffff; uint32_t high = value >> 32; __asm__ volatile ( "wrmsr\n\t" :: "a"(low), "d"(high), "c"(index) ); } uint64_t msr_read(uint32_t index) { uint32_t low; uint32_t high; __asm__ volatile ( "rdmsr\n\t" : "=a"(low), "=d"(high) : "c"(index) ); return ((uint64_t)high << 32)|low; } static struct segment_descriptor const fake_syscall_gdt[] = { { 0 }, { 0xffff, 0, 0, 0x9a, 0xaf, 0}, { 0xffff, 0, 0, 0x92, 0xcf, 0} }; static noreturn void fake_syscall_entry(void) { while (true); } SYSV_ABI void fake_syscall_return(void *target_rsp); static void install_syscall_handler(void) { union msr_lstar lstar = { fake_syscall_entry }; union msr_star star = { { 0, 1 << 3, 1 << 3 | 3 } }; msr_write(0xc0000082, lstar.value); msr_write(0xc0000081, star.value); uint64_t efer = msr_read(0xc0000080); efer |= 1; msr_write(0xc0000080, efer); } struct context_stack_frame { struct dtr64 lret_gdtr; uint64_t lret_ds; uint64_t lret_rip; uint64_t lret_cs; }; static noreturn 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); while (true); } #include int main(int argc, char **argv) { (void)argc; (void)argv; printf("DEBUG: alignof(struct dtr64) = %zu", alignof(struct dtr64)); printf("DEBUG: sizeof(struct dtr64) = %zu", sizeof(struct dtr64)); struct kjarna_boot_image boot_image = get_boot_image(); install_syscall_handler(); enter_boot_image(&boot_image); }