Skip to content

Commit 8bd7216

Browse files
mpredfearngregkh
authored andcommitted
MIPS: IRQ Stack: Unwind IRQ stack onto task stack
[ Upstream commit db8466c581cca1a08b505f1319c3ecd246f16fa8 ] When the separate IRQ stack was introduced, stack unwinding only proceeded as far as the top of the IRQ stack, leading to kernel backtraces being less useful, lacking the trace of what was interrupted. Fix this by providing a means for the kernel to unwind the IRQ stack onto the interrupted task stack. The processor state is saved to the kernel task stack on interrupt. The IRQ_STACK_START macro reserves an unsigned long at the top of the IRQ stack where the interrupted task stack pointer can be saved. After the active stack is switched to the IRQ stack, save the interrupted tasks stack pointer to the reserved location. Fix the stack unwinding code to look for the frame being the top of the IRQ stack and if so get the next frame from the saved location. The existing test does not work with the separate stack since the ra is no longer pointed at ret_from_{irq,exception}. The test to stop unwinding the stack 32 bytes from the top of a stack must be modified to allow unwinding to continue up to the location of the saved task stack pointer when on the IRQ stack. The low / high marks of the stack are set depending on whether the sp is on an irq stack or not. Signed-off-by: Matt Redfearn <matt.redfearn@imgtec.com> Cc: Paolo Bonzini <pbonzini@redhat.com> Cc: Marcin Nowakowski <marcin.nowakowski@imgtec.com> Cc: Masanari Iida <standby24x7@gmail.com> Cc: Chris Metcalf <cmetcalf@mellanox.com> Cc: James Hogan <james.hogan@imgtec.com> Cc: Paul Burton <paul.burton@imgtec.com> Cc: Ingo Molnar <mingo@kernel.org> Cc: Jason A. Donenfeld <jason@zx2c4.com> Cc: Andrew Morton <akpm@linux-foundation.org> Cc: linux-mips@linux-mips.org Cc: linux-kernel@vger.kernel.org Patchwork: https://patchwork.linux-mips.org/patch/15788/ Signed-off-by: Ralf Baechle <ralf@linux-mips.org> Signed-off-by: Sasha Levin <alexander.levin@verizon.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
1 parent f7f46b3 commit 8bd7216

4 files changed

Lines changed: 60 additions & 20 deletions

File tree

arch/mips/include/asm/irq.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,24 @@
1818
#include <irq.h>
1919

2020
#define IRQ_STACK_SIZE THREAD_SIZE
21+
#define IRQ_STACK_START (IRQ_STACK_SIZE - sizeof(unsigned long))
2122

2223
extern void *irq_stack[NR_CPUS];
2324

25+
/*
26+
* The highest address on the IRQ stack contains a dummy frame put down in
27+
* genex.S (handle_int & except_vec_vi_handler) which is structured as follows:
28+
*
29+
* top ------------
30+
* | task sp | <- irq_stack[cpu] + IRQ_STACK_START
31+
* ------------
32+
* | | <- First frame of IRQ context
33+
* ------------
34+
*
35+
* task sp holds a copy of the task stack pointer where the struct pt_regs
36+
* from exception entry can be found.
37+
*/
38+
2439
static inline bool on_irq_stack(int cpu, unsigned long sp)
2540
{
2641
unsigned long low = (unsigned long)irq_stack[cpu];

arch/mips/kernel/asm-offsets.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ void output_thread_info_defines(void)
102102
DEFINE(_THREAD_SIZE, THREAD_SIZE);
103103
DEFINE(_THREAD_MASK, THREAD_MASK);
104104
DEFINE(_IRQ_STACK_SIZE, IRQ_STACK_SIZE);
105+
DEFINE(_IRQ_STACK_START, IRQ_STACK_START);
105106
BLANK();
106107
}
107108

arch/mips/kernel/genex.S

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -216,9 +216,11 @@ NESTED(handle_int, PT_SIZE, sp)
216216
beq t0, t1, 2f
217217

