450 lines
13 KiB
C++

#include "arch/CPU.h"
#include "Log.h"
#include "api/Mouse.h"
#include "arch/Keyboard.h"
#include "arch/Timer.h"
#include "arch/x86_64/CPU.h"
#include "arch/x86_64/IO.h"
#include "fs/devices/ConsoleDevice.h"
#include "fs/devices/MouseDevice.h"
#include "memory/MemoryManager.h"
#include "sys/Syscall.h"
#include "thread/Scheduler.h"
#include "video/TextConsole.h"
#include <bits/signal.h>
#include <cpuid.h>
#include <luna/CString.h>
#include <luna/CircularQueue.h>
#include <luna/Result.h>
#include <luna/SystemError.h>
#include <luna/Types.h>
extern "C" void enable_sse();
extern "C" void enable_write_protect();
extern "C" void enable_nx();
extern void setup_gdt();
extern void remap_pic();
extern void change_pic_masks(u8 pic1_mask, u8 pic2_mask);
extern void pic_eoi(unsigned char irq);
extern void pic_eoi(Registers* regs);
extern void setup_idt();
extern void init_mouse();
Thread* g_io_thread;
typedef void (*interrupt_handler_t)(Registers*, void*);
struct InterruptHandler
{
interrupt_handler_t function;
void* context;
};
static InterruptHandler irq_handlers[16];
void FPData::save()
{
asm volatile("fxsave (%0)" : : "r"(m_data));
m_already_saved = true;
}
void FPData::restore()
{
if (!m_already_saved) return;
asm volatile("fxrstor (%0)" : : "r"(m_data));
}
// Interrupt handling
#define FIXME_UNHANDLED_INTERRUPT(name) \
kerrorln("FIXME(interrupt): %s", name); \
CPU::efficient_halt();
#define PF_PRESENT 1 << 0
#define PF_WRITE 1 << 1
#define PF_USER 1 << 2
#define PF_RESERVED 1 << 3
#define PF_NX_VIOLATION 1 << 4
void decode_page_fault_error_code(u64 code)
{
kwarnln("Fault details: %s | %s | %s%s%s", (code & PF_PRESENT) ? "Present" : "Not present",
(code & PF_WRITE) ? "Write access" : "Read access", (code & PF_USER) ? "User mode" : "Kernel mode",
(code & PF_RESERVED) ? " | Reserved bits set" : "", (code & PF_NX_VIOLATION) ? " | NX violation" : "");
}
void handle_cpu_exception(int signo, const char* err, Registers* regs)
{
if (err) kerrorln("Caught CPU exception: %s", err);
kerrorln("RAX: %.16lx RBX: %.16lx RCX: %.16lx RDX: %.16lx", regs->rax, regs->rbx, regs->rcx, regs->rdx);
kerrorln("RBP: %.16lx RSP: %.16lx RDI: %.16lx RSI: %.16lx", regs->rbp, regs->rsp, regs->rdi, regs->rsi);
kerrorln("R8: %.16lx R9: %.16lx R10: %.16lx R11: %.16lx", regs->r8, regs->r9, regs->r10, regs->r11);
kerrorln("R12: %.16lx R13: %.16lx R14: %.16lx R15: %.16lx", regs->r12, regs->r13, regs->r14, regs->r15);
kerrorln("RIP: %.16lx CS: %.16lx SS: %.16lx FLAGS: %.16lx", regs->rip, regs->cs, regs->ss, regs->rflags);
CPU::print_stack_trace_at(regs);
if (!is_in_kernel(regs))
{
Scheduler::current()->send_signal(signo);
Scheduler::current()->process_pending_signals(regs);
return;
}
CPU::efficient_halt();
}
void handle_page_fault(Registers* regs)
{
u64 cr2;
asm volatile("mov %%cr2, %0" : "=r"(cr2));
kerrorln("Page fault while accessing %lx!", cr2);
decode_page_fault_error_code(regs->error);
handle_cpu_exception(SIGSEGV, nullptr, regs);
}
void handle_general_protection_fault(Registers* regs)
{
kerrorln("General protection fault at RIP %lx, error code %lx!", regs->rip, regs->error);
handle_cpu_exception(SIGSEGV, nullptr, regs);
}
extern "C" void handle_x86_exception(Registers* regs)
{
CPU::disable_interrupts();
switch (regs->isr)
{
case 0: handle_cpu_exception(SIGFPE, "Division by zero", regs); return;
case 1: FIXME_UNHANDLED_INTERRUPT("Debug interrupt");
case 2: FIXME_UNHANDLED_INTERRUPT("NMI (Non-maskable interrupt)");
case 3: FIXME_UNHANDLED_INTERRUPT("Breakpoint");
case 4: FIXME_UNHANDLED_INTERRUPT("Overflow");
case 5: FIXME_UNHANDLED_INTERRUPT("Bound range exceeded");
case 6: handle_cpu_exception(SIGILL, "Invalid opcode", regs); return;
case 7: FIXME_UNHANDLED_INTERRUPT("Device not available");
case 10: FIXME_UNHANDLED_INTERRUPT("Invalid TSS");
case 11: FIXME_UNHANDLED_INTERRUPT("Segment not present");
case 12: FIXME_UNHANDLED_INTERRUPT("Stack-segment fault");
case 13: handle_general_protection_fault(regs); return;
case 14: handle_page_fault(regs); return;
case 16: handle_cpu_exception(SIGFPE, "x87 floating-point exception", regs); return;
case 17: FIXME_UNHANDLED_INTERRUPT("Alignment check");
case 19: FIXME_UNHANDLED_INTERRUPT("SIMD floating-point exception");
case 20: FIXME_UNHANDLED_INTERRUPT("Virtualization exception");
case 21: FIXME_UNHANDLED_INTERRUPT("Control-protection exception");
default: fail("Caught invalid reserved exception or #DF/#MC, which shouldn't call handle_x86_exception");
}
}
CircularQueue<u8, 60> scancode_queue;
CircularQueue<moon::MousePacket, 20> mouse_queue;
void io_thread()
{
while (true)
{
u8 scancode;
moon::MousePacket packet;
if (scancode_queue.try_pop(scancode)) { ConsoleDevice::did_press_or_release_key(scancode); }
else if (mouse_queue.try_pop(packet)) { MouseDevice::add_mouse_event(packet); }
else
kernel_wait_for_event();
}
}
static void timer_interrupt(Registers* regs, void*)
{
Timer::tick();
if (should_invoke_scheduler()) Scheduler::invoke(regs);
}
static void keyboard_interrupt(Registers*, void*)
{
u8 scancode = IO::inb(0x60);
scancode_queue.try_push(scancode);
g_io_thread->wake_up();
}
// Called from _asm_interrupt_entry
extern "C" void arch_interrupt_entry(Registers* regs)
{
if (regs->isr < 32) handle_x86_exception(regs);
else if (regs->isr >= 32 && regs->isr < 48) // IRQ from the PIC
{
u64 irq = regs->irq;
auto handler = irq_handlers[irq];
if (!handler.function)
{
kwarnln("Unhandled IRQ catched! Halting.");
CPU::efficient_halt();
}
handler.function(regs, handler.context);
pic_eoi(regs);
}
else if (regs->isr == 66) // System call
{
SyscallArgs args = { regs->rdi, regs->rsi, regs->rdx, regs->r10, regs->r8, regs->r9 };
regs->rax = (u64)invoke_syscall(regs, args, regs->rax);
Scheduler::current()->process_pending_signals(regs);
}
else
{
kwarnln("Unhandled interrupt catched! Halting.");
CPU::efficient_halt();
}
}
extern "C" [[noreturn]] void arch_double_fault()
{
kerrorln("ERROR: Catched double fault");
CPU::efficient_halt();
}
extern "C" [[noreturn]] void arch_machine_check()
{
kerrorln("ERROR: Machine check failed");
CPU::efficient_halt();
}
// Generic CPU code
static bool test_nx()
{
u32 __unused, edx = 0;
if (!__get_cpuid(0x80000001, &__unused, &__unused, &__unused, &edx)) return 0;
return edx & (1 << 20);
}
namespace CPU
{
Result<StringView> identify()
{
static char brand_string[49];
u32 buf[4];
if (!__get_cpuid(0x80000002, &buf[0], &buf[1], &buf[2], &buf[3])) return err(ENOTSUP);
memcpy(brand_string, buf, 16);
if (!__get_cpuid(0x80000003, &buf[0], &buf[1], &buf[2], &buf[3])) return err(ENOTSUP);
memcpy(&brand_string[16], buf, 16);
if (!__get_cpuid(0x80000004, &buf[0], &buf[1], &buf[2], &buf[3])) return err(ENOTSUP);
memcpy(&brand_string[32], buf, 16);
brand_string[48] = 0; // null-terminate it :)
return StringView { brand_string, 48 };
}
StringView platform_string()
{
return "x86_64"_sv;
}
void platform_init()
{
enable_sse();
// enable_write_protect();
if (test_nx()) enable_nx();
else
kwarnln("not setting the NX bit as it is unsupported");
setup_gdt();
setup_idt();
memset(irq_handlers, 0, sizeof(irq_handlers));
register_interrupt(0, timer_interrupt, nullptr);
register_interrupt(1, keyboard_interrupt, nullptr);
}
void platform_finish_init()
{
g_io_thread = mark_critical(Scheduler::new_kernel_thread(io_thread, "[x86_64-io]"),
"Could not create the IO background thread!");
remap_pic();
init_mouse();
sync_interrupts();
}
void enable_interrupts()
{
asm volatile("sti");
}
void disable_interrupts()
{
asm volatile("cli");
}
bool save_interrupts()
{
u64 flags;
asm volatile("pushfq; pop %0" : "=r"(flags));
return flags & 0x200;
}
void restore_interrupts(bool saved)
{
if (saved) enable_interrupts();
else
disable_interrupts();
}
void wait_for_interrupt()
{
asm volatile("hlt");
}
[[noreturn]] void efficient_halt() // Halt the CPU, using the lowest power possible. On x86-64 we do this using
// the "hlt" instruction, which puts the CPU into a low-power idle state
// until the next interrupt arrives... and we disable interrupts beforehand.
{
asm volatile("cli"); // Disable interrupts
loop:
asm volatile("hlt"); // Let the cpu rest and pause until the next interrupt arrives... which in this case
// should be never (unless an NMI arrives) :)
goto loop; // Safeguard: if we ever wake up, start our low-power rest again
}
[[noreturn]] void idle_loop()
{
asm volatile("sti");
loop:
asm volatile("hlt");
goto loop;
}
void switch_kernel_stack(u64 top)
{
task_state_segment.rsp[0] = top;
}
struct StackFrame
{
StackFrame* next;
u64 instruction;
};
static void backtrace_impl(u64 base_pointer, void (*callback)(u64, void*), void* arg)
{
StackFrame* current_frame = (StackFrame*)base_pointer;
while (current_frame &&
#ifndef MOON_ENABLE_USERSPACE_STACK_TRACES
(u64)current_frame >= 0xFFFF'FFFF'8000'0000 &&
#endif
MemoryManager::validate_access(current_frame, sizeof(*current_frame), MemoryManager::DEFAULT_ACCESS) &&
current_frame->instruction)
{
callback(current_frame->instruction, arg);
current_frame = current_frame->next;
}
}
void get_stack_trace(void (*callback)(u64, void*), void* arg)
{
u64 rbp;
asm volatile("mov %%rbp, %0" : "=r"(rbp));
return backtrace_impl(rbp, callback, arg);
}
void print_stack_trace()
{
u64 rbp;
int frame_index = 0;
asm volatile("mov %%rbp, %0" : "=r"(rbp));
return backtrace_impl(
rbp,
[](u64 instruction, void* arg) {
int* ptr = (int*)arg;
kinfoln("#%d at %p", *ptr, (void*)instruction);
(*ptr)++;
},
&frame_index);
}
void get_stack_trace_at(Registers* regs, void (*callback)(u64, void*), void* arg)
{
callback(regs->rip, arg);
return backtrace_impl(regs->rbp, callback, arg);
}
void print_stack_trace_at(Registers* regs)
{
int frame_index = 0;
get_stack_trace_at(
regs,
[](u64 instruction, void* arg) {
int* ptr = (int*)arg;
kinfoln("#%d at %p", *ptr, (void*)instruction);
(*ptr)++;
},
&frame_index);
}
void pause()
{
asm volatile("pause");
}
u16 get_processor_id()
{
unsigned int unused;
unsigned int ebx = 0;
__get_cpuid(1, &unused, &ebx, &unused, &unused);
return (u16)(ebx >> 24);
}
bool register_interrupt(u8 interrupt, interrupt_handler_t handler, void* context)
{
if (irq_handlers[interrupt].function) return false;
irq_handlers[interrupt] = { handler, context };
sync_interrupts();
return true;
}
void sync_interrupts()
{
u8 pic1_mask, pic2_mask;
pic1_mask = pic2_mask = 0b11111111;
for (int i = 0; i < 8; i++)
{
if (irq_handlers[i].function) pic1_mask &= (u8)(~(1 << i));
if (irq_handlers[i + 8].function) pic2_mask &= (u8)(~(1 << i));
}
if (pic2_mask != 0b11111111) pic1_mask &= 0b11111011;
auto val = CPU::save_interrupts();
CPU::disable_interrupts();
change_pic_masks(pic1_mask, pic2_mask);
CPU::restore_interrupts(val);
}
#ifdef MOON_ENABLE_TESTING_FEATURES
// For tests! Must run QEMU with -device isa-debug-exit,iobase=0xf4,iosize=0x04.
void magic_exit(int status)
{
IO::outl(0xf4,
status ? 0x2 : 0x1); // QEMU exits with (status << 1) | 1. Zero would map to 0b11 (3), non-zero would
// map to 0b101 (5).
__builtin_unreachable();
}
#endif
}
// called by kernel_yield
extern "C" void switch_task(Registers* regs)
{
Scheduler::switch_task(regs);
}