arc: Support FIRQ handling when CONFIG_RGF_NUM_BANKS==1

For the EM Starterkit, one SOC I will soon be adding is EM7D.
This SOC has FIRQ, but only has one register bank.
Thus the interrupt handling for FIRQ needs to be different
when CONFIG_RGF_NUM_BANKS==1. The handler must instead push
registers onto the stack in the same stack frame layout that RIRQ uses.
This allows for context switch to be easily done since its compatible.
The common interrupt entry point _isr_enter must save r0 before using
it, because in the FIRQ 1-bank case, it would be destroyed otherwise.
So a global variable named saved_r0 has been added for this reason.
The stack cannot be used to save r0, because it first has to determine
whether its FIRQ or RIRQ here. This change has been tested on the
EM Starterkit with EM7D SOC changes -- coming soon. To make the review
easier, these 3 files are submitted first.

Also, exceptions will no longer use the _firq_stack.
This stack is not needed in the 1-bank case, but an exception stack
is needed. I've added a new stack called _exception_stack,
and made it be 512B, which should be enough for one exception.
See ZEP-966

Change-Id: I6f228b840da7c4db440dd1cfef4ae25336c87f0d
Signed-off-by: Chuck Jordan <cjordan@synopsys.com>
This commit is contained in:
Chuck Jordan 2016-10-15 09:50:27 -07:00 committed by Andrew Boie
commit ff303948d5
3 changed files with 160 additions and 31 deletions

View file

