For dumpfile active tasks, the saved SP may point into the per-cpu IRQ
stack instead of the task stack. The existing LoongArch64 unwinder only
uses the task stack buffer, so it can stop early or pick stale task-stack
words as caller frames.
Initialize the per-cpu IRQ stack addresses, switch the bt stack buffer
when an active task starts on an IRQ stack, and continue unwinding back to
the interrupted task stack using the task SP saved at the end of the IRQ
stack. This saved-SP slot matches the kernel IRQ_STACK_START layout.
When that saved task SP points to pt_regs, validate the saved ERA, SP and
RA before using them as the handoff frame.
Hypervisor dump PRSTATUS RA values from IRQ stack contexts are not always
a reliable caller. Keep them only for the narrow IRQ leaf case that
preserves the in-flight generic_handle_domain_irq -> handle_cpu_irq
chain; otherwise let ORC, exception frames, or the saved pt_regs handoff
determine the next frame.
Signed-off-by: Ming Wang <wangming01(a)loongson.cn>
---
defs.h | 2 +
loongarch64.c | 226 ++++++++++++++++++++++++++++++++++++++++++++++++--
2 files changed, 223 insertions(+), 5 deletions(-)
diff --git a/defs.h b/defs.h
index 30ad1ef0e0ea..f9752b7417f1 100644
--- a/defs.h
+++ b/defs.h
@@ -7360,6 +7360,8 @@ struct machine_specific {
ulong modules_vaddr;
ulong modules_end;
+ ulong irq_stack_size;
+ ulong *irq_stacks;
struct loongarch64_pt_regs *crash_task_regs;
struct loongarch64_ORC_data orc;
};
diff --git a/loongarch64.c b/loongarch64.c
index 2e9e28602b9b..c25a3bbf6d6c 100644
--- a/loongarch64.c
+++ b/loongarch64.c
@@ -70,6 +70,7 @@ typedef struct __attribute__((__packed__)) {
#define LOONGARCH64_INSN_SIZE 4
#define LOONGARCH64_VECSIZE 0x200
#define LOONGARCH64_EXCEPTION_VECTOR_NUM 128
+#define LOONGARCH64_IRQ_STACK_SAVED_SP_OFFSET 16
#define LOONGARCH64_EXCCODE_INT_START 64
#define LOONGARCH64_EXCCODE_INT_END 78
@@ -96,6 +97,9 @@ static void loongarch64_dump_exception_stack(struct bt_info *bt, char
*pt_regs);
static int loongarch64_is_exception_entry(struct syment *sym);
static ulong loongarch64_exception_pc(int cpu, ulong pc);
static int loongarch64_is_unwind_text(ulong pc);
+static int loongarch64_use_irq_prstatus_ra(ulong pc, ulong ra);
+static int loongarch64_valid_saved_regs(struct bt_info *bt,
+ struct loongarch64_pt_regs *regs, ulong *pc);
static int loongarch64_eframe_search(struct bt_info *bt);
static void loongarch64_display_full_frame(struct bt_info *bt,
struct loongarch64_unwind_frame *current,
@@ -108,10 +112,17 @@ static int loongarch64_get_frame(struct bt_info *bt, ulong *pcp,
ulong *spp);
static int loongarch64_init_active_task_regs(void);
static int loongarch64_get_crash_notes(void);
static int loongarch64_get_elf_notes(void);
+static void loongarch64_irq_stack_init(void);
static void loongarch64_ORC_init(void);
static int loongarch64_orc_unwind(struct bt_info *bt,
struct loongarch64_unwind_frame *current,
struct loongarch64_unwind_frame *previous);
+static int loongarch64_on_irq_stack(int cpu, ulong stkptr);
+static void loongarch64_set_irq_stack(struct bt_info *bt);
+static int loongarch64_switch_from_irq_stack(struct bt_info *bt,
+ struct loongarch64_unwind_frame *current);
+static int loongarch64_find_next_kernel_text(struct bt_info *bt,
+ struct loongarch64_unwind_frame *current);
/*
* 3 Levels paging PAGE_SIZE=16KB
@@ -480,6 +491,7 @@ loongarch64_back_trace_cmd(struct bt_info *bt)
char pt_regs[SIZE(pt_regs)];
int level = 0;
int invalid_ok = 1;
+ int on_irq_stack;
if (bt->flags & BT_REGS_NOT_FOUND)
return;
@@ -491,16 +503,30 @@ loongarch64_back_trace_cmd(struct bt_info *bt)
current.ra = 0;
current.fp = 0;
+ if (!INSTACK(current.sp, bt) &&
+ (bt->flags & BT_REGS_NOT_FOUND) == 0 &&
+ loongarch64_on_irq_stack(bt->tc->processor, current.sp))
+ loongarch64_set_irq_stack(bt);
+
if (!INSTACK(current.sp, bt))
return;
+ on_irq_stack = loongarch64_on_irq_stack(bt->tc->processor, current.sp);
if (bt->machdep) {
regs = (struct loongarch64_pt_regs *)bt->machdep;
- previous.pc = current.ra = regs->regs[LOONGARCH64_EF_RA];
+ /*
+ * PRSTATUS RA from IRQ stacks is not always a reliable caller for
+ * hypervisor dumps. Use it only for IRQ leaf frames where it keeps
+ * the in-flight interrupt call chain before crossing stacks.
+ */
+ if (!on_irq_stack ||
+ loongarch64_use_irq_prstatus_ra(current.pc,
+ regs->regs[LOONGARCH64_EF_RA]))
+ previous.pc = current.ra = regs->regs[LOONGARCH64_EF_RA];
current.fp = regs->regs[LOONGARCH64_EF_FP];
}
- while (current.sp <= bt->stacktop - SIZE(pt_regs)) {
+ while (current.sp < bt->stacktop) {
struct syment *symbol = NULL;
ulong offset;
@@ -511,14 +537,28 @@ loongarch64_back_trace_cmd(struct bt_info *bt)
fprintf(fp, "level %d pc %#lx ra %#lx sp %lx\n",
level, current.pc, current.ra, current.sp);
- if (!IS_KVADDR(current.pc) && !invalid_ok)
+ if (!IS_KVADDR(current.pc) && !invalid_ok) {
+ if (loongarch64_switch_from_irq_stack(bt, ¤t) ||
+ loongarch64_find_next_kernel_text(bt, ¤t)) {
+ invalid_ok = 1;
+ continue;
+ }
return;
+ }
symbol = value_search(current.pc, &offset);
- if (!symbol && !invalid_ok) {
+ if ((!symbol || !loongarch64_is_unwind_text(current.pc)) &&
+ !invalid_ok) {
+ if (loongarch64_switch_from_irq_stack(bt, ¤t) ||
+ loongarch64_find_next_kernel_text(bt, ¤t)) {
+ invalid_ok = 1;
+ continue;
+ }
error(FATAL, "PC is unknown symbol (%lx)", current.pc);
return;
}
+ if (symbol && !loongarch64_is_unwind_text(current.pc))
+ symbol = NULL;
invalid_ok = 0;
/*
@@ -557,7 +597,8 @@ loongarch64_back_trace_cmd(struct bt_info *bt)
if (loongarch64_orc_unwind(bt, ¤t, &previous)) {
/* ORC has already calculated the caller frame. */
- } else if (symbol && loongarch64_is_exception_entry(symbol)) {
+ } else if (symbol && loongarch64_is_exception_entry(symbol) &&
+ current.sp <= bt->stacktop - SIZE(pt_regs)) {
GET_STACK_DATA(current.sp, pt_regs, sizeof(pt_regs));
regs = (struct loongarch64_pt_regs *) (pt_regs + OFFSET(pt_regs_regs));
@@ -596,6 +637,12 @@ loongarch64_back_trace_cmd(struct bt_info *bt)
level, current.pc, current.ra, current.sp);
previous.sp = previous.pc = previous.ra = previous.fp = 0;
+
+ if (current.sp >= bt->stacktop &&
+ loongarch64_switch_from_irq_stack(bt, ¤t)) {
+ invalid_ok = 1;
+ continue;
+ }
}
}
@@ -868,6 +915,174 @@ loongarch64_is_unwind_text(ulong pc)
return TRUE;
}
+
+static int
+loongarch64_use_irq_prstatus_ra(ulong pc, ulong ra)
+{
+ struct syment *pcsym, *rasym;
+ ulong offset;
+
+ if (!loongarch64_is_unwind_text(pc) ||
+ !loongarch64_is_unwind_text(ra))
+ return FALSE;
+
+ pcsym = value_search(pc, &offset);
+ rasym = value_search(ra, &offset);
+ if (!pcsym || !rasym)
+ return FALSE;
+
+ return STREQ(pcsym->name, "generic_handle_domain_irq") &&
+ STREQ(rasym->name, "handle_cpu_irq");
+}
+
+static int
+loongarch64_valid_saved_regs(struct bt_info *bt,
+ struct loongarch64_pt_regs *regs, ulong *pc)
+{
+ ulong saved_pc;
+
+ saved_pc = loongarch64_exception_pc(bt->tc->processor, regs->csr_epc);
+ if (!loongarch64_is_unwind_text(saved_pc))
+ return FALSE;
+
+ if (!INSTACK(regs->regs[LOONGARCH64_EF_SP], bt))
+ return FALSE;
+
+ if (regs->regs[LOONGARCH64_EF_RA] &&
+ !loongarch64_is_unwind_text(regs->regs[LOONGARCH64_EF_RA]))
+ return FALSE;
+
+ *pc = saved_pc;
+ return TRUE;
+}
+
+static void
+loongarch64_irq_stack_init(void)
+{
+ int i;
+ struct syment *sp;
+ struct machine_specific *ms = machdep->machspec;
+ ulong p;
+
+ if (!(symbol_exists("irq_stack") &&
+ (sp = per_cpu_symbol_search("irq_stack"))))
+ return;
+
+ ms->irq_stack_size = machdep->stacksize;
+ if (!(ms->irq_stacks = (ulong *)malloc((size_t)(kt->cpus *
+ sizeof(ulong)))))
+ error(FATAL, "cannot malloc irq_stack addresses\n");
+
+ machdep->flags |= IRQSTACKS;
+
+ for (i = 0; i < kt->cpus; i++) {
+ p = kt->__per_cpu_offset[i] + sp->value;
+ if (!readmem(p, KVADDR, &ms->irq_stacks[i], sizeof(ulong),
+ "IRQ stack pointer", RETURN_ON_ERROR))
+ ms->irq_stacks[i] = 0;
+ }
+}
+
+static int
+loongarch64_on_irq_stack(int cpu, ulong stkptr)
+{
+ struct machine_specific *ms = machdep->machspec;
+
+ if (cpu < 0 || cpu >= kt->cpus || !ms->irq_stacks ||
+ !ms->irq_stacks[cpu] || !ms->irq_stack_size)
+ return FALSE;
+
+ return (stkptr >= ms->irq_stacks[cpu]) &&
+ (stkptr < (ms->irq_stacks[cpu] + ms->irq_stack_size));
+}
+
+static void
+loongarch64_set_irq_stack(struct bt_info *bt)
+{
+ struct machine_specific *ms = machdep->machspec;
+
+ bt->stackbase = ms->irq_stacks[bt->tc->processor];
+ bt->stacktop = bt->stackbase + ms->irq_stack_size;
+ alter_stackbuf(bt);
+}
+
+static int
+loongarch64_switch_from_irq_stack(struct bt_info *bt,
+ struct loongarch64_unwind_frame *current)
+{
+ struct machine_specific *ms = machdep->machspec;
+ struct loongarch64_pt_regs *regs;
+ char pt_regs[SIZE(pt_regs)];
+ ulong irq_stack, saved_sp_addr, saved_sp, saved_pc;
+ ulong task_stackbase, task_stacktop;
+
+ if (bt->tc->processor < 0 || bt->tc->processor >= kt->cpus ||
+ !ms->irq_stacks || !ms->irq_stack_size)
+ return FALSE;
+
+ irq_stack = ms->irq_stacks[bt->tc->processor];
+ if (!irq_stack || current->sp < irq_stack ||
+ current->sp > irq_stack + ms->irq_stack_size + 64)
+ return FALSE;
+
+ /* Matches the kernel's IRQ_STACK_START layout. */
+ saved_sp_addr = irq_stack + ms->irq_stack_size -
+ LOONGARCH64_IRQ_STACK_SAVED_SP_OFFSET;
+ if (!readmem(saved_sp_addr, KVADDR, &saved_sp, sizeof(saved_sp),
+ "saved task stack pointer", RETURN_ON_ERROR))
+ return FALSE;
+
+ task_stackbase = GET_STACKBASE(bt->task);
+ task_stacktop = GET_STACKTOP(bt->task);
+ if (saved_sp < task_stackbase || saved_sp >= task_stacktop)
+ return FALSE;
+
+ bt->stackbase = task_stackbase;
+ bt->stacktop = task_stacktop;
+ alter_stackbuf(bt);
+ if (saved_sp <= bt->stacktop - SIZE(pt_regs)) {
+ GET_STACK_DATA(saved_sp, pt_regs, sizeof(pt_regs));
+ regs = (struct loongarch64_pt_regs *)(pt_regs +
+ OFFSET(pt_regs_regs));
+ if (loongarch64_valid_saved_regs(bt, regs, &saved_pc)) {
+ current->pc = saved_pc;
+ current->sp = regs->regs[LOONGARCH64_EF_SP];
+ current->ra = regs->regs[LOONGARCH64_EF_RA];
+ current->fp = regs->regs[LOONGARCH64_EF_FP];
+ return TRUE;
+ }
+ }
+
+ current->sp = saved_sp;
+ current->ra = 0;
+ return loongarch64_find_next_kernel_text(bt, current);
+}
+
+static int
+loongarch64_find_next_kernel_text(struct bt_info *bt,
+ struct loongarch64_unwind_frame *current)
+{
+ ulong sp, pc;
+
+ if (!INSTACK(current->sp, bt))
+ return FALSE;
+
+ sp = current->sp + sizeof(ulong);
+ sp = roundup(sp, sizeof(ulong));
+ for (; sp < bt->stacktop; sp += sizeof(ulong)) {
+ GET_STACK_DATA(sp, &pc, sizeof(pc));
+ pc = loongarch64_exception_pc(bt->tc->processor, pc);
+ if (!loongarch64_is_unwind_text(pc))
+ continue;
+
+ current->pc = pc;
+ current->sp = sp;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
static int
loongarch64_orc_ip(ulong ip_entry_addr, ulong *ip)
{
@@ -1218,6 +1433,7 @@ loongarch64_stackframe_init(void)
MEMBER_OFFSET_INIT(elf_prstatus_pr_reg, "elf_prstatus", "pr_reg");
STRUCT_SIZE_INIT(note_buf, "note_buf_t");
+ loongarch64_irq_stack_init();
}
/*
--
2.43.0