[PATCH 23/35] x86/fpu: Add helpers for modifying supervisor xstate

Rick Edgecombe
2022-01-30T21:25:36Z

Add helpers that can be used to modify supervisor xstate safely for the
current task.

State for supervisors xstate based features can be live and
accesses via MSR's, or saved in memory in an xsave buffer. When the
kernel needs to modify this state it needs to be sure to operate on it
in the right place, so the modifications don't get clobbered.

In the past supervisor xstate features have used get_xsave_addr()
directly, and performed open coded logic handle operating on the saved
state correctly. This has posed two problems:
1. It has logic that has been gotten wrong more than once.
2. To reduce code, less common path's are not optimized. Determination
of which path's are less common is based on assumptions about far away
code that could change.

In addition, now that get_xsave_addr() is not available outside of the
core fpu code, there isn't even a way for these supervisor features to
modify the in memory state.

To resolve these problems, add some helpers that encapsulate the correct
logic to operate on the correct copy of the state. Map the MSR's to the
struct field location in a case statements in __get_xsave_member().

Use the helpers like this, to write to either the MSR or saved state:
void *xstate;

xstate = start_update_xsave_msrs(XFEATURE_FOO);
r = xsave_rdmsrl(state, MSR_IA32_FOO_1, &val)
if (r)
xsave_wrmsrl(state, MSR_IA32_FOO_2, FOO_ENABLE);
end_update_xsave_msrs();

Signed-off-by: Rick Edgecombe <rick.p.edgecombe@intel.com>
---

v1:
- New patch.

arch/x86/include/asm/fpu/api.h | 5 ++
arch/x86/kernel/fpu/xstate.c | 134 +++++++++++++++++++++++++++++++++
2 files changed, 139 insertions(+)

diff --git a/arch/x86/include/asm/fpu/api.h b/arch/x86/include/asm/fpu/api.h
index c83b3020350a..6aec27984b62 100644
--- a/arch/x86/include/asm/fpu/api.h
+++ b/arch/x86/include/asm/fpu/api.h
@@ -165,4 +165,9 @@ static inline bool fpstate_is_confidential(struct fpu_guest *gfpu)
struct task_struct;
extern long fpu_xstate_prctl(struct task_struct *tsk, int option, unsigned long arg2);

+void *start_update_xsave_msrs(int xfeature_nr);
+void end_update_xsave_msrs(void);
+int xsave_rdmsrl(void *state, unsigned int msr, unsigned long long *p);
+int xsave_wrmsrl(void *state, u32 msr, u64 val);
+int xsave_set_clear_bits_msrl(void *state, u32 msr, u64 set, u64 clear);
#endif /* _ASM_X86_FPU_API_H */
diff --git a/arch/x86/kernel/fpu/xstate.c b/arch/x86/kernel/fpu/xstate.c
index 44397202762b..c5e20e0d0725 100644
--- a/arch/x86/kernel/fpu/xstate.c
+++ b/arch/x86/kernel/fpu/xstate.c
@@ -1867,3 +1867,137 @@ int proc_pid_arch_status(struct seq_file *m, struct pid_namespace *ns,
return 0;
}
#endif /* CONFIG_PROC_PID_ARCH_STATUS */
+
+static u64 *__get_xsave_member(void *xstate, u32 msr)
+{
+ switch (msr) {
+ /* Currently there are no MSR's supported */
+ default:
+ WARN_ONCE(1, "x86/fpu: unsupported xstate msr (%u)\n", msr);
+ return NULL;
+ }
+}
+
+/*
+ * Return a pointer to the xstate for the feature if it should be used, or NULL
+ * if the MSRs should be written to directly. To do this safely, using the
+ * associated read/write helpers is required.
+ */
+void *start_update_xsave_msrs(int xfeature_nr)
+{
+ void *xstate;
+
+ /*
+ * fpregs_lock() only disables preemption (mostly). So modifing state
+ * in an interrupt could screw up some in progress fpregs operation,
+ * but appear to work. Warn about it.
+ */
+ WARN_ON_ONCE(!in_task());
+ WARN_ON_ONCE(current->flags & PF_KTHREAD);
+
+ fpregs_lock();
+
+ fpregs_assert_state_consistent();
+
+ /*
+ * If the registers don't need to be reloaded. Go ahead and operate on the
+ * registers.
+ */
+ if (!test_thread_flag(TIF_NEED_FPU_LOAD))
+ return NULL;
+
+ xstate = get_xsave_addr(&current->thread.fpu.fpstate->regs.xsave, xfeature_nr);
+
+ /*
+ * If regs are in the init state, they can't be retrieved from
+ * init_fpstate due to the init optimization, but are not nessarily
+ * zero. The only option is to restore to make everything live and
+ * operate on registers. This will clear TIF_NEED_FPU_LOAD.
+ *
+ * Otherwise, if not in the init state but TIF_NEED_FPU_LOAD is set,
+ * operate on the buffer. The registers will be restored before going
+ * to userspace in any case, but the task might get preempted before
+ * then, so this possibly saves an xsave.
+ */
+ if (!xstate)
+ fpregs_restore_userregs();
+ return xstate;
+}
+
+void end_update_xsave_msrs(void)
+{
+ fpregs_unlock();
+}
+
+/*
+ * When TIF_NEED_FPU_LOAD is set and fpregs_state_valid() is true, the saved
+ * state and fp state match. In this case, the kernel has some good options -
+ * it can skip the restore before returning to userspace or it could skip
+ * an xsave if preempted before then.
+ *
+ * But if this correspondence is broken by either a write to the in-memory
+ * buffer or the registers, the kernel needs to be notified so it doesn't miss
+ * an xsave or restore. __xsave_msrl_prepare_write() peforms this check and
+ * notifies the kernel if needed. Use before writes only, to not take away
+ * the kernel's options when not required.
+ *
+ * If TIF_NEED_FPU_LOAD is set, then the logic in start_update_xsave_msrs()
+ * must have resulted in targeting the in-memory state, so invaliding the
+ * registers is the right thing to do.
+ */
+static void __xsave_msrl_prepare_write(void)
+{
+ if (test_thread_flag(TIF_NEED_FPU_LOAD) &&
+ fpregs_state_valid(&current->thread.fpu, smp_processor_id()))
+ __fpu_invalidate_fpregs_state(&current->thread.fpu);
+}
+
+int xsave_rdmsrl(void *xstate, unsigned int msr, unsigned long long *p)
+{
+ u64 *member_ptr;
+
+ if (!xstate)
+ return rdmsrl_safe(msr, p);
+
+ member_ptr = __get_xsave_member(xstate, msr);
+ if (!member_ptr)
+ return 1;
+
+ *p = *member_ptr;
+
+ return 0;
+}
+
+int xsave_wrmsrl(void *xstate, u32 msr, u64 val)
+{
+ u64 *member_ptr;
+
+ __xsave_msrl_prepare_write();
+ if (!xstate)
+ return wrmsrl_safe(msr, val);
+
+ member_ptr = __get_xsave_member(xstate, msr);
+ if (!member_ptr)
+ return 1;
+
+ *member_ptr = val;
+
+ return 0;
+}
+
+int xsave_set_clear_bits_msrl(void *xstate, u32 msr, u64 set, u64 clear)
+{
+ u64 val, new_val;
+ int ret;
+
+ ret = xsave_rdmsrl(xstate, msr, &val);
+ if (ret)
+ return ret;
+
+ new_val = (val & ~clear) | set;
+
+ if (new_val != val)
+ return xsave_wrmsrl(xstate, msr, new_val);
+
+ return 0;
+}
--
2.17.1