Commit 0645211c43df0b96c51e12980066b3227e10b164
Committed by
Avi Kivity
1 parent
0c106b5aaa
Exists in
master
and in
20 other branches
KVM: Switch assigned device IRQ forwarding to threaded handler
This improves the IRQ forwarding for assigned devices: By using the kernel's threaded IRQ scheme, we can get rid of the latency-prone work queue and simplify the code in the same run. Moreover, we no longer have to hold assigned_dev_lock while raising the guest IRQ, which can be a lenghty operation as we may have to iterate over all VCPUs. The lock is now only used for synchronizing masking vs. unmasking of INTx-type IRQs, thus is renames to intx_lock. Acked-by: Alex Williamson <alex.williamson@redhat.com> Acked-by: Michael S. Tsirkin <mst@redhat.com> Signed-off-by: Jan Kiszka <jan.kiszka@siemens.com> Signed-off-by: Marcelo Tosatti <mtosatti@redhat.com>
Showing 2 changed files with 36 additions and 83 deletions Side-by-side Diff
include/linux/kvm_host.h
| ... | ... | @@ -470,16 +470,8 @@ |
| 470 | 470 | void (*irq_acked)(struct kvm_irq_ack_notifier *kian); |
| 471 | 471 | }; |
| 472 | 472 | |
| 473 | -#define KVM_ASSIGNED_MSIX_PENDING 0x1 | |
| 474 | -struct kvm_guest_msix_entry { | |
| 475 | - u32 vector; | |
| 476 | - u16 entry; | |
| 477 | - u16 flags; | |
| 478 | -}; | |
| 479 | - | |
| 480 | 473 | struct kvm_assigned_dev_kernel { |
| 481 | 474 | struct kvm_irq_ack_notifier ack_notifier; |
| 482 | - struct work_struct interrupt_work; | |
| 483 | 475 | struct list_head list; |
| 484 | 476 | int assigned_dev_id; |
| 485 | 477 | int host_segnr; |
| 486 | 478 | |
| ... | ... | @@ -490,13 +482,13 @@ |
| 490 | 482 | bool host_irq_disabled; |
| 491 | 483 | struct msix_entry *host_msix_entries; |
| 492 | 484 | int guest_irq; |
| 493 | - struct kvm_guest_msix_entry *guest_msix_entries; | |
| 485 | + struct msix_entry *guest_msix_entries; | |
| 494 | 486 | unsigned long irq_requested_type; |
| 495 | 487 | int irq_source_id; |
| 496 | 488 | int flags; |
| 497 | 489 | struct pci_dev *dev; |
| 498 | 490 | struct kvm *kvm; |
| 499 | - spinlock_t assigned_dev_lock; | |
| 491 | + spinlock_t intx_lock; | |
| 500 | 492 | }; |
| 501 | 493 | |
| 502 | 494 | struct kvm_irq_mask_notifier { |
virt/kvm/assigned-dev.c
| ... | ... | @@ -55,58 +55,31 @@ |
| 55 | 55 | return index; |
| 56 | 56 | } |
| 57 | 57 | |
| 58 | -static void kvm_assigned_dev_interrupt_work_handler(struct work_struct *work) | |
| 58 | +static irqreturn_t kvm_assigned_dev_thread(int irq, void *dev_id) | |
| 59 | 59 | { |
| 60 | - struct kvm_assigned_dev_kernel *assigned_dev; | |
| 61 | - int i; | |
| 60 | + struct kvm_assigned_dev_kernel *assigned_dev = dev_id; | |
| 61 | + u32 vector; | |
| 62 | + int index; | |
| 62 | 63 | |
| 63 | - assigned_dev = container_of(work, struct kvm_assigned_dev_kernel, | |
| 64 | - interrupt_work); | |
| 64 | + if (assigned_dev->irq_requested_type & KVM_DEV_IRQ_HOST_INTX) { | |
| 65 | + spin_lock(&assigned_dev->intx_lock); | |
| 66 | + disable_irq_nosync(irq); | |
| 67 | + assigned_dev->host_irq_disabled = true; | |
| 68 | + spin_unlock(&assigned_dev->intx_lock); | |
| 69 | + } | |
| 65 | 70 | |
| 66 | - spin_lock_irq(&assigned_dev->assigned_dev_lock); | |
| 67 | 71 | if (assigned_dev->irq_requested_type & KVM_DEV_IRQ_HOST_MSIX) { |
| 68 | - struct kvm_guest_msix_entry *guest_entries = | |
| 69 | - assigned_dev->guest_msix_entries; | |
| 70 | - for (i = 0; i < assigned_dev->entries_nr; i++) { | |
| 71 | - if (!(guest_entries[i].flags & | |
| 72 | - KVM_ASSIGNED_MSIX_PENDING)) | |
| 73 | - continue; | |
| 74 | - guest_entries[i].flags &= ~KVM_ASSIGNED_MSIX_PENDING; | |
| 72 | + index = find_index_from_host_irq(assigned_dev, irq); | |
| 73 | + if (index >= 0) { | |
| 74 | + vector = assigned_dev-> | |
| 75 | + guest_msix_entries[index].vector; | |
| 75 | 76 | kvm_set_irq(assigned_dev->kvm, |
| 76 | - assigned_dev->irq_source_id, | |
| 77 | - guest_entries[i].vector, 1); | |
| 77 | + assigned_dev->irq_source_id, vector, 1); | |
| 78 | 78 | } |
| 79 | 79 | } else |
| 80 | 80 | kvm_set_irq(assigned_dev->kvm, assigned_dev->irq_source_id, |
| 81 | 81 | assigned_dev->guest_irq, 1); |
| 82 | 82 | |
| 83 | - spin_unlock_irq(&assigned_dev->assigned_dev_lock); | |
| 84 | -} | |
| 85 | - | |
| 86 | -static irqreturn_t kvm_assigned_dev_intr(int irq, void *dev_id) | |
| 87 | -{ | |
| 88 | - unsigned long flags; | |
| 89 | - struct kvm_assigned_dev_kernel *assigned_dev = | |
| 90 | - (struct kvm_assigned_dev_kernel *) dev_id; | |
| 91 | - | |
| 92 | - spin_lock_irqsave(&assigned_dev->assigned_dev_lock, flags); | |
| 93 | - if (assigned_dev->irq_requested_type & KVM_DEV_IRQ_HOST_MSIX) { | |
| 94 | - int index = find_index_from_host_irq(assigned_dev, irq); | |
| 95 | - if (index < 0) | |
| 96 | - goto out; | |
| 97 | - assigned_dev->guest_msix_entries[index].flags |= | |
| 98 | - KVM_ASSIGNED_MSIX_PENDING; | |
| 99 | - } | |
| 100 | - | |
| 101 | - schedule_work(&assigned_dev->interrupt_work); | |
| 102 | - | |
| 103 | - if (assigned_dev->irq_requested_type & KVM_DEV_IRQ_GUEST_INTX) { | |
| 104 | - disable_irq_nosync(irq); | |
| 105 | - assigned_dev->host_irq_disabled = true; | |
| 106 | - } | |
| 107 | - | |
| 108 | -out: | |
| 109 | - spin_unlock_irqrestore(&assigned_dev->assigned_dev_lock, flags); | |
| 110 | 83 | return IRQ_HANDLED; |
| 111 | 84 | } |
| 112 | 85 | |
| ... | ... | @@ -114,7 +87,6 @@ |
| 114 | 87 | static void kvm_assigned_dev_ack_irq(struct kvm_irq_ack_notifier *kian) |
| 115 | 88 | { |
| 116 | 89 | struct kvm_assigned_dev_kernel *dev; |
| 117 | - unsigned long flags; | |
| 118 | 90 | |
| 119 | 91 | if (kian->gsi == -1) |
| 120 | 92 | return; |
| 121 | 93 | |
| ... | ... | @@ -127,12 +99,12 @@ |
| 127 | 99 | /* The guest irq may be shared so this ack may be |
| 128 | 100 | * from another device. |
| 129 | 101 | */ |
| 130 | - spin_lock_irqsave(&dev->assigned_dev_lock, flags); | |
| 102 | + spin_lock(&dev->intx_lock); | |
| 131 | 103 | if (dev->host_irq_disabled) { |
| 132 | 104 | enable_irq(dev->host_irq); |
| 133 | 105 | dev->host_irq_disabled = false; |
| 134 | 106 | } |
| 135 | - spin_unlock_irqrestore(&dev->assigned_dev_lock, flags); | |
| 107 | + spin_unlock(&dev->intx_lock); | |
| 136 | 108 | } |
| 137 | 109 | |
| 138 | 110 | static void deassign_guest_irq(struct kvm *kvm, |
| 139 | 111 | |
| 140 | 112 | |
| 141 | 113 | |
| 142 | 114 | |
| ... | ... | @@ -155,29 +127,20 @@ |
| 155 | 127 | struct kvm_assigned_dev_kernel *assigned_dev) |
| 156 | 128 | { |
| 157 | 129 | /* |
| 158 | - * In kvm_free_device_irq, cancel_work_sync return true if: | |
| 159 | - * 1. work is scheduled, and then cancelled. | |
| 160 | - * 2. work callback is executed. | |
| 130 | + * We disable irq here to prevent further events. | |
| 161 | 131 | * |
| 162 | - * The first one ensured that the irq is disabled and no more events | |
| 163 | - * would happen. But for the second one, the irq may be enabled (e.g. | |
| 164 | - * for MSI). So we disable irq here to prevent further events. | |
| 165 | - * | |
| 166 | 132 | * Notice this maybe result in nested disable if the interrupt type is |
| 167 | 133 | * INTx, but it's OK for we are going to free it. |
| 168 | 134 | * |
| 169 | 135 | * If this function is a part of VM destroy, please ensure that till |
| 170 | 136 | * now, the kvm state is still legal for probably we also have to wait |
| 171 | - * interrupt_work done. | |
| 137 | + * on a currently running IRQ handler. | |
| 172 | 138 | */ |
| 173 | 139 | if (assigned_dev->irq_requested_type & KVM_DEV_IRQ_HOST_MSIX) { |
| 174 | 140 | int i; |
| 175 | 141 | for (i = 0; i < assigned_dev->entries_nr; i++) |
| 176 | - disable_irq_nosync(assigned_dev-> | |
| 177 | - host_msix_entries[i].vector); | |
| 142 | + disable_irq(assigned_dev->host_msix_entries[i].vector); | |
| 178 | 143 | |
| 179 | - cancel_work_sync(&assigned_dev->interrupt_work); | |
| 180 | - | |
| 181 | 144 | for (i = 0; i < assigned_dev->entries_nr; i++) |
| 182 | 145 | free_irq(assigned_dev->host_msix_entries[i].vector, |
| 183 | 146 | (void *)assigned_dev); |
| ... | ... | @@ -188,8 +151,7 @@ |
| 188 | 151 | pci_disable_msix(assigned_dev->dev); |
| 189 | 152 | } else { |
| 190 | 153 | /* Deal with MSI and INTx */ |
| 191 | - disable_irq_nosync(assigned_dev->host_irq); | |
| 192 | - cancel_work_sync(&assigned_dev->interrupt_work); | |
| 154 | + disable_irq(assigned_dev->host_irq); | |
| 193 | 155 | |
| 194 | 156 | free_irq(assigned_dev->host_irq, (void *)assigned_dev); |
| 195 | 157 | |
| ... | ... | @@ -268,8 +230,9 @@ |
| 268 | 230 | * on the same interrupt line is not a happy situation: there |
| 269 | 231 | * are going to be long delays in accepting, acking, etc. |
| 270 | 232 | */ |
| 271 | - if (request_irq(dev->host_irq, kvm_assigned_dev_intr, | |
| 272 | - 0, "kvm_assigned_intx_device", (void *)dev)) | |
| 233 | + if (request_threaded_irq(dev->host_irq, NULL, kvm_assigned_dev_thread, | |
| 234 | + IRQF_ONESHOT, "kvm_assigned_intx_device", | |
| 235 | + (void *)dev)) | |
| 273 | 236 | return -EIO; |
| 274 | 237 | return 0; |
| 275 | 238 | } |
| ... | ... | @@ -287,8 +250,8 @@ |
| 287 | 250 | } |
| 288 | 251 | |
| 289 | 252 | dev->host_irq = dev->dev->irq; |
| 290 | - if (request_irq(dev->host_irq, kvm_assigned_dev_intr, 0, | |
| 291 | - "kvm_assigned_msi_device", (void *)dev)) { | |
| 253 | + if (request_threaded_irq(dev->host_irq, NULL, kvm_assigned_dev_thread, | |
| 254 | + 0, "kvm_assigned_msi_device", (void *)dev)) { | |
| 292 | 255 | pci_disable_msi(dev->dev); |
| 293 | 256 | return -EIO; |
| 294 | 257 | } |
| ... | ... | @@ -313,10 +276,10 @@ |
| 313 | 276 | return r; |
| 314 | 277 | |
| 315 | 278 | for (i = 0; i < dev->entries_nr; i++) { |
| 316 | - r = request_irq(dev->host_msix_entries[i].vector, | |
| 317 | - kvm_assigned_dev_intr, 0, | |
| 318 | - "kvm_assigned_msix_device", | |
| 319 | - (void *)dev); | |
| 279 | + r = request_threaded_irq(dev->host_msix_entries[i].vector, | |
| 280 | + NULL, kvm_assigned_dev_thread, | |
| 281 | + 0, "kvm_assigned_msix_device", | |
| 282 | + (void *)dev); | |
| 320 | 283 | if (r) |
| 321 | 284 | goto err; |
| 322 | 285 | } |
| 323 | 286 | |
| ... | ... | @@ -557,12 +520,10 @@ |
| 557 | 520 | match->host_devfn = assigned_dev->devfn; |
| 558 | 521 | match->flags = assigned_dev->flags; |
| 559 | 522 | match->dev = dev; |
| 560 | - spin_lock_init(&match->assigned_dev_lock); | |
| 523 | + spin_lock_init(&match->intx_lock); | |
| 561 | 524 | match->irq_source_id = -1; |
| 562 | 525 | match->kvm = kvm; |
| 563 | 526 | match->ack_notifier.irq_acked = kvm_assigned_dev_ack_irq; |
| 564 | - INIT_WORK(&match->interrupt_work, | |
| 565 | - kvm_assigned_dev_interrupt_work_handler); | |
| 566 | 527 | |
| 567 | 528 | list_add(&match->list, &kvm->arch.assigned_dev_head); |
| 568 | 529 | |
| ... | ... | @@ -654,9 +615,9 @@ |
| 654 | 615 | r = -ENOMEM; |
| 655 | 616 | goto msix_nr_out; |
| 656 | 617 | } |
| 657 | - adev->guest_msix_entries = kzalloc( | |
| 658 | - sizeof(struct kvm_guest_msix_entry) * | |
| 659 | - entry_nr->entry_nr, GFP_KERNEL); | |
| 618 | + adev->guest_msix_entries = | |
| 619 | + kzalloc(sizeof(struct msix_entry) * entry_nr->entry_nr, | |
| 620 | + GFP_KERNEL); | |
| 660 | 621 | if (!adev->guest_msix_entries) { |
| 661 | 622 | kfree(adev->host_msix_entries); |
| 662 | 623 | r = -ENOMEM; |