b594318259
This array was used to 'cache' some frame info about scheduler functions to speed up get_wchan(). This array was 1Ko size and was only used when CONFIG_KALLSYMS was set but declared for all configs. Rather than make the array statement conditional, this patches removes this array and its uses. Indeed the common case doesn't seem to use this array and get_wchan() is not a critical path anyways. It results in a smaller bss and a smaller/cleaner code: text data bss dec hex filename 2543808 254148 139296 2937252 2cd1a4 vmlinux-new-get-wchan 2544080 254148 143392 2941620 2ce2b4 vmlinux~old Signed-off-by: Franck Bui-Huu <vagabon.xyz@gmail.com> Signed-off-by: Ralf Baechle <ralf@linux-mips.org>
474 lines
11 KiB
C
474 lines
11 KiB
C
/*
|
|
* 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.
|
|
* Copyright (C) 2005, 2006 by Ralf Baechle (ralf@linux-mips.org)
|
|
* Copyright (C) 1999, 2000 Silicon Graphics, Inc.
|
|
* Copyright (C) 2004 Thiemo Seufer
|
|
*/
|
|
#include <linux/errno.h>
|
|
#include <linux/module.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/stddef.h>
|
|
#include <linux/unistd.h>
|
|
#include <linux/ptrace.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/mman.h>
|
|
#include <linux/personality.h>
|
|
#include <linux/sys.h>
|
|
#include <linux/user.h>
|
|
#include <linux/a.out.h>
|
|
#include <linux/init.h>
|
|
#include <linux/completion.h>
|
|
#include <linux/kallsyms.h>
|
|
|
|
#include <asm/abi.h>
|
|
#include <asm/bootinfo.h>
|
|
#include <asm/cpu.h>
|
|
#include <asm/dsp.h>
|
|
#include <asm/fpu.h>
|
|
#include <asm/pgtable.h>
|
|
#include <asm/system.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>
|
|
#ifdef CONFIG_MIPS_MT_SMTC
|
|
#include <asm/mipsmtregs.h>
|
|
extern void smtc_idle_loop_hook(void);
|
|
#endif /* CONFIG_MIPS_MT_SMTC */
|
|
|
|
/*
|
|
* The idle thread. There's no useful work to be done, so just try to conserve
|
|
* power and have a low exit latency (ie sit in a loop waiting for somebody to
|
|
* say that they'd like to reschedule)
|
|
*/
|
|
ATTRIB_NORET void cpu_idle(void)
|
|
{
|
|
/* endless idle loop with no priority at all */
|
|
while (1) {
|
|
while (!need_resched()) {
|
|
#ifdef CONFIG_MIPS_MT_SMTC
|
|
smtc_idle_loop_hook();
|
|
#endif /* CONFIG_MIPS_MT_SMTC */
|
|
if (cpu_wait)
|
|
(*cpu_wait)();
|
|
}
|
|
preempt_enable_no_resched();
|
|
schedule();
|
|
preempt_disable();
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Native o32 and N64 ABI without DSP ASE
|
|
*/
|
|
struct mips_abi mips_abi = {
|
|
.do_signal = do_signal,
|
|
#ifdef CONFIG_TRAD_SIGNALS
|
|
.setup_frame = setup_frame,
|
|
#endif
|
|
.setup_rt_frame = setup_rt_frame
|
|
};
|
|
|
|
#ifdef CONFIG_MIPS32_O32
|
|
/*
|
|
* o32 compatibility on 64-bit kernels, without DSP ASE
|
|
*/
|
|
struct mips_abi mips_abi_32 = {
|
|
.do_signal = do_signal32,
|
|
.setup_frame = setup_frame_32,
|
|
.setup_rt_frame = setup_rt_frame_32
|
|
};
|
|
#endif /* CONFIG_MIPS32_O32 */
|
|
|
|
#ifdef CONFIG_MIPS32_N32
|
|
/*
|
|
* N32 on 64-bit kernels, without DSP ASE
|
|
*/
|
|
struct mips_abi mips_abi_n32 = {
|
|
.do_signal = do_signal,
|
|
.setup_rt_frame = setup_rt_frame_n32
|
|
};
|
|
#endif /* CONFIG_MIPS32_N32 */
|
|
|
|
asmlinkage void ret_from_fork(void);
|
|
|
|
void start_thread(struct pt_regs * regs, unsigned long pc, unsigned long sp)
|
|
{
|
|
unsigned long status;
|
|
|
|
/* New thread loses kernel privileges. */
|
|
status = regs->cp0_status & ~(ST0_CU0|ST0_CU1|KU_MASK);
|
|
#ifdef CONFIG_64BIT
|
|
status &= ~ST0_FR;
|
|
status |= (current->thread.mflags & MF_32BIT_REGS) ? 0 : ST0_FR;
|
|
#endif
|
|
status |= KU_USER;
|
|
regs->cp0_status = status;
|
|
clear_used_math();
|
|
lose_fpu();
|
|
if (cpu_has_dsp)
|
|
__init_dsp();
|
|
regs->cp0_epc = pc;
|
|
regs->regs[29] = sp;
|
|
current_thread_info()->addr_limit = USER_DS;
|
|
}
|
|
|
|
void exit_thread(void)
|
|
{
|
|
}
|
|
|
|
void flush_thread(void)
|
|
{
|
|
}
|
|
|
|
int copy_thread(int nr, unsigned long clone_flags, unsigned long usp,
|
|
unsigned long unused, struct task_struct *p, struct pt_regs *regs)
|
|
{
|
|
struct thread_info *ti = task_thread_info(p);
|
|
struct pt_regs *childregs;
|
|
long childksp;
|
|
p->set_child_tid = p->clear_child_tid = NULL;
|
|
|
|
childksp = (unsigned long)task_stack_page(p) + THREAD_SIZE - 32;
|
|
|
|
preempt_disable();
|
|
|
|
if (is_fpu_owner())
|
|
save_fp(p);
|
|
|
|
if (cpu_has_dsp)
|
|
save_dsp(p);
|
|
|
|
preempt_enable();
|
|
|
|
/* set up new TSS. */
|
|
childregs = (struct pt_regs *) childksp - 1;
|
|
*childregs = *regs;
|
|
childregs->regs[7] = 0; /* Clear error flag */
|
|
|
|
#if defined(CONFIG_BINFMT_IRIX)
|
|
if (current->personality != PER_LINUX) {
|
|
/* Under IRIX things are a little different. */
|
|
childregs->regs[3] = 1;
|
|
regs->regs[3] = 0;
|
|
}
|
|
#endif
|
|
childregs->regs[2] = 0; /* Child gets zero as return value */
|
|
regs->regs[2] = p->pid;
|
|
|
|
if (childregs->cp0_status & ST0_CU0) {
|
|
childregs->regs[28] = (unsigned long) ti;
|
|
childregs->regs[29] = childksp;
|
|
ti->addr_limit = KERNEL_DS;
|
|
} else {
|
|
childregs->regs[29] = usp;
|
|
ti->addr_limit = USER_DS;
|
|
}
|
|
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.
|
|
*/
|
|
p->thread.cp0_status = read_c0_status() & ~(ST0_CU2|ST0_CU1);
|
|
childregs->cp0_status &= ~(ST0_CU2|ST0_CU1);
|
|
clear_tsk_thread_flag(p, TIF_USEDFPU);
|
|
|
|
#ifdef CONFIG_MIPS_MT_FPAFF
|
|
/*
|
|
* FPU affinity support is cleaner if we track the
|
|
* user-visible CPU affinity from the very beginning.
|
|
* The generic cpus_allowed mask will already have
|
|
* been copied from the parent before copy_thread
|
|
* is invoked.
|
|
*/
|
|
p->thread.user_cpus_allowed = p->cpus_allowed;
|
|
#endif /* CONFIG_MIPS_MT_FPAFF */
|
|
|
|
if (clone_flags & CLONE_SETTLS)
|
|
ti->tp_value = regs->regs[7];
|
|
|
|
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;
|
|
}
|
|
|
|
void elf_dump_regs(elf_greg_t *gp, struct pt_regs *regs)
|
|
{
|
|
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
|
|
}
|
|
|
|
int dump_task_regs (struct task_struct *tsk, elf_gregset_t *regs)
|
|
{
|
|
elf_dump_regs(*regs, task_pt_regs(tsk));
|
|
return 1;
|
|
}
|
|
|
|
int dump_task_fpu (struct task_struct *t, elf_fpregset_t *fpr)
|
|
{
|
|
memcpy(fpr, &t->thread.fpu, sizeof(current->thread.fpu));
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Create a kernel thread
|
|
*/
|
|
ATTRIB_NORET void kernel_thread_helper(void *arg, int (*fn)(void *))
|
|
{
|
|
do_exit(fn(arg));
|
|
}
|
|
|
|
long kernel_thread(int (*fn)(void *), void *arg, unsigned long flags)
|
|
{
|
|
struct pt_regs regs;
|
|
|
|
memset(®s, 0, sizeof(regs));
|
|
|
|
regs.regs[4] = (unsigned long) arg;
|
|
regs.regs[5] = (unsigned long) fn;
|
|
regs.cp0_epc = (unsigned long) kernel_thread_helper;
|
|
regs.cp0_status = read_c0_status();
|
|
#if defined(CONFIG_CPU_R3000) || defined(CONFIG_CPU_TX39XX)
|
|
regs.cp0_status &= ~(ST0_KUP | ST0_IEC);
|
|
regs.cp0_status |= ST0_IEP;
|
|
#else
|
|
regs.cp0_status |= ST0_EXL;
|
|
#endif
|
|
|
|
/* Ok, create the new process.. */
|
|
return do_fork(flags | CLONE_VM | CLONE_UNTRACED, 0, ®s, 0, NULL, NULL);
|
|
}
|
|
|
|
/*
|
|
*
|
|
*/
|
|
struct mips_frame_info {
|
|
void *func;
|
|
unsigned long func_size;
|
|
int frame_size;
|
|
int pc_offset;
|
|
};
|
|
|
|
static inline int is_ra_save_ins(union mips_instruction *ip)
|
|
{
|
|
/* 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;
|
|
}
|
|
|
|
static inline int is_jal_jalr_jr_ins(union mips_instruction *ip)
|
|
{
|
|
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;
|
|
}
|
|
|
|
static inline int is_sp_move_ins(union mips_instruction *ip)
|
|
{
|
|
/* 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;
|
|
return 0;
|
|
}
|
|
|
|
static int get_frame_info(struct mips_frame_info *info)
|
|
{
|
|
union mips_instruction *ip = info->func;
|
|
unsigned max_insns = info->func_size / sizeof(union mips_instruction);
|
|
unsigned i;
|
|
|
|
info->pc_offset = -1;
|
|
info->frame_size = 0;
|
|
|
|
if (!ip)
|
|
goto err;
|
|
|
|
if (max_insns == 0)
|
|
max_insns = 128U; /* unknown function size */
|
|
max_insns = min(128U, max_insns);
|
|
|
|
for (i = 0; i < max_insns; i++, ip++) {
|
|
|
|
if (is_jal_jalr_jr_ins(ip))
|
|
break;
|
|
if (!info->frame_size) {
|
|
if (is_sp_move_ins(ip))
|
|
info->frame_size = - ip->i_format.simmediate;
|
|
continue;
|
|
}
|
|
if (info->pc_offset == -1 && is_ra_save_ins(ip)) {
|
|
info->pc_offset =
|
|
ip->i_format.simmediate / sizeof(long);
|
|
break;
|
|
}
|
|
}
|
|
if (info->frame_size && info->pc_offset >= 0) /* nested */
|
|
return 0;
|
|
if (info->pc_offset < 0) /* leaf */
|
|
return 1;
|
|
/* prologue seems boggus... */
|
|
err:
|
|
return -1;
|
|
}
|
|
|
|
static struct mips_frame_info schedule_mfi __read_mostly;
|
|
|
|
static int __init frame_info_init(void)
|
|
{
|
|
unsigned long size = 0;
|
|
#ifdef CONFIG_KALLSYMS
|
|
unsigned long ofs;
|
|
char *modname;
|
|
char namebuf[KSYM_NAME_LEN + 1];
|
|
|
|
kallsyms_lookup((unsigned long)schedule, &size, &ofs, &modname, namebuf);
|
|
#endif
|
|
schedule_mfi.func = schedule;
|
|
schedule_mfi.func_size = size;
|
|
|
|
get_frame_info(&schedule_mfi);
|
|
|
|
/*
|
|
* Without schedule() frame info, result given by
|
|
* thread_saved_pc() and get_wchan() are not reliable.
|
|
*/
|
|
if (schedule_mfi.pc_offset < 0)
|
|
printk("Can't analyze schedule() prologue at %p\n", schedule);
|
|
|
|
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;
|
|
if (schedule_mfi.pc_offset < 0)
|
|
return 0;
|
|
return ((unsigned long *)t->reg29)[schedule_mfi.pc_offset];
|
|
}
|
|
|
|
|
|
#ifdef CONFIG_KALLSYMS
|
|
/* 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;
|
|
struct mips_frame_info info;
|
|
char *modname;
|
|
char namebuf[KSYM_NAME_LEN + 1];
|
|
unsigned long size, ofs;
|
|
int leaf;
|
|
|
|
stack_page = (unsigned long)task_stack_page(task);
|
|
if (!stack_page)
|
|
return 0;
|
|
|
|
if (!kallsyms_lookup(pc, &size, &ofs, &modname, namebuf))
|
|
return 0;
|
|
/*
|
|
* Return ra if an exception occured at the first instruction
|
|
*/
|
|
if (unlikely(ofs == 0))
|
|
return ra;
|
|
|
|
info.func = (void *)(pc - ofs);
|
|
info.func_size = ofs; /* analyze from start to ofs */
|
|
leaf = get_frame_info(&info);
|
|
if (leaf < 0)
|
|
return 0;
|
|
|
|
if (*sp < stack_page ||
|
|
*sp + info.frame_size > stack_page + THREAD_SIZE - 32)
|
|
return 0;
|
|
|
|
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.
|
|
*/
|
|
pc = pc != ra ? ra : 0;
|
|
else
|
|
pc = ((unsigned long *)(*sp))[info.pc_offset];
|
|
|
|
*sp += info.frame_size;
|
|
return __kernel_text_address(pc) ? pc : 0;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* 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;
|
|
#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))
|
|
pc = unwind_stack(task, &sp, pc, 0);
|
|
#endif
|
|
|
|
out:
|
|
return pc;
|
|
}
|