/*
 * Copyright (c) 2006-2021, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2021-7-14      JasonHu      first version
 */

#include <rthw.h>
#include <stddef.h>
#include <rtconfig.h>
#include <rtdbg.h>

#ifdef ARCH_MM_MMU

#include <stackframe.h>
#include <interrupt.h>
#include <segment.h>

#include <mmu.h>
#include <page.h>
#include <lwp_mm_area.h>
#include <lwp_user_mm.h>
#include <lwp_arch.h>

#ifdef RT_USING_SIGNALS
#include <lwp_signal.h>
#endif /* RT_USING_SIGNALS */

extern size_t g_mmu_table[];

int arch_expand_user_stack(void *addr)
{
    int ret = 0;
    size_t stack_addr = (size_t)addr;

    stack_addr &= ~PAGE_OFFSET_MASK;
    if ((stack_addr >= (size_t)USER_STACK_VSTART) && (stack_addr < (size_t)USER_STACK_VEND))
    {
        void *map = lwp_map_user(lwp_self(), (void *)stack_addr, PAGE_SIZE, RT_FALSE);

        if (map || lwp_user_accessable(addr, 1))
        {
            ret = 1;    /* map success */
        }
        else /* map failed, send signal SIGSEGV */
        {
#ifdef RT_USING_SIGNALS
            dbg_log(DBG_ERROR, "[fault] thread %s mapped addr %p failed!\n", rt_thread_self()->parent.name, addr);
            lwp_thread_kill(rt_thread_self(), SIGSEGV);
            ret = 1;    /* return 1, will return back to intr, then check exit */
#endif
        }
    }
    else    /* not stack, send signal SIGSEGV */
    {
#ifdef RT_USING_SIGNALS
        dbg_log(DBG_ERROR, "[fault] thread %s access unmapped addr %p!\n", rt_thread_self()->parent.name, addr);
        lwp_thread_kill(rt_thread_self(), SIGSEGV);
        ret = 1;    /* return 1, will return back to intr, then check exit */
#endif
    }
    return ret;
}

void *get_thread_kernel_stack_top(rt_thread_t thread)
{
    return RT_NULL;
}

/**
 * don't support this in i386, it's ok!
 */
void *arch_get_user_sp()
{
    return RT_NULL;
}

int arch_user_space_init(struct rt_lwp *lwp)
{
    rt_size_t *mmu_table;

    mmu_table = (rt_size_t *)rt_pages_alloc_ext(0, PAGE_ANY_AVAILABLE);
    if (!mmu_table)
    {
        return -1;
    }
    rt_memset(mmu_table, 0, ARCH_PAGE_SIZE);

    lwp->end_heap = USER_HEAP_VADDR;
    memcpy(mmu_table, g_mmu_table, ARCH_PAGE_SIZE / 4);
    memset((rt_uint8_t *)mmu_table + ARCH_PAGE_SIZE / 4, 0, ARCH_PAGE_SIZE / 4 * 3);
    rt_hw_cpu_dcache_ops(RT_HW_CACHE_FLUSH, mmu_table, ARCH_PAGE_SIZE);
    if (rt_hw_mmu_map_init(&lwp->mmu_info, (void*)USER_VADDR_START, USER_VADDR_TOP - USER_VADDR_START, mmu_table, PV_OFFSET) < 0)
    {
        rt_pages_free(mmu_table, 0);
        return -1;
    }
    return 0;
}

void *arch_kernel_mmu_table_get(void)
{
    return (void *)((char *)g_mmu_table);
}

void arch_user_space_vtable_free(struct rt_lwp *lwp)
{
    if (lwp && lwp->mmu_info.vtable)
    {
        rt_pages_free(lwp->mmu_info.vtable, 0);
        lwp->mmu_info.vtable = NULL;
    }
}

void arch_set_thread_area(void *p)
{
    rt_hw_seg_tls_set((rt_ubase_t) p);
    rt_thread_t cur = rt_thread_self();
    cur->thread_idr = p; /* update thread idr after first set */
}

void *arch_get_tidr(void)
{
    rt_thread_t cur = rt_thread_self();
    if (!cur->lwp)  /* no lwp, don't get thread idr from tls seg */
        return NULL;
    return (void *)rt_hw_seg_tls_get();   /* get thread idr from tls seg */
}

void arch_set_tidr(void *p)
{
    rt_thread_t cur = rt_thread_self();
    if (!cur->lwp) /* no lwp, don't set thread idr to tls seg */
        return;
    rt_hw_seg_tls_set((rt_ubase_t) p); /* set tls seg addr as thread idr */
}

