18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci#include <linux/oprofile.h>
38c2ecf20Sopenharmony_ci#include <linux/sched.h>
48c2ecf20Sopenharmony_ci#include <linux/mm.h>
58c2ecf20Sopenharmony_ci#include <linux/uaccess.h>
68c2ecf20Sopenharmony_ci#include <asm/ptrace.h>
78c2ecf20Sopenharmony_ci#include <asm/stacktrace.h>
88c2ecf20Sopenharmony_ci#include <linux/stacktrace.h>
98c2ecf20Sopenharmony_ci#include <linux/kernel.h>
108c2ecf20Sopenharmony_ci#include <asm/sections.h>
118c2ecf20Sopenharmony_ci#include <asm/inst.h>
128c2ecf20Sopenharmony_ci
138c2ecf20Sopenharmony_cistruct stackframe {
148c2ecf20Sopenharmony_ci	unsigned long sp;
158c2ecf20Sopenharmony_ci	unsigned long pc;
168c2ecf20Sopenharmony_ci	unsigned long ra;
178c2ecf20Sopenharmony_ci};
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_cistatic inline int get_mem(unsigned long addr, unsigned long *result)
208c2ecf20Sopenharmony_ci{
218c2ecf20Sopenharmony_ci	unsigned long *address = (unsigned long *) addr;
228c2ecf20Sopenharmony_ci	if (!access_ok(address, sizeof(unsigned long)))
238c2ecf20Sopenharmony_ci		return -1;
248c2ecf20Sopenharmony_ci	if (__copy_from_user_inatomic(result, address, sizeof(unsigned long)))
258c2ecf20Sopenharmony_ci		return -3;
268c2ecf20Sopenharmony_ci	return 0;
278c2ecf20Sopenharmony_ci}
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_ci/*
308c2ecf20Sopenharmony_ci * These two instruction helpers were taken from process.c
318c2ecf20Sopenharmony_ci */
328c2ecf20Sopenharmony_cistatic inline int is_ra_save_ins(union mips_instruction *ip)
338c2ecf20Sopenharmony_ci{
348c2ecf20Sopenharmony_ci	/* sw / sd $ra, offset($sp) */
358c2ecf20Sopenharmony_ci	return (ip->i_format.opcode == sw_op || ip->i_format.opcode == sd_op)
368c2ecf20Sopenharmony_ci		&& ip->i_format.rs == 29 && ip->i_format.rt == 31;
378c2ecf20Sopenharmony_ci}
388c2ecf20Sopenharmony_ci
398c2ecf20Sopenharmony_cistatic inline int is_sp_move_ins(union mips_instruction *ip)
408c2ecf20Sopenharmony_ci{
418c2ecf20Sopenharmony_ci	/* addiu/daddiu sp,sp,-imm */
428c2ecf20Sopenharmony_ci	if (ip->i_format.rs != 29 || ip->i_format.rt != 29)
438c2ecf20Sopenharmony_ci		return 0;
448c2ecf20Sopenharmony_ci	if (ip->i_format.opcode == addiu_op || ip->i_format.opcode == daddiu_op)
458c2ecf20Sopenharmony_ci		return 1;
468c2ecf20Sopenharmony_ci	return 0;
478c2ecf20Sopenharmony_ci}
488c2ecf20Sopenharmony_ci
498c2ecf20Sopenharmony_ci/*
508c2ecf20Sopenharmony_ci * Looks for specific instructions that mark the end of a function.
518c2ecf20Sopenharmony_ci * This usually means we ran into the code area of the previous function.
528c2ecf20Sopenharmony_ci */
538c2ecf20Sopenharmony_cistatic inline int is_end_of_function_marker(union mips_instruction *ip)
548c2ecf20Sopenharmony_ci{
558c2ecf20Sopenharmony_ci	/* jr ra */
568c2ecf20Sopenharmony_ci	if (ip->r_format.func == jr_op && ip->r_format.rs == 31)
578c2ecf20Sopenharmony_ci		return 1;
588c2ecf20Sopenharmony_ci	/* lui gp */
598c2ecf20Sopenharmony_ci	if (ip->i_format.opcode == lui_op && ip->i_format.rt == 28)
608c2ecf20Sopenharmony_ci		return 1;
618c2ecf20Sopenharmony_ci	return 0;
628c2ecf20Sopenharmony_ci}
638c2ecf20Sopenharmony_ci
648c2ecf20Sopenharmony_ci/*
658c2ecf20Sopenharmony_ci * TODO for userspace stack unwinding:
668c2ecf20Sopenharmony_ci * - handle cases where the stack is adjusted inside a function
678c2ecf20Sopenharmony_ci *     (generally doesn't happen)
688c2ecf20Sopenharmony_ci * - find optimal value for max_instr_check
698c2ecf20Sopenharmony_ci * - try to find a better way to handle leaf functions
708c2ecf20Sopenharmony_ci */
718c2ecf20Sopenharmony_ci
728c2ecf20Sopenharmony_cistatic inline int unwind_user_frame(struct stackframe *old_frame,
738c2ecf20Sopenharmony_ci				    const unsigned int max_instr_check)
748c2ecf20Sopenharmony_ci{
758c2ecf20Sopenharmony_ci	struct stackframe new_frame = *old_frame;
768c2ecf20Sopenharmony_ci	off_t ra_offset = 0;
778c2ecf20Sopenharmony_ci	size_t stack_size = 0;
788c2ecf20Sopenharmony_ci	unsigned long addr;
798c2ecf20Sopenharmony_ci
808c2ecf20Sopenharmony_ci	if (old_frame->pc == 0 || old_frame->sp == 0 || old_frame->ra == 0)
818c2ecf20Sopenharmony_ci		return -9;
828c2ecf20Sopenharmony_ci
838c2ecf20Sopenharmony_ci	for (addr = new_frame.pc; (addr + max_instr_check > new_frame.pc)
848c2ecf20Sopenharmony_ci		&& (!ra_offset || !stack_size); --addr) {
858c2ecf20Sopenharmony_ci		union mips_instruction ip;
868c2ecf20Sopenharmony_ci
878c2ecf20Sopenharmony_ci		if (get_mem(addr, (unsigned long *) &ip))
888c2ecf20Sopenharmony_ci			return -11;
898c2ecf20Sopenharmony_ci
908c2ecf20Sopenharmony_ci		if (is_sp_move_ins(&ip)) {
918c2ecf20Sopenharmony_ci			int stack_adjustment = ip.i_format.simmediate;
928c2ecf20Sopenharmony_ci			if (stack_adjustment > 0)
938c2ecf20Sopenharmony_ci				/* This marks the end of the previous function,
948c2ecf20Sopenharmony_ci				   which means we overran. */
958c2ecf20Sopenharmony_ci				break;
968c2ecf20Sopenharmony_ci			stack_size = (unsigned long) stack_adjustment;
978c2ecf20Sopenharmony_ci		} else if (is_ra_save_ins(&ip)) {
988c2ecf20Sopenharmony_ci			int ra_slot = ip.i_format.simmediate;
998c2ecf20Sopenharmony_ci			if (ra_slot < 0)
1008c2ecf20Sopenharmony_ci				/* This shouldn't happen. */
1018c2ecf20Sopenharmony_ci				break;
1028c2ecf20Sopenharmony_ci			ra_offset = ra_slot;
1038c2ecf20Sopenharmony_ci		} else if (is_end_of_function_marker(&ip))
1048c2ecf20Sopenharmony_ci			break;
1058c2ecf20Sopenharmony_ci	}
1068c2ecf20Sopenharmony_ci
1078c2ecf20Sopenharmony_ci	if (!ra_offset || !stack_size)
1088c2ecf20Sopenharmony_ci		goto done;
1098c2ecf20Sopenharmony_ci
1108c2ecf20Sopenharmony_ci	if (ra_offset) {
1118c2ecf20Sopenharmony_ci		new_frame.ra = old_frame->sp + ra_offset;
1128c2ecf20Sopenharmony_ci		if (get_mem(new_frame.ra, &(new_frame.ra)))
1138c2ecf20Sopenharmony_ci			return -13;
1148c2ecf20Sopenharmony_ci	}
1158c2ecf20Sopenharmony_ci
1168c2ecf20Sopenharmony_ci	if (stack_size) {
1178c2ecf20Sopenharmony_ci		new_frame.sp = old_frame->sp + stack_size;
1188c2ecf20Sopenharmony_ci		if (get_mem(new_frame.sp, &(new_frame.sp)))
1198c2ecf20Sopenharmony_ci			return -14;
1208c2ecf20Sopenharmony_ci	}
1218c2ecf20Sopenharmony_ci
1228c2ecf20Sopenharmony_ci	if (new_frame.sp > old_frame->sp)
1238c2ecf20Sopenharmony_ci		return -2;
1248c2ecf20Sopenharmony_ci
1258c2ecf20Sopenharmony_cidone:
1268c2ecf20Sopenharmony_ci	new_frame.pc = old_frame->ra;
1278c2ecf20Sopenharmony_ci	*old_frame = new_frame;
1288c2ecf20Sopenharmony_ci
1298c2ecf20Sopenharmony_ci	return 0;
1308c2ecf20Sopenharmony_ci}
1318c2ecf20Sopenharmony_ci
1328c2ecf20Sopenharmony_cistatic inline void do_user_backtrace(unsigned long low_addr,
1338c2ecf20Sopenharmony_ci				     struct stackframe *frame,
1348c2ecf20Sopenharmony_ci				     unsigned int depth)
1358c2ecf20Sopenharmony_ci{
1368c2ecf20Sopenharmony_ci	const unsigned int max_instr_check = 512;
1378c2ecf20Sopenharmony_ci	const unsigned long high_addr = low_addr + THREAD_SIZE;
1388c2ecf20Sopenharmony_ci
1398c2ecf20Sopenharmony_ci	while (depth-- && !unwind_user_frame(frame, max_instr_check)) {
1408c2ecf20Sopenharmony_ci		oprofile_add_trace(frame->ra);
1418c2ecf20Sopenharmony_ci		if (frame->sp < low_addr || frame->sp > high_addr)
1428c2ecf20Sopenharmony_ci			break;
1438c2ecf20Sopenharmony_ci	}
1448c2ecf20Sopenharmony_ci}
1458c2ecf20Sopenharmony_ci
1468c2ecf20Sopenharmony_ci#ifndef CONFIG_KALLSYMS
1478c2ecf20Sopenharmony_cistatic inline void do_kernel_backtrace(unsigned long low_addr,
1488c2ecf20Sopenharmony_ci				       struct stackframe *frame,
1498c2ecf20Sopenharmony_ci				       unsigned int depth) { }
1508c2ecf20Sopenharmony_ci#else
1518c2ecf20Sopenharmony_cistatic inline void do_kernel_backtrace(unsigned long low_addr,
1528c2ecf20Sopenharmony_ci				       struct stackframe *frame,
1538c2ecf20Sopenharmony_ci				       unsigned int depth)
1548c2ecf20Sopenharmony_ci{
1558c2ecf20Sopenharmony_ci	while (depth-- && frame->pc) {
1568c2ecf20Sopenharmony_ci		frame->pc = unwind_stack_by_address(low_addr,
1578c2ecf20Sopenharmony_ci						    &(frame->sp),
1588c2ecf20Sopenharmony_ci						    frame->pc,
1598c2ecf20Sopenharmony_ci						    &(frame->ra));
1608c2ecf20Sopenharmony_ci		oprofile_add_trace(frame->ra);
1618c2ecf20Sopenharmony_ci	}
1628c2ecf20Sopenharmony_ci}
1638c2ecf20Sopenharmony_ci#endif
1648c2ecf20Sopenharmony_ci
1658c2ecf20Sopenharmony_civoid notrace op_mips_backtrace(struct pt_regs *const regs, unsigned int depth)
1668c2ecf20Sopenharmony_ci{
1678c2ecf20Sopenharmony_ci	struct stackframe frame = { .sp = regs->regs[29],
1688c2ecf20Sopenharmony_ci				    .pc = regs->cp0_epc,
1698c2ecf20Sopenharmony_ci				    .ra = regs->regs[31] };
1708c2ecf20Sopenharmony_ci	const int userspace = user_mode(regs);
1718c2ecf20Sopenharmony_ci	const unsigned long low_addr = ALIGN(frame.sp, THREAD_SIZE);
1728c2ecf20Sopenharmony_ci
1738c2ecf20Sopenharmony_ci	if (userspace)
1748c2ecf20Sopenharmony_ci		do_user_backtrace(low_addr, &frame, depth);
1758c2ecf20Sopenharmony_ci	else
1768c2ecf20Sopenharmony_ci		do_kernel_backtrace(low_addr, &frame, depth);
1778c2ecf20Sopenharmony_ci}
178