@ -34,10 +34,15 @@
GTEXT(_firq_enter) GTEXT(_firq_enter)
GTEXT(_firq_exit) GTEXT(_firq_exit)
GTEXT(_firq_stack_setup) GTEXT(_firq_stack_setup)
#if CONFIG_RGF_NUM_BANKS != 1
GDATA(_firq_stack) GDATA(_firq_stack)
SECTION_VAR(NOINIT, _firq_stack) SECTION_VAR(NOINIT, _firq_stack)
.space CONFIG_FIRQ_STACK_SIZE .space CONFIG_FIRQ_STACK_SIZE
#else
GDATA(saved_r0)
#endif
/** /**
* *
@ -63,10 +68,13 @@ SECTION_VAR(NOINIT, _firq_stack)
SECTION_FUNC(TEXT, _firq_enter) SECTION_FUNC(TEXT, _firq_enter)
/* /*
* ATTENTION: * ATTENTION:
* firq uses a 2nd register bank so GPRs do not need to be saved. * If CONFIG_RGF_NUM_BANKS>1, firq uses a 2nd register bank so GPRs do
*/ * not need to be saved.
* If CONFIG_RGF_NUM_BANKS==1, firq must use the stack to save registers.
* This has already been done by _isr_enter.
*/
#ifdef CONFIG_ARC_STACK_CHECKING #ifdef CONFIG_ARC_STACK_CHECKING
/* disable stack checking */ /* disable stack checking */
@ -75,6 +83,7 @@ SECTION_FUNC(TEXT, _firq_enter)
kflag r2 kflag r2
#endif #endif
#if CONFIG_RGF_NUM_BANKS != 1
#ifndef CONFIG_FIRQ_NO_LPCC #ifndef CONFIG_FIRQ_NO_LPCC
/* /*
* Save LP_START/LP_COUNT/LP_END because called handler might use. * Save LP_START/LP_COUNT/LP_END because called handler might use.
@ -84,6 +93,7 @@ SECTION_FUNC(TEXT, _firq_enter)
mov r23,lp_count mov r23,lp_count
lr r24, [_ARC_V2_LP_START] lr r24, [_ARC_V2_LP_START]
lr r25, [_ARC_V2_LP_END] lr r25, [_ARC_V2_LP_END]
#endif
#endif #endif
j @_isr_demux j @_isr_demux
@ -97,11 +107,13 @@ SECTION_FUNC(TEXT, _firq_enter)
SECTION_FUNC(TEXT, _firq_exit) SECTION_FUNC(TEXT, _firq_exit)
#if CONFIG_RGF_NUM_BANKS != 1
#ifndef CONFIG_FIRQ_NO_LPCC #ifndef CONFIG_FIRQ_NO_LPCC
/* restore lp_count, lp_start, lp_end from r23-r25 */ /* restore lp_count, lp_start, lp_end from r23-r25 */
mov lp_count,r23 mov lp_count,r23
sr r24, [_ARC_V2_LP_START] sr r24, [_ARC_V2_LP_START]
sr r25, [_ARC_V2_LP_END] sr r25, [_ARC_V2_LP_END]
#endif
#endif #endif
mov_s r1, _nanokernel mov_s r1, _nanokernel
@ -120,6 +132,32 @@ SECTION_FUNC(TEXT, _firq_exit)
breq r3, 1, _check_if_current_is_the_task breq r3, 1, _check_if_current_is_the_task
#if CONFIG_RGF_NUM_BANKS == 1
_firq_scratch_restore:
add sp,sp,4 /* don't need r0 from stack */
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
sr r0, [_ARC_V2_LP_END]
pop_s r0
sr r0, [_ARC_V2_LP_START]
pop_s r0
mov lp_count,r0
ld r0,[saved_r0]
add sp,sp,8 /* don't need ilink & status32_po from stack */
#endif
rtie rtie
#endif #endif
@ -130,17 +168,26 @@ _check_if_current_is_the_task:
ld_s r0, [r2, __tTCS_flags_OFFSET] ld_s r0, [r2, __tTCS_flags_OFFSET]
and.f r0, r0, PREEMPTIBLE and.f r0, r0, PREEMPTIBLE
bnz _check_if_a_fiber_is_ready bnz _check_if_a_fiber_is_ready
#if CONFIG_RGF_NUM_BANKS == 1
b _firq_scratch_restore
#else
rtie rtie
#endif
.balign 4 .balign 4
_check_if_a_fiber_is_ready: _check_if_a_fiber_is_ready:
ld_s r0, [r1, __tNANO_fiber_OFFSET] /* incoming fiber in r0 */ ld_s r0, [r1, __tNANO_fiber_OFFSET] /* incoming fiber in r0 */
brne r0, 0, _firq_reschedule brne r0, 0, _firq_reschedule
#if CONFIG_RGF_NUM_BANKS == 1
b _firq_scratch_restore
#else
rtie rtie
#endif
.balign 4 .balign 4
_firq_reschedule: _firq_reschedule:
#if CONFIG_RGF_NUM_BANKS != 1
/* /*
* We know there is no interrupted interrupt of lower priority at this * We know there is no interrupted interrupt of lower priority at this
* point, so when switching back to register bank 0, it will contain the * point, so when switching back to register bank 0, it will contain the
@ -164,6 +211,7 @@ _firq_reschedule:
st_s r0, [sp, __tISF_status32_OFFSET] st_s r0, [sp, __tISF_status32_OFFSET]
st ilink, [sp, __tISF_pc_OFFSET] /* ilink into pc */ st ilink, [sp, __tISF_pc_OFFSET] /* ilink into pc */
#endif
mov_s r1, _nanokernel mov_s r1, _nanokernel
ld_s r2, [r1, __tNANO_current_OFFSET] ld_s r2, [r1, __tNANO_current_OFFSET]
@ -245,13 +293,14 @@ _firq_no_reschedule:
/** /**
* *
* @brief Install the FIRQ stack in register bank 1 * @brief Install the FIRQ stack in register bank 1 if CONFIG_RGF_NUM_BANK!=1
* *
* @return N/A * @return N/A
*/ */
SECTION_FUNC(TEXT, _firq_stack_setup) SECTION_FUNC(TEXT, _firq_stack_setup)
#if CONFIG_RGF_NUM_BANKS != 1
lr r0, [_ARC_V2_STATUS32] lr r0, [_ARC_V2_STATUS32]
and r0, r0, ~_ARC_V2_STATUS32_RB(7) and r0, r0, ~_ARC_V2_STATUS32_RB(7)
or r0, r0, _ARC_V2_STATUS32_RB(1) or r0, r0, _ARC_V2_STATUS32_RB(1)
@ -267,5 +316,6 @@ SECTION_FUNC(TEXT, _firq_stack_setup)
lr r0, [_ARC_V2_STATUS32] lr r0, [_ARC_V2_STATUS32]
and r0, r0, ~_ARC_V2_STATUS32_RB(7) and r0, r0, ~_ARC_V2_STATUS32_RB(7)
kflag r0 kflag r0
#endif
j_s [blink] j_s [blink]

