Linux Audio

Check our new training course

Embedded Linux Audio

Check our new training course
with Creative Commons CC-BY-SA
lecture materials

Bootlin logo

Elixir Cross Referencer

Loading...
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
/*
 * Copyright (c) 2014-2015 Wind River Systems, Inc.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

/**
 * @file
 * @brief Wrapper around ISRs with logic for context switching
 *
 *
 * Wrapper installed in vector table for handling dynamic interrupts that accept
 * a parameter.
 */

#include <offsets_short.h>
#include <toolchain.h>
#include <linker/sections.h>
#include <sw_isr_table.h>
#include <kernel_structs.h>
#include <arch/cpu.h>
#include <swap_macros.h>

GTEXT(_isr_wrapper)
GTEXT(_isr_demux)

GDATA(exc_nest_count)
SECTION_VAR(BSS, exc_nest_count)
	.balign 4
	.word 0


#if CONFIG_RGF_NUM_BANKS == 1
GDATA(saved_r0)

SECTION_VAR(BSS, saved_r0)
	.balign 4
	.word 0
#else
GDATA(saved_sp)

SECTION_VAR(BSS, saved_sp)
	.balign 4
	.word 0
#endif

#if defined(CONFIG_SYS_POWER_MANAGEMENT)
GTEXT(_sys_power_save_idle_exit)
#endif

