5bfb5d690f
Run idle threads with preempt disabled. Also corrected a bugs in arm26's cpu_idle (make it actually call schedule()). How did it ever work before? Might fix the CPU hotplugging hang which Nigel Cunningham noted. We think the bug hits if the idle thread is preempted after checking need_resched() and before going to sleep, then the CPU offlined. After calling stop_machine_run, the CPU eventually returns from preemption and into the idle thread and goes to sleep. The CPU will continue executing previous idle and have no chance to call play_dead. By disabling preemption until we are ready to explicitly schedule, this bug is fixed and the idle threads generally become more robust. From: alexs <ashepard@u.washington.edu> PPC build fix From: Yoichi Yuasa <yuasa@hh.iij4u.or.jp> MIPS build fix Signed-off-by: Nick Piggin <npiggin@suse.de> Signed-off-by: Yoichi Yuasa <yuasa@hh.iij4u.or.jp> Signed-off-by: Andrew Morton <akpm@osdl.org> Signed-off-by: Linus Torvalds <torvalds@osdl.org>
393 lines
8.7 KiB
C
393 lines
8.7 KiB
C
/* process.c: FRV specific parts of process handling
|
|
*
|
|
* Copyright (C) 2003-5 Red Hat, Inc. All Rights Reserved.
|
|
* Written by David Howells (dhowells@redhat.com)
|
|
* - Derived from arch/m68k/kernel/process.c
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version
|
|
* 2 of the License, or (at your option) any later version.
|
|
*/
|
|
|
|
#include <linux/config.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/smp.h>
|
|
#include <linux/smp_lock.h>
|
|
#include <linux/stddef.h>
|
|
#include <linux/unistd.h>
|
|
#include <linux/ptrace.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/user.h>
|
|
#include <linux/elf.h>
|
|
#include <linux/reboot.h>
|
|
#include <linux/interrupt.h>
|
|
|
|
#include <asm/uaccess.h>
|
|
#include <asm/system.h>
|
|
#include <asm/setup.h>
|
|
#include <asm/pgtable.h>
|
|
#include <asm/gdb-stub.h>
|
|
#include <asm/mb-regs.h>
|
|
|
|
#include "local.h"
|
|
|
|
asmlinkage void ret_from_fork(void);
|
|
|
|
#include <asm/pgalloc.h>
|
|
|
|
struct task_struct *alloc_task_struct(void)
|
|
{
|
|
struct task_struct *p = kmalloc(THREAD_SIZE, GFP_KERNEL);
|
|
if (p)
|
|
atomic_set((atomic_t *)(p+1), 1);
|
|
return p;
|
|
}
|
|
|
|
void free_task_struct(struct task_struct *p)
|
|
{
|
|
if (atomic_dec_and_test((atomic_t *)(p+1)))
|
|
kfree(p);
|
|
}
|
|
|
|
static void core_sleep_idle(void)
|
|
{
|
|
#ifdef LED_DEBUG_SLEEP
|
|
/* Show that we're sleeping... */
|
|
__set_LEDS(0x55aa);
|
|
#endif
|
|
frv_cpu_core_sleep();
|
|
#ifdef LED_DEBUG_SLEEP
|
|
/* ... and that we woke up */
|
|
__set_LEDS(0);
|
|
#endif
|
|
mb();
|
|
}
|
|
|
|
void (*idle)(void) = core_sleep_idle;
|
|
|
|
/*
|
|
* 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)
|
|
*/
|
|
void cpu_idle(void)
|
|
{
|
|
int cpu = smp_processor_id();
|
|
|
|
/* endless idle loop with no priority at all */
|
|
while (1) {
|
|
while (!need_resched()) {
|
|
irq_stat[cpu].idle_timestamp = jiffies;
|
|
|
|
if (!frv_dma_inprogress && idle)
|
|
idle();
|
|
}
|
|
|
|
preempt_enable_no_resched();
|
|
schedule();
|
|
preempt_disable();
|
|
}
|
|
}
|
|
|
|
void machine_restart(char * __unused)
|
|
{
|
|
unsigned long reset_addr;
|
|
#ifdef CONFIG_GDBSTUB
|
|
gdbstub_exit(0);
|
|
#endif
|
|
|
|
if (PSR_IMPLE(__get_PSR()) == PSR_IMPLE_FR551)
|
|
reset_addr = 0xfefff500;
|
|
else
|
|
reset_addr = 0xfeff0500;
|
|
|
|
/* Software reset. */
|
|
asm volatile(" dcef @(gr0,gr0),1 ! membar !"
|
|
" sti %1,@(%0,0) !"
|
|
" nop ! nop ! nop ! nop ! nop ! "
|
|
" nop ! nop ! nop ! nop ! nop ! "
|
|
" nop ! nop ! nop ! nop ! nop ! "
|
|
" nop ! nop ! nop ! nop ! nop ! "
|
|
: : "r" (reset_addr), "r" (1) );
|
|
|
|
for (;;)
|
|
;
|
|
}
|
|
|
|
void machine_halt(void)
|
|
{
|
|
#ifdef CONFIG_GDBSTUB
|
|
gdbstub_exit(0);
|
|
#endif
|
|
|
|
for (;;);
|
|
}
|
|
|
|
void machine_power_off(void)
|
|
{
|
|
#ifdef CONFIG_GDBSTUB
|
|
gdbstub_exit(0);
|
|
#endif
|
|
|
|
for (;;);
|
|
}
|
|
|
|
void flush_thread(void)
|
|
{
|
|
#if 0 //ndef NO_FPU
|
|
unsigned long zero = 0;
|
|
#endif
|
|
set_fs(USER_DS);
|
|
}
|
|
|
|
inline unsigned long user_stack(const struct pt_regs *regs)
|
|
{
|
|
while (regs->next_frame)
|
|
regs = regs->next_frame;
|
|
return user_mode(regs) ? regs->sp : 0;
|
|
}
|
|
|
|
asmlinkage int sys_fork(void)
|
|
{
|
|
#ifndef CONFIG_MMU
|
|
/* fork almost works, enough to trick you into looking elsewhere:-( */
|
|
return -EINVAL;
|
|
#else
|
|
return do_fork(SIGCHLD, user_stack(__frame), __frame, 0, NULL, NULL);
|
|
#endif
|
|
}
|
|
|
|
asmlinkage int sys_vfork(void)
|
|
{
|
|
return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, user_stack(__frame), __frame, 0,
|
|
NULL, NULL);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/*
|
|
* clone a process
|
|
* - tlsptr is retrieved by copy_thread()
|
|
*/
|
|
asmlinkage int sys_clone(unsigned long clone_flags, unsigned long newsp,
|
|
int __user *parent_tidptr, int __user *child_tidptr,
|
|
int __user *tlsptr)
|
|
{
|
|
if (!newsp)
|
|
newsp = user_stack(__frame);
|
|
return do_fork(clone_flags, newsp, __frame, 0, parent_tidptr, child_tidptr);
|
|
} /* end sys_clone() */
|
|
|
|
/*****************************************************************************/
|
|
/*
|
|
* This gets called before we allocate a new thread and copy
|
|
* the current task into it.
|
|
*/
|
|
void prepare_to_copy(struct task_struct *tsk)
|
|
{
|
|
//unlazy_fpu(tsk);
|
|
} /* end prepare_to_copy() */
|
|
|
|
/*****************************************************************************/
|
|
/*
|
|
* set up the kernel stack and exception frames for a new process
|
|
*/
|
|
int copy_thread(int nr, unsigned long clone_flags,
|
|
unsigned long usp, unsigned long topstk,
|
|
struct task_struct *p, struct pt_regs *regs)
|
|
{
|
|
struct pt_regs *childregs0, *childregs, *regs0;
|
|
|
|
regs0 = __kernel_frame0_ptr;
|
|
childregs0 = (struct pt_regs *)
|
|
((unsigned long) p->thread_info + THREAD_SIZE - USER_CONTEXT_SIZE);
|
|
childregs = childregs0;
|
|
|
|
/* set up the userspace frame (the only place that the USP is stored) */
|
|
*childregs0 = *regs0;
|
|
|
|
childregs0->gr8 = 0;
|
|
childregs0->sp = usp;
|
|
childregs0->next_frame = NULL;
|
|
|
|
/* set up the return kernel frame if called from kernel_thread() */
|
|
if (regs != regs0) {
|
|
childregs--;
|
|
*childregs = *regs;
|
|
childregs->sp = (unsigned long) childregs0;
|
|
childregs->next_frame = childregs0;
|
|
childregs->gr15 = (unsigned long) p->thread_info;
|
|
childregs->gr29 = (unsigned long) p;
|
|
}
|
|
|
|
p->set_child_tid = p->clear_child_tid = NULL;
|
|
|
|
p->thread.frame = childregs;
|
|
p->thread.curr = p;
|
|
p->thread.sp = (unsigned long) childregs;
|
|
p->thread.fp = 0;
|
|
p->thread.lr = 0;
|
|
p->thread.pc = (unsigned long) ret_from_fork;
|
|
p->thread.frame0 = childregs0;
|
|
|
|
/* the new TLS pointer is passed in as arg #5 to sys_clone() */
|
|
if (clone_flags & CLONE_SETTLS)
|
|
childregs->gr29 = childregs->gr12;
|
|
|
|
save_user_regs(p->thread.user);
|
|
|
|
return 0;
|
|
} /* end copy_thread() */
|
|
|
|
/*
|
|
* fill in the user structure for a core dump..
|
|
*/
|
|
void dump_thread(struct pt_regs *regs, struct user *dump)
|
|
{
|
|
#if 0
|
|
/* changed the size calculations - should hopefully work better. lbt */
|
|
dump->magic = CMAGIC;
|
|
dump->start_code = 0;
|
|
dump->start_stack = user_stack(regs) & ~(PAGE_SIZE - 1);
|
|
dump->u_tsize = ((unsigned long) current->mm->end_code) >> PAGE_SHIFT;
|
|
dump->u_dsize = ((unsigned long) (current->mm->brk + (PAGE_SIZE-1))) >> PAGE_SHIFT;
|
|
dump->u_dsize -= dump->u_tsize;
|
|
dump->u_ssize = 0;
|
|
|
|
if (dump->start_stack < TASK_SIZE)
|
|
dump->u_ssize = ((unsigned long) (TASK_SIZE - dump->start_stack)) >> PAGE_SHIFT;
|
|
|
|
dump->regs = *(struct user_context *) regs;
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* sys_execve() executes a new program.
|
|
*/
|
|
asmlinkage int sys_execve(char *name, char **argv, char **envp)
|
|
{
|
|
int error;
|
|
char * filename;
|
|
|
|
lock_kernel();
|
|
filename = getname(name);
|
|
error = PTR_ERR(filename);
|
|
if (IS_ERR(filename))
|
|
goto out;
|
|
error = do_execve(filename, argv, envp, __frame);
|
|
putname(filename);
|
|
out:
|
|
unlock_kernel();
|
|
return error;
|
|
}
|
|
|
|
unsigned long get_wchan(struct task_struct *p)
|
|
{
|
|
struct pt_regs *regs0;
|
|
unsigned long fp, pc;
|
|
unsigned long stack_limit;
|
|
int count = 0;
|
|
if (!p || p == current || p->state == TASK_RUNNING)
|
|
return 0;
|
|
|
|
stack_limit = (unsigned long) (p + 1);
|
|
fp = p->thread.fp;
|
|
regs0 = p->thread.frame0;
|
|
|
|
do {
|
|
if (fp < stack_limit || fp >= (unsigned long) regs0 || fp & 3)
|
|
return 0;
|
|
|
|
pc = ((unsigned long *) fp)[2];
|
|
|
|
/* FIXME: This depends on the order of these functions. */
|
|
if (!in_sched_functions(pc))
|
|
return pc;
|
|
|
|
fp = *(unsigned long *) fp;
|
|
} while (count++ < 16);
|
|
|
|
return 0;
|
|
}
|
|
|
|
unsigned long thread_saved_pc(struct task_struct *tsk)
|
|
{
|
|
/* Check whether the thread is blocked in resume() */
|
|
if (in_sched_functions(tsk->thread.pc))
|
|
return ((unsigned long *)tsk->thread.fp)[2];
|
|
else
|
|
return tsk->thread.pc;
|
|
}
|
|
|
|
int elf_check_arch(const struct elf32_hdr *hdr)
|
|
{
|
|
unsigned long hsr0 = __get_HSR(0);
|
|
unsigned long psr = __get_PSR();
|
|
|
|
if (hdr->e_machine != EM_FRV)
|
|
return 0;
|
|
|
|
switch (hdr->e_flags & EF_FRV_GPR_MASK) {
|
|
case EF_FRV_GPR64:
|
|
if ((hsr0 & HSR0_GRN) == HSR0_GRN_32)
|
|
return 0;
|
|
case EF_FRV_GPR32:
|
|
case 0:
|
|
break;
|
|
default:
|
|
return 0;
|
|
}
|
|
|
|
switch (hdr->e_flags & EF_FRV_FPR_MASK) {
|
|
case EF_FRV_FPR64:
|
|
if ((hsr0 & HSR0_FRN) == HSR0_FRN_32)
|
|
return 0;
|
|
case EF_FRV_FPR32:
|
|
case EF_FRV_FPR_NONE:
|
|
case 0:
|
|
break;
|
|
default:
|
|
return 0;
|
|
}
|
|
|
|
if ((hdr->e_flags & EF_FRV_MULADD) == EF_FRV_MULADD)
|
|
if (PSR_IMPLE(psr) != PSR_IMPLE_FR405 &&
|
|
PSR_IMPLE(psr) != PSR_IMPLE_FR451)
|
|
return 0;
|
|
|
|
switch (hdr->e_flags & EF_FRV_CPU_MASK) {
|
|
case EF_FRV_CPU_GENERIC:
|
|
break;
|
|
case EF_FRV_CPU_FR300:
|
|
case EF_FRV_CPU_SIMPLE:
|
|
case EF_FRV_CPU_TOMCAT:
|
|
default:
|
|
return 0;
|
|
case EF_FRV_CPU_FR400:
|
|
if (PSR_IMPLE(psr) != PSR_IMPLE_FR401 &&
|
|
PSR_IMPLE(psr) != PSR_IMPLE_FR405 &&
|
|
PSR_IMPLE(psr) != PSR_IMPLE_FR451 &&
|
|
PSR_IMPLE(psr) != PSR_IMPLE_FR551)
|
|
return 0;
|
|
break;
|
|
case EF_FRV_CPU_FR450:
|
|
if (PSR_IMPLE(psr) != PSR_IMPLE_FR451)
|
|
return 0;
|
|
break;
|
|
case EF_FRV_CPU_FR500:
|
|
if (PSR_IMPLE(psr) != PSR_IMPLE_FR501)
|
|
return 0;
|
|
break;
|
|
case EF_FRV_CPU_FR550:
|
|
if (PSR_IMPLE(psr) != PSR_IMPLE_FR551)
|
|
return 0;
|
|
break;
|
|
}
|
|
|
|
return 1;
|
|
}
|