View file

@ -44,11 +44,19 @@ GTEXT(__ev_extension)
GTEXT(__ev_div_zero) GTEXT(__ev_div_zero)
GTEXT(__ev_dc_error) GTEXT(__ev_dc_error)
GTEXT(__ev_maligned) GTEXT(__ev_maligned)
GDATA(_firq_stack)
SECTION_VAR(BSS, saved_stack_pointer) SECTION_VAR(BSS, saved_stack_pointer)
.word 0 .word 0
#if CONFIG_RGF_NUM_BANKS == 1
GDATA(_exception_stack)
SECTION_VAR(NOINIT, _exception_stack)
.space 512
/* note: QUARK_SE_C1000_SS can't afford 512B */
#else
GDATA(_firq_stack)
#endif
#if CONFIG_NUM_IRQ_PRIO_LEVELS == 1 #if CONFIG_NUM_IRQ_PRIO_LEVELS == 1
#error "NUM_IRQ_PRIO_LEVELS==1 is not supported." #error "NUM_IRQ_PRIO_LEVELS==1 is not supported."
/* The code below sets bit 1 in AUX_IRQ_ACT and thus requires /* The code below sets bit 1 in AUX_IRQ_ACT and thus requires
@ -80,10 +88,8 @@ SECTION_SUBSEC_FUNC(TEXT,__fault,__ev_maligned)
/* /*
* Before invoking exception handler, the kernel switches to an exception * Before invoking exception handler, the kernel switches to an exception
* stack, which is really the FIRQ stack, to save the faulting thread's * stack, to save the faulting thread's registers.
* registers. It can use the FIRQ stack because it knows it is unused * The exception is fatal and all the kernel can do is just print
* since it is save to assume that if an exception has happened in FIRQ
* handler, the problem is fatal and all the kernel can do is just print
* a diagnostic message and halt. * a diagnostic message and halt.
*/ */
@ -97,8 +103,13 @@ SECTION_SUBSEC_FUNC(TEXT,__fault,__ev_maligned)
#endif #endif
st sp, [saved_stack_pointer] st sp, [saved_stack_pointer]
#if CONFIG_RGF_NUM_BANKS == 1
mov_s sp, _exception_stack
add sp, sp, 512
#else
mov_s sp, _firq_stack mov_s sp, _firq_stack
add sp, sp, CONFIG_FIRQ_STACK_SIZE add sp, sp, CONFIG_FIRQ_STACK_SIZE
#endif
/* save caller saved registers */ /* save caller saved registers */
_create_irq_stack_frame _create_irq_stack_frame
@ -123,10 +134,8 @@ GTEXT(_irq_do_offload);
SECTION_SUBSEC_FUNC(TEXT,__fault,__ev_trap) SECTION_SUBSEC_FUNC(TEXT,__fault,__ev_trap)
/* /*
* Before invoking exception handler, the kernel switches to an exception * Before invoking exception handler, the kernel switches to an exception
* stack, which is really the FIRQ stack, to save the faulting thread's * stack to save the faulting thread's registers.
* registers. It can use the FIRQ stack because it knows it is unused * The exception is fatal and all the kernel can do is just print
* since it is safe to assume that if an exception has happened in FIRQ
* handler, the problem is fatal and all the kernel can do is just print
* a diagnostic message and halt. * a diagnostic message and halt.
*/ */
@ -141,8 +150,13 @@ SECTION_SUBSEC_FUNC(TEXT,__fault,__ev_trap)
#ifndef CONFIG_MICROKERNEL #ifndef CONFIG_MICROKERNEL
st sp, [saved_stack_pointer] st sp, [saved_stack_pointer]
#if CONFIG_RGF_NUM_BANKS == 1
mov_s sp, _exception_stack
add sp, sp, 512
#else
mov_s sp, _firq_stack mov_s sp, _firq_stack
add sp, sp, CONFIG_FIRQ_STACK_SIZE add sp, sp, CONFIG_FIRQ_STACK_SIZE
#endif
#endif #endif
/* save caller saved registers */ /* save caller saved registers */

View file

