git: a95cb95e12e5 - main - linux(4): Preserve fpu fxsave state across signal delivery on amd64.

From: Dmitry Chagin <dchagin_at_FreeBSD.org>
Date: Thu, 02 Feb 2023 17:22:06 UTC
The branch main has been updated by dchagin:

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

commit a95cb95e12e537dbe70f9de18cc0fe98e4a5ebf5
Author:     Dmitry Chagin <dchagin@FreeBSD.org>
AuthorDate: 2023-02-02 17:21:37 +0000
Commit:     Dmitry Chagin <dchagin@FreeBSD.org>
CommitDate: 2023-02-02 17:21:37 +0000

    linux(4): Preserve fpu fxsave state across signal delivery on amd64.
    
    PR:                     240768
    Reviewed by:            kib
    Differential Revision:  https://reviews.freebsd.org/D38302
    MFC after:              1 week
---
 sys/amd64/linux/linux_sysvec.c     | 70 ++++++++++++++++++++++++++++++++++----
 sys/x86/linux/linux_x86_sigframe.h |  7 ++--
 2 files changed, 68 insertions(+), 9 deletions(-)

diff --git a/sys/amd64/linux/linux_sysvec.c b/sys/amd64/linux/linux_sysvec.c
index 96cdf5e5c818..c75dc6a26437 100644
--- a/sys/amd64/linux/linux_sysvec.c
+++ b/sys/amd64/linux/linux_sysvec.c
@@ -319,21 +319,22 @@ int
 linux_rt_sigreturn(struct thread *td, struct linux_rt_sigreturn_args *args)
 {
 	struct proc *p;
-	struct l_ucontext uc;
+	struct l_rt_sigframe sf;
 	struct l_sigcontext *context;
 	struct trapframe *regs;
+	mcontext_t mc;
 	unsigned long rflags;
 	sigset_t bmask;
-	int error;
+	int error, i;
 	ksiginfo_t ksi;
 
 	regs = td->td_frame;
-	error = copyin((void *)regs->tf_rbx, &uc, sizeof(uc));
+	error = copyin((void *)regs->tf_rbx, &sf, sizeof(sf));
 	if (error != 0)
 		return (error);
 
 	p = td->td_proc;
-	context = &uc.uc_mcontext;
+	context = &sf.sf_uc.uc_mcontext;
 	rflags = context->sc_rflags;
 
 	/*
@@ -372,7 +373,7 @@ linux_rt_sigreturn(struct thread *td, struct linux_rt_sigreturn_args *args)
 		return (EINVAL);
 	}
 
-	linux_to_bsd_sigset(&uc.uc_sigmask, &bmask);
+	linux_to_bsd_sigset(&sf.sf_uc.uc_sigmask, &bmask);
 	kern_sigprocmask(td, SIG_SETMASK, &bmask, NULL, 0);
 
 	regs->tf_rdi    = context->sc_rdi;
@@ -396,6 +397,37 @@ linux_rt_sigreturn(struct thread *td, struct linux_rt_sigreturn_args *args)
 	regs->tf_err    = context->sc_err;
 	regs->tf_rflags = rflags;
 
+	if (sf.sf_uc.uc_mcontext.sc_fpstate != NULL) {
+		struct savefpu *svfp = (struct savefpu *)mc.mc_fpstate;
+
+		bzero(&mc, sizeof(mc));
+		mc.mc_ownedfp = _MC_FPOWNED_FPU;
+		mc.mc_fpformat = _MC_FPFMT_XMM;
+
+		svfp->sv_env.en_cw = sf.sf_fs.cwd;
+		svfp->sv_env.en_sw = sf.sf_fs.swd;
+		svfp->sv_env.en_tw = sf.sf_fs.twd;
+		svfp->sv_env.en_opcode = sf.sf_fs.fop;
+		svfp->sv_env.en_rip = sf.sf_fs.rip;
+		svfp->sv_env.en_rdp = sf.sf_fs.rdp;
+		svfp->sv_env.en_mxcsr = sf.sf_fs.mxcsr;
+		svfp->sv_env.en_mxcsr_mask = sf.sf_fs.mxcsr_mask;
+		/* FPU registers */
+		for (i = 0; i < nitems(svfp->sv_fp); ++i)
+			bcopy(&sf.sf_fs.st[i], svfp->sv_fp[i].fp_acc.fp_bytes,
+			    sizeof(svfp->sv_fp[i].fp_acc.fp_bytes));
+		/* SSE registers */
+		for (i = 0; i < nitems(svfp->sv_xmm); ++i)
+			bcopy(&sf.sf_fs.xmm[i], svfp->sv_xmm[i].xmm_bytes,
+			    sizeof(svfp->sv_xmm[i].xmm_bytes));
+		error = set_fpcontext(td, &mc, NULL, 0);
+		if (error != 0) {
+			uprintf("pid %d comm %s linux can't restore fpu state %d\n",
+			    p->p_pid, p->p_comm, error);
+			return (error);
+		}
+	}
+
 	set_pcb_flags(td->td_pcb, PCB_FULL_IRET);
 	return (EJUSTRETURN);
 }
