/* * Copyright (c) 2016 Cadence Design Systems, Inc. * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include .extern _interrupt_stack .extern _kernel #ifdef CONFIG_SYS_CLOCK_EXISTS .extern _timer_int_handler #endif .set _interrupt_stack_top, _interrupt_stack + CONFIG_ISR_STACK_SIZE /* * _zxt_dispatch(_kernel_t *_kernel, _thread_t *_thread) * At this point, the a2 register contains the '&_kernel' and the * thread to be swapped in (&_thread) is in a3. */ .text .globl _zxt_dispatch .type _zxt_dispatch,@function .align 4 _zxt_dispatch: /* Updated current thread: _kernel.current := _kernel.ready_q.cache */ s32i a3, a2, KERNEL_OFFSET(current) /* _kernel.current := _thread */ l32i sp, a3, THREAD_OFFSET(sp) /* sp := _thread->topOfStack; */ /* Determine the type of stack frame. */ l32i a2, sp, XT_STK_exit /* exit dispatcher or solicited flag */ bnez a2, .L_frxt_dispatch_stk .L_frxt_dispatch_sol: /* Solicited stack frame. Restore retval from _Swap */ l32i a2, a3, THREAD_OFFSET(retval) l32i a3, sp, XT_SOL_ps #ifdef __XTENSA_CALL0_ABI__ l32i a12, sp, XT_SOL_a12 l32i a13, sp, XT_SOL_a13 l32i a14, sp, XT_SOL_a14 l32i a15, sp, XT_SOL_a15 #endif l32i a0, sp, XT_SOL_pc #if XCHAL_CP_NUM > 0 /* Ensure wsr.CPENABLE is complete (should be, it was cleared on * entry). */ rsync #endif /* As soons as PS is restored, interrupts can happen. No need to sync * PS. */ wsr a3, PS #ifdef __XTENSA_CALL0_ABI__ addi sp, sp, XT_SOL_FRMSZ ret #else retw #endif .L_frxt_dispatch_stk: #if XCHAL_CP_NUM > 0 /* Restore CPENABLE from task's co-processor save area. */ l16ui a3, a3, THREAD_OFFSET(cpEnable) /* a3 := cp_state->cpenable */ wsr a3, CPENABLE #endif #ifdef CONFIG_STACK_SENTINEL #ifdef __XTENSA_CALL0_ABI__ call0 _check_stack_sentinel #else call4 _check_stack_sentinel #endif #endif /* * Interrupt stack frame. * Restore full context and return to exit dispatcher. */ call0 _xt_context_restore /* In Call0 ABI, restore callee-saved regs (A12, A13 already * restored). */ #ifdef __XTENSA_CALL0_ABI__ l32i a14, sp, XT_STK_a14 l32i a15, sp, XT_STK_a15 #endif #if XCHAL_CP_NUM > 0 /* Ensure wsr.CPENABLE has completed. */ rsync #endif /* * Must return via the exit dispatcher corresponding to the entrypoint * from which this was called. Interruptee's A0, A1, PS, PC are * restored and the interrupt stack frame is deallocated in the exit * dispatcher. */ l32i a0, sp, XT_STK_exit ret /* * _zxt_int_enter * void _zxt_int_enter(void) * * Implements the Xtensa RTOS porting layer's XT_RTOS_INT_ENTER function for * freeRTOS. Saves the rest of the interrupt context (not already saved). * May only be called from assembly code by the 'call0' instruction, with * interrupts disabled. * See the detailed description of the XT_RTOS_ENTER macro in xtensa_rtos.h. */ .globl _zxt_int_enter .type _zxt_int_enter,@function .align 4 _zxt_int_enter: /* Save a12-13 in the stack frame as required by _xt_context_save. */ s32i a12, a1, XT_STK_a12 s32i a13, a1, XT_STK_a13 /* Save return address in a safe place (free a0). */ mov a12, a0 /* Save the rest of the interrupted context (preserves A12-13). */ call0 _xt_context_save /* * Save interrupted task's SP in TCB only if not nesting. Manage * nesting directly rather than call the generic IntEnter() (in * windowed ABI we can't call a C function here anyway because PS.EXCM * is still set). */ movi a2, _kernel /* a2 := _kernel */ l32i a3, a2, KERNEL_OFFSET(nested) /* a3 := _kernel->nested */ addi a3, a3, 1 /* increment nesting count */ s32i a3, a2, KERNEL_OFFSET(nested) /* save nesting count */ bnei a3, 1, .Lnested /* !=0 before incr, so nested */ l32i a3, a2, KERNEL_OFFSET(current)/* a3 := _kernel->current */ s32i a1, a3, THREAD_OFFSET(sp) /* save SP to Current top of stack */ movi a1, _interrupt_stack_top /* a1 = top of intr stack */ .Lnested: 1: mov a0, a12 /* restore return addr and return */ ret /* * _zxt_int_exit * void _zxt_int_exit(void) * * Implements the Xtensa RTOS porting layer's XT_RTOS_INT_EXIT function for * Zephyr. If required, calls vPortYieldFromInt() to perform task context * switching, restore the (possibly) new task's context, and return to the exit * dispatcher saved in the task's stack frame at XT_STK_EXIT. May only be * called from assembly code by the 'call0' instruction. Does not return to * caller. See the description of the XT_RTOS_ENTER macro in xtensa_rtos.h. */ .globl _zxt_int_exit .type _zxt_int_exit,@function .align 4 _zxt_int_exit: rsil a0, XCHAL_EXCM_LEVEL /* lock out interrupts */ movi a2, _kernel l32i a3, a2, KERNEL_OFFSET(nested) /* _kernel->nested */ addi a3, a3, -1 /* decrement nesting count */ s32i a3, a2, KERNEL_OFFSET(nested) /* save nesting count */ bnez a3, .Lnesting /* !=0 after decr so still nested */ /* * When using call0 ABI callee-saved registers a12-15 need to be saved * before enabling preemption. They were already saved by * _zxt_int_enter(). */ #ifdef __XTENSA_CALL0_ABI__ s32i a14, a1, XT_STK_a14 s32i a15, a1, XT_STK_a15 #endif #if XCHAL_CP_NUM > 0 l32i a3, a2, KERNEL_OFFSET(current) /* _thread := _kernel->current */ rsr a5, CPENABLE s16i a5, a3, THREAD_OFFSET(cpEnable) /* cp_state->cpenable = CPENABLE */ movi a3, 0 wsr a3, CPENABLE /* disable all co-processors */ #endif l32i a3, a2, KERNEL_OFFSET(current) /* _thread := _kernel.current */ /* * Non-preemptible thread ? Do not schedule (see explanation of * preempt field in kernel_struct.h). */ movi a4, _NON_PREEMPT_THRESHOLD l16ui a5, a3, THREAD_OFFSET(preempt) bgeu a5, a4, .noReschedule /* _thread := _kernel.ready_q.cache */ l32i a3, a2, KERNEL_OFFSET(ready_q_cache) .noReschedule: /* * Swap threads if any is to be swapped in. */ call0 _zxt_dispatch /* (_kernel@a2, _thread@a3) */ /* Never returns here. */ .Lnesting: /* * We come here only if there was no context switch, that is if this * is a nested interrupt, or the interrupted task was not preempted. * In either case there's no need to load the SP. */ /* Restore full context from interrupt stack frame */ call0 _xt_context_restore /* * Must return via the exit dispatcher corresponding to the entrypoint * from which this was called. Interruptee's A0, A1, PS, PC are * restored and the interrupt stack frame is deallocated in the exit * dispatcher. */ l32i a0, sp, XT_STK_exit ret /* * _zxt_timer_int * void _zxt_timer_int(void) * * Implements Xtensa RTOS porting layer's XT_RTOS_TIMER_INT function. Called * every timer interrupt. Manages the tick timer and calls * xPortSysTickHandler() every tick. See the detailed description of the * XT_RTOS_ENTER macro in xtensa_rtos.h. Callable from C. Implemented in * assmebly code for performance. * */ .globl _zxt_timer_int .type _zxt_timer_int,@function .align 4 _zxt_timer_int: /* * Xtensa timers work by comparing a cycle counter with a preset value. * Once the match occurs an interrupt is generated, and the handler has * to set a new cycle count into the comparator. To avoid clock drift * due to interrupt latency, the new cycle count is computed from the * old, not the time the interrupt was serviced. However if a timer * interrupt is ever serviced more than one tick late, it is necessary * to process multiple ticks until the new cycle count is in the * future, otherwise the next timer interrupt would not occur until * after the cycle counter had wrapped (2^32 cycles later). * * do { * ticks++; * old_ccompare = read_ccompare_i(); * write_ccompare_i( old_ccompare + divisor ); * service one tick; * diff = read_ccount() - old_ccompare; * } while ( diff > divisor ); */ ENTRY(16) .L_xt_timer_int_catchup: #ifdef CONFIG_SYS_CLOCK_EXISTS #if CONFIG_XTENSA_INTERNAL_TIMER || (CONFIG_XTENSA_TIMER_IRQ < 0) /* Update the timer comparator for the next tick. */ #ifdef XT_CLOCK_FREQ movi a2, XT_TICK_DIVISOR /* a2 = comparator increment */ #else movi a3, _xt_tick_divisor l32i a2, a3, 0 /* a2 = comparator increment */ #endif rsr a3, XT_CCOMPARE /* a3 = old comparator value */ add a4, a3, a2 /* a4 = new comparator value */ wsr a4, XT_CCOMPARE /* update comp. and clear interrupt */ esync #endif /* CONFIG_XTENSA_INTERNAL_TIMER || (CONFIG_XTENSA_TIMER_IRQ < 0) */ #ifdef __XTENSA_CALL0_ABI__ /* Preserve a2 and a3 across C calls. */ s32i a2, sp, 4 s32i a3, sp, 8 /* TODO: movi a2, _xt_interrupt_table */ movi a3, _timer_int_handler /* TODO: l32i a2, a2, 0 */ callx0 a3 /* Restore a2 and a3. */ l32i a2, sp, 4 l32i a3, sp, 8 #else /* TODO: movi a6, _xt_interrupt_table */ movi a7, _timer_int_handler /* TODO: l32i a6, a6, 0 */ callx4 a7 #endif #if CONFIG_XTENSA_INTERNAL_TIMER || (CONFIG_XTENSA_TIMER_IRQ < 0) /* Check if we need to process more ticks to catch up. */ esync /* ensure comparator update complete */ rsr a4, CCOUNT /* a4 = cycle count */ sub a4, a4, a3 /* diff = ccount - old comparator */ blt a2, a4, .L_xt_timer_int_catchup /* repeat while diff > divisor */ #endif /* CONFIG_XTENSA_INTERNAL_TIMER || (CONFIG_XTENSA_TIMER_IRQ < 0) */ #endif RET(16) /* * _zxt_tick_timer_init * void _zxt_tick_timer_init(void) * * Initialize timer and timer interrupt handler (_xt_tick_divisor_init() has * already been been called). * Callable from C (obeys ABI conventions on entry). * */ .globl _zxt_tick_timer_init .type _zxt_tick_timer_init,@function .align 4 _zxt_tick_timer_init: ENTRY(48) #ifdef CONFIG_SYS_CLOCK_EXISTS #if CONFIG_XTENSA_INTERNAL_TIMER || (CONFIG_XTENSA_TIMER_IRQ < 0) /* Set up the periodic tick timer (assume enough time to complete * init). */ #ifdef XT_CLOCK_FREQ movi a3, XT_TICK_DIVISOR #else movi a2, _xt_tick_divisor l32i a3, a2, 0 #endif rsr a2, CCOUNT /* current cycle count */ add a2, a2, a3 /* time of first timer interrupt */ wsr a2, XT_CCOMPARE /* set the comparator */ /* Enable the timer interrupt at the device level. Don't write directly to the INTENABLE register because it may be virtualized. */ #ifdef __XTENSA_CALL0_ABI__ movi a2, XT_TIMER_INTEN call0 _xt_ints_on #else movi a6, XT_TIMER_INTEN call4 _xt_ints_on #endif #endif #endif /* CONFIG_XTENSA_INTERNAL_TIMER || (CONFIG_XTENSA_TIMER_IRQ < 0) */ RET(48) /* * _zxt_task_coproc_state * void _zxt_task_coproc_state(void) * * Implements the Xtensa RTOS porting layer's XT_RTOS_CP_STATE function. * * May only be called when a task is running, not within an interrupt handler * (returns 0 in that case). * May only be called from assembly code by the 'call0' instruction. * Does NOT obey ABI conventions. * Returns in A15 a pointer to the base of the co-processor state save area * for the current task. * See the detailed description of the XT_RTOS_ENTER macro in xtensa_rtos.h. * */ #if XCHAL_CP_NUM > 0 .globl _zxt_task_coproc_state .type _zxt_task_coproc_state,@function .align 4 _zxt_task_coproc_state: movi a2, _kernel l32i a15, a2, KERNEL_OFFSET(nested) bnez a15, 1f l32i a2, a2, KERNEL_OFFSET(current) beqz a2, 1f addi a15, a2, THREAD_OFFSET(cpStack) ret 1: movi a15, 0 2: ret #endif /* XCHAL_CP_NUM > 0 */