Skip to content

Commit 9a3ee64

Browse files
abrodkinioannisg
authored andcommitted
arc: interrupts: Explain return from interrupt to cooperative thread
The code in question is very non-trivial so without good explanation it takes a lot of time to realize what's done there and why it still works in the end. Here I'm trying to save a couple of man-days for the next developers who's going to touch that piece of code. Signed-off-by: Alexey Brodkin <[email protected]>
1 parent aa9b762 commit 9a3ee64

File tree

1 file changed

+168
-0
lines changed

1 file changed

+168
-0
lines changed

arch/arc/core/regular_irq.S

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,168 @@ GTEXT(_rirq_common_interrupt_swap)
3535
* TODO: Revist this if FIRQ becomes configurable.
3636
*/
3737

38+
/*
39+
40+
===========================================================
41+
RETURN FROM INTERRUPT TO COOPERATIVE THREAD
42+
===========================================================
43+
44+
That's a special case because:
45+
1. We return from IRQ handler to a cooperative thread
46+
2. During IRQ handling context switch did happen
47+
3. Returning to a thread which previously gave control
48+
to another thread because of:
49+
- Calling k_sleep()
50+
- Explicitly yielding
51+
- Bumping into locked sync primitive etc
52+
53+
What (3) means is before passing control to another thread our thread
54+
in question:
55+
a. Stashed all precious caller-saved registers on its stack
56+
b. Pushed return address to the top of the stack as well
57+
58+
That's how thread's stack looks like right before jumping to another thread:
59+
----------------------------->8---------------------------------
60+
PRE-CONTEXT-SWITCH STACK
61+
62+
lower_addr, let's say: 0x1000
63+
64+
--------------------------------------
65+
SP -> | Return address; PC (Program Counter), in fact value taken from
66+
| BLINK register in z_arch_switch()
67+
--------------------------------------
68+
| STATUS32 value, we explicitly save it here for later usage, read-on
69+
--------------------------------------
70+
| Caller-saved registers: some of R0-R12
71+
--------------------------------------
72+
|...
73+
|...
74+
75+
higher_addr, let's say: 0x2000
76+
----------------------------->8---------------------------------
77+
78+
When context gets switched the kernel saves callee-saved registers in the
79+
thread's stack right on top of pre-switch contents so that's what we have:
80+
----------------------------->8---------------------------------
81+
POST-CONTEXT-SWITCH STACK
82+
83+
lower_addr, let's say: 0x1000
84+
85+
--------------------------------------
86+
SP -> | Callee-saved registers: see struct _callee_saved_stack{}
87+
| |- R13
88+
| |- R14
89+
| | ...
90+
| \- FP
91+
| ...
92+
--------------------------------------
93+
| Return address; PC (Program Counter)
94+
--------------------------------------
95+
| STATUS32 value
96+
--------------------------------------
97+
| Caller-saved registers: some of R0-R12
98+
--------------------------------------
99+
|...
100+
|...
101+
102+
higher_addr, let's say: 0x2000
103+
----------------------------->8---------------------------------
104+
105+
So how do we return in such a complex scenario.
106+
107+
First we restore callee-saved regs with help of _load_callee_saved_regs().
108+
Now we're back to PRE-CONTEXT-SWITCH STACK (see above).
109+
110+
Logically our next step is to load return address from the top of the stack
111+
and jump to that address to continue execution of the desired thread, but
112+
we're still in interrupt handling mode and the only way to return to normal
113+
execution mode is to execute "rtie" instruction. And here we need to deal
114+
with peculiarities of return from IRQ on ARCv2 cores.
115+
116+
Instead of simple jump to a return address stored in the tip of thread's stack
117+
(with subsequent interrupt enable) ARCv2 core additionally automatically
118+
restores some registers from stack. Most important ones are
119+
PC ("Program Counter") which holds address of the next instruction to execute
120+
and STATUS32 which holds imortant flags including global interrupt enable,
121+
zero, carry etc.
122+
123+
To make things worse depending on ARC core configuration and run-time setup
124+
of certain features different set of registers will be restored.
125+
126+
Typically those same registers are automatically saved on stack on entry to
127+
an interrupt, but remember we're returning to the thread which was
128+
not interrupted by interrupt and so on its stack there're no automatically
129+
saved registers, still inevitably on RTIE execution register restoration
130+
will happen. So if we do nothing special we'll end-up with that:
131+
----------------------------->8---------------------------------
132+
lower_addr, let's say: 0x1000
133+
134+
--------------------------------------
135+
# | Return address; PC (Program Counter)
136+
| --------------------------------------
137+
| | STATUS32 value
138+
| --------------------------------------
139+
|
140+
sizeof(_irq_stack_frame)
141+
|
142+
| | Caller-saved registers: R0-R12
143+
V --------------------------------------
144+
|...
145+
SP -> | < Some data on thread's stack>
146+
|...
147+
148+
higher_addr, let's say: 0x2000
149+
----------------------------->8---------------------------------
150+
151+
I.e. we'll go much deeper down the stack over needed return address, read
152+
some value from unexpected location in stack and will try to jump there.
153+
Nobody knows were we end-up then.
154+
155+
To work-around that problem we need to mimic existance of IRQ stack frame
156+
of which we really only need return address obviously to return where we
157+
need to. For that we just shift SP so that it points sizeof(_irq_stack_frame)
158+
above like that:
159+
----------------------------->8---------------------------------
160+
lower_addr, let's say: 0x1000
161+
162+
SP -> |
163+
A | < Some unrelated data >
164+
| |
165+
|
166+
sizeof(_irq_stack_frame)
167+
|
168+
| --------------------------------------
169+
| | Return address; PC (Program Counter)
170+
| --------------------------------------
171+
# | STATUS32 value
172+
--------------------------------------
173+
| Caller-saved registers: R0-R12
174+
--------------------------------------
175+
|...
176+
| < Some data on thread's stack>
177+
|...
178+
179+
higher_addr, let's say: 0x2000
180+
----------------------------->8---------------------------------
181+
182+
Indeed R0-R13 "restored" from IRQ stack frame will contain garbage but
183+
it makes no difference because we're returning to execution of code as if
184+
we're returning from yet another function call and so we will restore
185+
all needed registers from the stack.
186+
187+
One other important remark here is R13.
188+
189+
CPU hardware automatically save/restore registers in pairs and since we
190+
wanted to save/restore R12 in IRQ stack frame as a caller-saved register we
191+
just happen to do that for R13 as well. But given compiler treats it as
192+
a callee-saved register we save/restore it separately in _callee_saved_stack
193+
structure. And when we restore callee-saved registers from stack we among
194+
other registers recover R13. But later on return from IRQ with RTIE
195+
instruction, R13 will be "restored" again from fake IRQ stack frame and
196+
if we don't copy correct R13 value to fake IRQ stack frame R13 value
197+
will be corrupted.
198+
199+
*/
38200

39201
/**
40202
*
@@ -207,6 +369,12 @@ _rirq_return_from_coop:
207369
bset r0, r0, _ARC_V2_SEC_STAT_IRM_BIT
208370
sflag r0
209371
#endif
372+
373+
/*
374+
* See verbose explanation of
375+
* RETURN FROM INTERRUPT TO COOPERATIVE THREAD above
376+
*/
377+
210378
/* carve fake stack */
211379
sub sp, sp, ___isf_t_pc_OFFSET
212380

0 commit comments

Comments
 (0)