@ -35,6 +35,13 @@
GTEXT(_isr_enter) GTEXT(_isr_enter)
GTEXT(_isr_demux) GTEXT(_isr_demux)
#if CONFIG_RGF_NUM_BANKS == 1
GDATA(saved_r0)
SECTION_VAR(BSS, saved_r0)
.word 0
#endif
#if defined(CONFIG_NANOKERNEL) && defined(CONFIG_TICKLESS_IDLE) #if defined(CONFIG_NANOKERNEL) && defined(CONFIG_TICKLESS_IDLE)
GTEXT(_power_save_idle_exit) GTEXT(_power_save_idle_exit)
#endif #endif
@ -94,16 +101,23 @@ official documentation calls the regular interrupts 'IRQs', but the internals
of the kernel call them 'RIRQs' to differentiate from the 'irq' subsystem, of the kernel call them 'RIRQs' to differentiate from the 'irq' subsystem,
which is the interrupt API/layer of abstraction. which is the interrupt API/layer of abstraction.
FIRQs can be used to allow ISRs to run without having to save any context, For FIRQ, there are two cases, depending upon the value of
since they work with a second register bank. However, they can be somewhat more CONFIG_RGF_NUM_BANKS.
limited than RIRQs since the register bank does not copy every possible
register that is needed to implement all available instructions: an example is CONFIG_RGF_NUM_BANKS==1 case:
that the 'loop' registers (lp_count, lp_end, lp_start) are not present in the Scratch registers are pushed onto the current stack just as they are with
second bank. The kernel thus takes upon itself to save these extra registers, RIRQ. See the above frame layout. Unlike RIRQ, the status32_p0 and ilink
if the FIRQ is made known to the kernel. It is possible for a FIRQ to operate registers are where status32 and the program counter are located, so these
outside of the kernel, but care must be taken to only use instructions that need to be pushed.
only use the banked registers. RIRQs must always use the kernel's interrupt
entry and exit mechanisms. 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 kernel is able to handle transitions to and from FIRQ, RIRQ and threads
(fibers/task). The contexts are saved 'lazily': the minimum amount of work is (fibers/task). The contexts are saved 'lazily': the minimum amount of work is
@ -124,14 +138,24 @@ o FIRQ
the FIRQ does not take a scheduling decision and leaves it the RIRQ to 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. handle. This limits the amount of code that has to run at interrupt-level.
GPRs are banked, loop registers are saved in a global structure upon CONFIG_RGF_NUM_BANKS==1 case:
interrupt entry. If returning to a fiber, loop registers are poppped and the 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 fiber, loop registers are restored and the
CPU switches back to bank 0 for the GPRs. If a context switch is 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 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 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 and then the callee-saved GPRs are saved in the TCS. status32_p0 and
ilink are saved in this case, not status32 and pc. 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 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 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 thread are saved. Care must be taken not to touch any register before saving
@ -161,8 +185,10 @@ From coop:
From FIRQ: From FIRQ:
The processor is back to using bank0, not bank1 anymore, because it had to When CONFIG_RGF_NUM_BANKS==1, context switch is done as it is for RIRQ.
save the outgoing context from bank0, and now has to load the incoming one 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. into bank0.
o to coop o to coop
@ -202,16 +228,55 @@ From RIRQ:
*/ */
SECTION_FUNC(TEXT, _isr_enter) SECTION_FUNC(TEXT, _isr_enter)
#if CONFIG_RGF_NUM_BANKS == 1
st r0,[saved_r0]
#endif
lr r0, [_ARC_V2_AUX_IRQ_ACT] lr r0, [_ARC_V2_AUX_IRQ_ACT]
ffs r0, r0 ffs r0, r0
cmp r0, 0 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
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 r3, _firq_exit
mov.z r2, _firq_enter mov.z r2, _firq_enter
mov.nz r3, _rirq_exit mov.nz r3, _rirq_exit
mov.nz r2, _rirq_enter mov.nz r2, _rirq_enter
j_s [r2] j_s [r2]
#endif
#ifdef CONFIG_KERNEL_EVENT_LOGGER_SLEEP #ifdef CONFIG_KERNEL_EVENT_LOGGER_SLEEP
GTEXT(_sys_k_event_logger_exit_sleep) GTEXT(_sys_k_event_logger_exit_sleep)