x86/fpu: Allow setting of XSAVE state
[cascardo/linux.git] / arch / x86 / kernel / fpu / xstate.c
index a63ca80..30d144f 100644 (file)
@@ -678,6 +678,19 @@ void fpu__resume_cpu(void)
                xsetbv(XCR_XFEATURE_ENABLED_MASK, xfeatures_mask);
 }
 
+/*
+ * Given an xstate feature mask, calculate where in the xsave
+ * buffer the state is.  Callers should ensure that the buffer
+ * is valid.
+ *
+ * Note: does not work for compacted buffers.
+ */
+void *__raw_xsave_addr(struct xregs_state *xsave, int xstate_feature_mask)
+{
+       int feature_nr = fls64(xstate_feature_mask) - 1;
+
+       return (void *)xsave + xstate_comp_offsets[feature_nr];
+}
 /*
  * Given the xsave area and a state inside, this function returns the
  * address of the state.
@@ -698,7 +711,6 @@ void fpu__resume_cpu(void)
  */
 void *get_xsave_addr(struct xregs_state *xsave, int xstate_feature)
 {
-       int feature_nr = fls64(xstate_feature) - 1;
        /*
         * Do we even *have* xsave state?
         */
@@ -726,7 +738,7 @@ void *get_xsave_addr(struct xregs_state *xsave, int xstate_feature)
        if (!(xsave->header.xfeatures & xstate_feature))
                return NULL;
 
-       return (void *)xsave + xstate_comp_offsets[feature_nr];
+       return __raw_xsave_addr(xsave, xstate_feature);
 }
 EXPORT_SYMBOL_GPL(get_xsave_addr);
 
@@ -761,3 +773,85 @@ const void *get_xsave_field_ptr(int xsave_state)
 
        return get_xsave_addr(&fpu->state.xsave, xsave_state);
 }
+
+
+/*
+ * Set xfeatures (aka XSTATE_BV) bit for a feature that we want
+ * to take out of its "init state".  This will ensure that an
+ * XRSTOR actually restores the state.
+ */
+static void fpu__xfeature_set_non_init(struct xregs_state *xsave,
+               int xstate_feature_mask)
+{
+       xsave->header.xfeatures |= xstate_feature_mask;
+}
+
+/*
+ * This function is safe to call whether the FPU is in use or not.
+ *
+ * Note that this only works on the current task.
+ *
+ * Inputs:
+ *     @xsave_state: state which is defined in xsave.h (e.g. XFEATURE_MASK_FP,
+ *     XFEATURE_MASK_SSE, etc...)
+ *     @xsave_state_ptr: a pointer to a copy of the state that you would
+ *     like written in to the current task's FPU xsave state.  This pointer
+ *     must not be located in the current tasks's xsave area.
+ * Output:
+ *     address of the state in the xsave area or NULL if the state
+ *     is not present or is in its 'init state'.
+ */
+static void fpu__xfeature_set_state(int xstate_feature_mask,
+               void *xstate_feature_src, size_t len)
+{
+       struct xregs_state *xsave = &current->thread.fpu.state.xsave;
+       struct fpu *fpu = &current->thread.fpu;
+       void *dst;
+
+       if (!boot_cpu_has(X86_FEATURE_XSAVE)) {
+               WARN_ONCE(1, "%s() attempted with no xsave support", __func__);
+               return;
+       }
+
+       /*
+        * Tell the FPU code that we need the FPU state to be in
+        * 'fpu' (not in the registers), and that we need it to
+        * be stable while we write to it.
+        */
+       fpu__current_fpstate_write_begin();
+
+       /*
+        * This method *WILL* *NOT* work for compact-format
+        * buffers.  If the 'xstate_feature_mask' is unset in
+        * xcomp_bv then we may need to move other feature state
+        * "up" in the buffer.
+        */
+       if (xsave->header.xcomp_bv & xstate_feature_mask) {
+               WARN_ON_ONCE(1);
+               goto out;
+       }
+
+       /* find the location in the xsave buffer of the desired state */
+       dst = __raw_xsave_addr(&fpu->state.xsave, xstate_feature_mask);
+
+       /*
+        * Make sure that the pointer being passed in did not
+        * come from the xsave buffer itself.
+        */
+       WARN_ONCE(xstate_feature_src == dst, "set from xsave buffer itself");
+
+       /* put the caller-provided data in the location */
+       memcpy(dst, xstate_feature_src, len);
+
+       /*
+        * Mark the xfeature so that the CPU knows there is state
+        * in the buffer now.
+        */
+       fpu__xfeature_set_non_init(xsave, xstate_feature_mask);
+out:
+       /*
+        * We are done writing to the 'fpu'.  Reenable preeption
+        * and (possibly) move the fpstate back in to the fpregs.
+        */
+       fpu__current_fpstate_write_end();
+}