218218
/* Switch to IRQ stack */
219-
li t1, _IRQ_STACK_SIZE
219+
li t1, _IRQ_STACK_START
220220
PTR_ADD sp, t0, t1
221221

222+
/* Save task's sp on IRQ stack so that unwinding can follow it */
223+
LONG_S s1, 0(sp)
222224
2:
223225
jal plat_irq_dispatch
224226

@@ -326,9 +328,11 @@ NESTED(except_vec_vi_handler, 0, sp)
326328
beq t0, t1, 2f
327329

328330
/* Switch to IRQ stack */
329-
li t1, _IRQ_STACK_SIZE
331+
li t1, _IRQ_STACK_START
330332
PTR_ADD sp, t0, t1
331333

334+
/* Save task's sp on IRQ stack so that unwinding can follow it */
335+
LONG_S s1, 0(sp)
332336
2:
333337
jalr v0
334338

arch/mips/kernel/process.c

Lines changed: 38 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -483,31 +483,52 @@ unsigned long notrace unwind_stack_by_address(unsigned long stack_page,
483483
unsigned long pc,
484484
unsigned long *ra)
485485
{
486+
unsigned long low, high, irq_stack_high;
486487
struct mips_frame_info info;
487488
unsigned long size, ofs;
489+
struct pt_regs *regs;
488490
int leaf;
489-
extern void ret_from_irq(void);
490-
extern void ret_from_exception(void);
491491

492492
if (!stack_page)
493493
return 0;
494494

495495
/*
496-
* If we reached the bottom of interrupt context,
497-
* return saved pc in pt_regs.
496+
* IRQ stacks start at IRQ_STACK_START
497+
* task stacks at THREAD_SIZE - 32
498498
*/
499-
if (pc == (unsigned long)ret_from_irq ||
500-
pc == (unsigned long)ret_from_exception) {
501-
struct pt_regs *regs;
502-
if (*sp >= stack_page &&
503-
*sp + sizeof(*regs) <= stack_page + THREAD_SIZE - 32) {
504-
regs = (struct pt_regs *)*sp;
505-
pc = regs->cp0_epc;
506-
if (!user_mode(regs) && __kernel_text_address(pc)) {
507-
*sp = regs->regs[29];
508-
*ra = regs->regs[31];
509-
return pc;
510-
}
499+
low = stack_page;
500+
if (!preemptible() && on_irq_stack(raw_smp_processor_id(), *sp)) {
501+
high = stack_page + IRQ_STACK_START;
502+
irq_stack_high = high;
503+
} else {
504+
high = stack_page + THREAD_SIZE - 32;
505+
irq_stack_high = 0;
506+
}
507+
508+
/*
509+
* If we reached the top of the interrupt stack, start unwinding
510+
* the interrupted task stack.
511+
*/
512+
if (unlikely(*sp == irq_stack_high)) {
513+
unsigned long task_sp = *(unsigned long *)*sp;
514+
515+
/*
516+
* Check that the pointer saved in the IRQ stack head points to
517+
* something within the stack of the current task
518+
*/
519+
if (!object_is_on_stack((void *)task_sp))
520+
return 0;
521+
522+
/*
523+
* Follow pointer to tasks kernel stack frame where interrupted
524+
* state was saved.
525+
*/
526+
regs = (struct pt_regs *)task_sp;
527+
pc = regs->cp0_epc;
528+
if (!user_mode(regs) && __kernel_text_address(pc)) {
529+
*sp = regs->regs[29];
530+
*ra = regs->regs[31];
531+
return pc;
511532
}
512533
return 0;
513534
}
@@ -528,8 +549,7 @@ unsigned long notrace unwind_stack_by_address(unsigned long stack_page,
528549
if (leaf < 0)
529550
return 0;
530551

531-
if (*sp < stack_page ||
532-
*sp + info.frame_size > stack_page + THREAD_SIZE - 32)
552+
if (*sp < low || *sp + info.frame_size > high)
533553
return 0;
534554

535555
if (leaf)

0 commit comments

Comments
 (0)