@@ -414,8 +446,10 @@ linux_rt_sendsig(sig_t catcher, ksiginfo_t *ksi, sigset_t *mask)
 	struct sigacts *psp;
 	caddr_t sp;
 	struct trapframe *regs;
+	struct savefpu *svfp;
+	mcontext_t mc;
 	int sig, code;
-	int oonstack, issiginfo;
+	int oonstack, issiginfo, i;
 
 	td = curthread;
 	p = td->td_proc;
@@ -477,6 +511,29 @@ linux_rt_sendsig(sig_t catcher, ksiginfo_t *ksi, sigset_t *mask)
 	mtx_unlock(&psp->ps_mtx);
 	PROC_UNLOCK(p);
 
+	get_fpcontext(td, &mc, NULL, NULL);
+	KASSERT(mc.mc_fpformat != _MC_FPFMT_NODEV, ("fpu not present"));
+	svfp = (struct savefpu *)mc.mc_fpstate;
+
+	sf.sf_fs.cwd = svfp->sv_env.en_cw;
+	sf.sf_fs.swd = svfp->sv_env.en_sw;
+	sf.sf_fs.twd = svfp->sv_env.en_tw;
+	sf.sf_fs.fop = svfp->sv_env.en_opcode;
+	sf.sf_fs.rip = svfp->sv_env.en_rip;
+	sf.sf_fs.rdp = svfp->sv_env.en_rdp;
+	sf.sf_fs.mxcsr = svfp->sv_env.en_mxcsr;
+	sf.sf_fs.mxcsr_mask = svfp->sv_env.en_mxcsr_mask;
+	/* FPU registers */
+	for (i = 0; i < nitems(svfp->sv_fp); ++i)
+		bcopy(svfp->sv_fp[i].fp_acc.fp_bytes, &sf.sf_fs.st[i],
+		    sizeof(svfp->sv_fp[i].fp_acc.fp_bytes));
+	/* SSE registers */
+	for (i = 0; i < nitems(svfp->sv_xmm); ++i)
+		bcopy(svfp->sv_xmm[i].xmm_bytes, &sf.sf_fs.xmm[i],
+		    sizeof(svfp->sv_xmm[i].xmm_bytes));
+	sf.sf_uc.uc_mcontext.sc_fpstate = (struct l_fpstate *)((caddr_t)sfp +
+	    offsetof(struct l_rt_sigframe, sf_fs));
+
 	/* Translate the signal. */
 	sig = bsd_to_linux_signal(sig);
 	/* Fill in POSIX parts. */
@@ -490,6 +547,7 @@ linux_rt_sendsig(sig_t catcher, ksiginfo_t *ksi, sigset_t *mask)
 		sigexit(td, SIGILL);
 	}
 
+	fpstate_drop(td);
 	/* Build the argument list for the signal handler. */
 	regs->tf_rdi = sig;			/* arg 1 in %rdi */
 	regs->tf_rax = 0;
diff --git a/sys/x86/linux/linux_x86_sigframe.h b/sys/x86/linux/linux_x86_sigframe.h
index 75d9a104a345..74e7a36e2e71 100644
--- a/sys/x86/linux/linux_x86_sigframe.h
+++ b/sys/x86/linux/linux_x86_sigframe.h
@@ -138,10 +138,10 @@ struct l_fpstate {
 	u_int64_t rdp;
 	u_int32_t mxcsr;
 	u_int32_t mxcsr_mask;
-	u_int32_t st_space[32];
-	u_int32_t xmm_space[64];
+	u_int8_t st[8][16];
+	u_int8_t xmm[16][16];
 	u_int32_t reserved2[24];
-};
+} __aligned(16);
 
 struct l_sigcontext {
 	l_ulong		sc_r8;
@@ -189,6 +189,7 @@ struct l_ucontext {
 struct l_rt_sigframe {
 	struct l_ucontext	sf_uc;
 	struct l_siginfo	sf_si;
+	struct l_fpstate 	sf_fs;
 };
 
 #endif /* __i386__ || (__amd64__ && COMPAT_LINUX32) */