aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDmitry Chagin <dchagin@FreeBSD.org>2023-06-08 22:33:26 +0000
committerDmitry Chagin <dchagin@FreeBSD.org>2023-06-29 08:16:03 +0000
commite40bcfdff7cc0596cd4e748e42a1947fd9f5d430 (patch)
tree906d7ce7b0054db97a533f2fbc156f1c1a2e8c2b
parent4b6a3ed70d8cda6463320c464d4eff11f4aaf526 (diff)
downloadsrc-e40bcfdff7cc0596cd4e748e42a1947fd9f5d430.tar.gz
src-e40bcfdff7cc0596cd4e748e42a1947fd9f5d430.zip
linux(4): Preserve fpu xsave state across signal delivery on amd64
PR: 270247 Reviewed by: kib Differential Revision: https://reviews.freebsd.org/D40444 MFC after: 2 weeks (cherry picked from commit cbbac560911521c0ded3e06e713107176855fae4)
-rw-r--r--sys/amd64/linux/linux_sysvec.c99
-rw-r--r--sys/x86/linux/linux_x86_sigframe.h20
2 files changed, 114 insertions, 5 deletions
diff --git a/sys/amd64/linux/linux_sysvec.c b/sys/amd64/linux/linux_sysvec.c
index 97f49f8a7727..290e21247b0d 100644
--- a/sys/amd64/linux/linux_sysvec.c
+++ b/sys/amd64/linux/linux_sysvec.c
@@ -295,6 +295,54 @@ linux_fxrstor(struct thread *td, mcontext_t *mcp, struct l_sigcontext *sc)
}
static int
+linux_xrstor(struct thread *td, mcontext_t *mcp, struct l_sigcontext *sc)
+{
+ struct savefpu *fp = (struct savefpu *)&mcp->mc_fpstate[0];
+ char *xfpustate;
+ struct proc *p;
+ uint32_t magic2;
+ int error;
+
+ p = td->td_proc;
+ mcp->mc_xfpustate_len = cpu_max_ext_state_size - sizeof(struct savefpu);
+
+ /* Legacy region of an xsave area. */
+ error = copyin(PTRIN(sc->sc_fpstate), fp, sizeof(mcp->mc_fpstate));
+ if (error != 0)
+ return (error);
+ bzero(&fp->sv_pad[0], sizeof(fp->sv_pad));
+
+ /* Extended region of an xsave area. */
+ sc->sc_fpstate += sizeof(mcp->mc_fpstate);
+ xfpustate = (char *)fpu_save_area_alloc();
+ error = copyin(PTRIN(sc->sc_fpstate), xfpustate, mcp->mc_xfpustate_len);
+ if (error != 0) {
+ fpu_save_area_free((struct savefpu *)xfpustate);
+ uprintf("pid %d (%s): linux xrstor failed\n", p->p_pid,
+ td->td_name);
+ return (error);
+ }
+
+ /* Linux specific end of xsave area marker. */
+ sc->sc_fpstate += mcp->mc_xfpustate_len;
+ error = copyin(PTRIN(sc->sc_fpstate), &magic2, LINUX_FP_XSTATE_MAGIC2_SIZE);
+ if (error != 0 || magic2 != LINUX_FP_XSTATE_MAGIC2) {
+ fpu_save_area_free((struct savefpu *)xfpustate);
+ uprintf("pid %d (%s): sigreturn magic2 0x%x error %d\n",
+ p->p_pid, td->td_name, magic2, error);
+ return (error);
+ }
+
+ error = set_fpcontext(td, mcp, xfpustate, mcp->mc_xfpustate_len);
+ fpu_save_area_free((struct savefpu *)xfpustate);
+ if (error != 0) {
+ uprintf("pid %d (%s): sigreturn set_fpcontext error %d\n",
+ p->p_pid, td->td_name, error);
+ }
+ return (error);
+}
+
+static int
linux_copyin_fpstate(struct thread *td, struct l_ucontext *uc)
{
mcontext_t mc;
@@ -303,7 +351,10 @@ linux_copyin_fpstate(struct thread *td, struct l_ucontext *uc)
mc.mc_ownedfp = _MC_FPOWNED_FPU;
mc.mc_fpformat = _MC_FPFMT_XMM;
- return (linux_fxrstor(td, &mc, &uc->uc_mcontext));
+ if ((uc->uc_flags & LINUX_UC_FP_XSTATE) != 0)
+ return (linux_xrstor(td, &mc, &uc->uc_mcontext));
+ else
+ return (linux_fxrstor(td, &mc, &uc->uc_mcontext));
}
/*
@@ -411,19 +462,59 @@ linux_fxsave(mcontext_t *mcp, void *ufp)
}
static int
+linux_xsave(mcontext_t *mcp, char *xfpusave, char *ufp)
+{
+ struct l_fpstate *fx = (struct l_fpstate *)&mcp->mc_fpstate[0];
+ uint32_t magic2;
+ int error;
+
+ /* Legacy region of an xsave area. */
+ fx->sw_reserved.magic1 = LINUX_FP_XSTATE_MAGIC1;
+ fx->sw_reserved.xstate_size = mcp->mc_xfpustate_len + sizeof(*fx);
+ fx->sw_reserved.extended_size = fx->sw_reserved.xstate_size +
+ LINUX_FP_XSTATE_MAGIC2_SIZE;
+ fx->sw_reserved.xfeatures = xsave_mask;
+
+ error = copyout(fx, ufp, sizeof(*fx));
+ if (error != 0)
+ return (error);
+ ufp += sizeof(*fx);
+
+ /* Extended region of an xsave area. */
+ error = copyout(xfpusave, ufp, mcp->mc_xfpustate_len);
+ if (error != 0)
+ return (error);
+
+ /* Linux specific end of xsave area marker. */
+ ufp += mcp->mc_xfpustate_len;
+ magic2 = LINUX_FP_XSTATE_MAGIC2;
+ return (copyout(&magic2, ufp, LINUX_FP_XSTATE_MAGIC2_SIZE));
+}
+
+static int
linux_copyout_fpstate(struct thread *td, struct l_ucontext *uc, char **sp)
{
+ size_t xfpusave_len;
+ char *xfpusave;
mcontext_t mc;
char *ufp = *sp;
- get_fpcontext(td, &mc, NULL, NULL);
+ get_fpcontext(td, &mc, &xfpusave, &xfpusave_len);
KASSERT(mc.mc_fpformat != _MC_FPFMT_NODEV, ("fpu not present"));
- /* fxsave area */
+ /* Room for fxsave area. */
ufp -= sizeof(struct l_fpstate);
+ if (xfpusave != NULL) {
+ /* Room for xsave area. */
+ ufp -= (xfpusave_len + LINUX_FP_XSTATE_MAGIC2_SIZE);
+ uc->uc_flags |= LINUX_UC_FP_XSTATE;
+ }
*sp = ufp = (char *)((unsigned long)ufp & ~0x3Ful);
- return (linux_fxsave(&mc, ufp));
+ if (xfpusave != NULL)
+ return (linux_xsave(&mc, xfpusave, ufp));
+ else
+ return (linux_fxsave(&mc, ufp));
}
/*
diff --git a/sys/x86/linux/linux_x86_sigframe.h b/sys/x86/linux/linux_x86_sigframe.h
index e5687069651f..c748073a3457 100644
--- a/sys/x86/linux/linux_x86_sigframe.h
+++ b/sys/x86/linux/linux_x86_sigframe.h
@@ -35,6 +35,20 @@
#ifndef _X86_LINUX_SIGFRAME_H_
#define _X86_LINUX_SIGFRAME_H_
+#define LINUX_UC_FP_XSTATE 0x1
+
+#define LINUX_FP_XSTATE_MAGIC1 0x46505853U
+#define LINUX_FP_XSTATE_MAGIC2 0x46505845U
+#define LINUX_FP_XSTATE_MAGIC2_SIZE sizeof(uint32_t)
+
+struct l_fpx_sw_bytes {
+ uint32_t magic1;
+ uint32_t extended_size;
+ uint64_t xfeatures;
+ uint32_t xstate_size;
+ uint32_t padding[7];
+};
+
#if defined(__i386__) || (defined(__amd64__) && defined(COMPAT_LINUX32))
/* The Linux sigcontext, pretty much a standard 386 trapframe. */
@@ -140,7 +154,11 @@ struct l_fpstate {
u_int32_t mxcsr_mask;
u_int8_t st[8][16];
u_int8_t xmm[16][16];
- u_int32_t reserved2[24];
+ u_int32_t reserved2[12];
+ union {
+ u_int32_t reserved3[12];
+ struct l_fpx_sw_bytes sw_reserved;
+ };
} __aligned(16);
struct l_sigcontext {