| /* |
| * Copyright (c) 2011-2015 Wind River Systems, Inc. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| /** |
| * @file |
| * @brief Exception management support for IA-32 architecture |
| * |
| * This module implements assembly routines to manage exceptions (synchronous |
| * interrupts) on the Intel IA-32 architecture. More specifically, |
| * exceptions are implemented in this module. The stubs are invoked when entering |
| * and exiting a C exception handler. |
| */ |
| |
| #define _ASMLANGUAGE |
| |
| #include <nano_private.h> |
| #include <arch/x86/asm.h> |
| #include <arch/x86/arch.h> /* For MK_ISR_NAME */ |
| #include <offsets.h> /* nanokernel structure offset definitions */ |
| |
| |
| #include <asmPrv.h> |
| |
| /* exports (internal APIs) */ |
| |
| GTEXT(_ExcEnt) |
| GTEXT(_ExcEntNoErr) |
| GTEXT(_ExcExit) |
| GTEXT(_DynExcStubsBegin) |
| GTEXT(_DynExcStubsNoErrBegin) |
| |
| /* externs (internal APIs) */ |
| |
| /** |
| * |
| * @brief Inform the kernel of an exception with no error code |
| * |
| * This is very similar to _ExcEnt() but the stack is first massaged |
| * so that a dummy error code is inserted. |
| */ |
| SECTION_FUNC(TEXT, _ExcEntNoErr) |
| /* Clear direction flag, auto-restored when the exception exits */ |
| cld |
| |
| /* Stash current value of ECX to free up the register */ |
| pushl %ecx |
| |
| /* Save the return address of the stub into ECX */ |
| movl 4(%esp), %ecx |
| |
| /* |
| * The spot for the error code contains useless data, but |
| * we don't particularly care since it will be unused. |
| */ |
| jmp _ExcEntSetupDone |
| |
| /** |
| * |
| * @brief Inform the kernel of an exception |
| * |
| * This function is called from the exception stub created by nanoCpuExcConnect() |
| * to inform the kernel of an exception. This routine currently does |
| * _not_ increment a thread/interrupt specific exception count. Also, |
| * execution of the exception handler occurs on the current stack, i.e. |
| * _ExcEnt() does not switch to another stack. The volatile integer |
| * registers are saved on the stack, and control is returned back to the |
| * exception stub. |
| * |
| * WARNINGS |
| * |
| * Host-based tools and the target-based GDB agent depend on the stack frame |
| * created by this routine to determine the locations of volatile registers. |
| * These tools must be updated to reflect any changes to the stack frame. |
| * |
| * @return N/A |
| * |
| * C function prototype: |
| * |
| * void _ExcEnt (void); |
| * |
| */ |
| |
| SECTION_FUNC(TEXT, _ExcEnt) |
| |
| /* |
| * The _IntVecSet() routine creates an interrupt-gate descriptor for |
| * all connections. The processor will automatically clear the IF |
| * bit in the EFLAGS register upon execution of the handler, thus |
| * _ExcEnt() (and _IntEnt) need not issue an 'cli' as the first |
| * instruction. |
| */ |
| |
| |
| /* |
| * Note that the processor has pushed both the EFLAGS register |
| * and the linear return address (cs:eip) onto the stack prior |
| * to invoking the handler specified in the IDT. |
| * |
| * Clear the direction flag. It is automatically restored when the |
| * exception exits. |
| */ |
| |
| cld |
| |
| |
| /* |
| * Swap ecx and return address on the current stack; |
| * this saves ecx on the stack without losing knowledge |
| * of how to get back to the exception stub. |
| */ |
| xchgl %ecx, (%esp) |
| |
| BRANCH_LABEL(_ExcEntSetupDone) |
| |
| /* By the time we get here, the stack should look like this: |
| * ESP -> ECX (excepting task) |
| * Exception Error code (or junk) |
| * EIP (excepting task) |
| * CS (excepting task) |
| * EFLAGS (excepting task) |
| * ... |
| * |
| * ECX now contains the EIP of the calling exception stub */ |
| |
| /* |
| * Push the remaining volatile registers on the existing stack. |
| */ |
| |
| pushl %eax |
| pushl %edx |
| |
| /* |
| * Push the cooperative registers on the existing stack as they are |
| * required by debug tools. |
| */ |
| |
| pushl %edi |
| pushl %esi |
| pushl %ebx |
| pushl %ebp |
| |
| leal 44(%esp), %eax /* Calculate ESP before interrupt occurred */ |
| pushl %eax /* Save calculated ESP */ |
| |
| /* ESP is pointing to the ESF at this point */ |
| |
| #if defined(CONFIG_FP_SHARING) || defined(CONFIG_GDB_INFO) |
| |
| movl _nanokernel + __tNANO_current_OFFSET, %ecx |
| |
| incl __tTCS_excNestCount_OFFSET(%ecx) /* inc exception nest count */ |
| |
| #ifdef CONFIG_GDB_INFO |
| |
| /* |
| * Save the pointer to the stack frame (NANO_ESF *) in |
| * the current execution context if this is the outermost exception. |
| * The ESF pointer is used by debug tools to locate the volatile |
| * registers and the stack of the preempted thread. |
| */ |
| |
| testl $EXC_ACTIVE, __tTCS_flags_OFFSET (%ecx) |
| jne alreadyInException |
| movl %esp, __tTCS_esfPtr_OFFSET(%ecx) |
| |
| BRANCH_LABEL(alreadyInException) |
| |
| #endif /* CONFIG_GDB_INFO */ |
| |
| /* |
| * Set the EXC_ACTIVE bit in the TCS of the current thread. |
| * This enables _Swap() to preserve the thread's FP registers |
| * (where needed) if the exception handler causes a context switch. |
| * It also indicates to debug tools that an exception is being |
| * handled in the event of a context switch. |
| */ |
| |
| orl $EXC_ACTIVE, __tTCS_flags_OFFSET(%ecx) |
| |
| #endif /* CONFIG_FP_SHARING || CONFIG_GDB_INFO */ |
| |
| |
| |
| /* |
| * restore interrupt enable state, then "return" back to exception stub |
| * |
| * interrupts are enabled only if they were allowed at the time |
| * the exception was triggered -- this protects kernel level code |
| * that mustn't be interrupted |
| * |
| * Test IF bit of saved EFLAGS and re-enable interrupts if IF=1. |
| */ |
| |
| /* ESP is still pointing to the ESF at this point */ |
| |
| testl $0x200, __NANO_ESF_eflags_OFFSET(%esp) |
| je allDone |
| sti |
| |
| BRANCH_LABEL(allDone) |
| #if CONFIG_X86_IAMCU |
| movl %esp, %eax /* NANO_ESF * parameter */ |
| #else |
| pushl %esp /* push NANO_ESF * parameter */ |
| #endif |
| jmp *%ecx /* "return" back to stub */ |
| |
| |
| /** |
| * |
| * @brief Inform the kernel of an exception exit |
| * |
| * This function is called from the exception stub created by nanoCpuExcConnect() |
| * to inform the kernel that the processing of an exception has |
| * completed. This routine restores the volatile integer registers and |
| * then control is returned back to the interrupted thread or ISR. |
| * |
| * @return N/A |
| * |
| * C function prototype: |
| * |
| * void _ExcExit (void); |
| * |
| */ |
| |
| SECTION_FUNC(TEXT, _ExcExit) |
| /* On entry, interrupts may or may not be enabled. */ |
| |
| #ifndef CONFIG_X86_IAMCU |
| popl %ecx /* discard the NANO_ESF * parameter */ |
| #endif |
| |
| #if defined(CONFIG_FP_SHARING) || defined(CONFIG_GDB_INFO) |
| |
| movl _nanokernel + __tNANO_current_OFFSET, %ecx |
| |
| /* |
| * Must lock interrupts to prevent outside interference. |
| * (Using "lock" prefix would be nicer, but this won't work |
| * on platforms that don't respect the CPU's bus lock signal.) |
| */ |
| |
| cli |
| |
| /* |
| * Determine whether exiting from a nested interrupt. |
| */ |
| |
| decl __tTCS_excNestCount_OFFSET(%ecx) /* dec exception nest count */ |
| |
| cmpl $0, __tTCS_excNestCount_OFFSET(%ecx) |
| jne nestedException |
| |
| /* |
| * Clear the EXC_ACTIVE bit in the tTCS of the current execution context |
| * if we are not in a nested exception (ie, when we exit the outermost |
| * exception). |
| */ |
| |
| andl $~EXC_ACTIVE, __tTCS_flags_OFFSET (%ecx) |
| |
| BRANCH_LABEL(nestedException) |
| #endif /* CONFIG_FP_SHARING || CONFIG_GDB_INFO */ |
| |
| /* |
| * Pop the non-volatile registers from the stack. |
| * Note that debug tools may have altered the saved register values while |
| * the task was stopped, and we want to pick up the altered values. |
| */ |
| |
| popl %ebp /* Discard saved ESP */ |
| popl %ebp |
| popl %ebx |
| popl %esi |
| popl %edi |
| |
| /* restore edx and ecx which are always saved on the stack */ |
| |
| popl %edx |
| popl %eax |
| popl %ecx |
| |
| addl $4, %esp /* "pop" error code */ |
| |
| /* Pop of EFLAGS will re-enable interrupts and restore direction flag */ |
| iret |
| |
| /* Static exception handler stubs */ |
| #if CONFIG_FP_SHARING |
| SYS_NANO_CPU_EXC_CONNECT(_FpNotAvailableExcHandler,IV_DEVICE_NOT_AVAILABLE) |
| #endif /* CONFIG_FP_SHARING */ |
| |
| #if CONFIG_EXCEPTION_DEBUG |
| |
| #define EXC_HANDLER(vec) NANO_CPU_EXC_CONNECT_NO_ERR(handle_exc_##vec, vec, 0) |
| #define EXC_HANDLER_CODE(vec) NANO_CPU_EXC_CONNECT(handle_exc_##vec, vec, 0) |
| |
| EXC_HANDLER(IV_DIVIDE_ERROR) |
| EXC_HANDLER(IV_NON_MASKABLE_INTERRUPT) |
| EXC_HANDLER(IV_OVERFLOW) |
| EXC_HANDLER(IV_BOUND_RANGE) |
| EXC_HANDLER(IV_INVALID_OPCODE) |
| #ifndef CONFIG_FP_SHARING |
| EXC_HANDLER(IV_DEVICE_NOT_AVAILABLE) |
| #endif |
| EXC_HANDLER_CODE(IV_DOUBLE_FAULT) |
| EXC_HANDLER_CODE(IV_INVALID_TSS) |
| EXC_HANDLER_CODE(IV_SEGMENT_NOT_PRESENT) |
| EXC_HANDLER_CODE(IV_STACK_FAULT) |
| EXC_HANDLER_CODE(IV_GENERAL_PROTECTION) |
| EXC_HANDLER_CODE(IV_PAGE_FAULT) |
| EXC_HANDLER(IV_X87_FPU_FP_ERROR) |
| EXC_HANDLER_CODE(IV_ALIGNMENT_CHECK) |
| EXC_HANDLER(IV_MACHINE_CHECK) |
| #endif /* CONFIG_EXCEPTION_DEBUG */ |
| |
| #if ALL_DYN_EXC_STUBS > 0 |
| BRANCH_LABEL(_DynExcStubCommon) |
| call _common_dynamic_exc_handler |
| #ifndef CONFIG_X86_IAMCU |
| /* Cleanse the stack of stub_num */ |
| pop %eax |
| #endif |
| /* Clean up and call IRET */ |
| jmp _ExcExit |
| |
| /* |
| * Create nice labels for all the stubs so we can see where we |
| * are in a debugger |
| */ |
| .altmacro |
| .macro __EXC_STUB_NUM id |
| BRANCH_LABEL(_DynExcStub\id) |
| .endm |
| .macro EXC_STUB_NUM id |
| __EXC_STUB_NUM %id |
| .endm |
| |
| stub_num = 0 |
| |
| SECTION_FUNC(TEXT, _DynExcStubsBegin) |
| #if CONFIG_NUM_DYNAMIC_EXC_STUBS > 0 |
| |
| /* Create all the dynamic IRQ stubs |
| * |
| * NOTE: Please update DYN_STUB_SIZE in include/arch/x86/arch.h if you change |
| * how large the generated stubs are, otherwise _get_dynamic_stub() will |
| * be unable to correctly determine the offset |
| */ |
| .rept ((CONFIG_NUM_DYNAMIC_EXC_STUBS + DYN_STUB_PER_BLOCK - 1) / DYN_STUB_PER_BLOCK) |
| block_counter = 0 |
| .rept DYN_STUB_PER_BLOCK |
| .if stub_num < CONFIG_NUM_DYNAMIC_EXC_STUBS |
| EXC_STUB_NUM stub_num |
| /* |
| * TODO: make this call in _DynExcStubCommon, saving |
| * 5 bytes per stub. Some voodoo will be necessary |
| * in _ExcEnt/_ExcExit to transplant the pushed |
| * stub_num to the irq stack |
| */ |
| call _ExcEnt |
| |
| #if CONFIG_X86_IAMCU |
| movl $stub_num, %edx |
| #else |
| /* |
| * 2-byte push imm8. Consumed by |
| * _common_dynamic_exc_handler(), see excconnect.c |
| */ |
| push $stub_num |
| #endif |
| |
| /* |
| * Check to make sure this isn't the last stub in |
| * a block, in which case we just fall through |
| */ |
| .if (block_counter <> (DYN_STUB_PER_BLOCK - 1) && \ |
| (stub_num <> CONFIG_NUM_DYNAMIC_EXC_STUBS - 1)) |
| /* This should always be a 2-byte jmp rel8 */ |
| jmp 1f |
| .endif |
| stub_num = stub_num + 1 |
| block_counter = block_counter + 1 |
| .endif |
| .endr |
| /* |
| * This must a 5-bvte jump rel32, which is why _DynStubCommon |
| * is before the actual stubs |
| */ |
| 1: jmp _DynExcStubCommon |
| .endr |
| #endif |
| |
| SECTION_FUNC(TEXT, _DynExcStubsNoErrBegin) |
| #if CONFIG_NUM_DYNAMIC_EXC_NOERR_STUBS > 0 |
| |
| /* Same as above, but these stubs push a dummy error code as they will be |
| * associated with exception that don't push one of their own. |
| * Note that we don't reset stub_num to 0, we have a single set of indices |
| * for error/non-error stubs */ |
| .rept ((CONFIG_NUM_DYNAMIC_EXC_NOERR_STUBS + DYN_STUB_PER_BLOCK - 1) / DYN_STUB_PER_BLOCK) |
| block_counter = 0 |
| .rept DYN_STUB_PER_BLOCK |
| .if stub_num < ALL_DYN_EXC_STUBS |
| EXC_STUB_NUM stub_num |
| /* |
| * TODO: make this call in _DynExcStubCommon, saving |
| * 5 bytes per stub. Some voodoo will be necessary |
| * in _ExcEnt/_ExcExit to transplant the pushed |
| * stub_num to the irq stack |
| */ |
| call _ExcEntNoErr |
| #if CONFIG_X86_IAMCU |
| movl $stub_num, %edx |
| #else |
| /* |
| * 2-byte push imm8. Consumed by |
| * _common_dynamic_exc_handler(), see excconnect.c |
| */ |
| push $stub_num |
| #endif |
| |
| /* |
| * Check to make sure this isn't the last stub in |
| * a block, in which case we just fall through |
| */ |
| .if (block_counter <> (DYN_STUB_PER_BLOCK - 1) && \ |
| (stub_num <> ALL_DYN_EXC_STUBS - 1)) |
| /* This should always be a 2-byte jmp rel8 */ |
| jmp 1f |
| .endif |
| stub_num = stub_num + 1 |
| block_counter = block_counter + 1 |
| .endif |
| .endr |
| /* |
| * This must a 5-bvte jump rel32, which is why _DynStubCommon |
| * is before the actual stubs |
| */ |
| 1: jmp _DynExcStubCommon |
| .endr |
| |
| #endif /* CONFIG_NUM_DYNAMIC_EXC_NOERR_STUBS */ |
| #endif /* ALL_DYN_EXC_STUBS */ |