1/*
2 * Copyright (c) 2014 Wind River Systems, Inc.
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7/**
8 * @file
9 * @brief Handling of transitions to-and-from fast IRQs (FIRQ)
10 *
11 * This module implements the code for handling entry to and exit from Fast IRQs.
12 *
13 * See isr_wrapper.S for details.
14 */
15
16#include <zephyr/kernel_structs.h>
17#include <offsets_short.h>
18#include <zephyr/toolchain.h>
19#include <zephyr/linker/sections.h>
20#include <zephyr/arch/cpu.h>
21#include <swap_macros.h>
22
23GTEXT(_firq_enter)
24GTEXT(_firq_exit)
25
26/**
27 * @brief Work to be done before handing control to a FIRQ ISR
28 *
29 * The processor switches to a second register bank so registers from the
30 * current bank do not have to be preserved yet. The only issue is the LP_START/
31 * LP_COUNT/LP_END registers, which are not banked. These can be saved
32 * in available callee saved registers.
33 *
34 * If all FIRQ ISRs are programmed such that there are no use of the LP
35 * registers (ie. no LPcc instruction), and CONFIG_ARC_STACK_CHECKING is
36 * not set, then the kernel can be configured to not save and restore them.
37 *
38 * When entering a FIRQ, interrupts might as well be locked: the processor is
39 * running at its highest priority, and cannot be interrupted by any other
40 * interrupt. An exception, however, can be taken.
41 *
42 * Assumption by _isr_demux: r3 is untouched by _firq_enter.
43 */
44
45SECTION_FUNC(TEXT, _firq_enter)
46/*
47 * ATTENTION:
48 * If CONFIG_RGF_NUM_BANKS>1, firq uses a 2nd register bank so GPRs do
49 * not need to be saved.
50 * If CONFIG_RGF_NUM_BANKS==1, firq must use the stack to save registers.
51 * This has already been done by _isr_wrapper.
52 */
53#ifdef CONFIG_ARC_STACK_CHECKING
54#ifdef CONFIG_ARC_SECURE_FIRMWARE
55	lr r2, [_ARC_V2_SEC_STAT]
56	bclr r2, r2, _ARC_V2_SEC_STAT_SSC_BIT
57	sflag r2
58#else
59	/* disable stack checking */
60	lr r2, [_ARC_V2_STATUS32]
61	bclr r2, r2, _ARC_V2_STATUS32_SC_BIT
62	kflag r2
63#endif
64#endif
65
66#if CONFIG_RGF_NUM_BANKS != 1
67	/*
68	 * Save LP_START/LP_COUNT/LP_END because called handler might use.
69	 * Save these in callee saved registers to avoid using memory.
70	 * These will be saved by the compiler if it needs to spill them.
71	 */
72	mov r23,lp_count
73	lr r24, [_ARC_V2_LP_START]
74	lr r25, [_ARC_V2_LP_END]
75#endif
76
77	/* check whether irq stack is used */
78	_check_and_inc_int_nest_counter r0, r1
79
80	bne.d firq_nest
81	mov_s r0, sp
82
83	_get_curr_cpu_irq_stack sp
84#if CONFIG_RGF_NUM_BANKS != 1
85	b firq_nest_1
86firq_nest:
87	/*
88	 * because firq and rirq share the same interrupt stack,
89	 * switch back to original register bank to get correct sp.
90	 * to get better firq latency, an approach is to prepare
91	 * separate interrupt stack for firq and do not do thread
92	 * switch in firq.
93	 */
94	lr r1, [_ARC_V2_STATUS32]
95	and r1, r1, ~_ARC_V2_STATUS32_RB(7)
96	kflag r1
97
98	/* here use _ARC_V2_USER_SP and ilink to exchange sp
99	 * save original value of _ARC_V2_USER_SP and ilink into
100	 * the stack of interrupted context first, then restore them later
101	 */
102	push ilink
103	PUSHAX ilink, _ARC_V2_USER_SP
104
105	/* sp here is the sp of interrupted context */
106	sr sp, [_ARC_V2_USER_SP]
107	/* here, bank 0 sp must go back to the value before push and
108	 * PUSHAX as we will switch to bank1, the pop and POPAX later will
109	 * change bank1's sp, not bank0's sp
110	 */
111	add sp, sp, 8
112
113	/* switch back to banked reg, only ilink can be used */
114	lr ilink, [_ARC_V2_STATUS32]
115	or ilink, ilink, _ARC_V2_STATUS32_RB(1)
116	kflag ilink
117	lr sp, [_ARC_V2_USER_SP]
118
119	POPAX ilink, _ARC_V2_USER_SP
120	pop ilink
121firq_nest_1:
122#else
123firq_nest:
124#endif
125	push_s r0
126	j _isr_demux
127
128
129
130/**
131 * @brief Work to be done exiting a FIRQ
132 */
133
134SECTION_FUNC(TEXT, _firq_exit)
135
136#if CONFIG_RGF_NUM_BANKS != 1
137	/* restore lp_count, lp_start, lp_end from r23-r25 */
138	mov lp_count,r23
139	sr r24, [_ARC_V2_LP_START]
140	sr r25, [_ARC_V2_LP_END]
141#endif
142	_dec_int_nest_counter r0, r1
143
144	_check_nest_int_by_irq_act r0, r1
145
146	jne _firq_no_switch
147
148	/* sp is struct k_thread **old of z_arc_switch_in_isr which is a wrapper of
149	 * z_get_next_switch_handle. r0 contains the 1st thread in ready queue. If it isn't NULL,
150	 * then do switch to this thread.
151	 */
152	_get_next_switch_handle
153
154	CMPR r0, 0
155	bne _firq_switch
156
157	/* fall to no switch */
158
159.align 4
160_firq_no_switch:
161	/* restore interrupted context' sp */
162	pop sp
163	/*
164	 * Keeping this code block close to those that use it allows using brxx
165	 * instruction instead of a pair of cmp and bxx
166	 */
167#if CONFIG_RGF_NUM_BANKS == 1
168	_pop_irq_stack_frame
169#endif
170	rtie
171
172.align 4
173_firq_switch:
174	/* restore interrupted context' sp */
175	pop sp
176
177#if CONFIG_RGF_NUM_BANKS != 1
178/*
179 * save r0, r2 in irq stack for a while, as they will be changed by register
180 * bank switch
181 */
182	_get_curr_cpu_irq_stack r1
183	st r0, [r1, -4]
184	st r2, [r1, -8]
185
186	/*
187	 * We know there is no interrupted interrupt of lower priority at this
188	 * point, so when switching back to register bank 0, it will contain the
189	 * registers from the interrupted thread.
190	 */
191#if defined(CONFIG_USERSPACE)
192/* when USERSPACE is configured, here need to consider the case where firq comes
193 * out in user mode, according to ARCv2 ISA and nsim, the following micro ops
194 * will be executed:
195 *	sp<-reg bank1'sp
196 *      switch between sp and _ARC_V2_USER_SP
197 * then:
198 *      sp is the sp of kernel stack of interrupted thread
199 *      _ARC_V2_USER_SP is reg bank1'sp
200 *      the sp of user stack of interrupted thread is reg bank0'sp
201 * if firq comes out in kernel mode, the following micro ops will be executed:
202 *      sp<-reg bank'sp
203 * so, sw needs to do necessary handling to set up the correct sp
204 */
205	lr r0, [_ARC_V2_AUX_IRQ_ACT]
206	bbit0 r0, 31, _firq_from_kernel
207	aex sp, [_ARC_V2_USER_SP]
208	lr r0, [_ARC_V2_STATUS32]
209	and r0, r0, ~_ARC_V2_STATUS32_RB(7)
210	kflag r0
211	aex sp, [_ARC_V2_USER_SP]
212	b _firq_create_irq_stack_frame
213_firq_from_kernel:
214#endif
215	/* chose register bank #0 */
216	lr r0, [_ARC_V2_STATUS32]
217	and r0, r0, ~_ARC_V2_STATUS32_RB(7)
218	kflag r0
219
220_firq_create_irq_stack_frame:
221	/* we're back on the outgoing thread's stack */
222	_create_irq_stack_frame
223
224	/*
225	 * In a FIRQ, STATUS32 of the outgoing thread is in STATUS32_P0 and the
226	 * PC in ILINK: save them in status32/pc respectively.
227	 */
228
229	lr r0, [_ARC_V2_STATUS32_P0]
230	st_s r0, [sp, ___isf_t_status32_OFFSET]
231
232	st ilink, [sp, ___isf_t_pc_OFFSET] /* ilink into pc */
233/*
234 * load r0, r2 from irq stack
235 */
236	_get_curr_cpu_irq_stack r1
237	ld r0, [r1, -4]
238	ld r2, [r1, -8]
239#endif
240	/* r2 is old thread */
241	st _CAUSE_FIRQ, [r2, _thread_offset_to_relinquish_cause]
242
243	_irq_store_old_thread_callee_regs
244
245	/* mov new thread (r0) to r2 */
246
247	mov r2, r0
248	_load_new_thread_callee_regs
249
250	breq r3, _CAUSE_RIRQ, _firq_switch_from_rirq
251	nop_s
252	breq r3, _CAUSE_FIRQ, _firq_switch_from_firq
253	nop_s
254
255	/* fall through */
256
257.align 4
258_firq_switch_from_coop:
259
260	_set_misc_regs_irq_switch_from_coop
261
262	/* pc into ilink */
263	pop_s r0
264	mov ilink, r0
265
266	pop_s r0 /* status32 into r0 */
267	sr r0, [_ARC_V2_STATUS32_P0]
268
269#ifdef CONFIG_INSTRUMENT_THREAD_SWITCHING
270	push_s blink
271
272	bl z_thread_mark_switched_in
273
274	pop_s blink
275#endif
276	rtie
277
278.align 4
279_firq_switch_from_rirq:
280_firq_switch_from_firq:
281
282	_set_misc_regs_irq_switch_from_irq
283
284	_pop_irq_stack_frame
285
286	ld ilink, [sp, -4] /* status32 into ilink */
287	sr ilink, [_ARC_V2_STATUS32_P0]
288	ld ilink, [sp, -8] /* pc into ilink */
289
290#ifdef CONFIG_INSTRUMENT_THREAD_SWITCHING
291	push_s blink
292
293	bl z_thread_mark_switched_in
294
295	pop_s blink
296#endif
297	/* LP registers are already restored, just switch back to bank 0 */
298	rtie
299