mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-10-05 16:37:50 +00:00
LoongArch fixes for v6.2-rc5
-----BEGIN PGP SIGNATURE----- iQJKBAABCAA0FiEEzOlt8mkP+tbeiYy5AoYrw/LiJnoFAmPHczkWHGNoZW5odWFj YWlAa2VybmVsLm9yZwAKCRAChivD8uImenqQD/9iviY7RBfifv3eHijZL6I1SCoW 1AhJc5/+BoW2z9X86BAJ1de+U5EZUl6iHSwsNTu4hm/xUPTliZyZsjQ2ODSBGCZQ 8FvNaLwXFwVgsPoVH7q1/tRUF4njqpQqb0NGhmCa9IQQTn2xrgaZOPOp0dU7OVqR uQOf318c8cG4smdecPqAo8uiAvH4IusHCB4MmPip4RiOIlH1Mq2qx9i6cRdJKpWl BOmT3chP3EJ6tyzOgfKxegHJ+p8q4Ex3WYmyNHkuxXTi85zQuWQzcyVgY3kQE622 U1D0yqZghuXkOQY0KzAYCvEGJkoUYF1b8owhF4sjg5qHxby5wLBKZEAFvyU7I0kM 8EVTQqm4d8T7AavXFTEou4GGl2tpHyDeiiTeZvSleQ1Usgd0WJ6Doems4Uo1+E+T CNcg3gPmhLHme9Z9GDV3jIcI+uBMN2Hvgjh3MO9nfQioRJUCE95oTSdHKqIPjfkI 3TdX04eyM7Ed4KHW/mfViByxd0v57zNXSpABasvNqN24CjDIIGFbM1GtKJyrHFCH dLdyDMOhnTNnlArtXS/vTzhM0zEw622ru97QzLN65Ezg3D3ZsVapafxcJheaEUha rdGptVTPY2GF8m2iqJ4KrVmnFVGm0XL4XijnW2Cco4LiVvn9NuvBUV4TP4aTsBtq 4bwSh20tQAjpGGgwSQ== =Qedu -----END PGP SIGNATURE----- Merge tag 'loongarch-fixes-6.2-1' of git://git.kernel.org/pub/scm/linux/kernel/git/chenhuacai/linux-loongson Pull LoongArch fixes from Huacai Chen: "Fix a missing elf_hwcap, fix some stack unwinder bugs and two trivial cleanups" * tag 'loongarch-fixes-6.2-1' of git://git.kernel.org/pub/scm/linux/kernel/git/chenhuacai/linux-loongson: LoongArch: Add generic ex-handler unwind in prologue unwinder LoongArch: Strip guess unwinder out from prologue unwinder LoongArch: Use correct sp value to get graph addr in stack unwinders LoongArch: Get frame info in unwind_start() when regs is not available LoongArch: Adjust PC value when unwind next frame in unwinder LoongArch: Simplify larch_insn_gen_xxx implementation LoongArch: Use common function sign_extend64() LoongArch: Add HWCAP_LOONGARCH_CPUCFG to elf_hwcap
This commit is contained in:
commit
84bd7e08a7
14 changed files with 249 additions and 215 deletions
|
@ -10,8 +10,6 @@
|
|||
#define FTRACE_REGS_PLT_IDX 1
|
||||
#define NR_FTRACE_PLTS 2
|
||||
|
||||
#define GRAPH_FAKE_OFFSET (sizeof(struct pt_regs) - offsetof(struct pt_regs, regs[1]))
|
||||
|
||||
#ifdef CONFIG_FUNCTION_TRACER
|
||||
|
||||
#define MCOUNT_INSN_SIZE 4 /* sizeof mcount call */
|
||||
|
|
|
@ -377,14 +377,6 @@ static inline bool unsigned_imm_check(unsigned long val, unsigned int bit)
|
|||
return val < (1UL << bit);
|
||||
}
|
||||
|
||||
static inline unsigned long sign_extend(unsigned long val, unsigned int idx)
|
||||
{
|
||||
if (!is_imm_negative(val, idx + 1))
|
||||
return ((1UL << idx) - 1) & val;
|
||||
else
|
||||
return ~((1UL << idx) - 1) | val;
|
||||
}
|
||||
|
||||
#define DEF_EMIT_REG0I26_FORMAT(NAME, OP) \
|
||||
static inline void emit_##NAME(union loongarch_instruction *insn, \
|
||||
int offset) \
|
||||
|
@ -401,6 +393,7 @@ static inline void emit_##NAME(union loongarch_instruction *insn, \
|
|||
}
|
||||
|
||||
DEF_EMIT_REG0I26_FORMAT(b, b_op)
|
||||
DEF_EMIT_REG0I26_FORMAT(bl, bl_op)
|
||||
|
||||
#define DEF_EMIT_REG1I20_FORMAT(NAME, OP) \
|
||||
static inline void emit_##NAME(union loongarch_instruction *insn, \
|
||||
|
|
|
@ -8,7 +8,9 @@
|
|||
#define _ASM_UNWIND_H
|
||||
|
||||
#include <linux/sched.h>
|
||||
#include <linux/ftrace.h>
|
||||
|
||||
#include <asm/ptrace.h>
|
||||
#include <asm/stacktrace.h>
|
||||
|
||||
enum unwinder_type {
|
||||
|
@ -20,11 +22,13 @@ struct unwind_state {
|
|||
char type; /* UNWINDER_XXX */
|
||||
struct stack_info stack_info;
|
||||
struct task_struct *task;
|
||||
bool first, error, is_ftrace;
|
||||
bool first, error, reset;
|
||||
int graph_idx;
|
||||
unsigned long sp, pc, ra;
|
||||
};
|
||||
|
||||
bool default_next_frame(struct unwind_state *state);
|
||||
|
||||
void unwind_start(struct unwind_state *state,
|
||||
struct task_struct *task, struct pt_regs *regs);
|
||||
bool unwind_next_frame(struct unwind_state *state);
|
||||
|
@ -40,4 +44,39 @@ static inline bool unwind_error(struct unwind_state *state)
|
|||
return state->error;
|
||||
}
|
||||
|
||||
#define GRAPH_FAKE_OFFSET (sizeof(struct pt_regs) - offsetof(struct pt_regs, regs[1]))
|
||||
|
||||
static inline unsigned long unwind_graph_addr(struct unwind_state *state,
|
||||
unsigned long pc, unsigned long cfa)
|
||||
{
|
||||
return ftrace_graph_ret_addr(state->task, &state->graph_idx,
|
||||
pc, (unsigned long *)(cfa - GRAPH_FAKE_OFFSET));
|
||||
}
|
||||
|
||||
static __always_inline void __unwind_start(struct unwind_state *state,
|
||||
struct task_struct *task, struct pt_regs *regs)
|
||||
{
|
||||
memset(state, 0, sizeof(*state));
|
||||
if (regs) {
|
||||
state->sp = regs->regs[3];
|
||||
state->pc = regs->csr_era;
|
||||
state->ra = regs->regs[1];
|
||||
} else if (task && task != current) {
|
||||
state->sp = thread_saved_fp(task);
|
||||
state->pc = thread_saved_ra(task);
|
||||
state->ra = 0;
|
||||
} else {
|
||||
state->sp = (unsigned long)__builtin_frame_address(0);
|
||||
state->pc = (unsigned long)__builtin_return_address(0);
|
||||
state->ra = 0;
|
||||
}
|
||||
state->task = task;
|
||||
get_stack_info(state->sp, state->task, &state->stack_info);
|
||||
state->pc = unwind_graph_addr(state, state->pc, state->sp);
|
||||
}
|
||||
|
||||
static __always_inline unsigned long __unwind_get_return_address(struct unwind_state *state)
|
||||
{
|
||||
return unwind_done(state) ? 0 : state->pc;
|
||||
}
|
||||
#endif /* _ASM_UNWIND_H */
|
||||
|
|
|
@ -8,7 +8,7 @@ extra-y := vmlinux.lds
|
|||
obj-y += head.o cpu-probe.o cacheinfo.o env.o setup.o entry.o genex.o \
|
||||
traps.o irq.o idle.o process.o dma.o mem.o io.o reset.o switch.o \
|
||||
elf.o syscall.o signal.o time.o topology.o inst.o ptrace.o vdso.o \
|
||||
alternative.o unaligned.o
|
||||
alternative.o unaligned.o unwind.o
|
||||
|
||||
obj-$(CONFIG_ACPI) += acpi.o
|
||||
obj-$(CONFIG_EFI) += efi.o
|
||||
|
|
|
@ -74,7 +74,7 @@ static void __init_or_module recompute_jump(union loongarch_instruction *buf,
|
|||
switch (src->reg0i26_format.opcode) {
|
||||
case b_op:
|
||||
case bl_op:
|
||||
jump_addr = cur_pc + sign_extend((si_h << 16 | si_l) << 2, 27);
|
||||
jump_addr = cur_pc + sign_extend64((si_h << 16 | si_l) << 2, 27);
|
||||
if (in_alt_jump(jump_addr, start, end))
|
||||
return;
|
||||
offset = jump_addr - pc;
|
||||
|
@ -93,7 +93,7 @@ static void __init_or_module recompute_jump(union loongarch_instruction *buf,
|
|||
fallthrough;
|
||||
case beqz_op:
|
||||
case bnez_op:
|
||||
jump_addr = cur_pc + sign_extend((si_h << 16 | si_l) << 2, 22);
|
||||
jump_addr = cur_pc + sign_extend64((si_h << 16 | si_l) << 2, 22);
|
||||
if (in_alt_jump(jump_addr, start, end))
|
||||
return;
|
||||
offset = jump_addr - pc;
|
||||
|
@ -112,7 +112,7 @@ static void __init_or_module recompute_jump(union loongarch_instruction *buf,
|
|||
case bge_op:
|
||||
case bltu_op:
|
||||
case bgeu_op:
|
||||
jump_addr = cur_pc + sign_extend(si << 2, 17);
|
||||
jump_addr = cur_pc + sign_extend64(si << 2, 17);
|
||||
if (in_alt_jump(jump_addr, start, end))
|
||||
return;
|
||||
offset = jump_addr - pc;
|
||||
|
|
|
@ -94,7 +94,7 @@ static void cpu_probe_common(struct cpuinfo_loongarch *c)
|
|||
c->options = LOONGARCH_CPU_CPUCFG | LOONGARCH_CPU_CSR |
|
||||
LOONGARCH_CPU_TLB | LOONGARCH_CPU_VINT | LOONGARCH_CPU_WATCH;
|
||||
|
||||
elf_hwcap |= HWCAP_LOONGARCH_CRC32;
|
||||
elf_hwcap = HWCAP_LOONGARCH_CPUCFG | HWCAP_LOONGARCH_CRC32;
|
||||
|
||||
config = read_cpucfg(LOONGARCH_CPUCFG1);
|
||||
if (config & CPUCFG1_UAL) {
|
||||
|
|
|
@ -67,14 +67,17 @@ SYM_FUNC_END(except_vec_cex)
|
|||
.macro BUILD_HANDLER exception handler prep
|
||||
.align 5
|
||||
SYM_FUNC_START(handle_\exception)
|
||||
666:
|
||||
BACKUP_T0T1
|
||||
SAVE_ALL
|
||||
build_prep_\prep
|
||||
move a0, sp
|
||||
la.abs t0, do_\handler
|
||||
jirl ra, t0, 0
|
||||
668:
|
||||
RESTORE_ALL_AND_RET
|
||||
SYM_FUNC_END(handle_\exception)
|
||||
SYM_DATA(unwind_hint_\exception, .word 668b - 666b)
|
||||
.endm
|
||||
|
||||
BUILD_HANDLER ade ade badv
|
||||
|
|
|
@ -58,7 +58,6 @@ u32 larch_insn_gen_nop(void)
|
|||
u32 larch_insn_gen_b(unsigned long pc, unsigned long dest)
|
||||
{
|
||||
long offset = dest - pc;
|
||||
unsigned int immediate_l, immediate_h;
|
||||
union loongarch_instruction insn;
|
||||
|
||||
if ((offset & 3) || offset < -SZ_128M || offset >= SZ_128M) {
|
||||
|
@ -66,15 +65,7 @@ u32 larch_insn_gen_b(unsigned long pc, unsigned long dest)
|
|||
return INSN_BREAK;
|
||||
}
|
||||
|
||||
offset >>= 2;
|
||||
|
||||
immediate_l = offset & 0xffff;
|
||||
offset >>= 16;
|
||||
immediate_h = offset & 0x3ff;
|
||||
|
||||
insn.reg0i26_format.opcode = b_op;
|
||||
insn.reg0i26_format.immediate_l = immediate_l;
|
||||
insn.reg0i26_format.immediate_h = immediate_h;
|
||||
emit_b(&insn, offset >> 2);
|
||||
|
||||
return insn.word;
|
||||
}
|
||||
|
@ -82,7 +73,6 @@ u32 larch_insn_gen_b(unsigned long pc, unsigned long dest)
|
|||
u32 larch_insn_gen_bl(unsigned long pc, unsigned long dest)
|
||||
{
|
||||
long offset = dest - pc;
|
||||
unsigned int immediate_l, immediate_h;
|
||||
union loongarch_instruction insn;
|
||||
|
||||
if ((offset & 3) || offset < -SZ_128M || offset >= SZ_128M) {
|
||||
|
@ -90,15 +80,7 @@ u32 larch_insn_gen_bl(unsigned long pc, unsigned long dest)
|
|||
return INSN_BREAK;
|
||||
}
|
||||
|
||||
offset >>= 2;
|
||||
|
||||
immediate_l = offset & 0xffff;
|
||||
offset >>= 16;
|
||||
immediate_h = offset & 0x3ff;
|
||||
|
||||
insn.reg0i26_format.opcode = bl_op;
|
||||
insn.reg0i26_format.immediate_l = immediate_l;
|
||||
insn.reg0i26_format.immediate_h = immediate_h;
|
||||
emit_bl(&insn, offset >> 2);
|
||||
|
||||
return insn.word;
|
||||
}
|
||||
|
@ -107,10 +89,7 @@ u32 larch_insn_gen_or(enum loongarch_gpr rd, enum loongarch_gpr rj, enum loongar
|
|||
{
|
||||
union loongarch_instruction insn;
|
||||
|
||||
insn.reg3_format.opcode = or_op;
|
||||
insn.reg3_format.rd = rd;
|
||||
insn.reg3_format.rj = rj;
|
||||
insn.reg3_format.rk = rk;
|
||||
emit_or(&insn, rd, rj, rk);
|
||||
|
||||
return insn.word;
|
||||
}
|
||||
|
@ -124,9 +103,7 @@ u32 larch_insn_gen_lu12iw(enum loongarch_gpr rd, int imm)
|
|||
{
|
||||
union loongarch_instruction insn;
|
||||
|
||||
insn.reg1i20_format.opcode = lu12iw_op;
|
||||
insn.reg1i20_format.rd = rd;
|
||||
insn.reg1i20_format.immediate = imm;
|
||||
emit_lu12iw(&insn, rd, imm);
|
||||
|
||||
return insn.word;
|
||||
}
|
||||
|
@ -135,9 +112,7 @@ u32 larch_insn_gen_lu32id(enum loongarch_gpr rd, int imm)
|
|||
{
|
||||
union loongarch_instruction insn;
|
||||
|
||||
insn.reg1i20_format.opcode = lu32id_op;
|
||||
insn.reg1i20_format.rd = rd;
|
||||
insn.reg1i20_format.immediate = imm;
|
||||
emit_lu32id(&insn, rd, imm);
|
||||
|
||||
return insn.word;
|
||||
}
|
||||
|
@ -146,10 +121,7 @@ u32 larch_insn_gen_lu52id(enum loongarch_gpr rd, enum loongarch_gpr rj, int imm)
|
|||
{
|
||||
union loongarch_instruction insn;
|
||||
|
||||
insn.reg2i12_format.opcode = lu52id_op;
|
||||
insn.reg2i12_format.rd = rd;
|
||||
insn.reg2i12_format.rj = rj;
|
||||
insn.reg2i12_format.immediate = imm;
|
||||
emit_lu52id(&insn, rd, rj, imm);
|
||||
|
||||
return insn.word;
|
||||
}
|
||||
|
@ -158,10 +130,7 @@ u32 larch_insn_gen_jirl(enum loongarch_gpr rd, enum loongarch_gpr rj, unsigned l
|
|||
{
|
||||
union loongarch_instruction insn;
|
||||
|
||||
insn.reg2i16_format.opcode = jirl_op;
|
||||
insn.reg2i16_format.rd = rd;
|
||||
insn.reg2i16_format.rj = rj;
|
||||
insn.reg2i16_format.immediate = (dest - pc) >> 2;
|
||||
emit_jirl(&insn, rj, rd, (dest - pc) >> 2);
|
||||
|
||||
return insn.word;
|
||||
}
|
||||
|
|
|
@ -191,20 +191,14 @@ int copy_thread(struct task_struct *p, const struct kernel_clone_args *args)
|
|||
|
||||
unsigned long __get_wchan(struct task_struct *task)
|
||||
{
|
||||
unsigned long pc;
|
||||
unsigned long pc = 0;
|
||||
struct unwind_state state;
|
||||
|
||||
if (!try_get_task_stack(task))
|
||||
return 0;
|
||||
|
||||
unwind_start(&state, task, NULL);
|
||||
state.sp = thread_saved_fp(task);
|
||||
get_stack_info(state.sp, state.task, &state.stack_info);
|
||||
state.pc = thread_saved_ra(task);
|
||||
#ifdef CONFIG_UNWINDER_PROLOGUE
|
||||
state.type = UNWINDER_PROLOGUE;
|
||||
#endif
|
||||
for (; !unwind_done(&state); unwind_next_frame(&state)) {
|
||||
for (unwind_start(&state, task, NULL);
|
||||
!unwind_done(&state); unwind_next_frame(&state)) {
|
||||
pc = unwind_get_return_address(&state);
|
||||
if (!pc)
|
||||
break;
|
||||
|
|
|
@ -72,9 +72,6 @@ static void show_backtrace(struct task_struct *task, const struct pt_regs *regs,
|
|||
if (!task)
|
||||
task = current;
|
||||
|
||||
if (user_mode(regs))
|
||||
state.type = UNWINDER_GUESS;
|
||||
|
||||
printk("%sCall Trace:", loglvl);
|
||||
for (unwind_start(&state, task, pregs);
|
||||
!unwind_done(&state); unwind_next_frame(&state)) {
|
||||
|
|
32
arch/loongarch/kernel/unwind.c
Normal file
32
arch/loongarch/kernel/unwind.c
Normal file
|
@ -0,0 +1,32 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (C) 2022-2023 Loongson Technology Corporation Limited
|
||||
*/
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/ftrace.h>
|
||||
|
||||
#include <asm/unwind.h>
|
||||
|
||||
bool default_next_frame(struct unwind_state *state)
|
||||
{
|
||||
struct stack_info *info = &state->stack_info;
|
||||
unsigned long addr;
|
||||
|
||||
if (unwind_done(state))
|
||||
return false;
|
||||
|
||||
do {
|
||||
for (state->sp += sizeof(unsigned long);
|
||||
state->sp < info->end; state->sp += sizeof(unsigned long)) {
|
||||
addr = *(unsigned long *)(state->sp);
|
||||
state->pc = unwind_graph_addr(state, addr, state->sp + 8);
|
||||
if (__kernel_text_address(state->pc))
|
||||
return true;
|
||||
}
|
||||
|
||||
state->sp = info->next_sp;
|
||||
|
||||
} while (!get_stack_info(state->sp, state->task, info));
|
||||
|
||||
return false;
|
||||
}
|
|
@ -2,37 +2,18 @@
|
|||
/*
|
||||
* Copyright (C) 2022 Loongson Technology Corporation Limited
|
||||
*/
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/ftrace.h>
|
||||
|
||||
#include <asm/unwind.h>
|
||||
|
||||
unsigned long unwind_get_return_address(struct unwind_state *state)
|
||||
{
|
||||
if (unwind_done(state))
|
||||
return 0;
|
||||
else if (state->first)
|
||||
return state->pc;
|
||||
|
||||
return *(unsigned long *)(state->sp);
|
||||
return __unwind_get_return_address(state);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(unwind_get_return_address);
|
||||
|
||||
void unwind_start(struct unwind_state *state, struct task_struct *task,
|
||||
struct pt_regs *regs)
|
||||
{
|
||||
memset(state, 0, sizeof(*state));
|
||||
|
||||
if (regs) {
|
||||
state->sp = regs->regs[3];
|
||||
state->pc = regs->csr_era;
|
||||
}
|
||||
|
||||
state->task = task;
|
||||
state->first = true;
|
||||
|
||||
get_stack_info(state->sp, state->task, &state->stack_info);
|
||||
|
||||
__unwind_start(state, task, regs);
|
||||
if (!unwind_done(state) && !__kernel_text_address(state->pc))
|
||||
unwind_next_frame(state);
|
||||
}
|
||||
|
@ -40,30 +21,6 @@ EXPORT_SYMBOL_GPL(unwind_start);
|
|||
|
||||
bool unwind_next_frame(struct unwind_state *state)
|
||||
{
|
||||
struct stack_info *info = &state->stack_info;
|
||||
unsigned long addr;
|
||||
|
||||
if (unwind_done(state))
|
||||
return false;
|
||||
|
||||
if (state->first)
|
||||
state->first = false;
|
||||
|
||||
do {
|
||||
for (state->sp += sizeof(unsigned long);
|
||||
state->sp < info->end;
|
||||
state->sp += sizeof(unsigned long)) {
|
||||
addr = *(unsigned long *)(state->sp);
|
||||
state->pc = ftrace_graph_ret_addr(state->task, &state->graph_idx,
|
||||
addr, (unsigned long *)(state->sp - GRAPH_FAKE_OFFSET));
|
||||
if (__kernel_text_address(addr))
|
||||
return true;
|
||||
}
|
||||
|
||||
state->sp = info->next_sp;
|
||||
|
||||
} while (!get_stack_info(state->sp, state->task, info));
|
||||
|
||||
return false;
|
||||
return default_next_frame(state);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(unwind_next_frame);
|
||||
|
|
|
@ -2,61 +2,116 @@
|
|||
/*
|
||||
* Copyright (C) 2022 Loongson Technology Corporation Limited
|
||||
*/
|
||||
#include <linux/cpumask.h>
|
||||
#include <linux/ftrace.h>
|
||||
#include <linux/kallsyms.h>
|
||||
|
||||
#include <asm/inst.h>
|
||||
#include <asm/loongson.h>
|
||||
#include <asm/ptrace.h>
|
||||
#include <asm/setup.h>
|
||||
#include <asm/unwind.h>
|
||||
|
||||
static inline void unwind_state_fixup(struct unwind_state *state)
|
||||
extern const int unwind_hint_ade;
|
||||
extern const int unwind_hint_ale;
|
||||
extern const int unwind_hint_bp;
|
||||
extern const int unwind_hint_fpe;
|
||||
extern const int unwind_hint_fpu;
|
||||
extern const int unwind_hint_lsx;
|
||||
extern const int unwind_hint_lasx;
|
||||
extern const int unwind_hint_lbt;
|
||||
extern const int unwind_hint_ri;
|
||||
extern const int unwind_hint_watch;
|
||||
extern unsigned long eentry;
|
||||
#ifdef CONFIG_NUMA
|
||||
extern unsigned long pcpu_handlers[NR_CPUS];
|
||||
#endif
|
||||
|
||||
static inline bool scan_handlers(unsigned long entry_offset)
|
||||
{
|
||||
int idx, offset;
|
||||
|
||||
if (entry_offset >= EXCCODE_INT_START * VECSIZE)
|
||||
return false;
|
||||
|
||||
idx = entry_offset / VECSIZE;
|
||||
offset = entry_offset % VECSIZE;
|
||||
switch (idx) {
|
||||
case EXCCODE_ADE:
|
||||
return offset == unwind_hint_ade;
|
||||
case EXCCODE_ALE:
|
||||
return offset == unwind_hint_ale;
|
||||
case EXCCODE_BP:
|
||||
return offset == unwind_hint_bp;
|
||||
case EXCCODE_FPE:
|
||||
return offset == unwind_hint_fpe;
|
||||
case EXCCODE_FPDIS:
|
||||
return offset == unwind_hint_fpu;
|
||||
case EXCCODE_LSXDIS:
|
||||
return offset == unwind_hint_lsx;
|
||||
case EXCCODE_LASXDIS:
|
||||
return offset == unwind_hint_lasx;
|
||||
case EXCCODE_BTDIS:
|
||||
return offset == unwind_hint_lbt;
|
||||
case EXCCODE_INE:
|
||||
return offset == unwind_hint_ri;
|
||||
case EXCCODE_WATCH:
|
||||
return offset == unwind_hint_watch;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static inline bool fix_exception(unsigned long pc)
|
||||
{
|
||||
#ifdef CONFIG_NUMA
|
||||
int cpu;
|
||||
|
||||
for_each_possible_cpu(cpu) {
|
||||
if (!pcpu_handlers[cpu])
|
||||
continue;
|
||||
if (scan_handlers(pc - pcpu_handlers[cpu]))
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
return scan_handlers(pc - eentry);
|
||||
}
|
||||
|
||||
/*
|
||||
* As we meet ftrace_regs_entry, reset first flag like first doing
|
||||
* tracing. Prologue analysis will stop soon because PC is at entry.
|
||||
*/
|
||||
static inline bool fix_ftrace(unsigned long pc)
|
||||
{
|
||||
#ifdef CONFIG_DYNAMIC_FTRACE
|
||||
static unsigned long ftrace = (unsigned long)ftrace_call + 4;
|
||||
|
||||
if (state->pc == ftrace)
|
||||
state->is_ftrace = true;
|
||||
return pc == (unsigned long)ftrace_call + LOONGARCH_INSN_SIZE;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
unsigned long unwind_get_return_address(struct unwind_state *state)
|
||||
static inline bool unwind_state_fixup(struct unwind_state *state)
|
||||
{
|
||||
if (!fix_exception(state->pc) && !fix_ftrace(state->pc))
|
||||
return false;
|
||||
|
||||
if (unwind_done(state))
|
||||
return 0;
|
||||
else if (state->type)
|
||||
return state->pc;
|
||||
else if (state->first)
|
||||
return state->pc;
|
||||
|
||||
return *(unsigned long *)(state->sp);
|
||||
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(unwind_get_return_address);
|
||||
|
||||
static bool unwind_by_guess(struct unwind_state *state)
|
||||
{
|
||||
struct stack_info *info = &state->stack_info;
|
||||
unsigned long addr;
|
||||
|
||||
for (state->sp += sizeof(unsigned long);
|
||||
state->sp < info->end;
|
||||
state->sp += sizeof(unsigned long)) {
|
||||
addr = *(unsigned long *)(state->sp);
|
||||
state->pc = ftrace_graph_ret_addr(state->task, &state->graph_idx,
|
||||
addr, (unsigned long *)(state->sp - GRAPH_FAKE_OFFSET));
|
||||
if (__kernel_text_address(addr))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
state->reset = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* LoongArch function prologue is like follows,
|
||||
* [instructions not use stack var]
|
||||
* addi.d sp, sp, -imm
|
||||
* st.d xx, sp, offset <- save callee saved regs and
|
||||
* st.d yy, sp, offset save ra if function is nest.
|
||||
* [others instructions]
|
||||
*/
|
||||
static bool unwind_by_prologue(struct unwind_state *state)
|
||||
{
|
||||
long frame_ra = -1;
|
||||
unsigned long frame_size = 0;
|
||||
unsigned long size, offset, pc = state->pc;
|
||||
unsigned long size, offset, pc;
|
||||
struct pt_regs *regs;
|
||||
struct stack_info *info = &state->stack_info;
|
||||
union loongarch_instruction *ip, *ip_end;
|
||||
|
@ -64,20 +119,21 @@ static bool unwind_by_prologue(struct unwind_state *state)
|
|||
if (state->sp >= info->end || state->sp < info->begin)
|
||||
return false;
|
||||
|
||||
if (state->is_ftrace) {
|
||||
/*
|
||||
* As we meet ftrace_regs_entry, reset first flag like first doing
|
||||
* tracing. Prologue analysis will stop soon because PC is at entry.
|
||||
*/
|
||||
if (state->reset) {
|
||||
regs = (struct pt_regs *)state->sp;
|
||||
state->first = true;
|
||||
state->is_ftrace = false;
|
||||
state->reset = false;
|
||||
state->pc = regs->csr_era;
|
||||
state->ra = regs->regs[1];
|
||||
state->sp = regs->regs[3];
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* When first is not set, the PC is a return address in the previous frame.
|
||||
* We need to adjust its value in case overflow to the next symbol.
|
||||
*/
|
||||
pc = state->pc - (state->first ? 0 : LOONGARCH_INSN_SIZE);
|
||||
if (!kallsyms_lookup_size_offset(pc, &size, &offset))
|
||||
return false;
|
||||
|
||||
|
@ -93,6 +149,10 @@ static bool unwind_by_prologue(struct unwind_state *state)
|
|||
ip++;
|
||||
}
|
||||
|
||||
/*
|
||||
* Can't find stack alloc action, PC may be in a leaf function. Only the
|
||||
* first being true is reasonable, otherwise indicate analysis is broken.
|
||||
*/
|
||||
if (!frame_size) {
|
||||
if (state->first)
|
||||
goto first;
|
||||
|
@ -110,6 +170,7 @@ static bool unwind_by_prologue(struct unwind_state *state)
|
|||
ip++;
|
||||
}
|
||||
|
||||
/* Can't find save $ra action, PC may be in a leaf function, too. */
|
||||
if (frame_ra < 0) {
|
||||
if (state->first) {
|
||||
state->sp = state->sp + frame_size;
|
||||
|
@ -118,88 +179,47 @@ static bool unwind_by_prologue(struct unwind_state *state)
|
|||
return false;
|
||||
}
|
||||
|
||||
if (state->first)
|
||||
state->first = false;
|
||||
|
||||
state->pc = *(unsigned long *)(state->sp + frame_ra);
|
||||
state->sp = state->sp + frame_size;
|
||||
goto out;
|
||||
|
||||
first:
|
||||
state->first = false;
|
||||
if (state->pc == state->ra)
|
||||
return false;
|
||||
|
||||
state->pc = state->ra;
|
||||
|
||||
out:
|
||||
unwind_state_fixup(state);
|
||||
return !!__kernel_text_address(state->pc);
|
||||
state->first = false;
|
||||
return unwind_state_fixup(state) || __kernel_text_address(state->pc);
|
||||
}
|
||||
|
||||
void unwind_start(struct unwind_state *state, struct task_struct *task,
|
||||
struct pt_regs *regs)
|
||||
static bool next_frame(struct unwind_state *state)
|
||||
{
|
||||
memset(state, 0, sizeof(*state));
|
||||
|
||||
if (regs && __kernel_text_address(regs->csr_era)) {
|
||||
state->pc = regs->csr_era;
|
||||
state->sp = regs->regs[3];
|
||||
state->ra = regs->regs[1];
|
||||
state->type = UNWINDER_PROLOGUE;
|
||||
}
|
||||
|
||||
state->task = task;
|
||||
state->first = true;
|
||||
|
||||
get_stack_info(state->sp, state->task, &state->stack_info);
|
||||
|
||||
if (!unwind_done(state) && !__kernel_text_address(state->pc))
|
||||
unwind_next_frame(state);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(unwind_start);
|
||||
|
||||
bool unwind_next_frame(struct unwind_state *state)
|
||||
{
|
||||
struct stack_info *info = &state->stack_info;
|
||||
struct pt_regs *regs;
|
||||
unsigned long pc;
|
||||
struct pt_regs *regs;
|
||||
struct stack_info *info = &state->stack_info;
|
||||
|
||||
if (unwind_done(state))
|
||||
return false;
|
||||
|
||||
do {
|
||||
switch (state->type) {
|
||||
case UNWINDER_GUESS:
|
||||
state->first = false;
|
||||
if (unwind_by_guess(state))
|
||||
return true;
|
||||
break;
|
||||
if (unwind_by_prologue(state)) {
|
||||
state->pc = unwind_graph_addr(state, state->pc, state->sp);
|
||||
return true;
|
||||
}
|
||||
|
||||
case UNWINDER_PROLOGUE:
|
||||
if (unwind_by_prologue(state)) {
|
||||
state->pc = ftrace_graph_ret_addr(state->task, &state->graph_idx,
|
||||
state->pc, (unsigned long *)(state->sp - GRAPH_FAKE_OFFSET));
|
||||
return true;
|
||||
}
|
||||
if (info->type == STACK_TYPE_IRQ && info->end == state->sp) {
|
||||
regs = (struct pt_regs *)info->next_sp;
|
||||
pc = regs->csr_era;
|
||||
|
||||
if (info->type == STACK_TYPE_IRQ &&
|
||||
info->end == state->sp) {
|
||||
regs = (struct pt_regs *)info->next_sp;
|
||||
pc = regs->csr_era;
|
||||
if (user_mode(regs) || !__kernel_text_address(pc))
|
||||
return false;
|
||||
|
||||
if (user_mode(regs) || !__kernel_text_address(pc))
|
||||
return false;
|
||||
state->first = true;
|
||||
state->pc = pc;
|
||||
state->ra = regs->regs[1];
|
||||
state->sp = regs->regs[3];
|
||||
get_stack_info(state->sp, state->task, info);
|
||||
|
||||
state->first = true;
|
||||
state->ra = regs->regs[1];
|
||||
state->sp = regs->regs[3];
|
||||
state->pc = ftrace_graph_ret_addr(state->task, &state->graph_idx,
|
||||
pc, (unsigned long *)(state->sp - GRAPH_FAKE_OFFSET));
|
||||
get_stack_info(state->sp, state->task, info);
|
||||
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
state->sp = info->next_sp;
|
||||
|
@ -208,4 +228,36 @@ bool unwind_next_frame(struct unwind_state *state)
|
|||
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned long unwind_get_return_address(struct unwind_state *state)
|
||||
{
|
||||
return __unwind_get_return_address(state);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(unwind_get_return_address);
|
||||
|
||||
void unwind_start(struct unwind_state *state, struct task_struct *task,
|
||||
struct pt_regs *regs)
|
||||
{
|
||||
__unwind_start(state, task, regs);
|
||||
state->type = UNWINDER_PROLOGUE;
|
||||
state->first = true;
|
||||
|
||||
/*
|
||||
* The current PC is not kernel text address, we cannot find its
|
||||
* relative symbol. Thus, prologue analysis will be broken. Luckily,
|
||||
* we can use the default_next_frame().
|
||||
*/
|
||||
if (!__kernel_text_address(state->pc)) {
|
||||
state->type = UNWINDER_GUESS;
|
||||
if (!unwind_done(state))
|
||||
unwind_next_frame(state);
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(unwind_start);
|
||||
|
||||
bool unwind_next_frame(struct unwind_state *state)
|
||||
{
|
||||
return state->type == UNWINDER_PROLOGUE ?
|
||||
next_frame(state) : default_next_frame(state);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(unwind_next_frame);
|
||||
|
|
|
@ -251,7 +251,7 @@ static void output_pgtable_bits_defines(void)
|
|||
}
|
||||
|
||||
#ifdef CONFIG_NUMA
|
||||
static unsigned long pcpu_handlers[NR_CPUS];
|
||||
unsigned long pcpu_handlers[NR_CPUS];
|
||||
#endif
|
||||
extern long exception_handlers[VECSIZE * 128 / sizeof(long)];
|
||||
|
||||
|
|
Loading…
Reference in a new issue