Commit c270ce39 authored by Thomas Gleixner's avatar Thomas Gleixner Committed by Paolo Bonzini
Browse files

x86/fpu: Add guest support to xfd_enable_feature()



Guest support for dynamically enabled FPU features requires a few
modifications to the enablement function which is currently invoked from
the #NM handler:

  1) Use guest permissions and sizes for the update

  2) Update fpu_guest state accordingly

  3) Take into account that the enabling can be triggered either from a
     running guest via XSETBV and MSR_IA32_XFD write emulation or from
     a guest restore. In the latter case the guests fpstate is not the
     current tasks active fpstate.

Split the function and implement the guest mechanics throughout the
callchain.

Signed-off-by: default avatarThomas Gleixner <tglx@linutronix.de>
Signed-off-by: default avatarJing Liu <jing2.liu@intel.com>
Signed-off-by: default avatarYang Zhong <yang.zhong@intel.com>
Message-Id: <20220105123532.12586-7-yang.zhong@intel.com>
[Add 32-bit stub for __xfd_enable_feature. - Paolo]
Signed-off-by: default avatarPaolo Bonzini <pbonzini@redhat.com>
parent b0237dad
Loading
Loading
Loading
Loading
+54 −39
Original line number Diff line number Diff line
@@ -1499,29 +1499,6 @@ void fpstate_free(struct fpu *fpu)
		vfree(fpu->fpstate);
}

/**
 * fpu_install_fpstate - Update the active fpstate in the FPU
 *
 * @fpu:	A struct fpu * pointer
 * @newfps:	A struct fpstate * pointer
 *
 * Returns:	A null pointer if the last active fpstate is the embedded
 *		one or the new fpstate is already installed;
 *		otherwise, a pointer to the old fpstate which has to
 *		be freed by the caller.
 */
static struct fpstate *fpu_install_fpstate(struct fpu *fpu,
					   struct fpstate *newfps)
{
	struct fpstate *oldfps = fpu->fpstate;

	if (fpu->fpstate == newfps)
		return NULL;

	fpu->fpstate = newfps;
	return oldfps != &fpu->__fpstate ? oldfps : NULL;
}

/**
 * fpstate_realloc - Reallocate struct fpstate for the requested new features
 *
@@ -1529,6 +1506,7 @@ static struct fpstate *fpu_install_fpstate(struct fpu *fpu,
 *		of that task
 * @ksize:	The required size for the kernel buffer
 * @usize:	The required size for user space buffers
 * @guest_fpu:	Pointer to a guest FPU container. NULL for host allocations
 *
 * Note vs. vmalloc(): If the task with a vzalloc()-allocated buffer
 * terminates quickly, vfree()-induced IPIs may be a concern, but tasks
@@ -1537,13 +1515,13 @@ static struct fpstate *fpu_install_fpstate(struct fpu *fpu,
 * Returns: 0 on success, -ENOMEM on allocation error.
 */
