summaryrefslogtreecommitdiffstats
path: root/sys/amd64
diff options
context:
space:
mode:
authorjhb <jhb@FreeBSD.org>2014-05-18 04:33:24 +0000
committerjhb <jhb@FreeBSD.org>2014-05-18 04:33:24 +0000
commit5e2f766c6cd64afa82187b1855e1fec05976819a (patch)
treec325b0c1bd8da28081e6b2811566135735b388c8 /sys/amd64
parent79fa7e0a74ae908ace3288fb0e05ab959d088373 (diff)
downloadFreeBSD-src-5e2f766c6cd64afa82187b1855e1fec05976819a.zip
FreeBSD-src-5e2f766c6cd64afa82187b1855e1fec05976819a.tar.gz
MFC 259737, 262646:
Fix a couple of issues with vcpu state: - Add a parameter to 'vcpu_set_state()' to enforce that the vcpu is in the IDLE state before the requested state transition. This guarantees that there is exactly one ioctl() operating on a vcpu at any point in time and prevents unintended state transitions. - Fix a race between VMRUN() and vcpu_notify_event() due to 'vcpu->hostcpu' being updated outside of the vcpu_lock().
Diffstat (limited to 'sys/amd64')
-rw-r--r--sys/amd64/include/vmm.h3
-rw-r--r--sys/amd64/vmm/vmm.c75
-rw-r--r--sys/amd64/vmm/vmm_dev.c10
3 files changed, 63 insertions, 25 deletions
diff --git a/sys/amd64/include/vmm.h b/sys/amd64/include/vmm.h
index fab7e74..cc7c7ad 100644
--- a/sys/amd64/include/vmm.h
+++ b/sys/amd64/include/vmm.h
@@ -177,7 +177,8 @@ enum vcpu_state {
VCPU_SLEEPING,
};
-int vcpu_set_state(struct vm *vm, int vcpu, enum vcpu_state state);
+int vcpu_set_state(struct vm *vm, int vcpu, enum vcpu_state state,
+ bool from_idle);
enum vcpu_state vcpu_get_state(struct vm *vm, int vcpu, int *hostcpu);
static int __inline
diff --git a/sys/amd64/vmm/vmm.c b/sys/amd64/vmm/vmm.c
index 2c86068..d727dd9 100644
--- a/sys/amd64/vmm/vmm.c
+++ b/sys/amd64/vmm/vmm.c
@@ -837,13 +837,35 @@ save_guest_fpustate(struct vcpu *vcpu)
static VMM_STAT(VCPU_IDLE_TICKS, "number of ticks vcpu was idle");
static int
-vcpu_set_state_locked(struct vcpu *vcpu, enum vcpu_state newstate)
+vcpu_set_state_locked(struct vcpu *vcpu, enum vcpu_state newstate,
+ bool from_idle)
{
int error;
vcpu_assert_locked(vcpu);
/*
+ * State transitions from the vmmdev_ioctl() must always begin from
+ * the VCPU_IDLE state. This guarantees that there is only a single
+ * ioctl() operating on a vcpu at any point.
+ */
+ if (from_idle) {
+ while (vcpu->state != VCPU_IDLE)
+ msleep_spin(&vcpu->state, &vcpu->mtx, "vmstat", hz);
+ } else {
+ KASSERT(vcpu->state != VCPU_IDLE, ("invalid transition from "
+ "vcpu idle state"));
+ }
+
+ if (vcpu->state == VCPU_RUNNING) {
+ KASSERT(vcpu->hostcpu == curcpu, ("curcpu %d and hostcpu %d "
+ "mismatch for running vcpu", curcpu, vcpu->hostcpu));
+ } else {
+ KASSERT(vcpu->hostcpu == NOCPU, ("Invalid hostcpu %d for a "
+ "vcpu that is not running", vcpu->hostcpu));
+ }
+
+ /*
* The following state transitions are allowed:
* IDLE -> FROZEN -> IDLE
* FROZEN -> RUNNING -> FROZEN
@@ -863,12 +885,19 @@ vcpu_set_state_locked(struct vcpu *vcpu, enum vcpu_state newstate)
break;
}
- if (error == 0)
- vcpu->state = newstate;
+ if (error)
+ return (EBUSY);
+
+ vcpu->state = newstate;
+ if (newstate == VCPU_RUNNING)
+ vcpu->hostcpu = curcpu;
else
- error = EBUSY;
+ vcpu->hostcpu = NOCPU;
- return (error);
+ if (newstate == VCPU_IDLE)
+ wakeup(&vcpu->state);
+
+ return (0);
}
static void
@@ -876,7 +905,7 @@ vcpu_require_state(struct vm *vm, int vcpuid, enum vcpu_state newstate)
{
int error;
- if ((error = vcpu_set_state(vm, vcpuid, newstate)) != 0)
+ if ((error = vcpu_set_state(vm, vcpuid, newstate, false)) != 0)
panic("Error %d setting state to %d\n", error, newstate);
}
@@ -885,7 +914,7 @@ vcpu_require_state_locked(struct vcpu *vcpu, enum vcpu_state newstate)
{
int error;
- if ((error = vcpu_set_state_locked(vcpu, newstate)) != 0)
+ if ((error = vcpu_set_state_locked(vcpu, newstate, false)) != 0)
panic("Error %d setting state to %d", error, newstate);
}
@@ -1131,9 +1160,7 @@ restart:
restore_guest_fpustate(vcpu);
vcpu_require_state(vm, vcpuid, VCPU_RUNNING);
- vcpu->hostcpu = curcpu;
error = VMRUN(vm->cookie, vcpuid, rip, pmap, &vm->rendezvous_func);
- vcpu->hostcpu = NOCPU;
vcpu_require_state(vm, vcpuid, VCPU_FROZEN);
save_guest_fpustate(vcpu);
@@ -1343,7 +1370,8 @@ vm_iommu_domain(struct vm *vm)
}
int
-vcpu_set_state(struct vm *vm, int vcpuid, enum vcpu_state newstate)
+vcpu_set_state(struct vm *vm, int vcpuid, enum vcpu_state newstate,
+ bool from_idle)
{
int error;
struct vcpu *vcpu;
@@ -1354,7 +1382,7 @@ vcpu_set_state(struct vm *vm, int vcpuid, enum vcpu_state newstate)
vcpu = &vm->vcpu[vcpuid];
vcpu_lock(vcpu);
- error = vcpu_set_state_locked(vcpu, newstate);
+ error = vcpu_set_state_locked(vcpu, newstate, from_idle);
vcpu_unlock(vcpu);
return (error);
@@ -1475,19 +1503,28 @@ vcpu_notify_event(struct vm *vm, int vcpuid, bool lapic_intr)
vcpu_lock(vcpu);
hostcpu = vcpu->hostcpu;
- if (hostcpu == NOCPU) {
- if (vcpu->state == VCPU_SLEEPING)
- wakeup_one(vcpu);
- } else {
- if (vcpu->state != VCPU_RUNNING)
- panic("invalid vcpu state %d", vcpu->state);
+ if (vcpu->state == VCPU_RUNNING) {
+ KASSERT(hostcpu != NOCPU, ("vcpu running on invalid hostcpu"));
if (hostcpu != curcpu) {
- if (lapic_intr)
+ if (lapic_intr) {
vlapic_post_intr(vcpu->vlapic, hostcpu,
vmm_ipinum);
- else
+ } else {
ipi_cpu(hostcpu, vmm_ipinum);
+ }
+ } else {
+ /*
+ * If the 'vcpu' is running on 'curcpu' then it must
+ * be sending a notification to itself (e.g. SELF_IPI).
+ * The pending event will be picked up when the vcpu
+ * transitions back to guest context.
+ */
}
+ } else {
+ KASSERT(hostcpu == NOCPU, ("vcpu state %d not consistent "
+ "with hostcpu %d", vcpu->state, hostcpu));
+ if (vcpu->state == VCPU_SLEEPING)
+ wakeup_one(vcpu);
}
vcpu_unlock(vcpu);
}
diff --git a/sys/amd64/vmm/vmm_dev.c b/sys/amd64/vmm/vmm_dev.c
index 02847c2..4b5f691 100644
--- a/sys/amd64/vmm/vmm_dev.c
+++ b/sys/amd64/vmm/vmm_dev.c
@@ -197,7 +197,7 @@ vmmdev_ioctl(struct cdev *cdev, u_long cmd, caddr_t data, int fflag,
goto done;
}
- error = vcpu_set_state(sc->vm, vcpu, VCPU_FROZEN);
+ error = vcpu_set_state(sc->vm, vcpu, VCPU_FROZEN, true);
if (error)
goto done;
@@ -214,14 +214,14 @@ vmmdev_ioctl(struct cdev *cdev, u_long cmd, caddr_t data, int fflag,
*/
error = 0;
for (vcpu = 0; vcpu < VM_MAXCPU; vcpu++) {
- error = vcpu_set_state(sc->vm, vcpu, VCPU_FROZEN);
+ error = vcpu_set_state(sc->vm, vcpu, VCPU_FROZEN, true);
if (error)
break;
}
if (error) {
while (--vcpu >= 0)
- vcpu_set_state(sc->vm, vcpu, VCPU_IDLE);
+ vcpu_set_state(sc->vm, vcpu, VCPU_IDLE, false);
goto done;
}
@@ -387,10 +387,10 @@ vmmdev_ioctl(struct cdev *cdev, u_long cmd, caddr_t data, int fflag,
}
if (state_changed == 1) {
- vcpu_set_state(sc->vm, vcpu, VCPU_IDLE);
+ vcpu_set_state(sc->vm, vcpu, VCPU_IDLE, false);
} else if (state_changed == 2) {
for (vcpu = 0; vcpu < VM_MAXCPU; vcpu++)
- vcpu_set_state(sc->vm, vcpu, VCPU_IDLE);
+ vcpu_set_state(sc->vm, vcpu, VCPU_IDLE, false);
}
done:
OpenPOWER on IntegriCloud