static void lwp_user_stack_init(rt_hw_stack_frame_t *frame)
{
    frame->ds = frame->es = USER_DATA_SEL;
    frame->cs = USER_CODE_SEL;
    frame->ss = USER_STACK_SEL;
    frame->gs = USER_TLS_SEL;
    frame->fs = 0;  /* unused */

    frame->edi = frame->esi = \
    frame->ebp = frame->esp_dummy = 0;
    frame->eax = frame->ebx = \
    frame->ecx = frame->edx = 0;

    frame->error_code = 0;
    frame->vec_no = 0;

    frame->eflags = (EFLAGS_MBS | EFLAGS_IF_1 | EFLAGS_IOPL_3);
}

extern void lwp_switch_to_user(void *frame);
/**
 * user entry, set frame.
 * at the end of execute, we need enter user mode,
 * in x86, we can set stack, arg, text entry in a stack frame,
 * then pop then into register, final use iret to switch kernel mode to user mode.
 */
void arch_start_umode(void *args, const void *text, void *ustack, void *k_stack)
{
    rt_uint8_t *stk = k_stack;
    stk -= sizeof(struct rt_hw_stack_frame);
    struct rt_hw_stack_frame *frame = (struct rt_hw_stack_frame *)stk;

    lwp_user_stack_init(frame);
    frame->esp = (rt_uint32_t)ustack - 32;
    frame->ebx = (rt_uint32_t)args;
    frame->eip = (rt_uint32_t)text;
    lwp_switch_to_user(frame);
    /* should never return */
}

void lwp_exec_user(void *args, void *kernel_stack, void *user_entry)
{
    arch_start_umode(args, (const void *)user_entry, (void *)USER_STACK_VEND, kernel_stack);
}

extern void lwp_thread_return();
extern void lwp_thread_return_end();

static void *lwp_copy_return_code_to_user_stack(void *ustack)
{
    size_t size = (size_t)lwp_thread_return_end - (size_t)lwp_thread_return;
    void *retcode = (void *)((size_t)ustack - size);
    memcpy(retcode, (void *)lwp_thread_return, size);
    return retcode;
}

/**
 * when called sys_thread_create, need create a thread, after thread stared, will come here,
 * like arch_start_umode, will enter user mode, but we must set thread exit function. it looks like:
 * void func(void *arg)
 * {
 *      ...
 * }
 * when thread func return, we must call exit code to exit thread, or not the program runs away.
 * so we need copy exit code to user and call exit code when func return.
 */
void arch_crt_start_umode(void *args, const void *text, void *ustack, void *k_stack)
{
    RT_ASSERT(ustack != NULL);

    rt_uint8_t *stk;
    stk  = (rt_uint8_t *)((rt_uint8_t *)k_stack + sizeof(rt_ubase_t));
    stk  = (rt_uint8_t *)RT_ALIGN_DOWN(((rt_ubase_t)stk), sizeof(rt_ubase_t));
    stk -= sizeof(struct rt_hw_stack_frame);
    struct rt_hw_stack_frame *frame = (struct rt_hw_stack_frame *)stk;

    lwp_user_stack_init(frame);

    /* make user thread stack */
    unsigned long *retcode = lwp_copy_return_code_to_user_stack(ustack);    /* copy ret code */
    unsigned long *retstack = (unsigned long *)RT_ALIGN_DOWN(((rt_ubase_t)retcode), sizeof(rt_ubase_t));

    /**
     * x86 call stack
     *
     * retcode here
     *
     * arg n
     * arg n - 1
     * ...
     * arg 2
     * arg 1
     * arg 0
     * eip (caller return addr, point to retcode)
     * esp
     */
    *(--retstack) = (unsigned long) args;       /* arg */
    *(--retstack) = (unsigned long) retcode;    /* ret eip */

    frame->esp = (rt_uint32_t)retstack;
    frame->eip = (rt_uint32_t)text;
    lwp_switch_to_user(frame);
    /* should never return */
}

rt_thread_t rt_thread_sp_to_thread(void *spmember_addr)
{
    return (rt_thread_t)(((rt_ubase_t)spmember_addr) - (offsetof(struct rt_thread, sp)));
}

/**
 * set exec context for fork/clone.
 * user_stack(unused)
 */
