| /* |
| * 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); |
| } |