aboutsummaryrefslogtreecommitdiff
path: root/hw/msix.c
diff options
context:
space:
mode:
Diffstat (limited to 'hw/msix.c')
-rw-r--r--hw/msix.c200
1 files changed, 197 insertions, 3 deletions
diff --git a/hw/msix.c b/hw/msix.c
index 35f244e78..338a16fb1 100644
--- a/hw/msix.c
+++ b/hw/msix.c
@@ -19,6 +19,7 @@
#include "msix.h"
#include "pci.h"
#include "range.h"
+#include "kvm.h"
#define MSIX_CAP_LENGTH 12
@@ -36,6 +37,93 @@
#define MSIX_MAX_ENTRIES 32
+/* KVM specific MSIX helpers */
+static void kvm_msix_free(PCIDevice *dev)
+{
+ int vector, changed = 0;
+
+ for (vector = 0; vector < dev->msix_entries_nr; ++vector) {
+ if (dev->msix_entry_used[vector]) {
+ kvm_msi_message_del(&dev->msix_irq_entries[vector]);
+ changed = 1;
+ }
+ }
+ if (changed) {
+ kvm_irqchip_commit_routes(kvm_state);
+ }
+}
+
+static void kvm_msix_message_from_vector(PCIDevice *dev, unsigned vector,
+ KVMMsiMessage *kmm)
+{
+ uint8_t *table_entry = dev->msix_table_page + vector * PCI_MSIX_ENTRY_SIZE;
+
+ kmm->addr_lo = pci_get_long(table_entry + PCI_MSIX_ENTRY_LOWER_ADDR);
+ kmm->addr_hi = pci_get_long(table_entry + PCI_MSIX_ENTRY_UPPER_ADDR);
+ kmm->data = pci_get_long(table_entry + PCI_MSIX_ENTRY_DATA);
+}
+
+static void kvm_msix_update(PCIDevice *dev, int vector,
+ int was_masked, int is_masked)
+{
+ KVMMsiMessage new_entry, *entry;
+ int mask_cleared = was_masked && !is_masked;
+ int r;
+
+ /* It is only legal to change an entry when it is masked. Therefore, it is
+ * enough to update the routing in kernel when mask is being cleared. */
+ if (!mask_cleared) {
+ return;
+ }
+ if (!dev->msix_entry_used[vector]) {
+ return;
+ }
+
+ entry = dev->msix_irq_entries + vector;
+ kvm_msix_message_from_vector(dev, vector, &new_entry);
+ r = kvm_msi_message_update(entry, &new_entry);
+ if (r < 0) {
+ fprintf(stderr, "%s: kvm_update_msix failed: %s\n", __func__,
+ strerror(-r));
+ exit(1);
+ }
+ if (r > 0) {
+ *entry = new_entry;
+ r = kvm_irqchip_commit_routes(kvm_state);
+ if (r) {
+ fprintf(stderr, "%s: kvm_commit_irq_routes failed: %s\n", __func__,
+ strerror(-r));
+ exit(1);
+ }
+ }
+}
+
+static int kvm_msix_vector_add(PCIDevice *dev, unsigned vector)
+{
+ KVMMsiMessage *kmm = dev->msix_irq_entries + vector;
+ int r;
+
+ kvm_msix_message_from_vector(dev, vector, kmm);
+ r = kvm_msi_message_add(kmm);
+ if (r < 0) {
+ fprintf(stderr, "%s: kvm_add_msix failed: %s\n", __func__, strerror(-r));
+ return r;
+ }
+
+ r = kvm_irqchip_commit_routes(kvm_state);
+ if (r < 0) {
+ fprintf(stderr, "%s: kvm_commit_irq_routes failed: %s\n", __func__, strerror(-r));
+ return r;
+ }
+ return 0;
+}
+
+static void kvm_msix_vector_del(PCIDevice *dev, unsigned vector)
+{
+ kvm_msi_message_del(&dev->msix_irq_entries[vector]);
+ kvm_irqchip_commit_routes(kvm_state);
+}
+
/* Add MSI-X capability to the config space for the device. */
/* Given a bar and its size, add MSI-X table on top of it
* and fill MSI-X capability in the config space.
@@ -137,6 +225,12 @@ static void msix_handle_mask_update(PCIDevice *dev, int vector, bool was_masked)
return;
}
+ if (dev->msix_mask_notifier) {
+ int ret;
+ ret = dev->msix_mask_notifier(dev, vector, is_masked);
+ assert(ret >= 0);
+ }
+
if (!is_masked && msix_is_pending(dev, vector)) {
msix_clr_pending(dev, vector);
msix_notify(dev, vector);
@@ -195,6 +289,9 @@ static void msix_mmio_write(void *opaque, target_phys_addr_t addr,
was_masked = msix_is_masked(dev, vector);
pci_set_long(dev->msix_table_page + offset, val);
+ if (kvm_enabled() && kvm_irqchip_in_kernel()) {
+ kvm_msix_update(dev, vector, was_masked, msix_is_masked(dev, vector));
+ }
msix_handle_mask_update(dev, vector, was_masked);
}
@@ -221,11 +318,18 @@ static void msix_mmio_setup(PCIDevice *d, MemoryRegion *bar)
static void msix_mask_all(struct PCIDevice *dev, unsigned nentries)
{
- int vector;
+ int vector, r;
for (vector = 0; vector < nentries; ++vector) {
unsigned offset =
vector * PCI_MSIX_ENTRY_SIZE + PCI_MSIX_ENTRY_VECTOR_CTRL;
+ int was_masked = msix_is_masked(dev, vector);
dev->msix_table_page[offset] |= PCI_MSIX_ENTRY_CTRL_MASKBIT;
+ if (was_masked != msix_is_masked(dev, vector) &&
+ dev->msix_mask_notifier) {
+ r = dev->msix_mask_notifier(dev, vector,
+ msix_is_masked(dev, vector));
+ assert(r >= 0);
+ }
}
}
@@ -244,6 +348,7 @@ int msix_init(struct PCIDevice *dev, unsigned short nentries,
if (nentries > MSIX_MAX_ENTRIES)
return -EINVAL;
+ dev->msix_mask_notifier = NULL;
dev->msix_entry_used = g_malloc0(MSIX_MAX_ENTRIES *
sizeof *dev->msix_entry_used);
@@ -258,6 +363,11 @@ int msix_init(struct PCIDevice *dev, unsigned short nentries,
if (ret)
goto err_config;
+ if (kvm_enabled() && kvm_irqchip_in_kernel()) {
+ dev->msix_irq_entries = g_malloc(nentries *
+ sizeof *dev->msix_irq_entries);
+ }
+
dev->cap_present |= QEMU_PCI_CAP_MSIX;
msix_mmio_setup(dev, bar);
return 0;
@@ -276,6 +386,10 @@ static void msix_free_irq_entries(PCIDevice *dev)
{
int vector;
+ if (kvm_enabled() && kvm_irqchip_in_kernel()) {
+ kvm_msix_free(dev);
+ }
+
for (vector = 0; vector < dev->msix_entries_nr; ++vector) {
dev->msix_entry_used[vector] = 0;
msix_clr_pending(dev, vector);
@@ -306,6 +420,8 @@ int msix_uninit(PCIDevice *dev, MemoryRegion *bar)
dev->msix_table_page = NULL;
g_free(dev->msix_entry_used);
dev->msix_entry_used = NULL;
+ g_free(dev->msix_irq_entries);
+ dev->msix_irq_entries = NULL;
dev->cap_present &= ~QEMU_PCI_CAP_MSIX;
return 0;
}
@@ -317,7 +433,6 @@ void msix_save(PCIDevice *dev, QEMUFile *f)
if (!(dev->cap_present & QEMU_PCI_CAP_MSIX)) {
return;
}
-
qemu_put_buffer(f, dev->msix_table_page, n * PCI_MSIX_ENTRY_SIZE);
qemu_put_buffer(f, dev->msix_table_page + MSIX_PAGE_PENDING, (n + 7) / 8);
}
@@ -372,6 +487,11 @@ void msix_notify(PCIDevice *dev, unsigned vector)
return;
}
+ if (kvm_enabled() && kvm_irqchip_in_kernel()) {
+ kvm_irqchip_set_irq(kvm_state, dev->msix_irq_entries[vector].gsi, 1);
+ return;
+ }
+
address = pci_get_quad(table_entry + PCI_MSIX_ENTRY_LOWER_ADDR);
data = pci_get_long(table_entry + PCI_MSIX_ENTRY_DATA);
stl_le_phys(address, data);
@@ -399,9 +519,17 @@ void msix_reset(PCIDevice *dev)
/* Mark vector as used. */
int msix_vector_use(PCIDevice *dev, unsigned vector)
{
+ int ret;
if (vector >= dev->msix_entries_nr)
return -EINVAL;
- dev->msix_entry_used[vector]++;
+ if (kvm_enabled() && kvm_irqchip_in_kernel() &&
+ !dev->msix_entry_used[vector]) {
+ ret = kvm_msix_vector_add(dev, vector);
+ if (ret) {
+ return ret;
+ }
+ }
+ ++dev->msix_entry_used[vector];
return 0;
}
@@ -414,6 +542,9 @@ void msix_vector_unuse(PCIDevice *dev, unsigned vector)
if (--dev->msix_entry_used[vector]) {
return;
}
+ if (kvm_enabled() && kvm_irqchip_in_kernel()) {
+ kvm_msix_vector_del(dev, vector);
+ }
msix_clr_pending(dev, vector);
}
@@ -423,3 +554,66 @@ void msix_unuse_all_vectors(PCIDevice *dev)
return;
msix_free_irq_entries(dev);
}
+
+/* Invoke the notifier if vector entry is used and unmasked. */
+static int msix_notify_if_unmasked(PCIDevice *dev, unsigned vector, int masked)
+{
+ assert(dev->msix_mask_notifier);
+ if (!dev->msix_entry_used[vector] || msix_is_masked(dev, vector)) {
+ return 0;
+ }
+ return dev->msix_mask_notifier(dev, vector, masked);
+}
+
+static int msix_set_mask_notifier_for_vector(PCIDevice *dev, unsigned vector)
+{
+ /* Notifier has been set. Invoke it on unmasked vectors. */
+ return msix_notify_if_unmasked(dev, vector, 0);
+}
+
+static int msix_unset_mask_notifier_for_vector(PCIDevice *dev, unsigned vector)
+{
+ /* Notifier will be unset. Invoke it to mask unmasked entries. */
+ return msix_notify_if_unmasked(dev, vector, 1);
+}
+
+int msix_set_mask_notifier(PCIDevice *dev, msix_mask_notifier_func f)
+{
+ int r, n;
+ assert(!dev->msix_mask_notifier);
+ dev->msix_mask_notifier = f;
+ for (n = 0; n < dev->msix_entries_nr; ++n) {
+ r = msix_set_mask_notifier_for_vector(dev, n);
+ if (r < 0) {
+ goto undo;
+ }
+ }
+ return 0;
+
+undo:
+ while (--n >= 0) {
+ msix_unset_mask_notifier_for_vector(dev, n);
+ }
+ dev->msix_mask_notifier = NULL;
+ return r;
+}
+
+int msix_unset_mask_notifier(PCIDevice *dev)
+{
+ int r, n;
+ assert(dev->msix_mask_notifier);
+ for (n = 0; n < dev->msix_entries_nr; ++n) {
+ r = msix_unset_mask_notifier_for_vector(dev, n);
+ if (r < 0) {
+ goto undo;
+ }
+ }
+ dev->msix_mask_notifier = NULL;
+ return 0;
+
+undo:
+ while (--n >= 0) {
+ msix_set_mask_notifier_for_vector(dev, n);
+ }
+ return r;
+}