void arch_set_thread_context(void *exit_addr, void *new_thread_stack, void *user_stack, void **thread_sp)
{
    /**
     * thread kernel stack was set to tss.esp0, when intrrupt/syscall occur,
     * the stack frame will store in kernel stack top, so we can get the stack
     * frame by kernel stack top.
     */
    rt_hw_stack_frame_t *frame = (rt_hw_stack_frame_t *)((rt_ubase_t)new_thread_stack - sizeof(rt_hw_stack_frame_t));

    frame->eax = 0; /* child return 0 */

    rt_hw_context_t *context = (rt_hw_context_t *) (((rt_uint32_t *)frame) - HW_CONTEXT_MEMBER_NR);
    context->eip = (void *)exit_addr;    /* when thread started, jump to intr exit for enter user mode */
    context->ebp = context->ebx = context->esi = context->edi = 0;

    /**
     * set sp as the address of first member of rt_hw_context,
     * when scheduler call switch, pop stack from context stack.
     */
    *thread_sp = (void *)&context->ebp;

    /**
     * after set context, the stack like this:
     *
     * -----------
     * stack frame| eax = 0
     * -----------
     * context(only HW_CONTEXT_MEMBER_NR)| eip = rt_hw_intr_exit
     * -----------
     * thread sp  | to <- rt_hw_context_switch(from, to)
     * -----------
     */
}

#ifdef RT_USING_SIGNALS

#define SIGNAL_RET_CODE_SIZE    16

struct rt_signal_frame
{
    char *ret_addr; /* return addr when handler return */
    int signo;   /* signal for user handler arg  */
    rt_hw_stack_frame_t frame; /* save kernel signal stack */
    char ret_code[SIGNAL_RET_CODE_SIZE];    /* save return code  */
};
typedef struct rt_signal_frame rt_signal_frame_t;

extern void lwp_signal_return();
extern void lwp_signal_return_end();

void lwp_try_do_signal(rt_hw_stack_frame_t *frame)
{
    if (!lwp_signal_check())
        return;

    /* 1. backup signal mask */
    int signal = lwp_signal_backup((void *) frame->esp, (void *) frame->eip, (void *) frame->eflags);

    /* 2. get signal handler */
    lwp_sighandler_t handler = lwp_sighandler_get(signal);
    if (handler == RT_NULL) /* no handler, ignore  */
    {
        lwp_signal_restore();
        return;
    }

    rt_base_t level = rt_hw_interrupt_disable();
    /* 3. backup frame */
    rt_signal_frame_t *sig_frame = (rt_signal_frame_t *)((frame->esp - sizeof(rt_signal_frame_t)) & -8UL);
    memcpy(&sig_frame->frame, frame, sizeof(rt_hw_stack_frame_t));
    sig_frame->signo = signal;

    /**
     * 4. copy user return code into user stack
     *
     * save current frame on user stack. the user stack like:
     *
     * ----------
     * user code stack
     * ----------+ -> esp before enter kernel
     * signal frame
     * ----------+ -> esp when handle signal handler
     * signal handler stack
     * ----------
     */
    size_t ret_code_size = (size_t)lwp_signal_return_end - (size_t)lwp_signal_return;
    memcpy(sig_frame->ret_code, (void *)lwp_signal_return, ret_code_size);
    sig_frame->ret_addr = sig_frame->ret_code;

    /* 5. jmp to user execute handler, update frame register info */
    lwp_user_stack_init(frame);
    frame->eip = (rt_uint32_t) handler;
    frame->esp = (rt_uint32_t) sig_frame;

    rt_hw_interrupt_enable(level);
}

void lwp_signal_do_return(rt_hw_stack_frame_t *frame)
{
    /**
     * ASSUME: in x86, each stack push and pop element is 4 byte. so STACK_ELEM_SIZE = sizeof(int) => 4.
     * when signal handler return, the stack move to the buttom of signal frame.
     * but return will pop eip from esp, then {esp += STACK_ELEM_SIZE}, thus {esp = (signal frame) + STACK_ELEM_SIZE}.
     * so {(signal frame) = esp - STACK_ELEM_SIZE}
     */
    rt_signal_frame_t *sig_frame = (rt_signal_frame_t *)(frame->esp - sizeof(rt_uint32_t));
    memcpy(frame, &sig_frame->frame, sizeof(rt_hw_stack_frame_t));

    /**
     * restore signal info, but don't use rt_user_context,
     * we use sig_frame to restore stack frame
     */
    lwp_signal_restore();
}
#endif /* RT_USING_SIGNALS */

#endif  /* ARCH_MM_MMU */
