zephyr/arch/x86/core/thread.c
Andrew Boie 507852a4ad kernel: introduce opaque data type for stacks
Historically, stacks were just character buffers and could be treated
as such if the user wanted to look inside the stack data, and also
declared as an array of the desired stack size.

This is no longer the case. Certain architectures will create a memory
region much larger to account for MPU/MMU guard pages. Unfortunately,
the kernel interfaces treat both the declared stack, and the valid
stack buffer within it as the same char * data type, even though these
absolutely cannot be used interchangeably.

We introduce an opaque k_thread_stack_t which gets instantiated by
K_THREAD_STACK_DECLARE(), this is no longer treated by the compiler
as a character pointer, even though it really is.

To access the real stack buffer within, the result of
K_THREAD_STACK_BUFFER() can be used, which will return a char * type.

This should catch a bunch of programming mistakes at build time:

- Declaring a character array outside of K_THREAD_STACK_DECLARE() and
  passing it to K_THREAD_CREATE
- Directly examining the stack created by K_THREAD_STACK_DECLARE()
  which is not actually the memory desired and may trigger a CPU
  exception

Signed-off-by: Andrew Boie <andrew.p.boie@intel.com>
2017-08-01 16:43:15 -07:00

268 lines
8.6 KiB
C

/*
* Copyright (c) 2010-2015 Wind River Systems, Inc.
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* @brief Thread support primitives
*
* This module provides core thread related primitives for the IA-32
* processor architecture.
*/
#ifdef CONFIG_INIT_STACKS
#include <string.h>
#endif /* CONFIG_INIT_STACKS */
#include <toolchain.h>
#include <linker/sections.h>
#include <kernel_structs.h>
#include <wait_q.h>
#include <mmustructs.h>
/* forward declaration */
#if defined(CONFIG_GDB_INFO) || defined(CONFIG_DEBUG_INFO) \
|| defined(CONFIG_X86_IAMCU)
void _thread_entry_wrapper(_thread_entry_t, void *,
void *, void *);
#endif
/**
*
* @brief Initialize a new execution thread
*
* This function is utilized to initialize all execution threads (both fiber
* and task). The 'priority' parameter will be set to -1 for the creation of
* task.
*
* This function is called by _new_thread() to initialize tasks.
*
* @param thread pointer to thread struct memory
* @param pStackMem pointer to thread stack memory
* @param stackSize size of a stack in bytes
* @param priority thread priority
* @param options thread options: K_ESSENTIAL, K_FP_REGS, K_SSE_REGS
*
* @return N/A
*/
static void _new_thread_internal(char *pStackMem, unsigned int stackSize,
int priority,
unsigned int options,
struct k_thread *thread)
{
unsigned long *pInitialCtx;
#if (defined(CONFIG_FP_SHARING) || defined(CONFIG_GDB_INFO))
thread->arch.excNestCount = 0;
#endif /* CONFIG_FP_SHARING || CONFIG_GDB_INFO */
/*
* The creation of the initial stack for the task has already been done.
* Now all that is needed is to set the ESP. However, we have been passed
* the base address of the stack which is past the initial stack frame.
* Therefore some of the calculations done in the other routines that
* initialize the stack frame need to be repeated.
*/
pInitialCtx = (unsigned long *)STACK_ROUND_DOWN(pStackMem + stackSize);
#ifdef CONFIG_THREAD_MONITOR
/*
* In debug mode thread->entry give direct access to the thread entry
* and the corresponding parameters.
*/
thread->entry = (struct __thread_entry *)(pInitialCtx -
sizeof(struct __thread_entry));
#endif
/* The stack needs to be set up so that when we do an initial switch
* to it in the middle of _Swap(), it needs to be set up as follows:
* - 4 thread entry routine parameters
* - eflags
* - eip (so that _Swap() "returns" to the entry point)
* - edi, esi, ebx, ebp, eax
*/
pInitialCtx -= 11;
thread->callee_saved.esp = (unsigned long)pInitialCtx;
PRINTK("\nInitial context ESP = 0x%x\n", thread->coopReg.esp);
PRINTK("\nstruct thread * = 0x%x", thread);
thread_monitor_init(thread);
}
#if defined(CONFIG_GDB_INFO) || defined(CONFIG_DEBUG_INFO) \
|| defined(CONFIG_X86_IAMCU)
/**
*
* @brief Adjust stack/parameters before invoking _thread_entry
*
* This function adjusts the initial stack frame created by _new_thread() such
* that the GDB stack frame unwinders recognize it as the outermost frame in
* the thread's stack. For targets that use the IAMCU calling convention, the
* first three arguments are popped into eax, edx, and ecx. The function then
* jumps to _thread_entry().
*
* GDB normally stops unwinding a stack when it detects that it has
* reached a function called main(). Kernel tasks, however, do not have
* a main() function, and there does not appear to be a simple way of stopping
* the unwinding of the stack.
*
* SYS V Systems:
*
* Given the initial thread created by _new_thread(), GDB expects to find a
* return address on the stack immediately above the thread entry routine
* _thread_entry, in the location occupied by the initial EFLAGS.
* GDB attempts to examine the memory at this return address, which typically
* results in an invalid access to page 0 of memory.
*
* This function overwrites the initial EFLAGS with zero. When GDB subsequently
* attempts to examine memory at address zero, the PeekPoke driver detects
* an invalid access to address zero and returns an error, which causes the
* GDB stack unwinder to stop somewhat gracefully.
*
* The initial EFLAGS cannot be overwritten until after _Swap() has swapped in
* the new thread for the first time. This routine is called by _Swap() the
* first time that the new thread is swapped in, and it jumps to
* _thread_entry after it has done its work.
*
* IAMCU Systems:
*
* There is no EFLAGS on the stack when we get here. _thread_entry() takes
* four arguments, and we need to pop off the first three into the
* appropriate registers. Instead of using the 'call' instruction, we push
* a NULL return address onto the stack and jump into _thread_entry,
* ensuring the stack won't be unwound further. Placing some kind of return
* address on the stack is mandatory so this isn't conditionally compiled.
*
* __________________
* | param3 | <------ Top of the stack
* |__________________|
* | param2 | Stack Grows Down
* |__________________| |
* | param1 | V
* |__________________|
* | pEntry | <---- ESP when invoked by _Swap() on IAMCU
* |__________________|
* | initial EFLAGS | <---- ESP when invoked by _Swap() on Sys V
* |__________________| (Zeroed by this routine on Sys V)
*
*
*
* @return this routine does NOT return.
*/
__asm__("\t.globl _thread_entry\n"
"\t.section .text\n"
"_thread_entry_wrapper:\n" /* should place this func .S file and use
* SECTION_FUNC
*/
#ifdef CONFIG_X86_IAMCU
/* IAMCU calling convention has first 3 arguments supplied in
* registers not the stack
*/
"\tpopl %eax\n"
"\tpopl %edx\n"
"\tpopl %ecx\n"
"\tpushl $0\n" /* Null return address */
#elif defined(CONFIG_GDB_INFO) || defined(CONFIG_DEBUG_INFO)
"\tmovl $0, (%esp)\n" /* zero initialEFLAGS location */
#endif
"\tjmp _thread_entry\n");
#endif /* CONFIG_GDB_INFO || CONFIG_DEBUG_INFO) || CONFIG_X86_IAMCU */
/**
*
* @brief Create a new kernel execution thread
*
* This function is utilized to create execution threads for both fiber
* threads and kernel tasks.
*
* @param thread pointer to thread struct memory, including any space needed
* for extra coprocessor context
* @param pStackmem the pointer to aligned stack memory
* @param stackSize the stack size in bytes
* @param pEntry thread entry point routine
* @param parameter1 first param to entry point
* @param parameter2 second param to entry point
* @param parameter3 third param to entry point
* @param priority thread priority
* @param options thread options: K_ESSENTIAL, K_FP_REGS, K_SSE_REGS
*
*
* @return opaque pointer to initialized k_thread structure
*/
void _new_thread(struct k_thread *thread, k_thread_stack_t stack,
size_t stackSize,
_thread_entry_t pEntry,
void *parameter1, void *parameter2, void *parameter3,
int priority, unsigned int options)
{
char *pStackMem;
_ASSERT_VALID_PRIO(priority, pEntry);
unsigned long *pInitialThread;
#if CONFIG_X86_STACK_PROTECTION
_x86_mmu_set_flags(stack, MMU_PAGE_SIZE, MMU_ENTRY_NOT_PRESENT,
MMU_PTE_P_MASK);
#endif
pStackMem = K_THREAD_STACK_BUFFER(stack);
_new_thread_init(thread, pStackMem, stackSize, priority, options);
/* carve the thread entry struct from the "base" of the stack */
pInitialThread =
(unsigned long *)STACK_ROUND_DOWN(pStackMem + stackSize);
/*
* Create an initial context on the stack expected by the _Swap()
* primitive.
*/
/* push arguments required by _thread_entry() */
*--pInitialThread = (unsigned long)parameter3;
*--pInitialThread = (unsigned long)parameter2;
*--pInitialThread = (unsigned long)parameter1;
*--pInitialThread = (unsigned long)pEntry;
/* push initial EFLAGS; only modify IF and IOPL bits */
*--pInitialThread = (EflagsGet() & ~EFLAGS_MASK) | EFLAGS_INITIAL;
#if defined(CONFIG_GDB_INFO) || defined(CONFIG_DEBUG_INFO) \
|| defined(CONFIG_X86_IAMCU)
/*
* Arrange for the _thread_entry_wrapper() function to be called
* to adjust the stack before _thread_entry() is invoked.
*/
*--pInitialThread = (unsigned long)_thread_entry_wrapper;
#else /* defined(CONFIG_GDB_INFO) || defined(CONFIG_DEBUG_INFO) */
*--pInitialThread = (unsigned long)_thread_entry;
#endif /* defined(CONFIG_GDB_INFO) || defined(CONFIG_DEBUG_INFO) */
/*
* note: stack area for edi, esi, ebx, ebp, and eax registers can be
* left
* uninitialized, since _thread_entry() doesn't care about the values
* of these registers when it begins execution
*/
/*
* The k_thread structure is located at the "low end" of memory set
* aside for the thread's stack.
*/
_new_thread_internal(pStackMem, stackSize, priority, options, thread);
}