| /* |
| * Copyright (c) 2016 Cadence Design Systems, Inc. |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <xtensa_context.h> |
| #include <xtensa_timer.h> |
| #include <offsets_short.h> |
| #include <kernel_structs.h> |
| |
| .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 */ |
| /* _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 USE_INTERNAL_TIMER || (EXTERNAL_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 /* USE_INTERNAL_TIMER || (EXTERNAL_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 USE_INTERNAL_TIMER || (EXTERNAL_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 /* USE_INTERNAL_TIMER || (EXTERNAL_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 USE_INTERNAL_TIMER || (EXTERNAL_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 /* USE_INTERNAL_TIMER || (EXTERNAL_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 */ |
| |