static int fpstate_realloc(u64 xfeatures, unsigned int ksize,
			   unsigned int usize)
			   unsigned int usize, struct fpu_guest *guest_fpu)
{
	struct fpu *fpu = &current->thread.fpu;
	struct fpstate *curfps, *newfps = NULL;
	unsigned int fpsize;
	bool in_use;

	curfps = fpu->fpstate;
	fpsize = ksize + ALIGN(offsetof(struct fpstate, regs), 64);

	newfps = vzalloc(fpsize);
@@ -1553,28 +1531,55 @@ static int fpstate_realloc(u64 xfeatures, unsigned int ksize,
	newfps->user_size = usize;
	newfps->is_valloc = true;

	/*
	 * When a guest FPU is supplied, use @guest_fpu->fpstate
	 * as reference independent whether it is in use or not.
	 */
	curfps = guest_fpu ? guest_fpu->fpstate : fpu->fpstate;

	/* Determine whether @curfps is the active fpstate */
	in_use = fpu->fpstate == curfps;

	if (guest_fpu) {
		newfps->is_guest = true;
		newfps->is_confidential = curfps->is_confidential;
		newfps->in_use = curfps->in_use;
		guest_fpu->xfeatures |= xfeatures;
	}

	fpregs_lock();
	/*
	 * Ensure that the current state is in the registers before
	 * swapping fpstate as that might invalidate it due to layout
	 * changes.
	 * If @curfps is in use, ensure that the current state is in the
	 * registers before swapping fpstate as that might invalidate it
	 * due to layout changes.
	 */
	if (test_thread_flag(TIF_NEED_FPU_LOAD))
	if (in_use && test_thread_flag(TIF_NEED_FPU_LOAD))
		fpregs_restore_userregs();

	newfps->xfeatures = curfps->xfeatures | xfeatures;
	newfps->user_xfeatures = curfps->user_xfeatures | xfeatures;
	newfps->xfd = curfps->xfd & ~xfeatures;

	curfps = fpu_install_fpstate(fpu, newfps);

	/* Do the final updates within the locked region */
	xstate_init_xcomp_bv(&newfps->regs.xsave, newfps->xfeatures);
	xfd_update_state(newfps);

	if (guest_fpu) {
		guest_fpu->fpstate = newfps;
		/* If curfps is active, update the FPU fpstate pointer */
		if (in_use)
			fpu->fpstate = newfps;
	} else {
		fpu->fpstate = newfps;
	}

	if (in_use)
		xfd_update_state(fpu->fpstate);
	fpregs_unlock();

	/* Only free valloc'ed state */
	if (curfps && curfps->is_valloc)
		vfree(curfps);

	return 0;
}

@@ -1682,13 +1687,15 @@ static int xstate_request_perm(unsigned long idx, bool guest)
	return ret;
}

int xfd_enable_feature(u64 xfd_err)
int __xfd_enable_feature(u64 xfd_err, struct fpu_guest *guest_fpu)
{
	u64 xfd_event = xfd_err & XFEATURE_MASK_USER_DYNAMIC;
	struct fpu_state_perm *perm;
	unsigned int ksize, usize;
	struct fpu *fpu;

	if (!xfd_event) {
		if (!guest_fpu)
			pr_err_once("XFD: Invalid xfd error: %016llx\n", xfd_err);
		return 0;
	}
@@ -1697,14 +1704,16 @@ int xfd_enable_feature(u64 xfd_err)
	spin_lock_irq(&current->sighand->siglock);

	/* If not permitted let it die */
	if ((xstate_get_host_group_perm() & xfd_event) != xfd_event) {
	if ((xstate_get_group_perm(!!guest_fpu) & xfd_event) != xfd_event) {
		spin_unlock_irq(&current->sighand->siglock);
		return -EPERM;
	}

	fpu = &current->group_leader->thread.fpu;
	ksize = fpu->perm.__state_size;
	usize = fpu->perm.__user_state_size;
	perm = guest_fpu ? &fpu->guest_perm : &fpu->perm;
	ksize = perm->__state_size;
	usize = perm->__user_state_size;

	/*
	 * The feature is permitted. State size is sufficient.  Dropping
	 * the lock is safe here even if more features are added from
@@ -1717,10 +1726,16 @@ int xfd_enable_feature(u64 xfd_err)
	 * Try to allocate a new fpstate. If that fails there is no way
	 * out.
	 */
	if (fpstate_realloc(xfd_event, ksize, usize))
	if (fpstate_realloc(xfd_event, ksize, usize, guest_fpu))
		return -EFAULT;
	return 0;
}

int xfd_enable_feature(u64 xfd_err)
{
	return __xfd_enable_feature(xfd_err, NULL);
}

#else /* CONFIG_X86_64 */
static inline int xstate_request_perm(unsigned long idx, bool guest)
{
+6 −0
Original line number Diff line number Diff line
@@ -158,8 +158,14 @@ static inline void xfd_update_state(struct fpstate *fpstate)
		}
	}
}

extern int __xfd_enable_feature(u64 which, struct fpu_guest *guest_fpu);
#else
static inline void xfd_update_state(struct fpstate *fpstate) { }

static inline int __xfd_enable_feature(u64 which, struct fpu_guest *guest_fpu) {
	return -EPERM;
}
#endif

/*