/*
The symbols in this file are not real functions, and neither are
_rirq_enter/_firq_enter: they are jump points.

The flow is the following:

ISR -> _isr_wrapper -- + -> _rirq_enter -> _isr_demux -> ISR -> _rirq_exit
                       |
                       + -> _firq_enter -> _isr_demux -> ISR -> _firq_exit

Context switch explanation:

The context switch code is spread in these files:

  isr_wrapper.s, swap.s, swap_macros.s, fast_irq.s, regular_irq.s

IRQ stack frame layout:

  high address

           status32
              pc
           lp_count
           lp_start
            lp_end
            blink
             r13
             ...
    sp ->    r0

  low address

Registers not taken into account in the current implementation.
          jli_base
          ldi_base
          ei_base
          accl
          acch

The context switch code adopts this standard so that it is easier to follow:

  - r1 contains _kernel ASAP and is not overwritten over the lifespan of
    the functions.
  - r2 contains _kernel.current ASAP, and the incoming thread when we
    transition from outgoing thread to incoming thread

Not loading _kernel into r0 allows loading _kernel without stomping on
the parameter in r0 in _Swap().


ARCv2 processors have two kinds of interrupts: fast (FIRQ) and regular. The
official documentation calls the regular interrupts 'IRQs', but the internals
of the kernel call them 'RIRQs' to differentiate from the 'irq' subsystem,
which is the interrupt API/layer of abstraction.

For FIRQ, there are two cases, depending upon the value of
CONFIG_RGF_NUM_BANKS.

CONFIG_RGF_NUM_BANKS==1 case:
Scratch registers are pushed onto the current stack just as they are with
RIRQ. See the above frame layout. Unlike RIRQ, the status32_p0 and ilink
registers are where status32 and the program counter are located, so these
need to be pushed.

CONFIG_RGF_NUM_BANKS!=1 case:
The FIRQ handler has its own register bank for general purpose registers,
and thus it doesn't have to save them on a stack. The 'loop' registers
(lp_count, lp_end, lp_start), however, are not present in the
second bank. The handler saves these special registers in unused callee saved
registers (to avoid stack accesses). It is possible to register a FIRQ
handler that operates outside of the kernel, but care must be taken to only
use instructions that only use the banked registers.

The kernel is able to handle transitions to and from FIRQ, RIRQ and threads.
The contexts are saved 'lazily': the minimum amount of work is
done upfront, and the rest is done when needed:

o RIRQ

  All needed regisers to run C code in the ISR are saved automatically
  on the outgoing thread's stack: loop, status32, pc, and the caller-
  saved GPRs. That stack frame layout is pre-determined. If returning
  to a thread, the stack is popped and no registers have to be saved by
  the kernel. If a context switch is required, the callee-saved GPRs
  are then saved in the thread control structure (TCS).

o FIRQ

  First, a FIRQ can be interrupting a lower-priority RIRQ: if this is the case,
  the FIRQ does not take a scheduling decision and leaves it the RIRQ to
  handle. This limits the amount of code that has to run at interrupt-level.

  CONFIG_RGF_NUM_BANKS==1 case:
  Registers are saved on the stack frame just as they are for RIRQ.
  Context switch can happen just as it does in the RIRQ case, however,
  if the FIRQ interrupted a RIRQ, the FIRQ will return from interrupt and
  let the RIRQ do the context switch.  At entry, one register is needed in order
  to have code to save other registers. r0 is saved first in a global called
  saved_r0.

  CONFIG_RGF_NUM_BANKS!=1 case:
  During early initialization, the sp in the 2nd register bank is made to
  refer to _firq_stack. This allows for the FIRQ handler to use its own stack.
  GPRs are banked, loop registers are saved in unused callee saved regs upon
  interrupt entry. If returning to a thread, loop registers are restored and the
  CPU switches back to bank 0 for the GPRs. If a context switch is
  needed, at this point only are all the registers saved. First, a
  stack frame with the same layout as the automatic RIRQ one is created
  and then the callee-saved GPRs are saved in the TCS. status32_p0 and
  ilink are saved in this case, not status32 and pc.
  To create the stack frame, the FIRQ handling code must first go back to using
  bank0 of registers, since that is where the registers containing the exiting
  thread are saved. Care must be taken not to touch any register before saving
  them: the only one usable at that point is the stack pointer.

o coop

  When a coop context switch is done, the callee-saved registers are
  saved in the TCS. The other GPRs do not need to be saved, since the
  compiler has already placed them on the stack.

For restoring the contexts, there are six cases. In all cases, the
callee-saved registers of the incoming thread have to be restored. Then, there
are specifics for each case:

From coop:

  o to coop

    Restore interrupt lock level and do a normal function call return.

  o to any irq

    The incoming interrupted thread has an IRQ stack frame containing the
    caller-saved registers that has to be popped. status32 has to be restored,
    then we jump to the interrupted instruction.

From FIRQ:

  When CONFIG_RGF_NUM_BANKS==1, context switch is done as it is for RIRQ.
  When CONFIG_RGF_NUM_BANKS!=1, the processor is put back to using bank0,
  not bank1 anymore, because it had to save the outgoing context from bank0,
  and now has to load the incoming one
  into bank0.

  o to coop

    The address of the returning instruction from _Swap() is loaded in ilink and
    the saved status32 in status32_p0, taking care to adjust the interrupt lock
    state desired in status32_p0. The return value is put in r0.

  o to any irq

    The IRQ has saved the caller-saved registers in a stack frame, which must be
    popped, and statu32 and pc loaded in status32_p0 and ilink.

From RIRQ:

  o to coop

    The interrupt return mechanism in the processor expects a stack frame, but
    the outgoing context did not create one. A fake one is created here, with
    only the relevant values filled in: pc, status32 and the return value in r0.

    There is a discrepancy between the ABI from the ARCv2 docs, including the
    way the processor pushes GPRs in pairs in the IRQ stack frame, and the ABI
    GCC uses. r13 should be a callee-saved register, but GCC treats it as
    caller-saved. This means that the processor pushes it in the stack frame
    along with r12, but the compiler does not save it before entering a
    function. So, it is saved as part of the callee-saved registers, and
    restored there, but the processor restores it _a second time_ when popping
    the IRQ stack frame. Thus, the correct value must also be put in the fake
    stack frame when returning to a thread that context switched out
    cooperatively.

  o to any irq

    Both types of IRQs already have an IRQ stack frame: simply return from
    interrupt.
 */

SECTION_FUNC(TEXT, _isr_wrapper)
#ifdef CONFIG_EXECUTION_BENCHMARKING
	push_s r0
	push_s blink
	push_s r13
	push_s r12
	push r11
	push r10
	push r9
	push r8
	push r7
	push r6
	push r5
	push r4
	push_s r3
	push_s r2
	push_s r1

	bl read_timer_start_of_isr

	pop_s r1
	pop_s r2
	pop_s r3
	pop r4
	pop r5
	pop r6
	pop r7
	pop r8
	pop r9
	pop r10
	pop r11
	pop_s r12
	pop_s r13
	pop_s blink
	pop_s r0

#endif

#if CONFIG_ARC_FIRQ
#if CONFIG_RGF_NUM_BANKS == 1
	st r0,[saved_r0]
