rt-thread/components/lwp/arch/x86/i386/lwp_arch.c

372 lines
11 KiB
C

/*
* 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 */