mirror of
https://github.com/zephyrproject-rtos/zephyr
synced 2025-09-17 03:31:56 +00:00
- syscall.h now contains those APIs needed to support invoking calls from user code. Some stuff moved out of main kernel.h. - syscall_handler.h now contains directives useful for implementing system call handler functions. This header is not pulled in by kernel.h and is intended to be used by C files implementing kernel system calls and driver subsystem APIs. - syscall_list.h now contains the #defines for system call IDs. This list is expected to grow quite large so it is put in its own header. This is now an enumerated type instead of defines to make things easier as we introduce system calls over the new few months. In the fullness of time when we desire to have a fixed userspace/kernel ABI, this can always be converted to defines. Some new code added: - _SYSCALL_MEMORY() macro added to check memory regions passed up from userspace in handler functions - _syscall_invoke{7...10}() inline functions declare for invoking system calls with more than 6 arguments. 10 was chosen as the limit as that corresponds to the largest arg list we currently have which is for k_thread_create() Other changes - auto-generated K_SYSCALL_DECLARE* macros documented - _k_syscall_table in userspace.c is not a placeholder. There's no strong need to generate it and doing so would require the introduction of a third build phase. Signed-off-by: Andrew Boie <andrew.p.boie@intel.com>
183 lines
4.3 KiB
ArmAsm
183 lines
4.3 KiB
ArmAsm
/*
|
|
* Copyright (c) 2017 Intel Corporation
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <kernel_structs.h>
|
|
#include <arch/x86/asm.h>
|
|
#include <arch/cpu.h>
|
|
#include <offsets_short.h>
|
|
#include <syscall.h>
|
|
|
|
/* Exports */
|
|
GTEXT(_x86_syscall_entry_stub)
|
|
GTEXT(_x86_userspace_enter)
|
|
|
|
/* Imports */
|
|
GTEXT(_k_syscall_table)
|
|
|
|
/* Landing site for syscall SW IRQ. Marshal arguments and call C function for
|
|
* further processing. We're on the kernel stack for the invoking thread.
|
|
*/
|
|
SECTION_FUNC(TEXT, _x86_syscall_entry_stub)
|
|
sti /* re-enable interrupts */
|
|
cld /* clear direction flag, restored on 'iret' */
|
|
|
|
/* call_id is in ESI. bounds-check it, must be less than
|
|
* K_SYSCALL_LIMIT
|
|
*/
|
|
cmp $_SYSCALL_LIMIT, %esi
|
|
jae _bad_syscall
|
|
|
|
_id_ok:
|
|
/* Marshal arguments per calling convention to match what is expected
|
|
* for _k_syscall_handler_t functions
|
|
*/
|
|
push %esp /* ssf */
|
|
push %ebp /* arg6 */
|
|
push %edi /* arg5 */
|
|
push %ebx /* arg4 */
|
|
#ifndef CONFIG_X86_IAMCU
|
|
push %ecx /* arg3 */
|
|
push %edx /* arg2 */
|
|
push %eax /* arg1 */
|
|
#endif
|
|
|
|
/* from the call ID in ESI, load EBX with the actual function pointer
|
|
* to call by looking it up in the system call dispatch table
|
|
*/
|
|
xor %edi, %edi
|
|
mov _k_syscall_table(%edi, %esi, 4), %ebx
|
|
|
|
/* Run the handler, which is some entry in _k_syscall_table */
|
|
call *%ebx
|
|
|
|
/* EAX now contains return value. Pop or xor everything else to prevent
|
|
* information leak from kernel mode.
|
|
*/
|
|
#ifndef CONFIG_X86_IAMCU
|
|
pop %edx /* old arg1 value, discard it */
|
|
pop %edx
|
|
pop %ecx
|
|
#endif
|
|
pop %ebx
|
|
pop %edi
|
|
#ifndef CONFIG_X86_IAMCU
|
|
/* Discard ssf and arg6 */
|
|
add $8, %esp
|
|
#else
|
|
pop %ecx /* Clean ECX and get arg6 off the stack */
|
|
pop %edx /* Clean EDX and get ssf off the stack */
|
|
#endif
|
|
iret
|
|
|
|
_bad_syscall:
|
|
/* ESI had a bogus syscall value in it, replace with the bad syscall
|
|
* handler's ID, and put the bad ID as its first argument. This
|
|
* clobbers ESI but the bad syscall handler never returns
|
|
* anyway, it's going to generate a kernel oops
|
|
*/
|
|
mov %esi, %eax
|
|
mov $_SYSCALL_BAD, %esi
|
|
jmp _id_ok
|
|
|
|
|
|
/* FUNC_NORETURN void _x86_userspace_enter(k_thread_entry_t user_entry,
|
|
* void *p1, void *p2, void *p3,
|
|
* u32_t stack_end,
|
|
* u32_t stack_start)
|
|
*
|
|
* A one-way trip to userspace.
|
|
*/
|
|
SECTION_FUNC(TEXT, _x86_userspace_enter)
|
|
pop %esi /* Discard return address on stack */
|
|
|
|
/* Fetch parameters on the stack */
|
|
#ifndef CONFIG_X86_IAMCU
|
|
pop %eax /* user_entry */
|
|
pop %edx /* p1 */
|
|
pop %ecx /* p2 */
|
|
#endif
|
|
pop %esi /* p3 */
|
|
pop %ebx /* stack_end (high address) */
|
|
pop %edi /* stack_start (low address) */
|
|
|
|
/* Move to the kernel stack for this thread, so we can erase the
|
|
* user stack. The kernel stack is the page immediately before
|
|
* the user stack.
|
|
*
|
|
* For security reasons, we must erase the entire user stack.
|
|
* We don't know what previous contexts it was used and do not
|
|
* want to leak any information.
|
|
*/
|
|
mov %edi, %esp
|
|
|
|
/* Stash some registers we are going to need to erase the user
|
|
* stack.
|
|
*/
|
|
push %ecx
|
|
push %edi
|
|
push %eax
|
|
|
|
/* Compute size of user stack and put in ECX */
|
|
mov %ebx, %ecx
|
|
sub %edi, %ecx
|
|
|
|
#ifdef CONFIG_INIT_STACKS
|
|
mov $0xAAAAAAAA, %eax
|
|
#else
|
|
xor %eax, %eax
|
|
#endif
|
|
/* Fill ECX bytes of memory, 4 bytes at a time, starting at ES:EDI,
|
|
* with whatever is in EAX. Stack sizes are always at least 4-byte
|
|
* aligned.
|
|
*/
|
|
cld
|
|
rep stosl
|
|
|
|
/* Restore registers */
|
|
pop %eax
|
|
pop %edi
|
|
pop %ecx
|
|
|
|
/* Now set stack pointer to the base of the user stack. Now that this
|
|
* is set we won't need EBX any more.
|
|
*/
|
|
mov %ebx, %esp
|
|
|
|
/* Set segment registers (except CS and SS which are done in
|
|
* a special way by 'iret' below)
|
|
*/
|
|
mov $USER_DATA_SEG, %bx
|
|
mov %bx, %ds
|
|
mov %bx, %es
|
|
|
|
/* Push arguments to _thread_entry() */
|
|
push %esi /* p3 */
|
|
#ifndef CONFIG_X86_IAMCU
|
|
push %ecx /* p2 */
|
|
push %edx /* p1 */
|
|
push %eax /* user_entry */
|
|
#endif
|
|
/* NULL return address */
|
|
push $0
|
|
|
|
/* Save stack pointer at this position, this is where it will be
|
|
* when we land in _thread_entry()
|
|
*/
|
|
mov %esp, %edi
|
|
|
|
/* Inter-privilege 'iret' pops all of these. Need to fake an interrupt
|
|
* return to enter user mode as far calls cannot change privilege
|
|
* level
|
|
*/
|
|
push $USER_DATA_SEG /* SS */
|
|
push %edi /* ESP */
|
|
pushfl /* EFLAGS */
|
|
push $USER_CODE_SEG /* CS */
|
|
push $_thread_entry /* EIP */
|
|
|
|
/* We will land in _thread_entry() in user mode after this */
|
|
iret
|