1/*
2 * FreeRTOS Kernel V11.1.0
3 * Copyright (C) 2015-2019 Cadence Design Systems, Inc.
4 * Copyright (C) 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
5 *
6 * SPDX-License-Identifier: MIT
7 *
8 * Permission is hereby granted, free of charge, to any person obtaining a copy of
9 * this software and associated documentation files (the "Software"), to deal in
10 * the Software without restriction, including without limitation the rights to
11 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
12 * the Software, and to permit persons to whom the Software is furnished to do so,
13 * subject to the following conditions:
14 *
15 * The above copyright notice and this permission notice shall be included in all
16 * copies or substantial portions of the Software.
17 *
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
20 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
21 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
22 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
23 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 *
25 * https://www.FreeRTOS.org
26 * https://github.com/FreeRTOS
27 *
28 */
29
30#include "xtensa_rtos.h"
31
32#define TOPOFSTACK_OFFS                 0x00    /* StackType_t *pxTopOfStack */
33#define CP_TOPOFSTACK_OFFS              0x04    /* xMPU_SETTINGS.coproc_area */
34
35.extern pxCurrentTCB
36
37
38/*
39*******************************************************************************
40* Interrupt stack. The size of the interrupt stack is determined by the config
41* parameter "configISR_STACK_SIZE" in FreeRTOSConfig.h
42*******************************************************************************
43*/
44    .data
45    .align      16
46    .global     port_IntStack
47port_IntStack:
48    .space      configISR_STACK_SIZE
49port_IntStackTop:
50    .word       0
51port_switch_flag:
52    .word       0
53
54    .text
55/*
56*******************************************************************************
57* _frxt_setup_switch
58* void _frxt_setup_switch(void);
59*
60* Sets an internal flag indicating that a task switch is required on return
61* from interrupt handling.
62*
63*******************************************************************************
64*/
65    .global     _frxt_setup_switch
66    .type       _frxt_setup_switch,@function
67    .align      4
68_frxt_setup_switch:
69
70    ENTRY(16)
71
72    movi    a2, port_switch_flag
73    movi    a3, 1
74    s32i    a3, a2, 0
75
76    RET(16)
77
78/*
79*******************************************************************************
80*                                            _frxt_int_enter
81*                                       void _frxt_int_enter(void)
82*
83* Implements the Xtensa RTOS porting layer's XT_RTOS_INT_ENTER function for
84* freeRTOS. Saves the rest of the interrupt context (not already saved).
85* May only be called from assembly code by the 'call0' instruction, with
86* interrupts disabled.
87* See the detailed description of the XT_RTOS_ENTER macro in xtensa_rtos.h.
88*
89*******************************************************************************
90*/
91    .globl  _frxt_int_enter
92    .type   _frxt_int_enter,@function
93    .align  4
94_frxt_int_enter:
95
96    /* Save a12-13 in the stack frame as required by _xt_context_save. */
97    s32i    a12, a1, XT_STK_A12
98    s32i    a13, a1, XT_STK_A13
99
100    /* Save return address in a safe place (free a0). */
101    mov     a12, a0
102
103    /* Save the rest of the interrupted context (preserves A12-13). */
104    call0   _xt_context_save
105
106    /*
107    Save interrupted task's SP in TCB only if not nesting.
108    Manage nesting directly rather than call the generic IntEnter()
109    (in windowed ABI we can't call a C function here anyway because PS.EXCM is still set).
110    */
111    movi    a2,  port_xSchedulerRunning
112    movi    a3,  port_interruptNesting
113    l32i    a2,  a2, 0                  /* a2 = port_xSchedulerRunning     */
114    beqz    a2,  1f                     /* scheduler not running, no tasks */
115    l32i    a2,  a3, 0                  /* a2 = port_interruptNesting      */
116    addi    a2,  a2, 1                  /* increment nesting count         */
117    s32i    a2,  a3, 0                  /* save nesting count              */
118    bnei    a2,  1, .Lnested            /* !=0 before incr, so nested      */
119
120    movi    a2,  pxCurrentTCB
121    l32i    a2,  a2, 0                  /* a2 = current TCB                */
122    beqz    a2,  1f
123    s32i    a1,  a2, TOPOFSTACK_OFFS    /* pxCurrentTCB->pxTopOfStack = SP */
124    movi    a1,  port_IntStackTop       /* a1 = top of intr stack          */
125
126.Lnested:
1271:
128    mov     a0,  a12                    /* restore return addr and return  */
129    ret
130
131/*
132*******************************************************************************
133*                                            _frxt_int_exit
134*                                       void _frxt_int_exit(void)
135*
136* Implements the Xtensa RTOS porting layer's XT_RTOS_INT_EXIT function for
137* FreeRTOS. If required, calls vPortYieldFromInt() to perform task context
138* switching, restore the (possibly) new task's context, and return to the
139* exit dispatcher saved in the task's stack frame at XT_STK_EXIT.
140* May only be called from assembly code by the 'call0' instruction. Does not
141* return to caller.
142* See the description of the XT_RTOS_ENTER macro in xtensa_rtos.h.
143*
144*******************************************************************************
145*/
146    .globl  _frxt_int_exit
147    .type   _frxt_int_exit,@function
148    .align  4
149_frxt_int_exit:
150
151    movi    a2,  port_xSchedulerRunning
152    movi    a3,  port_interruptNesting
153    rsil    a0,  XCHAL_EXCM_LEVEL       /* lock out interrupts             */
154    l32i    a2,  a2, 0                  /* a2 = port_xSchedulerRunning     */
155    beqz    a2,  .Lnoswitch             /* scheduler not running, no tasks */
156    l32i    a2,  a3, 0                  /* a2 = port_interruptNesting      */
157    addi    a2,  a2, -1                 /* decrement nesting count         */
158    s32i    a2,  a3, 0                  /* save nesting count              */
159    bnez    a2,  .Lnesting              /* !=0 after decr so still nested  */
160
161    movi    a2,  pxCurrentTCB
162    l32i    a2,  a2, 0                  /* a2 = current TCB                */
163    beqz    a2,  1f                     /* no task ? go to dispatcher      */
164    l32i    a1,  a2, TOPOFSTACK_OFFS    /* SP = pxCurrentTCB->pxTopOfStack */
165
166    movi    a2,  port_switch_flag       /* address of switch flag          */
167    l32i    a3,  a2, 0                  /* a3 = port_switch_flag           */
168    beqz    a3,  .Lnoswitch             /* flag = 0 means no switch reqd   */
169    movi    a3,  0
170    s32i    a3,  a2, 0                  /* zero out the flag for next time */
171
1721:
173    /*
174    Call0 ABI callee-saved regs a12-15 need to be saved before possible preemption.
175    However a12-13 were already saved by _frxt_int_enter().
176    */
177    #ifdef __XTENSA_CALL0_ABI__
178    s32i    a14, a1, XT_STK_A14
179    s32i    a15, a1, XT_STK_A15
180    #endif
181
182    #ifdef __XTENSA_CALL0_ABI__
183    call0   vPortYieldFromInt       /* call dispatch inside the function; never returns */
184    #else
185    call4   vPortYieldFromInt       /* this one returns */
186    call0   _frxt_dispatch          /* tail-call dispatcher */
187    /* Never returns here. */
188    #endif
189
190.Lnoswitch:
191    /*
192    If we came here then about to resume the interrupted task.
193    */
194
195.Lnesting:
196    /*
197    We come here only if there was no context switch, that is if this
198    is a nested interrupt, or the interrupted task was not preempted.
199    In either case there's no need to load the SP.
200    */
201
202    /* Restore full context from interrupt stack frame */
203    call0   _xt_context_restore
204
205    /*
206    Must return via the exit dispatcher corresponding to the entrypoint from which
207    this was called. Interruptee's A0, A1, PS, PC are restored and the interrupt
208    stack frame is deallocated in the exit dispatcher.
209    */
210    l32i    a0,  a1, XT_STK_EXIT
211    ret
212
213
214/*
215**********************************************************************************************************
216*                                           _frxt_timer_int
217*                                      void _frxt_timer_int(void)
218*
219* Implements the Xtensa RTOS porting layer's XT_RTOS_TIMER_INT function for FreeRTOS.
220* Called every timer interrupt.
221* Manages the tick timer and calls xPortSysTickHandler() every tick.
222* See the detailed description of the XT_RTOS_ENTER macro in xtensa_rtos.h.
223*
224* Callable from C (obeys ABI conventions). Implemented in assmebly code for performance.
225*
226**********************************************************************************************************
227*/
228    .globl  _frxt_timer_int
229    .type   _frxt_timer_int,@function
230    .align  4
231_frxt_timer_int:
232
233    /*
234    Xtensa timers work by comparing a cycle counter with a preset value.  Once the match occurs
235    an interrupt is generated, and the handler has to set a new cycle count into the comparator.
236    To avoid clock drift due to interrupt latency, the new cycle count is computed from the old,
237    not the time the interrupt was serviced. However if a timer interrupt is ever serviced more
238    than one tick late, it is necessary to process multiple ticks until the new cycle count is
239    in the future, otherwise the next timer interrupt would not occur until after the cycle
240    counter had wrapped (2^32 cycles later).
241
242    do {
243        ticks++;
244        old_ccompare = read_ccompare_i();
245        write_ccompare_i( old_ccompare + divisor );
246        service one tick;
247        diff = read_ccount() - old_ccompare;
248    } while ( diff > divisor );
249    */
250
251    ENTRY(16)
252
253.L_xt_timer_int_catchup:
254
255    /* Update the timer comparator for the next tick. */
256    #ifdef XT_CLOCK_FREQ
257    movi    a2, XT_TICK_DIVISOR         /* a2 = comparator increment          */
258    #else
259    movi    a3, _xt_tick_divisor
260    l32i    a2, a3, 0                   /* a2 = comparator increment          */
261    #endif
262    rsr     a3, XT_CCOMPARE             /* a3 = old comparator value          */
263    add     a4, a3, a2                  /* a4 = new comparator value          */
264    wsr     a4, XT_CCOMPARE             /* update comp. and clear interrupt   */
265    esync
266
267    #ifdef __XTENSA_CALL0_ABI__
268    /* Preserve a2 and a3 across C calls. */
269    s32i    a2, sp, 4
270    s32i    a3, sp, 8
271    #endif
272
273    /* Call the FreeRTOS tick handler (see port.c). */
274    #ifdef __XTENSA_CALL0_ABI__
275    call0   xPortSysTickHandler
276    #else
277    call4   xPortSysTickHandler
278    #endif
279
280    #ifdef __XTENSA_CALL0_ABI__
281    /* Restore a2 and a3. */
282    l32i    a2, sp, 4
283    l32i    a3, sp, 8
284    #endif
285
286    /* Check if we need to process more ticks to catch up. */
287    esync                               /* ensure comparator update complete  */
288    rsr     a4, CCOUNT                  /* a4 = cycle count                   */
289    sub     a4, a4, a3                  /* diff = ccount - old comparator     */
290    blt     a2, a4, .L_xt_timer_int_catchup  /* repeat while diff > divisor */
291
292    RET(16)
293
294    /*
295**********************************************************************************************************
296*                                           _frxt_tick_timer_init
297*                                      void _frxt_tick_timer_init(void)
298*
299* Initialize timer and timer interrrupt handler (_xt_tick_divisor_init() has already been been called).
300* Callable from C (obeys ABI conventions on entry).
301*
302**********************************************************************************************************
303*/
304    .globl  _frxt_tick_timer_init
305    .type   _frxt_tick_timer_init,@function
306    .align  4
307_frxt_tick_timer_init:
308
309    ENTRY(16)
310
311    /* Set up the periodic tick timer (assume enough time to complete init). */
312    #ifdef XT_CLOCK_FREQ
313    movi    a3, XT_TICK_DIVISOR
314    #else
315    movi    a2, _xt_tick_divisor
316    l32i    a3, a2, 0
317    #endif
318    rsr     a2, CCOUNT              /* current cycle count */
319    add     a2, a2, a3              /* time of first timer interrupt */
320    wsr     a2, XT_CCOMPARE         /* set the comparator */
321
322    /*
323    Enable the timer interrupt at the device level. Don't write directly
324    to the INTENABLE register because it may be virtualized.
325    */
326    #ifdef __XTENSA_CALL0_ABI__
327    movi    a2, XT_TIMER_INTEN
328    call0   xt_ints_on
329    #else
330    movi    a6, XT_TIMER_INTEN
331    call4   xt_ints_on
332    #endif
333
334    RET(16)
335
336/*
337**********************************************************************************************************
338*                                    DISPATCH THE HIGH READY TASK
339*                                     void _frxt_dispatch(void)
340*
341* Switch context to the highest priority ready task, restore its state and dispatch control to it.
342*
343* This is a common dispatcher that acts as a shared exit path for all the context switch functions
344* including vPortYield() and vPortYieldFromInt(), all of which tail-call this dispatcher
345* (for windowed ABI vPortYieldFromInt() calls it indirectly via _frxt_int_exit() ).
346*
347* The Xtensa port uses different stack frames for solicited and unsolicited task suspension (see
348* comments on stack frames in xtensa_context.h). This function restores the state accordingly.
349* If restoring a task that solicited entry, restores the minimal state and leaves CPENABLE clear.
350* If restoring a task that was preempted, restores all state including the task's CPENABLE.
351*
352* Entry:
353*   pxCurrentTCB  points to the TCB of the task to suspend,
354*   Because it is tail-called without a true function entrypoint, it needs no 'entry' instruction.
355*
356* Exit:
357*   If incoming task called vPortYield() (solicited), this function returns as if from vPortYield().
358*   If incoming task was preempted by an interrupt, this function jumps to exit dispatcher.
359*
360**********************************************************************************************************
361*/
362    .globl  _frxt_dispatch
363    .type   _frxt_dispatch,@function
364    .align  4
365_frxt_dispatch:
366
367    #ifdef __XTENSA_CALL0_ABI__
368    call0   vTaskSwitchContext  // Get next TCB to resume
369    movi    a2, pxCurrentTCB
370    #else
371    movi    a2, pxCurrentTCB
372    call4   vTaskSwitchContext  // Get next TCB to resume
373    #endif
374    l32i    a3,  a2, 0
375    l32i    sp,  a3, TOPOFSTACK_OFFS     /* SP = next_TCB->pxTopOfStack;  */
376    s32i    a3,  a2, 0
377
378    /* Determine the type of stack frame. */
379    l32i    a2,  sp, XT_STK_EXIT        /* exit dispatcher or solicited flag */
380    bnez    a2,  .L_frxt_dispatch_stk
381
382.L_frxt_dispatch_sol:
383
384    /* Solicited stack frame. Restore minimal context and return from vPortYield(). */
385    l32i    a3,  sp, XT_SOL_PS
386    #ifdef __XTENSA_CALL0_ABI__
387    l32i    a12, sp, XT_SOL_A12
388    l32i    a13, sp, XT_SOL_A13
389    l32i    a14, sp, XT_SOL_A14
390    l32i    a15, sp, XT_SOL_A15
391    #endif
392    l32i    a0,  sp, XT_SOL_PC
393    #if XCHAL_CP_NUM > 0
394    /* Ensure wsr.CPENABLE is complete (should be, it was cleared on entry). */
395    rsync
396    #endif
397    /* As soons as PS is restored, interrupts can happen. No need to sync PS. */
398    wsr     a3,  PS
399    #ifdef __XTENSA_CALL0_ABI__
400    addi    sp,  sp, XT_SOL_FRMSZ
401    ret
402    #else
403    retw
404    #endif
405
406.L_frxt_dispatch_stk:
407
408    #if XCHAL_CP_NUM > 0
409    /* Restore CPENABLE from task's co-processor save area. */
410    movi    a3, pxCurrentTCB            /* cp_state =                       */
411    l32i    a3, a3, 0
412    l32i    a2, a3, CP_TOPOFSTACK_OFFS     /* StackType_t                       *pxStack; */
413    l16ui    a3, a2, XT_CPENABLE         /* CPENABLE = cp_state->cpenable;   */
414    wsr     a3, CPENABLE
415    #endif
416
417    /* Interrupt stack frame. Restore full context and return to exit dispatcher. */
418    call0   _xt_context_restore
419
420    /* In Call0 ABI, restore callee-saved regs (A12, A13 already restored). */
421    #ifdef __XTENSA_CALL0_ABI__
422    l32i    a14, sp, XT_STK_A14
423    l32i    a15, sp, XT_STK_A15
424    #endif
425
426    #if XCHAL_CP_NUM > 0
427    /* Ensure wsr.CPENABLE has completed. */
428    rsync
429    #endif
430
431    /*
432    Must return via the exit dispatcher corresponding to the entrypoint from which
433    this was called. Interruptee's A0, A1, PS, PC are restored and the interrupt
434    stack frame is deallocated in the exit dispatcher.
435    */
436    l32i    a0, sp, XT_STK_EXIT
437    ret
438
439
440/*
441**********************************************************************************************************
442*                            PERFORM A SOLICTED CONTEXT SWITCH (from a task)
443*                                        void vPortYield(void)
444*
445* This function saves the minimal state needed for a solicited task suspension, clears CPENABLE,
446* then tail-calls the dispatcher _frxt_dispatch() to perform the actual context switch
447*
448* At Entry:
449*   pxCurrentTCB  points to the TCB of the task to suspend
450*   Callable from C (obeys ABI conventions on entry).
451*
452* Does not return to caller.
453*
454**********************************************************************************************************
455*/
456    .globl  vPortYield
457    .type   vPortYield,@function
458    .align  4
459vPortYield:
460
461    #ifdef __XTENSA_CALL0_ABI__
462    addi    sp,  sp, -XT_SOL_FRMSZ
463    #else
464    entry   sp,  XT_SOL_FRMSZ
465    #endif
466
467    rsr     a2,  PS
468    s32i    a0,  sp, XT_SOL_PC
469    s32i    a2,  sp, XT_SOL_PS
470    #ifdef __XTENSA_CALL0_ABI__
471    s32i    a12, sp, XT_SOL_A12         /* save callee-saved registers      */
472    s32i    a13, sp, XT_SOL_A13
473    s32i    a14, sp, XT_SOL_A14
474    s32i    a15, sp, XT_SOL_A15
475    #else
476    /* Spill register windows. Calling xthal_window_spill() causes extra    */
477    /* spills and reloads, so we will set things up to call the _nw version */
478    /* instead to save cycles.                                              */
479    movi    a6,  ~(PS_WOE_MASK|PS_INTLEVEL_MASK)  /* spills a4-a7 if needed */
480    and     a2,  a2, a6                           /* clear WOE, INTLEVEL    */
481    addi    a2,  a2, XCHAL_EXCM_LEVEL             /* set INTLEVEL           */
482    wsr     a2,  PS
483    rsync
484    call0   xthal_window_spill_nw
485    l32i    a2,  sp, XT_SOL_PS                    /* restore PS             */
486    wsr     a2,  PS
487    #endif
488
489    rsil    a2,  XCHAL_EXCM_LEVEL       /* disable low/med interrupts       */
490
491    #if XCHAL_CP_NUM > 0
492    /* Save coprocessor callee-saved state (if any). At this point CPENABLE */
493    /* should still reflect which CPs were in use (enabled).                */
494    call0   _xt_coproc_savecs
495    #endif
496
497    movi    a2,  pxCurrentTCB
498    movi    a3,  0
499    l32i    a2,  a2, 0                  /* a2 = pxCurrentTCB                */
500    s32i    a3,  sp, XT_SOL_EXIT        /* 0 to flag as solicited frame     */
501    s32i    sp,  a2, TOPOFSTACK_OFFS    /* pxCurrentTCB->pxTopOfStack = SP  */
502
503    #if XCHAL_CP_NUM > 0
504    /* Clear CPENABLE, also in task's co-processor state save area. */
505    l32i    a2,  a2, CP_TOPOFSTACK_OFFS /* a2 = pxCurrentTCB->cp_state      */
506    movi    a3,  0
507    wsr     a3,  CPENABLE
508    beqz    a2,  1f
509    s16i     a3,  a2, XT_CPENABLE        /* clear saved cpenable             */
5101:
511    #endif
512
513    /* Tail-call dispatcher. */
514    call0   _frxt_dispatch
515    /* Never reaches here. */
516
517
518/*
519**********************************************************************************************************
520*                         PERFORM AN UNSOLICITED CONTEXT SWITCH (from an interrupt)
521*                                        void vPortYieldFromInt(void)
522*
523* This calls the context switch hook (removed), saves and clears CPENABLE, then tail-calls the dispatcher
524* _frxt_dispatch() to perform the actual context switch.
525*
526* At Entry:
527*   Interrupted task context has been saved in an interrupt stack frame at pxCurrentTCB->pxTopOfStack.
528*   pxCurrentTCB  points to the TCB of the task to suspend,
529*   Callable from C (obeys ABI conventions on entry).
530*
531* At Exit:
532*   Windowed ABI defers the actual context switch until the stack is unwound to interrupt entry.
533*   Call0 ABI tail-calls the dispatcher directly (no need to unwind) so does not return to caller.
534*
535**********************************************************************************************************
536*/
537    .globl  vPortYieldFromInt
538    .type   vPortYieldFromInt,@function
539    .align  4
540vPortYieldFromInt:
541
542    ENTRY(16)
543
544    #if XCHAL_CP_NUM > 0
545    /* Save CPENABLE in task's co-processor save area, and clear CPENABLE.  */
546    movi    a3, pxCurrentTCB            /* cp_state =                       */
547    l32i    a3, a3, 0
548    l32i    a2, a3, CP_TOPOFSTACK_OFFS
549
550    rsr     a3, CPENABLE
551    s16i     a3, a2, XT_CPENABLE         /* cp_state->cpenable = CPENABLE;   */
552    movi    a3, 0
553    wsr     a3, CPENABLE                /* disable all co-processors        */
554    #endif
555
556    #ifdef __XTENSA_CALL0_ABI__
557    /* Tail-call dispatcher. */
558    call0   _frxt_dispatch
559    /* Never reaches here. */
560    #else
561    RET(16)
562    #endif
563
564/*
565**********************************************************************************************************
566*                                        _frxt_task_coproc_state
567*                                   void _frxt_task_coproc_state(void)
568*
569* Implements the Xtensa RTOS porting layer's XT_RTOS_CP_STATE function for FreeRTOS.
570*
571* May only be called when a task is running, not within an interrupt handler (returns 0 in that case).
572* May only be called from assembly code by the 'call0' instruction. Does NOT obey ABI conventions.
573* Returns in A15 a pointer to the base of the co-processor state save area for the current task.
574* See the detailed description of the XT_RTOS_ENTER macro in xtensa_rtos.h.
575*
576**********************************************************************************************************
577*/
578#if XCHAL_CP_NUM > 0
579
580    .globl  _frxt_task_coproc_state
581    .type   _frxt_task_coproc_state,@function
582    .align  4
583_frxt_task_coproc_state:
584
585    movi    a15, port_xSchedulerRunning /* if (port_xSchedulerRunning              */
586    l32i    a15, a15, 0
587    beqz    a15, 1f
588    movi    a15, port_interruptNesting  /* && port_interruptNesting == 0           */
589    l32i    a15, a15, 0
590    bnez    a15, 1f
591    movi    a15, pxCurrentTCB
592    l32i    a15, a15, 0                 /* && pxCurrentTCB != 0) {                 */
593    beqz    a15, 2f
594    l32i    a15, a15, CP_TOPOFSTACK_OFFS
595    ret
596
5971:  movi    a15, 0
5982:  ret
599
600#endif /* XCHAL_CP_NUM > 0 */
601