diff options
author | Sheng Yang <sheng@linux.intel.com> | 2009-01-05 21:03:03 -0500 |
---|---|---|
committer | Avi Kivity <avi@redhat.com> | 2009-02-14 19:47:36 -0500 |
commit | ba4cef31d5a397b64ba6d3ff713ce06c62f0c597 (patch) | |
tree | 50a0c4cbcad5d543dd9572c5911cc288b88c3c56 | |
parent | ad8ba2cd44d4d39fb3fe55d5dcc565b19fc3a7fb (diff) |
KVM: Fix racy in kvm_free_assigned_irq
In the past, kvm_get_kvm() and kvm_put_kvm() was called in assigned device irq
handler and interrupt_work, in order to prevent cancel_work_sync() in
kvm_free_assigned_irq got a illegal state when waiting for interrupt_work done.
But it's tricky and still got two problems:
1. A bug ignored two conditions that cancel_work_sync() would return true result
in a additional kvm_put_kvm().
2. If interrupt type is MSI, we would got a window between cancel_work_sync()
and free_irq(), which interrupt would be injected again...
This patch discard the reference count used for irq handler and interrupt_work,
and ensure the legal state by moving the free function at the very beginning of
kvm_destroy_vm(). And the patch fix the second bug by disable irq before
cancel_work_sync(), which may result in nested disable of irq but OK for we are
going to free it.
Signed-off-by: Sheng Yang <sheng@linux.intel.com>
Signed-off-by: Avi Kivity <avi@redhat.com>
-rw-r--r-- | arch/x86/kvm/x86.c | 2 | ||||
-rw-r--r-- | virt/kvm/kvm_main.c | 27 |
2 files changed, 20 insertions, 9 deletions
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c index b0fc079f1bee..fc3e329f6ade 100644 --- a/arch/x86/kvm/x86.c +++ b/arch/x86/kvm/x86.c | |||
@@ -4129,11 +4129,11 @@ static void kvm_free_vcpus(struct kvm *kvm) | |||
4129 | 4129 | ||
4130 | void kvm_arch_sync_events(struct kvm *kvm) | 4130 | void kvm_arch_sync_events(struct kvm *kvm) |
4131 | { | 4131 | { |
4132 | kvm_free_all_assigned_devices(kvm); | ||
4132 | } | 4133 | } |
4133 | 4134 | ||
4134 | void kvm_arch_destroy_vm(struct kvm *kvm) | 4135 | void kvm_arch_destroy_vm(struct kvm *kvm) |
4135 | { | 4136 | { |
4136 | kvm_free_all_assigned_devices(kvm); | ||
4137 | kvm_iommu_unmap_guest(kvm); | 4137 | kvm_iommu_unmap_guest(kvm); |
4138 | kvm_free_pit(kvm); | 4138 | kvm_free_pit(kvm); |
4139 | kfree(kvm->arch.vpic); | 4139 | kfree(kvm->arch.vpic); |
diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c index 68e3f1ec1674..277ea7f39fc8 100644 --- a/virt/kvm/kvm_main.c +++ b/virt/kvm/kvm_main.c | |||
@@ -173,7 +173,6 @@ static void kvm_assigned_dev_interrupt_work_handler(struct work_struct *work) | |||
173 | assigned_dev->host_irq_disabled = false; | 173 | assigned_dev->host_irq_disabled = false; |
174 | } | 174 | } |
175 | mutex_unlock(&assigned_dev->kvm->lock); | 175 | mutex_unlock(&assigned_dev->kvm->lock); |
176 | kvm_put_kvm(assigned_dev->kvm); | ||
177 | } | 176 | } |
178 | 177 | ||
179 | static irqreturn_t kvm_assigned_dev_intr(int irq, void *dev_id) | 178 | static irqreturn_t kvm_assigned_dev_intr(int irq, void *dev_id) |
@@ -181,8 +180,6 @@ static irqreturn_t kvm_assigned_dev_intr(int irq, void *dev_id) | |||
181 | struct kvm_assigned_dev_kernel *assigned_dev = | 180 | struct kvm_assigned_dev_kernel *assigned_dev = |
182 | (struct kvm_assigned_dev_kernel *) dev_id; | 181 | (struct kvm_assigned_dev_kernel *) dev_id; |
183 | 182 | ||
184 | kvm_get_kvm(assigned_dev->kvm); | ||
185 | |||
186 | schedule_work(&assigned_dev->interrupt_work); | 183 | schedule_work(&assigned_dev->interrupt_work); |
187 | 184 | ||
188 | disable_irq_nosync(irq); | 185 | disable_irq_nosync(irq); |
@@ -213,6 +210,7 @@ static void kvm_assigned_dev_ack_irq(struct kvm_irq_ack_notifier *kian) | |||
213 | } | 210 | } |
214 | } | 211 | } |
215 | 212 | ||
213 | /* The function implicit hold kvm->lock mutex due to cancel_work_sync() */ | ||
216 | static void kvm_free_assigned_irq(struct kvm *kvm, | 214 | static void kvm_free_assigned_irq(struct kvm *kvm, |
217 | struct kvm_assigned_dev_kernel *assigned_dev) | 215 | struct kvm_assigned_dev_kernel *assigned_dev) |
218 | { | 216 | { |
@@ -228,11 +226,24 @@ static void kvm_free_assigned_irq(struct kvm *kvm, | |||
228 | if (!assigned_dev->irq_requested_type) | 226 | if (!assigned_dev->irq_requested_type) |
229 | return; | 227 | return; |
230 | 228 | ||
231 | if (cancel_work_sync(&assigned_dev->interrupt_work)) | 229 | /* |
232 | /* We had pending work. That means we will have to take | 230 | * In kvm_free_device_irq, cancel_work_sync return true if: |
233 | * care of kvm_put_kvm. | 231 | * 1. work is scheduled, and then cancelled. |
234 | */ | 232 | * 2. work callback is executed. |
235 | kvm_put_kvm(kvm); | 233 | * |
234 | * The first one ensured that the irq is disabled and no more events | ||
235 | * would happen. But for the second one, the irq may be enabled (e.g. | ||
236 | * for MSI). So we disable irq here to prevent further events. | ||
237 | * | ||
238 | * Notice this maybe result in nested disable if the interrupt type is | ||
239 | * INTx, but it's OK for we are going to free it. | ||
240 | * | ||
241 | * If this function is a part of VM destroy, please ensure that till | ||
242 | * now, the kvm state is still legal for probably we also have to wait | ||
243 | * interrupt_work done. | ||
244 | */ | ||
245 | disable_irq_nosync(assigned_dev->host_irq); | ||
246 | cancel_work_sync(&assigned_dev->interrupt_work); | ||
236 | 247 | ||
237 | free_irq(assigned_dev->host_irq, (void *)assigned_dev); | 248 | free_irq(assigned_dev->host_irq, (void *)assigned_dev); |
238 | 249 | ||