git: 35a1aa5f9c20 - main - [PowerPC] Fix outdated FP regs on fork(2) and friends

Justin Hibbits jhibbits at FreeBSD.org
Sat Sep 4 16:34:35 UTC 2021


The branch main has been updated by jhibbits:

URL: https://cgit.FreeBSD.org/src/commit/?id=35a1aa5f9c20a4c5ca881095972c0b71a032052f

commit 35a1aa5f9c20a4c5ca881095972c0b71a032052f
Author:     Brandon Bergren <bdragon at FreeBSD.org>
AuthorDate: 2021-05-03 02:12:18 +0000
Commit:     Justin Hibbits <jhibbits at FreeBSD.org>
CommitDate: 2021-09-04 16:33:03 +0000

    [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 <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;


More information about the dev-commits-src-main mailing list