|
@@ -168,3 +168,151 @@ static int vfp_notifier(struct notifier_block *self, unsigned long cmd, void *v)
|
|
|
u32 fpexc;
|
|
|
#ifdef CONFIG_SMP
|
|
|
unsigned int cpu;
|
|
|
+#endif
|
|
|
+
|
|
|
+ switch (cmd) {
|
|
|
+ case THREAD_NOTIFY_SWITCH:
|
|
|
+ fpexc = fmrx(FPEXC);
|
|
|
+
|
|
|
+#ifdef CONFIG_SMP
|
|
|
+ cpu = thread->cpu;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * On SMP, if VFP is enabled, save the old state in
|
|
|
+ * case the thread migrates to a different CPU. The
|
|
|
+ * restoring is done lazily.
|
|
|
+ */
|
|
|
+ if ((fpexc & FPEXC_EN) && vfp_current_hw_state[cpu])
|
|
|
+ vfp_save_state(vfp_current_hw_state[cpu], fpexc);
|
|
|
+#endif
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Always disable VFP so we can lazily save/restore the
|
|
|
+ * old state.
|
|
|
+ */
|
|
|
+ fmxr(FPEXC, fpexc & ~FPEXC_EN);
|
|
|
+ break;
|
|
|
+
|
|
|
+ case THREAD_NOTIFY_FLUSH:
|
|
|
+ vfp_thread_flush(thread);
|
|
|
+ break;
|
|
|
+
|
|
|
+ case THREAD_NOTIFY_EXIT:
|
|
|
+ vfp_thread_exit(thread);
|
|
|
+ break;
|
|
|
+
|
|
|
+ case THREAD_NOTIFY_COPY:
|
|
|
+ vfp_thread_copy(thread);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ return NOTIFY_DONE;
|
|
|
+}
|
|
|
+
|
|
|
+static struct notifier_block vfp_notifier_block = {
|
|
|
+ .notifier_call = vfp_notifier,
|
|
|
+};
|
|
|
+
|
|
|
+/*
|
|
|
+ * Raise a SIGFPE for the current process.
|
|
|
+ * sicode describes the signal being raised.
|
|
|
+ */
|
|
|
+static void vfp_raise_sigfpe(unsigned int sicode, struct pt_regs *regs)
|
|
|
+{
|
|
|
+ siginfo_t info;
|
|
|
+
|
|
|
+ memset(&info, 0, sizeof(info));
|
|
|
+
|
|
|
+ info.si_signo = SIGFPE;
|
|
|
+ info.si_code = sicode;
|
|
|
+ info.si_addr = (void __user *)(instruction_pointer(regs) - 4);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * This is the same as NWFPE, because it's not clear what
|
|
|
+ * this is used for
|
|
|
+ */
|
|
|
+ current->thread.error_code = 0;
|
|
|
+ current->thread.trap_no = 6;
|
|
|
+
|
|
|
+ send_sig_info(SIGFPE, &info, current);
|
|
|
+}
|
|
|
+
|
|
|
+static void vfp_panic(char *reason, u32 inst)
|
|
|
+{
|
|
|
+ int i;
|
|
|
+
|
|
|
+ pr_err("VFP: Error: %s\n", reason);
|
|
|
+ pr_err("VFP: EXC 0x%08x SCR 0x%08x INST 0x%08x\n",
|
|
|
+ fmrx(FPEXC), fmrx(FPSCR), inst);
|
|
|
+ for (i = 0; i < 32; i += 2)
|
|
|
+ pr_err("VFP: s%2u: 0x%08x s%2u: 0x%08x\n",
|
|
|
+ i, vfp_get_float(i), i+1, vfp_get_float(i+1));
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * Process bitmask of exception conditions.
|
|
|
+ */
|
|
|
+static void vfp_raise_exceptions(u32 exceptions, u32 inst, u32 fpscr, struct pt_regs *regs)
|
|
|
+{
|
|
|
+ int si_code = 0;
|
|
|
+
|
|
|
+ pr_debug("VFP: raising exceptions %08x\n", exceptions);
|
|
|
+
|
|
|
+ if (exceptions == VFP_EXCEPTION_ERROR) {
|
|
|
+ vfp_panic("unhandled bounce", inst);
|
|
|
+ vfp_raise_sigfpe(0, regs);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * If any of the status flags are set, update the FPSCR.
|
|
|
+ * Comparison instructions always return at least one of
|
|
|
+ * these flags set.
|
|
|
+ */
|
|
|
+ if (exceptions & (FPSCR_N|FPSCR_Z|FPSCR_C|FPSCR_V))
|
|
|
+ fpscr &= ~(FPSCR_N|FPSCR_Z|FPSCR_C|FPSCR_V);
|
|
|
+
|
|
|
+ fpscr |= exceptions;
|
|
|
+
|
|
|
+ fmxr(FPSCR, fpscr);
|
|
|
+
|
|
|
+#define RAISE(stat,en,sig) \
|
|
|
+ if (exceptions & stat && fpscr & en) \
|
|
|
+ si_code = sig;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * These are arranged in priority order, least to highest.
|
|
|
+ */
|
|
|
+ RAISE(FPSCR_DZC, FPSCR_DZE, FPE_FLTDIV);
|
|
|
+ RAISE(FPSCR_IXC, FPSCR_IXE, FPE_FLTRES);
|
|
|
+ RAISE(FPSCR_UFC, FPSCR_UFE, FPE_FLTUND);
|
|
|
+ RAISE(FPSCR_OFC, FPSCR_OFE, FPE_FLTOVF);
|
|
|
+ RAISE(FPSCR_IOC, FPSCR_IOE, FPE_FLTINV);
|
|
|
+
|
|
|
+ if (si_code)
|
|
|
+ vfp_raise_sigfpe(si_code, regs);
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * Emulate a VFP instruction.
|
|
|
+ */
|
|
|
+static u32 vfp_emulate_instruction(u32 inst, u32 fpscr, struct pt_regs *regs)
|
|
|
+{
|
|
|
+ u32 exceptions = VFP_EXCEPTION_ERROR;
|
|
|
+
|
|
|
+ pr_debug("VFP: emulate: INST=0x%08x SCR=0x%08x\n", inst, fpscr);
|
|
|
+
|
|
|
+ if (INST_CPRTDO(inst)) {
|
|
|
+ if (!INST_CPRT(inst)) {
|
|
|
+ /*
|
|
|
+ * CPDO
|
|
|
+ */
|
|
|
+ if (vfp_single(inst)) {
|
|
|
+ exceptions = vfp_single_cpdo(inst, fpscr);
|
|
|
+ } else {
|
|
|
+ exceptions = vfp_double_cpdo(inst, fpscr);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ /*
|
|
|
+ * A CPRT instruction can not appear in FPINST2, nor
|
|
|
+ * can it cause an exception. Therefore, we do not
|