#endif
	lr r0, [_ARC_V2_AUX_IRQ_ACT]
	ffs r0, r0
	cmp r0, 0
#if CONFIG_RGF_NUM_BANKS == 1
	bnz rirq_path
	/* 1-register bank FIRQ handling must save registers on stack */
	lr r0,[_ARC_V2_STATUS32_P0]
	push_s r0
	mov r0,ilink
	push_s r0
#ifdef CONFIG_CODE_DENSITY
	lr r0, [_ARC_V2_JLI_BASE]
	push_s r0
	lr r0, [_ARC_V2_LDI_BASE]
	push_s r0
	lr r0, [_ARC_V2_EI_BASE]
	push_s r0
#endif
	mov r0,lp_count
	push_s r0
	lr r0, [_ARC_V2_LP_START]
	push_s r0
	lr r0, [_ARC_V2_LP_END]
	push_s r0
	push_s blink
	push_s r13
	push_s r12
	push r11
	push r10
	push r9
	push r8
	push r7
	push r6
	push r5
	push r4
	push_s r3
	push_s r2
	push_s r1
	ld r0,[saved_r0]
	push_s r0
	mov r3, _firq_exit
	mov r2, _firq_enter
	j_s [r2]
rirq_path:
	mov r3, _rirq_exit
	mov r2, _rirq_enter
	j_s [r2]
#else
	mov.z r3, _firq_exit
	mov.z r2, _firq_enter
	mov.nz r3, _rirq_exit
	mov.nz r2, _rirq_enter
	j_s [r2]
#endif
#else

	mov r3, _rirq_exit
	mov r2, _rirq_enter
	j_s [r2]
#endif

#if defined(CONFIG_TRACING)
GTEXT(z_sys_trace_isr_enter)

.macro log_interrupt_k_event
	clri r0 /* do not interrupt event logger operations */
	push_s r0
	push_s blink
	jl z_sys_trace_isr_enter
	pop_s blink
	pop_s r0
	seti r0
.endm
#else
	#define log_interrupt_k_event
#endif

#if defined(CONFIG_SYS_POWER_MANAGEMENT)
.macro exit_tickless_idle
	clri r0 /* do not interrupt exiting tickless idle operations */
	push_s r1
	push_s r0
	mov_s r1, _kernel
	ld_s r0, [r1, _kernel_offset_to_idle] /* requested idle duration */
	breq r0, 0, _skip_sys_power_save_idle_exit

	st 0, [r1, _kernel_offset_to_idle] /* zero idle duration */
	push_s blink
	jl _sys_power_save_idle_exit
	pop_s blink

_skip_sys_power_save_idle_exit:
	pop_s r0
	pop_s r1
	seti r0
.endm
#else
	#define exit_tickless_idle
#endif

/* when getting here, r3 contains the interrupt exit stub to call */
SECTION_FUNC(TEXT, _isr_demux)
	push_s r3


	/* cannot be done before this point because we must be able to run C */
	/* r0 is available to be stomped here, and exit_tickless_idle uses it */
	exit_tickless_idle
	log_interrupt_k_event

	lr r0, [_ARC_V2_ICAUSE]
	/* handle software triggered interrupt */
	lr	r3, [_ARC_V2_AUX_IRQ_HINT]
	brne	r3, r0, irq_hint_handled
	sr	0, [_ARC_V2_AUX_IRQ_HINT]
irq_hint_handled:

	sub r0, r0, 16

	mov r1, _sw_isr_table
	add3 r0, r1, r0   /* table entries are 8-bytes wide */

	ld_s r1, [r0, 4] /* ISR into r1 */
#ifdef CONFIG_EXECUTION_BENCHMARKING
	push_s r0
	push_s blink
	push_s r13
	push_s r12
	push r11
	push r10
	push r9
	push r8
	push r7
	push r6
	push r5
	push r4
	push_s r3
	push_s r2
	push_s r1

	bl read_timer_end_of_isr

	pop_s r1
	pop_s r2
	pop_s r3
	pop r4
	pop r5
	pop r6
	pop r7
	pop r8
	pop r9
	pop r10
	pop r11
	pop_s r12
	pop_s r13
	pop_s blink
	pop_s r0

#endif
	jl_s.d [r1]
	ld_s r0, [r0] /* delay slot: ISR parameter into r0  */

	/* back from ISR, jump to exit stub */
	pop_s r3
	j_s [r3]
	nop