[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
This commit is contained in:
parent
b0025f9b7f
commit
35a1aa5f9c
@ -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;
|
||||
|
||||
|
@ -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 */
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -91,6 +91,7 @@
|
||||
#include <machine/frame.h>
|
||||
#include <machine/md_var.h>
|
||||
#include <machine/pcb.h>
|
||||
#include <machine/reg.h>
|
||||
|
||||
#include <dev/ofw/openfirm.h>
|
||||
|
||||
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user