diff options
Diffstat (limited to 'arch/xtensa/kernel/process.c')
| -rw-r--r-- | arch/xtensa/kernel/process.c | 511 |
1 files changed, 191 insertions, 320 deletions
diff --git a/arch/xtensa/kernel/process.c b/arch/xtensa/kernel/process.c index a7c4178c2a8..1c85323f01d 100644 --- a/arch/xtensa/kernel/process.c +++ b/arch/xtensa/kernel/process.c @@ -1,4 +1,3 @@ -// TODO verify coprocessor handling /* * arch/xtensa/kernel/process.c * @@ -21,45 +20,32 @@ #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/elf.h> #include <linux/init.h> #include <linux/prctl.h> #include <linux/init_task.h> #include <linux/module.h> #include <linux/mqueue.h> +#include <linux/fs.h> +#include <linux/slab.h> +#include <linux/rcupdate.h> #include <asm/pgtable.h> #include <asm/uaccess.h> -#include <asm/system.h> #include <asm/io.h> #include <asm/processor.h> #include <asm/platform.h> #include <asm/mmu.h> #include <asm/irq.h> -#include <asm/atomic.h> +#include <linux/atomic.h> #include <asm/asm-offsets.h> -#include <asm/coprocessor.h> +#include <asm/regs.h> extern void ret_from_fork(void); - -static struct fs_struct init_fs = INIT_FS; -static struct files_struct init_files = INIT_FILES; -static struct signal_struct init_signals = INIT_SIGNALS(init_signals); -static struct sighand_struct init_sighand = INIT_SIGHAND(init_sighand); -struct mm_struct init_mm = INIT_MM(init_mm); -EXPORT_SYMBOL(init_mm); - -union thread_union init_thread_union - __attribute__((__section__(".data.init_task"))) = -{ INIT_THREAD_INFO(init_task) }; - -struct task_struct init_task = INIT_TASK(init_task); -EXPORT_SYMBOL(init_task); +extern void ret_from_kernel_thread(void); struct task_struct *current_set[NR_CPUS] = {&init_task, }; @@ -67,148 +53,227 @@ void (*pm_power_off)(void) = NULL; EXPORT_SYMBOL(pm_power_off); -#if XCHAL_CP_NUM > 0 +#if XTENSA_HAVE_COPROCESSORS -/* - * Coprocessor ownership. - */ +void coprocessor_release_all(struct thread_info *ti) +{ + unsigned long cpenable; + int i; -coprocessor_info_t coprocessor_info[] = { - { 0, XTENSA_CPE_CP0_OFFSET }, - { 0, XTENSA_CPE_CP1_OFFSET }, - { 0, XTENSA_CPE_CP2_OFFSET }, - { 0, XTENSA_CPE_CP3_OFFSET }, - { 0, XTENSA_CPE_CP4_OFFSET }, - { 0, XTENSA_CPE_CP5_OFFSET }, - { 0, XTENSA_CPE_CP6_OFFSET }, - { 0, XTENSA_CPE_CP7_OFFSET }, -}; + /* Make sure we don't switch tasks during this operation. */ + + preempt_disable(); + + /* Walk through all cp owners and release it for the requested one. */ + + cpenable = ti->cpenable; + + for (i = 0; i < XCHAL_CP_MAX; i++) { + if (coprocessor_owner[i] == ti) { + coprocessor_owner[i] = 0; + cpenable &= ~(1 << i); + } + } + + ti->cpenable = cpenable; + coprocessor_clear_cpenable(); + + preempt_enable(); +} + +void coprocessor_flush_all(struct thread_info *ti) +{ + unsigned long cpenable; + int i; + + preempt_disable(); + + cpenable = ti->cpenable; + + for (i = 0; i < XCHAL_CP_MAX; i++) { + if ((cpenable & 1) != 0 && coprocessor_owner[i] == ti) + coprocessor_flush(ti, i); + cpenable >>= 1; + } + + preempt_enable(); +} #endif + /* * Powermanagement idle function, if any is provided by the platform. */ - -void cpu_idle(void) +void arch_cpu_idle(void) { - local_irq_enable(); - - /* endless idle loop with no priority at all */ - while (1) { - while (!need_resched()) - platform_idle(); - preempt_enable_no_resched(); - schedule(); - preempt_disable(); - } + platform_idle(); } /* - * Free current thread data structures etc.. + * This is called when the thread calls exit(). */ - void exit_thread(void) { - release_coprocessors(current); /* Empty macro if no CPs are defined */ +#if XTENSA_HAVE_COPROCESSORS + coprocessor_release_all(current_thread_info()); +#endif } +/* + * Flush thread state. This is called when a thread does an execve() + * Note that we flush coprocessor registers for the case execve fails. + */ void flush_thread(void) { - release_coprocessors(current); /* Empty macro if no CPs are defined */ +#if XTENSA_HAVE_COPROCESSORS + struct thread_info *ti = current_thread_info(); + coprocessor_flush_all(ti); + coprocessor_release_all(ti); +#endif +} + +/* + * this gets called so that we can store coprocessor state into memory and + * copy the current task into the new thread. + */ +int arch_dup_task_struct(struct task_struct *dst, struct task_struct *src) +{ +#if XTENSA_HAVE_COPROCESSORS + coprocessor_flush_all(task_thread_info(src)); +#endif + *dst = *src; + return 0; } /* * Copy thread. * + * There are two modes in which this function is called: + * 1) Userspace thread creation, + * regs != NULL, usp_thread_fn is userspace stack pointer. + * It is expected to copy parent regs (in case CLONE_VM is not set + * in the clone_flags) and set up passed usp in the childregs. + * 2) Kernel thread creation, + * regs == NULL, usp_thread_fn is the function to run in the new thread + * and thread_fn_arg is its parameter. + * childregs are not used for the kernel threads. + * * The stack layout for the new thread looks like this: * - * +------------------------+ <- sp in childregs (= tos) + * +------------------------+ * | childregs | * +------------------------+ <- thread.sp = sp in dummy-frame * | dummy-frame | (saved in dummy-frame spill-area) * +------------------------+ * - * We create a dummy frame to return to ret_from_fork: - * a0 points to ret_from_fork (simulating a call4) + * We create a dummy frame to return to either ret_from_fork or + * ret_from_kernel_thread: + * a0 points to ret_from_fork/ret_from_kernel_thread (simulating a call4) * sp points to itself (thread.sp) - * a2, a3 are unused. + * a2, a3 are unused for userspace threads, + * a2 points to thread_fn, a3 holds thread_fn arg for kernel threads. * * Note: This is a pristine frame, so we don't need any spill region on top of * childregs. + * + * The fun part: if we're keeping the same VM (i.e. cloning a thread, + * not an entire process), we're normally given a new usp, and we CANNOT share + * any live address register windows. If we just copy those live frames over, + * the two threads (parent and child) will overflow the same frames onto the + * parent stack at different times, likely corrupting the parent stack (esp. + * if the parent returns from functions that called clone() and calls new + * ones, before the child overflows its now old copies of its parent windows). + * One solution is to spill windows to the parent stack, but that's fairly + * involved. Much simpler to just not copy those live frames across. */ -int copy_thread(int nr, unsigned long clone_flags, unsigned long usp, - unsigned long unused, - struct task_struct * p, struct pt_regs * regs) +int copy_thread(unsigned long clone_flags, unsigned long usp_thread_fn, + unsigned long thread_fn_arg, struct task_struct *p) { - struct pt_regs *childregs; - unsigned long tos; - int user_mode = user_mode(regs); - - /* Set up new TSS. */ - tos = (unsigned long)task_stack_page(p) + THREAD_SIZE; - if (user_mode) - childregs = (struct pt_regs*)(tos - PT_USER_SIZE); - else - childregs = (struct pt_regs*)tos - 1; + struct pt_regs *childregs = task_pt_regs(p); - *childregs = *regs; +#if (XTENSA_HAVE_COPROCESSORS || XTENSA_HAVE_IO_PORTS) + struct thread_info *ti; +#endif /* Create a call4 dummy-frame: a0 = 0, a1 = childregs. */ *((int*)childregs - 3) = (unsigned long)childregs; *((int*)childregs - 4) = 0; - childregs->areg[1] = tos; - childregs->areg[2] = 0; - p->set_child_tid = p->clear_child_tid = NULL; - p->thread.ra = MAKE_RA_FOR_CALL((unsigned long)ret_from_fork, 0x1); p->thread.sp = (unsigned long)childregs; - if (user_mode(regs)) { - int len = childregs->wmask & ~0xf; + if (!(p->flags & PF_KTHREAD)) { + struct pt_regs *regs = current_pt_regs(); + unsigned long usp = usp_thread_fn ? + usp_thread_fn : regs->areg[1]; + + p->thread.ra = MAKE_RA_FOR_CALL( + (unsigned long)ret_from_fork, 0x1); + + /* This does not copy all the regs. + * In a bout of brilliance or madness, + * ARs beyond a0-a15 exist past the end of the struct. + */ + *childregs = *regs; childregs->areg[1] = usp; - memcpy(&childregs->areg[XCHAL_NUM_AREGS - len/4], - ®s->areg[XCHAL_NUM_AREGS - len/4], len); + childregs->areg[2] = 0; + + /* When sharing memory with the parent thread, the child + usually starts on a pristine stack, so we have to reset + windowbase, windowstart and wmask. + (Note that such a new thread is required to always create + an initial call4 frame) + The exception is vfork, where the new thread continues to + run on the parent's stack until it calls execve. This could + be a call8 or call12, which requires a legal stack frame + of the previous caller for the overflow handlers to work. + (Note that it's always legal to overflow live registers). + In this case, ensure to spill at least the stack pointer + of that frame. */ + + if (clone_flags & CLONE_VM) { + /* check that caller window is live and same stack */ + int len = childregs->wmask & ~0xf; + if (regs->areg[1] == usp && len != 0) { + int callinc = (regs->areg[0] >> 30) & 3; + int caller_ars = XCHAL_NUM_AREGS - callinc * 4; + put_user(regs->areg[caller_ars+1], + (unsigned __user*)(usp - 12)); + } + childregs->wmask = 1; + childregs->windowstart = 1; + childregs->windowbase = 0; + } else { + int len = childregs->wmask & ~0xf; + memcpy(&childregs->areg[XCHAL_NUM_AREGS - len/4], + ®s->areg[XCHAL_NUM_AREGS - len/4], len); + } + /* The thread pointer is passed in the '4th argument' (= a5) */ if (clone_flags & CLONE_SETTLS) - childregs->areg[2] = childregs->areg[6]; - + childregs->threadptr = childregs->areg[5]; } else { - /* In kernel space, we start a new thread with a new stack. */ - childregs->wmask = 1; + p->thread.ra = MAKE_RA_FOR_CALL( + (unsigned long)ret_from_kernel_thread, 1); + + /* pass parameters to ret_from_kernel_thread: + * a2 = thread_fn, a3 = thread_fn arg + */ + *((int *)childregs - 1) = thread_fn_arg; + *((int *)childregs - 2) = usp_thread_fn; + + /* Childregs are only used when we're going to userspace + * in which case start_thread will set them up. + */ } - return 0; -} +#if (XTENSA_HAVE_COPROCESSORS || XTENSA_HAVE_IO_PORTS) + ti = task_thread_info(p); + ti->cpenable = 0; +#endif -/* - * Create a kernel thread - */ - -int kernel_thread(int (*fn)(void *), void * arg, unsigned long flags) -{ - long retval; - __asm__ __volatile__ - ("mov a5, %4\n\t" /* preserve fn in a5 */ - "mov a6, %3\n\t" /* preserve and setup arg in a6 */ - "movi a2, %1\n\t" /* load __NR_clone for syscall*/ - "mov a3, sp\n\t" /* sp check and sys_clone */ - "mov a4, %5\n\t" /* load flags for syscall */ - "syscall\n\t" - "beq a3, sp, 1f\n\t" /* branch if parent */ - "callx4 a5\n\t" /* call fn */ - "movi a2, %2\n\t" /* load __NR_exit for syscall */ - "mov a3, a6\n\t" /* load fn return value */ - "syscall\n" - "1:\n\t" - "mov %0, a2\n\t" /* parent returns zero */ - :"=r" (retval) - :"i" (__NR_clone), "i" (__NR_exit), - "r" (arg), "r" (fn), - "r" (flags | CLONE_VM) - : "a2", "a3", "a4", "a5", "a6" ); - return retval; + return 0; } @@ -245,10 +310,6 @@ unsigned long get_wchan(struct task_struct *p) } /* - * do_copy_regs() gathers information from 'struct pt_regs' and - * 'current->thread.areg[]' to fill in the xtensa_gregset_t - * structure. - * * xtensa_gregset_t and 'struct pt_regs' are vastly different formats * of processor registers. Besides different ordering, * xtensa_gregset_t contains non-live register information that @@ -257,229 +318,39 @@ unsigned long get_wchan(struct task_struct *p) * */ -void do_copy_regs (xtensa_gregset_t *elfregs, struct pt_regs *regs, - struct task_struct *tsk) +void xtensa_elf_core_copy_regs (xtensa_gregset_t *elfregs, struct pt_regs *regs) { - int i, n, wb_offset; + unsigned long wb, ws, wm; + int live, last; - elfregs->xchal_config_id0 = XCHAL_HW_CONFIGID0; - elfregs->xchal_config_id1 = XCHAL_HW_CONFIGID1; + wb = regs->windowbase; + ws = regs->windowstart; + wm = regs->wmask; + ws = ((ws >> wb) | (ws << (WSBITS - wb))) & ((1 << WSBITS) - 1); - __asm__ __volatile__ ("rsr %0, 176\n" : "=a" (i)); - elfregs->cpux = i; - __asm__ __volatile__ ("rsr %0, 208\n" : "=a" (i)); - elfregs->cpuy = i; + /* Don't leak any random bits. */ + + memset(elfregs, 0, sizeof(*elfregs)); /* Note: PS.EXCM is not set while user task is running; its * being set in regs->ps is for exception handling convenience. */ elfregs->pc = regs->pc; - elfregs->ps = (regs->ps & ~XCHAL_PS_EXCM_MASK); - elfregs->exccause = regs->exccause; - elfregs->excvaddr = regs->excvaddr; - elfregs->windowbase = regs->windowbase; - elfregs->windowstart = regs->windowstart; + elfregs->ps = (regs->ps & ~(1 << PS_EXCM_BIT)); elfregs->lbeg = regs->lbeg; elfregs->lend = regs->lend; elfregs->lcount = regs->lcount; elfregs->sar = regs->sar; - elfregs->syscall = regs->syscall; - - /* Copy register file. - * The layout looks like this: - * - * | a0 ... a15 | Z ... Z | arX ... arY | - * current window unused saved frames - */ - - memset (elfregs->ar, 0, sizeof(elfregs->ar)); - - wb_offset = regs->windowbase * 4; - n = (regs->wmask&1)? 4 : (regs->wmask&2)? 8 : (regs->wmask&4)? 12 : 16; - - for (i = 0; i < n; i++) - elfregs->ar[(wb_offset + i) % XCHAL_NUM_AREGS] = regs->areg[i]; + elfregs->windowstart = ws; - n = (regs->wmask >> 4) * 4; - - for (i = XCHAL_NUM_AREGS - n; n > 0; i++, n--) - elfregs->ar[(wb_offset + i) % XCHAL_NUM_AREGS] = regs->areg[i]; + live = (wm & 2) ? 4 : (wm & 4) ? 8 : (wm & 8) ? 12 : 16; + last = XCHAL_NUM_AREGS - (wm >> 4) * 4; + memcpy(elfregs->a, regs->areg, live * 4); + memcpy(elfregs->a + last, regs->areg + last, (wm >> 4) * 16); } -void xtensa_elf_core_copy_regs (xtensa_gregset_t *elfregs, struct pt_regs *regs) +int dump_fpu(void) { - do_copy_regs ((xtensa_gregset_t *)elfregs, regs, current); -} - - -/* The inverse of do_copy_regs(). No error or sanity checking. */ - -void do_restore_regs (xtensa_gregset_t *elfregs, struct pt_regs *regs, - struct task_struct *tsk) -{ - int i, n, wb_offset; - - /* Note: PS.EXCM is not set while user task is running; it - * needs to be set in regs->ps is for exception handling convenience. - */ - - regs->pc = elfregs->pc; - regs->ps = (elfregs->ps | XCHAL_PS_EXCM_MASK); - regs->exccause = elfregs->exccause; - regs->excvaddr = elfregs->excvaddr; - regs->windowbase = elfregs->windowbase; - regs->windowstart = elfregs->windowstart; - regs->lbeg = elfregs->lbeg; - regs->lend = elfregs->lend; - regs->lcount = elfregs->lcount; - regs->sar = elfregs->sar; - regs->syscall = elfregs->syscall; - - /* Clear everything. */ - - memset (regs->areg, 0, sizeof(regs->areg)); - - /* Copy regs from live window frame. */ - - wb_offset = regs->windowbase * 4; - n = (regs->wmask&1)? 4 : (regs->wmask&2)? 8 : (regs->wmask&4)? 12 : 16; - - for (i = 0; i < n; i++) - regs->areg[(wb_offset+i) % XCHAL_NUM_AREGS] = elfregs->ar[i]; - - n = (regs->wmask >> 4) * 4; - - for (i = XCHAL_NUM_AREGS - n; n > 0; i++, n--) - regs->areg[(wb_offset+i) % XCHAL_NUM_AREGS] = elfregs->ar[i]; -} - -/* - * do_save_fpregs() gathers information from 'struct pt_regs' and - * 'current->thread' to fill in the elf_fpregset_t structure. - * - * Core files and ptrace use elf_fpregset_t. - */ - -void do_save_fpregs (elf_fpregset_t *fpregs, struct pt_regs *regs, - struct task_struct *tsk) -{ -#if XCHAL_HAVE_CP - - extern unsigned char _xtensa_reginfo_tables[]; - extern unsigned _xtensa_reginfo_table_size; - int i; - unsigned long flags; - - /* Before dumping coprocessor state from memory, - * ensure any live coprocessor contents for this - * task are first saved to memory: - */ - local_irq_save(flags); - - for (i = 0; i < XCHAL_CP_MAX; i++) { - if (tsk == coprocessor_info[i].owner) { - enable_coprocessor(i); - save_coprocessor_registers( - tsk->thread.cp_save+coprocessor_info[i].offset,i); - disable_coprocessor(i); - } - } - - local_irq_restore(flags); - - /* Now dump coprocessor & extra state: */ - memcpy((unsigned char*)fpregs, - _xtensa_reginfo_tables, _xtensa_reginfo_table_size); - memcpy((unsigned char*)fpregs + _xtensa_reginfo_table_size, - tsk->thread.cp_save, XTENSA_CP_EXTRA_SIZE); -#endif -} - -/* - * The inverse of do_save_fpregs(). - * Copies coprocessor and extra state from fpregs into regs and tsk->thread. - * Returns 0 on success, non-zero if layout doesn't match. - */ - -int do_restore_fpregs (elf_fpregset_t *fpregs, struct pt_regs *regs, - struct task_struct *tsk) -{ -#if XCHAL_HAVE_CP - - extern unsigned char _xtensa_reginfo_tables[]; - extern unsigned _xtensa_reginfo_table_size; - int i; - unsigned long flags; - - /* Make sure save area layouts match. - * FIXME: in the future we could allow restoring from - * a different layout of the same registers, by comparing - * fpregs' table with _xtensa_reginfo_tables and matching - * entries and copying registers one at a time. - * Not too sure yet whether that's very useful. - */ - - if( memcmp((unsigned char*)fpregs, - _xtensa_reginfo_tables, _xtensa_reginfo_table_size) ) { - return -1; - } - - /* Before restoring coprocessor state from memory, - * ensure any live coprocessor contents for this - * task are first invalidated. - */ - - local_irq_save(flags); - - for (i = 0; i < XCHAL_CP_MAX; i++) { - if (tsk == coprocessor_info[i].owner) { - enable_coprocessor(i); - save_coprocessor_registers( - tsk->thread.cp_save+coprocessor_info[i].offset,i); - coprocessor_info[i].owner = 0; - disable_coprocessor(i); - } - } - - local_irq_restore(flags); - - /* Now restore coprocessor & extra state: */ - - memcpy(tsk->thread.cp_save, - (unsigned char*)fpregs + _xtensa_reginfo_table_size, - XTENSA_CP_EXTRA_SIZE); -#endif return 0; } -/* - * Fill in the CP structure for a core dump for a particular task. - */ - -int -dump_task_fpu(struct pt_regs *regs, struct task_struct *task, elf_fpregset_t *r) -{ -/* see asm/coprocessor.h for this magic number 16 */ -#if XTENSA_CP_EXTRA_SIZE > 16 - do_save_fpregs (r, regs, task); - - /* For now, bit 16 means some extra state may be present: */ -// FIXME!! need to track to return more accurate mask - return 0x10000 | XCHAL_CP_MASK; -#else - return 0; /* no coprocessors active on this processor */ -#endif -} - -/* - * Fill in the CP structure for a core dump. - * This includes any FPU coprocessor. - * Here, we dump all coprocessors, and other ("extra") custom state. - * - * This function is called by elf_core_dump() in fs/binfmt_elf.c - * (in which case 'regs' comes from calls to do_coredump, see signals.c). - */ -int dump_fpu(struct pt_regs *regs, elf_fpregset_t *r) -{ - return dump_task_fpu(regs, current, r); -} |
