//! x86-64 implementation of system call interface

use core::arch::global_asm;

use abi::SyscallFunction;
use kernel_arch_x86::registers::{MSR_IA32_EFER, MSR_IA32_LSTAR, MSR_IA32_SFMASK, MSR_IA32_STAR};
use kernel_arch_x86_64::context::SyscallFrame;
use libk::task::{process::Process, thread::Thread};
use tock_registers::interfaces::{ReadWriteable, Writeable};

use crate::syscall::{self, raw_syscall_handler};

fn syscall_inner(frame: &mut SyscallFrame) {
    let trace_seq = Thread::current().trace_syscall_entry(frame);

    if frame.rax == usize::from(SyscallFunction::ExitSignal) {
        unsafe {
            syscall::handle_signal_exit(frame);
            return;
        }
    }
    if frame.rax == usize::from(SyscallFunction::Fork) {
        unsafe {
            Process::raw_fork(frame);
            return;
        }
    }

    let code = frame.rax;
    if code == usize::from(SyscallFunction::CreateDirectory) {
        log::debug!("frame in = {frame:#x?}");
    }
    let result = raw_syscall_handler(frame.rax, &frame.args);

    frame.rax = result as _;

    Thread::current().trace_syscall_exit(frame, trace_seq);

    if code == usize::from(SyscallFunction::CreateDirectory) {
        log::debug!("frame out = {frame:#x?}");
    }
}

extern "C" fn __x86_64_syscall_handler(frame: *mut SyscallFrame) {
    let frame = unsafe { &mut *frame };
    syscall_inner(frame);

    let thread = Thread::current();
    unsafe {
        thread.handle_pending_signals(frame);
    }
}

/// Initializes system call instruction support for the current CPU.
///
/// # Safety
///
/// Only meant to be called once per each CPU during their init.
pub unsafe fn init_syscall() {
    extern "C" {
        fn __x86_64_syscall_vector();
    }

    // Initialize syscall vector
    MSR_IA32_LSTAR.set(__x86_64_syscall_vector as u64);
    MSR_IA32_SFMASK.write(MSR_IA32_SFMASK::IF::Masked + MSR_IA32_SFMASK::TF::Masked);
    MSR_IA32_STAR.write(
        // On sysret, CS = val + 16 (0x23), SS = val + 8 (0x1B)
        MSR_IA32_STAR::SYSRET_CS_SS.val(0x1B - 8) +
        // On syscall, CS = val (0x08), SS = val + 8 (0x10)
        MSR_IA32_STAR::SYSCALL_CS_SS.val(0x08),
    );

    MSR_IA32_EFER.modify(MSR_IA32_EFER::SCE::Enable);
}

global_asm!(
    include_str!("syscall.S"),
    syscall_handler = sym __x86_64_syscall_handler,
    options(att_syntax)
);
