From 35a1aa5f9c20a4c5ca881095972c0b71a032052f Mon Sep 17 00:00:00 2001 From: Brandon Bergren Date: Sun, 2 May 2021 21:12:18 -0500 Subject: [PATCH] [PowerPC] Fix outdated FP regs on fork(2) and friends Summary: Failure to update the FP / vector state was causing daemon(3) to violate C ABI by failing to preserve nonvolatile registers. This was causing a weird issue where moused was not working on PowerBook G4s when daemonizing, but was working fine when running it foreground. Force saving off the same state that cpu_switch() does in cases where we are about to copy a thread. MFC after: 1 week Sponsored by: Tag1 Consulting, Inc. Test Plan: ``` /* * Test for ABI violation due to side effects of daemon(3). * * NOTE: Compile with -O2 to see the effect. */ /* Allow compiling for Linux too. */ static double test = 1234.56f; /* * This contrivance coerces clang to not bounce the double * off of memory again in main. */ void __attribute__((noinline)) print_double(int j1, int j2, double d) { printf("%f\n", d); } int main(int argc, char *argv[]) { print_double(0, 0, test); if (daemon(0, 1)) { } /* Compiler assumes nonvolatile regs are intact... */ print_double(0, 0, test); return(0); } ``` Working output: ``` 1234.560059 1234.560059 ``` Output in broken case: ``` 1234.560059 0.0 ``` Reviewers: #powerpc Subscribers: jhibbits, luporl, alfredo Tags: #powerpc Differential Revision: https://reviews.freebsd.org/D29851 --- sys/powerpc/include/reg.h | 5 +++ sys/powerpc/powerpc/exec_machdep.c | 72 ++++++++++++++++++++++++++++++ sys/powerpc/powerpc/swtch32.S | 2 + sys/powerpc/powerpc/swtch64.S | 2 + sys/powerpc/powerpc/vm_machdep.c | 5 +++ 5 files changed, 86 insertions(+) diff --git a/sys/powerpc/include/reg.h b/sys/powerpc/include/reg.h index a824792b0f12..c0bf807ac75e 100644 --- a/sys/powerpc/include/reg.h +++ b/sys/powerpc/include/reg.h @@ -68,6 +68,11 @@ int set_fpregs(struct thread *, struct fpreg *); int fill_dbregs(struct thread *, struct dbreg *); int set_dbregs(struct thread *, struct dbreg *); +/* + * MD interfaces. + */ +void cpu_save_thread_regs(struct thread *); + #ifdef COMPAT_FREEBSD32 struct image_params; diff --git a/sys/powerpc/powerpc/exec_machdep.c b/sys/powerpc/powerpc/exec_machdep.c index d90071f13650..7be7aa3ed241 100644 --- a/sys/powerpc/powerpc/exec_machdep.c +++ b/sys/powerpc/powerpc/exec_machdep.c @@ -570,6 +570,74 @@ cleanup_power_extras(struct thread *td) cleanup_fpscr(); } +/* + * Ensure the PCB has been updated in preparation for copying a thread. + * + * This is needed because normally this only happens during switching tasks, + * but when we are cloning a thread, we need the updated state before doing + * the actual copy, so the new thread inherits the current state instead of + * the state at the last task switch. + * + * Keep this in sync with the assembly code in cpu_switch()! + */ +void +cpu_save_thread_regs(struct thread *td) +{ + uint32_t pcb_flags; + struct pcb *pcb; + + KASSERT(td == curthread, + ("cpu_save_thread_regs: td is not curthread")); + + pcb = td->td_pcb; + + pcb_flags = pcb->pcb_flags; + +#if defined(__powerpc64__) + /* Are *any* FSCR flags in use? */ + if (pcb_flags & PCB_CFSCR) { + pcb->pcb_fscr = mfspr(SPR_FSCR); + + if (pcb->pcb_fscr & FSCR_EBB) { + pcb->pcb_ebb.ebbhr = mfspr(SPR_EBBHR); + pcb->pcb_ebb.ebbrr = mfspr(SPR_EBBRR); + pcb->pcb_ebb.bescr = mfspr(SPR_BESCR); + } + if (pcb->pcb_fscr & FSCR_LM) { + pcb->pcb_lm.lmrr = mfspr(SPR_LMRR); + pcb->pcb_lm.lmser = mfspr(SPR_LMSER); + } + if (pcb->pcb_fscr & FSCR_TAR) + pcb->pcb_tar = mfspr(SPR_TAR); + } + + /* + * This is outside of the PCB_CFSCR check because it can be set + * independently when running on POWER7/POWER8. + */ + if (pcb_flags & PCB_CDSCR) + pcb->pcb_dscr = mfspr(SPR_DSCRP); +#endif + +#if defined(__SPE__) + /* + * On E500v2, single-precision scalar instructions and access to + * SPEFSCR may be used without PSL_VEC turned on, as long as they + * limit themselves to the low word of the registers. + * + * As such, we need to unconditionally save SPEFSCR, even though + * it is also updated in save_vec_nodrop(). + */ + pcb->pcb_vec.vscr = mfspr(SPR_SPEFSCR); +#endif + + if (pcb_flags & PCB_FPU) + save_fpu_nodrop(td); + + if (pcb_flags & PCB_VEC) + save_vec_nodrop(td); +} + /* * Set set up registers on exec. */ @@ -1028,6 +1096,10 @@ cpu_copy_thread(struct thread *td, struct thread *td0) struct trapframe *tf; struct callframe *cf; + /* Ensure td0 pcb is up to date. */ + if (td == curthread) + cpu_save_thread_regs(td0); + pcb2 = td->td_pcb; /* Copy the upcall pcb */ diff --git a/sys/powerpc/powerpc/swtch32.S b/sys/powerpc/powerpc/swtch32.S index dba0171577a4..76bdd0b9af87 100644 --- a/sys/powerpc/powerpc/swtch32.S +++ b/sys/powerpc/powerpc/swtch32.S @@ -104,6 +104,8 @@ ENTRY(cpu_switch) mr %r16,%r5 /* and the new lock */ mr %r17,%r6 /* and the PCB */ + /* Keep this next section in sync with cpu_save_thread_regs()! */ + lwz %r18,PCB_FLAGS(%r17) /* Save FPU context if needed */ andi. %r7, %r18, PCB_FPU diff --git a/sys/powerpc/powerpc/swtch64.S b/sys/powerpc/powerpc/swtch64.S index fe59a166f3cb..49d50ad70f7a 100644 --- a/sys/powerpc/powerpc/swtch64.S +++ b/sys/powerpc/powerpc/swtch64.S @@ -131,6 +131,8 @@ ENTRY(cpu_switch) stdu %r1,-48(%r1) + /* Keep this next section in sync with cpu_save_thread_regs()! */ + lwz %r18, PCB_FLAGS(%r17) andi. %r7, %r18, PCB_CFSCR beq 1f diff --git a/sys/powerpc/powerpc/vm_machdep.c b/sys/powerpc/powerpc/vm_machdep.c index 4aab71ec036c..a7de9f0f6a1f 100644 --- a/sys/powerpc/powerpc/vm_machdep.c +++ b/sys/powerpc/powerpc/vm_machdep.c @@ -91,6 +91,7 @@ #include #include #include +#include #include @@ -121,6 +122,10 @@ cpu_fork(struct thread *td1, struct proc *p2, struct thread *td2, int flags) if ((flags & RFPROC) == 0) return; + /* Ensure td1 is up to date before copy. */ + if (td1 == curthread) + cpu_save_thread_regs(td1); + pcb = (struct pcb *)((td2->td_kstack + td2->td_kstack_pages * PAGE_SIZE - sizeof(struct pcb)) & ~0x2fUL); td2->td_pcb = pcb;