2005-04-16 16:20:36 -06:00
|
|
|
/*
|
|
|
|
* This file is subject to the terms and conditions of the GNU General Public
|
|
|
|
* License. See the file "COPYING" in the main directory of this archive
|
|
|
|
* for more details.
|
|
|
|
*
|
|
|
|
* Copyright (C) 1994 - 1999, 2000 by Ralf Baechle and others.
|
2006-02-08 06:38:18 -07:00
|
|
|
* Copyright (C) 2005, 2006 by Ralf Baechle (ralf@linux-mips.org)
|
2005-04-16 16:20:36 -06:00
|
|
|
* Copyright (C) 1999, 2000 Silicon Graphics, Inc.
|
|
|
|
* Copyright (C) 2004 Thiemo Seufer
|
2013-03-25 12:18:07 -06:00
|
|
|
* Copyright (C) 2013 Imagination Technologies Ltd.
|
2005-04-16 16:20:36 -06:00
|
|
|
*/
|
|
|
|
#include <linux/errno.h>
|
|
|
|
#include <linux/sched.h>
|
2007-10-11 16:46:09 -06:00
|
|
|
#include <linux/tick.h>
|
2005-04-16 16:20:36 -06:00
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/mm.h>
|
|
|
|
#include <linux/stddef.h>
|
|
|
|
#include <linux/unistd.h>
|
2011-07-28 16:46:31 -06:00
|
|
|
#include <linux/export.h>
|
2005-04-16 16:20:36 -06:00
|
|
|
#include <linux/ptrace.h>
|
|
|
|
#include <linux/mman.h>
|
|
|
|
#include <linux/personality.h>
|
|
|
|
#include <linux/sys.h>
|
|
|
|
#include <linux/user.h>
|
|
|
|
#include <linux/init.h>
|
|
|
|
#include <linux/completion.h>
|
2006-02-07 09:48:03 -07:00
|
|
|
#include <linux/kallsyms.h>
|
2007-07-19 06:04:21 -06:00
|
|
|
#include <linux/random.h>
|
2005-04-16 16:20:36 -06:00
|
|
|
|
2007-07-19 06:04:21 -06:00
|
|
|
#include <asm/asm.h>
|
2005-04-16 16:20:36 -06:00
|
|
|
#include <asm/bootinfo.h>
|
|
|
|
#include <asm/cpu.h>
|
2005-05-31 05:49:19 -06:00
|
|
|
#include <asm/dsp.h>
|
2005-04-16 16:20:36 -06:00
|
|
|
#include <asm/fpu.h>
|
|
|
|
#include <asm/pgtable.h>
|
|
|
|
#include <asm/mipsregs.h>
|
|
|
|
#include <asm/processor.h>
|
|
|
|
#include <asm/uaccess.h>
|
|
|
|
#include <asm/io.h>
|
|
|
|
#include <asm/elf.h>
|
|
|
|
#include <asm/isadep.h>
|
|
|
|
#include <asm/inst.h>
|
2006-09-26 08:44:01 -06:00
|
|
|
#include <asm/stacktrace.h>
|
2005-04-16 16:20:36 -06:00
|
|
|
|
2013-03-21 15:49:52 -06:00
|
|
|
#ifdef CONFIG_HOTPLUG_CPU
|
|
|
|
void arch_cpu_idle_dead(void)
|
2005-04-16 16:20:36 -06:00
|
|
|
{
|
2013-03-21 15:49:52 -06:00
|
|
|
/* What the heck is this check doing ? */
|
|
|
|
if (!cpu_isset(smp_processor_id(), cpu_callin_map))
|
|
|
|
play_dead();
|
|
|
|
}
|
|
|
|
#endif
|
2009-06-23 03:00:31 -06:00
|
|
|
|
2005-04-16 16:20:36 -06:00
|
|
|
asmlinkage void ret_from_fork(void);
|
2012-10-09 14:27:45 -06:00
|
|
|
asmlinkage void ret_from_kernel_thread(void);
|
2005-04-16 16:20:36 -06:00
|
|
|
|
|
|
|
void start_thread(struct pt_regs * regs, unsigned long pc, unsigned long sp)
|
|
|
|
{
|
|
|
|
unsigned long status;
|
|
|
|
|
|
|
|
/* New thread loses kernel privileges. */
|
2007-12-13 15:42:19 -07:00
|
|
|
status = regs->cp0_status & ~(ST0_CU0|ST0_CU1|ST0_FR|KU_MASK);
|
2005-04-16 16:20:36 -06:00
|
|
|
status |= KU_USER;
|
|
|
|
regs->cp0_status = status;
|
|
|
|
clear_used_math();
|
2006-10-08 09:10:01 -06:00
|
|
|
clear_fpu_owner();
|
2005-05-31 05:49:19 -06:00
|
|
|
if (cpu_has_dsp)
|
|
|
|
__init_dsp();
|
2005-04-16 16:20:36 -06:00
|
|
|
regs->cp0_epc = pc;
|
|
|
|
regs->regs[29] = sp;
|
|
|
|
}
|
|
|
|
|
|
|
|
void exit_thread(void)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void flush_thread(void)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2009-04-02 17:56:59 -06:00
|
|
|
int copy_thread(unsigned long clone_flags, unsigned long usp,
|
2012-10-22 20:51:14 -06:00
|
|
|
unsigned long arg, struct task_struct *p)
|
2005-04-16 16:20:36 -06:00
|
|
|
{
|
2006-01-12 02:06:08 -07:00
|
|
|
struct thread_info *ti = task_thread_info(p);
|
2012-10-22 20:51:14 -06:00
|
|
|
struct pt_regs *childregs, *regs = current_pt_regs();
|
2009-07-08 11:07:50 -06:00
|
|
|
unsigned long childksp;
|
2005-04-13 11:43:59 -06:00
|
|
|
p->set_child_tid = p->clear_child_tid = NULL;
|
2005-04-16 16:20:36 -06:00
|
|
|
|
2006-01-12 02:06:08 -07:00
|
|
|
childksp = (unsigned long)task_stack_page(p) + THREAD_SIZE - 32;
|
2005-04-16 16:20:36 -06:00
|
|
|
|
|
|
|
preempt_disable();
|
|
|
|
|
2005-05-31 05:49:19 -06:00
|
|
|
if (is_fpu_owner())
|
2005-04-16 16:20:36 -06:00
|
|
|
save_fp(p);
|
2005-05-31 05:49:19 -06:00
|
|
|
|
|
|
|
if (cpu_has_dsp)
|
|
|
|
save_dsp(p);
|
2005-04-16 16:20:36 -06:00
|
|
|
|
|
|
|
preempt_enable();
|
|
|
|
|
|
|
|
/* set up new TSS. */
|
|
|
|
childregs = (struct pt_regs *) childksp - 1;
|
2009-07-08 11:07:50 -06:00
|
|
|
/* Put the stack after the struct pt_regs. */
|
|
|
|
childksp = (unsigned long) childregs;
|
2012-10-09 14:27:45 -06:00
|
|
|
p->thread.cp0_status = read_c0_status() & ~(ST0_CU2|ST0_CU1);
|
|
|
|
if (unlikely(p->flags & PF_KTHREAD)) {
|
|
|
|
unsigned long status = p->thread.cp0_status;
|
|
|
|
memset(childregs, 0, sizeof(struct pt_regs));
|
|
|
|
ti->addr_limit = KERNEL_DS;
|
|
|
|
p->thread.reg16 = usp; /* fn */
|
|
|
|
p->thread.reg17 = arg;
|
|
|
|
p->thread.reg29 = childksp;
|
|
|
|
p->thread.reg31 = (unsigned long) ret_from_kernel_thread;
|
|
|
|
#if defined(CONFIG_CPU_R3000) || defined(CONFIG_CPU_TX39XX)
|
|
|
|
status = (status & ~(ST0_KUP | ST0_IEP | ST0_IEC)) |
|
|
|
|
((status & (ST0_KUC | ST0_IEC)) << 2);
|
|
|
|
#else
|
|
|
|
status |= ST0_EXL;
|
|
|
|
#endif
|
|
|
|
childregs->cp0_status = status;
|
|
|
|
return 0;
|
|
|
|
}
|
2005-04-16 16:20:36 -06:00
|
|
|
*childregs = *regs;
|
2013-01-22 04:59:30 -07:00
|
|
|
childregs->regs[7] = 0; /* Clear error flag */
|
|
|
|
childregs->regs[2] = 0; /* Child gets zero as return value */
|
2012-12-27 09:52:32 -07:00
|
|
|
if (usp)
|
|
|
|
childregs->regs[29] = usp;
|
2012-10-09 14:27:45 -06:00
|
|
|
ti->addr_limit = USER_DS;
|
2005-04-16 16:20:36 -06:00
|
|
|
|
|
|
|
p->thread.reg29 = (unsigned long) childregs;
|
|
|
|
p->thread.reg31 = (unsigned long) ret_from_fork;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* New tasks lose permission to use the fpu. This accelerates context
|
|
|
|
* switching for most programs since they don't use the fpu.
|
|
|
|
*/
|
|
|
|
childregs->cp0_status &= ~(ST0_CU2|ST0_CU1);
|
|
|
|
|
2008-09-09 13:33:36 -06:00
|
|
|
#ifdef CONFIG_MIPS_MT_SMTC
|
2006-04-05 02:45:47 -06:00
|
|
|
/*
|
2008-09-09 13:33:36 -06:00
|
|
|
* SMTC restores TCStatus after Status, and the CU bits
|
|
|
|
* are aliased there.
|
2006-04-05 02:45:47 -06:00
|
|
|
*/
|
2008-09-09 13:33:36 -06:00
|
|
|
childregs->cp0_tcstatus &= ~(ST0_CU2|ST0_CU1);
|
|
|
|
#endif
|
2005-04-16 16:20:36 -06:00
|
|
|
clear_tsk_thread_flag(p, TIF_USEDFPU);
|
|
|
|
|
2006-04-05 02:45:47 -06:00
|
|
|
#ifdef CONFIG_MIPS_MT_FPAFF
|
2008-09-09 07:19:10 -06:00
|
|
|
clear_tsk_thread_flag(p, TIF_FPUBOUND);
|
2006-04-05 02:45:47 -06:00
|
|
|
#endif /* CONFIG_MIPS_MT_FPAFF */
|
|
|
|
|
2005-04-13 11:43:59 -06:00
|
|
|
if (clone_flags & CLONE_SETTLS)
|
|
|
|
ti->tp_value = regs->regs[7];
|
|
|
|
|
2005-04-16 16:20:36 -06:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Fill in the fpu structure for a core dump.. */
|
|
|
|
int dump_fpu(struct pt_regs *regs, elf_fpregset_t *r)
|
|
|
|
{
|
|
|
|
memcpy(r, ¤t->thread.fpu, sizeof(current->thread.fpu));
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2005-12-16 15:40:47 -07:00
|
|
|
void elf_dump_regs(elf_greg_t *gp, struct pt_regs *regs)
|
2005-04-16 16:20:36 -06:00
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < EF_R0; i++)
|
|
|
|
gp[i] = 0;
|
|
|
|
gp[EF_R0] = 0;
|
|
|
|
for (i = 1; i <= 31; i++)
|
|
|
|
gp[EF_R0 + i] = regs->regs[i];
|
|
|
|
gp[EF_R26] = 0;
|
|
|
|
gp[EF_R27] = 0;
|
|
|
|
gp[EF_LO] = regs->lo;
|
|
|
|
gp[EF_HI] = regs->hi;
|
|
|
|
gp[EF_CP0_EPC] = regs->cp0_epc;
|
|
|
|
gp[EF_CP0_BADVADDR] = regs->cp0_badvaddr;
|
|
|
|
gp[EF_CP0_STATUS] = regs->cp0_status;
|
|
|
|
gp[EF_CP0_CAUSE] = regs->cp0_cause;
|
|
|
|
#ifdef EF_UNUSED0
|
|
|
|
gp[EF_UNUSED0] = 0;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2007-10-11 16:46:15 -06:00
|
|
|
int dump_task_regs(struct task_struct *tsk, elf_gregset_t *regs)
|
2005-03-14 03:16:59 -07:00
|
|
|
{
|
2006-01-12 02:06:07 -07:00
|
|
|
elf_dump_regs(*regs, task_pt_regs(tsk));
|
2005-03-14 03:16:59 -07:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2007-10-11 16:46:15 -06:00
|
|
|
int dump_task_fpu(struct task_struct *t, elf_fpregset_t *fpr)
|
2005-04-16 16:20:36 -06:00
|
|
|
{
|
|
|
|
memcpy(fpr, &t->thread.fpu, sizeof(current->thread.fpu));
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2013-06-12 11:08:54 -06:00
|
|
|
#ifdef CONFIG_CC_STACKPROTECTOR
|
|
|
|
#include <linux/stackprotector.h>
|
|
|
|
unsigned long __stack_chk_guard __read_mostly;
|
|
|
|
EXPORT_SYMBOL(__stack_chk_guard);
|
|
|
|
#endif
|
|
|
|
|
2006-08-18 08:18:09 -06:00
|
|
|
struct mips_frame_info {
|
|
|
|
void *func;
|
|
|
|
unsigned long func_size;
|
|
|
|
int frame_size;
|
|
|
|
int pc_offset;
|
|
|
|
};
|
2005-02-21 03:55:16 -07:00
|
|
|
|
2013-05-12 09:05:34 -06:00
|
|
|
#define J_TARGET(pc,target) \
|
|
|
|
(((unsigned long)(pc) & 0xf0000000) | ((target) << 2))
|
|
|
|
|
2006-08-03 01:29:15 -06:00
|
|
|
static inline int is_ra_save_ins(union mips_instruction *ip)
|
|
|
|
{
|
2013-03-25 12:18:07 -06:00
|
|
|
#ifdef CONFIG_CPU_MICROMIPS
|
|
|
|
union mips_instruction mmi;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* swsp ra,offset
|
|
|
|
* swm16 reglist,offset(sp)
|
|
|
|
* swm32 reglist,offset(sp)
|
|
|
|
* sw32 ra,offset(sp)
|
|
|
|
* jradiussp - NOT SUPPORTED
|
|
|
|
*
|
|
|
|
* microMIPS is way more fun...
|
|
|
|
*/
|
|
|
|
if (mm_insn_16bit(ip->halfword[0])) {
|
|
|
|
mmi.word = (ip->halfword[0] << 16);
|
|
|
|
return ((mmi.mm16_r5_format.opcode == mm_swsp16_op &&
|
|
|
|
mmi.mm16_r5_format.rt == 31) ||
|
|
|
|
(mmi.mm16_m_format.opcode == mm_pool16c_op &&
|
|
|
|
mmi.mm16_m_format.func == mm_swm16_op));
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
mmi.halfword[0] = ip->halfword[1];
|
|
|
|
mmi.halfword[1] = ip->halfword[0];
|
|
|
|
return ((mmi.mm_m_format.opcode == mm_pool32b_op &&
|
|
|
|
mmi.mm_m_format.rd > 9 &&
|
|
|
|
mmi.mm_m_format.base == 29 &&
|
|
|
|
mmi.mm_m_format.func == mm_swm32_func) ||
|
|
|
|
(mmi.i_format.opcode == mm_sw32_op &&
|
|
|
|
mmi.i_format.rs == 29 &&
|
|
|
|
mmi.i_format.rt == 31));
|
|
|
|
}
|
|
|
|
#else
|
2006-08-03 01:29:15 -06:00
|
|
|
/* sw / sd $ra, offset($sp) */
|
|
|
|
return (ip->i_format.opcode == sw_op || ip->i_format.opcode == sd_op) &&
|
|
|
|
ip->i_format.rs == 29 &&
|
|
|
|
ip->i_format.rt == 31;
|
2013-03-25 12:18:07 -06:00
|
|
|
#endif
|
2006-08-03 01:29:15 -06:00
|
|
|
}
|
|
|
|
|
MIPS: Fix sibling call handling in get_frame_info
Given a function, get_frame_info() analyzes its instructions
to figure out frame size and return address. get_frame_info()
works as follows:
1. analyze up to 128 instructions if the function size is unknown
2. search for 'addiu/daddiu sp,sp,-immed' for frame size
3. search for 'sw ra,offset(sp)' for return address
4. end search when it sees jr/jal/jalr
This leads to an issue when the given function is a sibling
call, example shown as follows.
801ca110 <schedule>:
801ca110: 8f820000 lw v0,0(gp)
801ca114: 8c420000 lw v0,0(v0)
801ca118: 080726f0 j 801c9bc0 <__schedule>
801ca11c: 00000000 nop
801ca120 <io_schedule>:
801ca120: 27bdffe8 addiu sp,sp,-24
801ca124: 3c028022 lui v0,0x8022
801ca128: afbf0014 sw ra,20(sp)
In this case, get_frame_info() cannot properly detect schedule's
frame info, and eventually returns io_schedule's instead.
This patch adds 'j' to the end search condition to workaround
sibling call cases.
Signed-off-by: Tony Wu <tung7970@gmail.com>
Cc: linux-mips@linux-mips.org
Patchwork: https://patchwork.linux-mips.org/patch/5236/
Signed-off-by: Ralf Baechle <ralf@linux-mips.org>
2013-05-12 09:04:29 -06:00
|
|
|
static inline int is_jump_ins(union mips_instruction *ip)
|
2006-08-03 01:29:15 -06:00
|
|
|
{
|
2013-03-25 12:18:07 -06:00
|
|
|
#ifdef CONFIG_CPU_MICROMIPS
|
|
|
|
/*
|
|
|
|
* jr16,jrc,jalr16,jalr16
|
|
|
|
* jal
|
|
|
|
* jalr/jr,jalr.hb/jr.hb,jalrs,jalrs.hb
|
|
|
|
* jraddiusp - NOT SUPPORTED
|
|
|
|
*
|
|
|
|
* microMIPS is kind of more fun...
|
|
|
|
*/
|
|
|
|
union mips_instruction mmi;
|
|
|
|
|
|
|
|
mmi.word = (ip->halfword[0] << 16);
|
|
|
|
|
|
|
|
if ((mmi.mm16_r5_format.opcode == mm_pool16c_op &&
|
|
|
|
(mmi.mm16_r5_format.rt & mm_jr16_op) == mm_jr16_op) ||
|
|
|
|
ip->j_format.opcode == mm_jal32_op)
|
|
|
|
return 1;
|
|
|
|
if (ip->r_format.opcode != mm_pool32a_op ||
|
|
|
|
ip->r_format.func != mm_pool32axf_op)
|
|
|
|
return 0;
|
|
|
|
return (((ip->u_format.uimmediate >> 6) & mm_jalr_op) == mm_jalr_op);
|
|
|
|
#else
|
MIPS: Fix sibling call handling in get_frame_info
Given a function, get_frame_info() analyzes its instructions
to figure out frame size and return address. get_frame_info()
works as follows:
1. analyze up to 128 instructions if the function size is unknown
2. search for 'addiu/daddiu sp,sp,-immed' for frame size
3. search for 'sw ra,offset(sp)' for return address
4. end search when it sees jr/jal/jalr
This leads to an issue when the given function is a sibling
call, example shown as follows.
801ca110 <schedule>:
801ca110: 8f820000 lw v0,0(gp)
801ca114: 8c420000 lw v0,0(v0)
801ca118: 080726f0 j 801c9bc0 <__schedule>
801ca11c: 00000000 nop
801ca120 <io_schedule>:
801ca120: 27bdffe8 addiu sp,sp,-24
801ca124: 3c028022 lui v0,0x8022
801ca128: afbf0014 sw ra,20(sp)
In this case, get_frame_info() cannot properly detect schedule's
frame info, and eventually returns io_schedule's instead.
This patch adds 'j' to the end search condition to workaround
sibling call cases.
Signed-off-by: Tony Wu <tung7970@gmail.com>
Cc: linux-mips@linux-mips.org
Patchwork: https://patchwork.linux-mips.org/patch/5236/
Signed-off-by: Ralf Baechle <ralf@linux-mips.org>
2013-05-12 09:04:29 -06:00
|
|
|
if (ip->j_format.opcode == j_op)
|
|
|
|
return 1;
|
2006-08-03 01:29:15 -06:00
|
|
|
if (ip->j_format.opcode == jal_op)
|
|
|
|
return 1;
|
|
|
|
if (ip->r_format.opcode != spec_op)
|
|
|
|
return 0;
|
|
|
|
return ip->r_format.func == jalr_op || ip->r_format.func == jr_op;
|
2013-03-25 12:18:07 -06:00
|
|
|
#endif
|
2006-08-03 01:29:15 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
static inline int is_sp_move_ins(union mips_instruction *ip)
|
|
|
|
{
|
2013-03-25 12:18:07 -06:00
|
|
|
#ifdef CONFIG_CPU_MICROMIPS
|
|
|
|
/*
|
|
|
|
* addiusp -imm
|
|
|
|
* addius5 sp,-imm
|
|
|
|
* addiu32 sp,sp,-imm
|
|
|
|
* jradiussp - NOT SUPPORTED
|
|
|
|
*
|
|
|
|
* microMIPS is not more fun...
|
|
|
|
*/
|
|
|
|
if (mm_insn_16bit(ip->halfword[0])) {
|
|
|
|
union mips_instruction mmi;
|
|
|
|
|
|
|
|
mmi.word = (ip->halfword[0] << 16);
|
|
|
|
return ((mmi.mm16_r3_format.opcode == mm_pool16d_op &&
|
|
|
|
mmi.mm16_r3_format.simmediate && mm_addiusp_func) ||
|
|
|
|
(mmi.mm16_r5_format.opcode == mm_pool16d_op &&
|
|
|
|
mmi.mm16_r5_format.rt == 29));
|
|
|
|
}
|
|
|
|
return (ip->mm_i_format.opcode == mm_addiu32_op &&
|
|
|
|
ip->mm_i_format.rt == 29 && ip->mm_i_format.rs == 29);
|
|
|
|
#else
|
2006-08-03 01:29:15 -06:00
|
|
|
/* addiu/daddiu sp,sp,-imm */
|
|
|
|
if (ip->i_format.rs != 29 || ip->i_format.rt != 29)
|
|
|
|
return 0;
|
|
|
|
if (ip->i_format.opcode == addiu_op || ip->i_format.opcode == daddiu_op)
|
|
|
|
return 1;
|
2013-03-25 12:18:07 -06:00
|
|
|
#endif
|
2006-08-03 01:29:15 -06:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2006-07-29 08:27:20 -06:00
|
|
|
static int get_frame_info(struct mips_frame_info *info)
|
2005-04-16 16:20:36 -06:00
|
|
|
{
|
2013-03-25 12:18:07 -06:00
|
|
|
#ifdef CONFIG_CPU_MICROMIPS
|
|
|
|
union mips_instruction *ip = (void *) (((char *) info->func) - 1);
|
|
|
|
#else
|
2006-08-03 01:29:15 -06:00
|
|
|
union mips_instruction *ip = info->func;
|
2013-03-25 12:18:07 -06:00
|
|
|
#endif
|
2006-08-18 08:18:08 -06:00
|
|
|
unsigned max_insns = info->func_size / sizeof(union mips_instruction);
|
|
|
|
unsigned i;
|
2006-08-03 01:29:15 -06:00
|
|
|
|
2005-04-16 16:20:36 -06:00
|
|
|
info->pc_offset = -1;
|
2006-02-07 09:48:03 -07:00
|
|
|
info->frame_size = 0;
|
2005-04-16 16:20:36 -06:00
|
|
|
|
2006-08-18 08:18:08 -06:00
|
|
|
if (!ip)
|
|
|
|
goto err;
|
|
|
|
|
|
|
|
if (max_insns == 0)
|
|
|
|
max_insns = 128U; /* unknown function size */
|
|
|
|
max_insns = min(128U, max_insns);
|
|
|
|
|
2006-08-03 01:29:15 -06:00
|
|
|
for (i = 0; i < max_insns; i++, ip++) {
|
|
|
|
|
MIPS: Fix sibling call handling in get_frame_info
Given a function, get_frame_info() analyzes its instructions
to figure out frame size and return address. get_frame_info()
works as follows:
1. analyze up to 128 instructions if the function size is unknown
2. search for 'addiu/daddiu sp,sp,-immed' for frame size
3. search for 'sw ra,offset(sp)' for return address
4. end search when it sees jr/jal/jalr
This leads to an issue when the given function is a sibling
call, example shown as follows.
801ca110 <schedule>:
801ca110: 8f820000 lw v0,0(gp)
801ca114: 8c420000 lw v0,0(v0)
801ca118: 080726f0 j 801c9bc0 <__schedule>
801ca11c: 00000000 nop
801ca120 <io_schedule>:
801ca120: 27bdffe8 addiu sp,sp,-24
801ca124: 3c028022 lui v0,0x8022
801ca128: afbf0014 sw ra,20(sp)
In this case, get_frame_info() cannot properly detect schedule's
frame info, and eventually returns io_schedule's instead.
This patch adds 'j' to the end search condition to workaround
sibling call cases.
Signed-off-by: Tony Wu <tung7970@gmail.com>
Cc: linux-mips@linux-mips.org
Patchwork: https://patchwork.linux-mips.org/patch/5236/
Signed-off-by: Ralf Baechle <ralf@linux-mips.org>
2013-05-12 09:04:29 -06:00
|
|
|
if (is_jump_ins(ip))
|
2006-02-07 09:48:03 -07:00
|
|
|
break;
|
2006-08-03 01:29:20 -06:00
|
|
|
if (!info->frame_size) {
|
|
|
|
if (is_sp_move_ins(ip))
|
2013-03-25 12:18:07 -06:00
|
|
|
{
|
|
|
|
#ifdef CONFIG_CPU_MICROMIPS
|
|
|
|
if (mm_insn_16bit(ip->halfword[0]))
|
|
|
|
{
|
|
|
|
unsigned short tmp;
|
|
|
|
|
|
|
|
if (ip->halfword[0] & mm_addiusp_func)
|
|
|
|
{
|
|
|
|
tmp = (((ip->halfword[0] >> 1) & 0x1ff) << 2);
|
|
|
|
info->frame_size = -(signed short)(tmp | ((tmp & 0x100) ? 0xfe00 : 0));
|
|
|
|
} else {
|
|
|
|
tmp = (ip->halfword[0] >> 1);
|
|
|
|
info->frame_size = -(signed short)(tmp & 0xf);
|
|
|
|
}
|
|
|
|
ip = (void *) &ip->halfword[1];
|
|
|
|
ip--;
|
|
|
|
} else
|
|
|
|
#endif
|
2006-08-03 01:29:20 -06:00
|
|
|
info->frame_size = - ip->i_format.simmediate;
|
2013-03-25 12:18:07 -06:00
|
|
|
}
|
2006-08-03 01:29:20 -06:00
|
|
|
continue;
|
2006-02-07 09:48:03 -07:00
|
|
|
}
|
2006-08-03 01:29:20 -06:00
|
|
|
if (info->pc_offset == -1 && is_ra_save_ins(ip)) {
|
2006-02-07 09:48:03 -07:00
|
|
|
info->pc_offset =
|
|
|
|
ip->i_format.simmediate / sizeof(long);
|
2006-08-03 01:29:20 -06:00
|
|
|
break;
|
2005-04-16 16:20:36 -06:00
|
|
|
}
|
|
|
|
}
|
2006-07-29 08:27:20 -06:00
|
|
|
if (info->frame_size && info->pc_offset >= 0) /* nested */
|
|
|
|
return 0;
|
|
|
|
if (info->pc_offset < 0) /* leaf */
|
|
|
|
return 1;
|
|
|
|
/* prologue seems boggus... */
|
2006-08-18 08:18:08 -06:00
|
|
|
err:
|
2006-07-29 08:27:20 -06:00
|
|
|
return -1;
|
2005-04-16 16:20:36 -06:00
|
|
|
}
|
|
|
|
|
2006-08-18 08:18:09 -06:00
|
|
|
static struct mips_frame_info schedule_mfi __read_mostly;
|
|
|
|
|
2013-05-12 09:05:34 -06:00
|
|
|
#ifdef CONFIG_KALLSYMS
|
|
|
|
static unsigned long get___schedule_addr(void)
|
|
|
|
{
|
|
|
|
return kallsyms_lookup_name("__schedule");
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
static unsigned long get___schedule_addr(void)
|
|
|
|
{
|
|
|
|
union mips_instruction *ip = (void *)schedule;
|
|
|
|
int max_insns = 8;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < max_insns; i++, ip++) {
|
|
|
|
if (ip->j_format.opcode == j_op)
|
|
|
|
return J_TARGET(ip, ip->j_format.target);
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2005-04-16 16:20:36 -06:00
|
|
|
static int __init frame_info_init(void)
|
|
|
|
{
|
2006-08-18 08:18:09 -06:00
|
|
|
unsigned long size = 0;
|
2006-02-07 09:48:03 -07:00
|
|
|
#ifdef CONFIG_KALLSYMS
|
2006-08-18 08:18:09 -06:00
|
|
|
unsigned long ofs;
|
2013-05-12 09:05:34 -06:00
|
|
|
#endif
|
|
|
|
unsigned long addr;
|
2006-08-18 08:18:09 -06:00
|
|
|
|
2013-05-12 09:05:34 -06:00
|
|
|
addr = get___schedule_addr();
|
|
|
|
if (!addr)
|
|
|
|
addr = (unsigned long)schedule;
|
|
|
|
|
|
|
|
#ifdef CONFIG_KALLSYMS
|
|
|
|
kallsyms_lookup_size_offset(addr, &size, &ofs);
|
2006-02-07 09:48:03 -07:00
|
|
|
#endif
|
2013-05-12 09:05:34 -06:00
|
|
|
schedule_mfi.func = (void *)addr;
|
2006-08-18 08:18:09 -06:00
|
|
|
schedule_mfi.func_size = size;
|
|
|
|
|
|
|
|
get_frame_info(&schedule_mfi);
|
2006-08-03 01:29:18 -06:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Without schedule() frame info, result given by
|
|
|
|
* thread_saved_pc() and get_wchan() are not reliable.
|
|
|
|
*/
|
2006-08-18 08:18:09 -06:00
|
|
|
if (schedule_mfi.pc_offset < 0)
|
2006-08-03 01:29:18 -06:00
|
|
|
printk("Can't analyze schedule() prologue at %p\n", schedule);
|
2006-02-07 09:48:03 -07:00
|
|
|
|
2005-04-16 16:20:36 -06:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
arch_initcall(frame_info_init);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Return saved PC of a blocked thread.
|
|
|
|
*/
|
|
|
|
unsigned long thread_saved_pc(struct task_struct *tsk)
|
|
|
|
{
|
|
|
|
struct thread_struct *t = &tsk->thread;
|
|
|
|
|
|
|
|
/* New born processes are a special case */
|
|
|
|
if (t->reg31 == (unsigned long) ret_from_fork)
|
|
|
|
return t->reg31;
|
2006-08-18 08:18:09 -06:00
|
|
|
if (schedule_mfi.pc_offset < 0)
|
2005-04-16 16:20:36 -06:00
|
|
|
return 0;
|
2006-08-18 08:18:09 -06:00
|
|
|
return ((unsigned long *)t->reg29)[schedule_mfi.pc_offset];
|
2005-04-16 16:20:36 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2006-07-29 08:27:20 -06:00
|
|
|
#ifdef CONFIG_KALLSYMS
|
2011-05-13 06:38:04 -06:00
|
|
|
/* generic stack unwinding function */
|
|
|
|
unsigned long notrace unwind_stack_by_address(unsigned long stack_page,
|
|
|
|
unsigned long *sp,
|
|
|
|
unsigned long pc,
|
|
|
|
unsigned long *ra)
|
2006-07-29 08:27:20 -06:00
|
|
|
{
|
|
|
|
struct mips_frame_info info;
|
|
|
|
unsigned long size, ofs;
|
2006-08-03 01:29:21 -06:00
|
|
|
int leaf;
|
2006-09-29 03:02:51 -06:00
|
|
|
extern void ret_from_irq(void);
|
|
|
|
extern void ret_from_exception(void);
|
2006-07-29 08:27:20 -06:00
|
|
|
|
|
|
|
if (!stack_page)
|
|
|
|
return 0;
|
|
|
|
|
2006-09-29 03:02:51 -06:00
|
|
|
/*
|
|
|
|
* If we reached the bottom of interrupt context,
|
|
|
|
* return saved pc in pt_regs.
|
|
|
|
*/
|
|
|
|
if (pc == (unsigned long)ret_from_irq ||
|
|
|
|
pc == (unsigned long)ret_from_exception) {
|
|
|
|
struct pt_regs *regs;
|
|
|
|
if (*sp >= stack_page &&
|
|
|
|
*sp + sizeof(*regs) <= stack_page + THREAD_SIZE - 32) {
|
|
|
|
regs = (struct pt_regs *)*sp;
|
|
|
|
pc = regs->cp0_epc;
|
|
|
|
if (__kernel_text_address(pc)) {
|
|
|
|
*sp = regs->regs[29];
|
|
|
|
*ra = regs->regs[31];
|
|
|
|
return pc;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
2006-10-13 05:37:35 -06:00
|
|
|
if (!kallsyms_lookup_size_offset(pc, &size, &ofs))
|
2006-07-29 08:27:20 -06:00
|
|
|
return 0;
|
2006-08-18 08:18:07 -06:00
|
|
|
/*
|
2011-03-30 19:57:33 -06:00
|
|
|
* Return ra if an exception occurred at the first instruction
|
2006-08-18 08:18:07 -06:00
|
|
|
*/
|
2006-09-29 03:02:51 -06:00
|
|
|
if (unlikely(ofs == 0)) {
|
|
|
|
pc = *ra;
|
|
|
|
*ra = 0;
|
|
|
|
return pc;
|
|
|
|
}
|
2006-07-29 08:27:20 -06:00
|
|
|
|
|
|
|
info.func = (void *)(pc - ofs);
|
|
|
|
info.func_size = ofs; /* analyze from start to ofs */
|
2006-08-03 01:29:21 -06:00
|
|
|
leaf = get_frame_info(&info);
|
|
|
|
if (leaf < 0)
|
2006-07-29 08:27:20 -06:00
|
|
|
return 0;
|
2006-08-03 01:29:21 -06:00
|
|
|
|
|
|
|
if (*sp < stack_page ||
|
|
|
|
*sp + info.frame_size > stack_page + THREAD_SIZE - 32)
|
2006-07-29 08:27:20 -06:00
|
|
|
return 0;
|
|
|
|
|
2006-08-03 01:29:21 -06:00
|
|
|
if (leaf)
|
|
|
|
/*
|
|
|
|
* For some extreme cases, get_frame_info() can
|
|
|
|
* consider wrongly a nested function as a leaf
|
|
|
|
* one. In that cases avoid to return always the
|
|
|
|
* same value.
|
|
|
|
*/
|
2006-09-29 03:02:51 -06:00
|
|
|
pc = pc != *ra ? *ra : 0;
|
2006-08-03 01:29:21 -06:00
|
|
|
else
|
|
|
|
pc = ((unsigned long *)(*sp))[info.pc_offset];
|
|
|
|
|
|
|
|
*sp += info.frame_size;
|
2006-09-29 03:02:51 -06:00
|
|
|
*ra = 0;
|
2006-08-03 01:29:21 -06:00
|
|
|
return __kernel_text_address(pc) ? pc : 0;
|
2006-07-29 08:27:20 -06:00
|
|
|
}
|
2011-05-13 06:38:04 -06:00
|
|
|
EXPORT_SYMBOL(unwind_stack_by_address);
|
|
|
|
|
|
|
|
/* used by show_backtrace() */
|
|
|
|
unsigned long unwind_stack(struct task_struct *task, unsigned long *sp,
|
|
|
|
unsigned long pc, unsigned long *ra)
|
|
|
|
{
|
|
|
|
unsigned long stack_page = (unsigned long)task_stack_page(task);
|
|
|
|
return unwind_stack_by_address(stack_page, sp, pc, ra);
|
|
|
|
}
|
2006-07-29 08:27:20 -06:00
|
|
|
#endif
|
2006-08-18 08:18:09 -06:00
|
|
|
|
|
|
|
/*
|
|
|
|
* get_wchan - a maintenance nightmare^W^Wpain in the ass ...
|
|
|
|
*/
|
|
|
|
unsigned long get_wchan(struct task_struct *task)
|
|
|
|
{
|
|
|
|
unsigned long pc = 0;
|
|
|
|
#ifdef CONFIG_KALLSYMS
|
|
|
|
unsigned long sp;
|
2006-09-29 03:02:51 -06:00
|
|
|
unsigned long ra = 0;
|
2006-08-18 08:18:09 -06:00
|
|
|
#endif
|
|
|
|
|
|
|
|
if (!task || task == current || task->state == TASK_RUNNING)
|
|
|
|
goto out;
|
|
|
|
if (!task_stack_page(task))
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
pc = thread_saved_pc(task);
|
|
|
|
|
|
|
|
#ifdef CONFIG_KALLSYMS
|
|
|
|
sp = task->thread.reg29 + schedule_mfi.frame_size;
|
|
|
|
|
|
|
|
while (in_sched_functions(pc))
|
2006-09-29 03:02:51 -06:00
|
|
|
pc = unwind_stack(task, &sp, pc, &ra);
|
2006-08-18 08:18:09 -06:00
|
|
|
#endif
|
|
|
|
|
|
|
|
out:
|
|
|
|
return pc;
|
|
|
|
}
|
2007-07-19 06:04:21 -06:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Don't forget that the stack pointer must be aligned on a 8 bytes
|
|
|
|
* boundary for 32-bits ABI and 16 bytes for 64-bits ABI.
|
|
|
|
*/
|
|
|
|
unsigned long arch_align_stack(unsigned long sp)
|
|
|
|
{
|
|
|
|
if (!(current->personality & ADDR_NO_RANDOMIZE) && randomize_va_space)
|
|
|
|
sp -= get_random_int() & ~PAGE_MASK;
|
|
|
|
|
|
|
|
return sp & ALMASK;
|
|
|
|
}
|