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: Thomas Gleixner <tglx@linutronix.de> Signed-off-by: Jing Liu <jing2.liu@intel.com> Signed-off-by: Yang 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: Paolo Bonzini <pbonzini@redhat.com>
This commit is contained in:
parent
b0237dad2d
commit
c270ce393d
@ -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 = ¤t->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();
|
||||
|
||||
vfree(curfps);
|
||||
/* Only free valloc'ed state */
|
||||
if (curfps && curfps->is_valloc)
|
||||
vfree(curfps);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -1682,14 +1687,16 @@ 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) {
|
||||
pr_err_once("XFD: Invalid xfd error: %016llx\n", xfd_err);
|
||||
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(¤t->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(¤t->sighand->siglock);
|
||||
return -EPERM;
|
||||
}
|
||||
|
||||
fpu = ¤t->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)
|
||||
{
|
||||
|
@ -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
|
||||
|
||||
/*
|
||||
|
Loading…
Reference in New Issue
Block a user