/* MN10300 FPU management * * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved. * Written by David Howells (dhowells@redhat.com) * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public Licence * as published by the Free Software Foundation; either version * 2 of the Licence, or (at your option) any later version. */ #include <asm/uaccess.h> #include <asm/fpu.h> #include <asm/elf.h> #include <asm/exceptions.h> struct task_struct *fpu_state_owner; /* * handle an exception due to the FPU being disabled */ asmlinkage void fpu_disabled(struct pt_regs *regs, enum exception_code code) { struct task_struct *tsk = current; if (!user_mode(regs)) die_if_no_fixup("An FPU Disabled exception happened in" " kernel space\n", regs, code); #ifdef CONFIG_FPU preempt_disable(); /* transfer the last process's FPU state to memory */ if (fpu_state_owner) { fpu_save(&fpu_state_owner->thread.fpu_state); fpu_state_owner->thread.uregs->epsw &= ~EPSW_FE; } /* the current process now owns the FPU state */ fpu_state_owner = tsk; regs->epsw |= EPSW_FE; /* load the FPU with the current process's FPU state or invent a new * clean one if the process doesn't have one */ if (is_using_fpu(tsk)) { fpu_restore(&tsk->thread.fpu_state); } else { fpu_init_state(); set_using_fpu(tsk); } preempt_enable(); #else { siginfo_t info; info.si_signo = SIGFPE; info.si_errno = 0; info.si_addr = (void *) tsk->thread.uregs->pc; info.si_code = FPE_FLTINV; force_sig_info(SIGFPE, &info, tsk); } #endif /* CONFIG_FPU */ } /* * handle an FPU operational exception * - there's a possibility that if the FPU is asynchronous, the signal might * be meant for a process other than the current one */ asmlinkage void fpu_exception(struct pt_regs *regs, enum exception_code code) { struct task_struct *tsk = fpu_state_owner; siginfo_t info; if (!user_mode(regs)) die_if_no_fixup("An FPU Operation exception happened in" " kernel space\n", regs, code); if (!tsk) die_if_no_fixup("An FPU Operation exception happened," " but the FPU is not in use", regs, code); info.si_signo = SIGFPE; info.si_errno = 0; info.si_addr = (void *) tsk->thread.uregs->pc; info.si_code = FPE_FLTINV; #ifdef CONFIG_FPU { u32 fpcr; /* get FPCR (we need to enable the FPU whilst we do this) */ asm volatile(" or %1,epsw \n" #ifdef CONFIG_MN10300_PROC_MN103E010 " nop \n" " nop \n" " nop \n" #endif " fmov fpcr,%0 \n" #ifdef CONFIG_MN10300_PROC_MN103E010 " nop \n" " nop \n" " nop \n" #endif " and %2,epsw \n" : "=&d"(fpcr) : "i"(EPSW_FE), "i"(~EPSW_FE) ); if (fpcr & FPCR_EC_Z) info.si_code = FPE_FLTDIV; else if (fpcr & FPCR_EC_O) info.si_code = FPE_FLTOVF; else if (fpcr & FPCR_EC_U) info.si_code = FPE_FLTUND; else if (fpcr & FPCR_EC_I) info.si_code = FPE_FLTRES; } #endif force_sig_info(SIGFPE, &info, tsk); } /* * save the FPU state to a signal context */ int fpu_setup_sigcontext(struct fpucontext *fpucontext) { #ifdef CONFIG_FPU struct task_struct *tsk = current; if (!is_using_fpu(tsk)) return 0; /* transfer the current FPU state to memory and cause fpu_init() to be * triggered by the next attempted FPU operation by the current * process. */ preempt_disable(); if (fpu_state_owner == tsk) { fpu_save(&tsk->thread.fpu_state); fpu_state_owner->thread.uregs->epsw &= ~EPSW_FE; fpu_state_owner = NULL; } preempt_enable(); /* we no longer have a valid current FPU state */ clear_using_fpu(tsk); /* transfer the saved FPU state onto the userspace stack */ if (copy_to_user(fpucontext, &tsk->thread.fpu_state, min(sizeof(struct fpu_state_struct), sizeof(struct fpucontext)))) return -1; return 1; #else return 0; #endif } /* * kill a process's FPU state during restoration after signal handling */ void fpu_kill_state(struct task_struct *tsk) { #ifdef CONFIG_FPU /* disown anything left in the FPU */ preempt_disable(); if (fpu_state_owner == tsk) { fpu_state_owner->thread.uregs->epsw &= ~EPSW_FE; fpu_state_owner = NULL; } preempt_enable(); #endif /* we no longer have a valid current FPU state */ clear_using_fpu(tsk); } /* * restore the FPU state from a signal context */ int fpu_restore_sigcontext(struct fpucontext *fpucontext) { struct task_struct *tsk = current; int ret; /* load up the old FPU state */ ret = copy_from_user(&tsk->thread.fpu_state, fpucontext, min(sizeof(struct fpu_state_struct), sizeof(struct fpucontext))); if (!ret) set_using_fpu(tsk); return ret; } /* * fill in the FPU structure for a core dump */ int dump_fpu(struct pt_regs *regs, elf_fpregset_t *fpreg) { struct task_struct *tsk = current; int fpvalid; fpvalid = is_using_fpu(tsk); if (fpvalid) { unlazy_fpu(tsk); memcpy(fpreg, &tsk->thread.fpu_state, sizeof(*fpreg)); } return fpvalid; }